A from-scratch, inode-based filesystem engine structurally identical to ext2 with ext3-style ordered journaling, a Go HTTP/WebSocket API, and a React visualizer.
# 1. Start the backend (formats a fresh 64 MB disk image on first run)
cd fs-engine && go run ./cmd/server
# 2. In another terminal, start the React frontend
cd fs-engine/web && npm install && npm run dev
# 3. Open the browser
open http://localhost:5173Backend runs on :8080, frontend dev server on :5173 (proxies
/api,/sse,/wsto :8080).
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser β
β ββββββββββββ ββββββββββββ ββββββββββββββ βββββββββββββββ β
β β Block β β Inode β β Journal β β Terminal β β
β β Map β βInspector β β Viewer β β (xterm.js) β β
β ββββββ¬ββββββ ββββββ¬ββββββ βββββββ¬βββββββ ββββββββ¬βββββββ β
β ββββββββββββββ΄ββββββββββββββ΄ββββββββββββββββ β
β REST /api/* β SSE /sse β WS /ws/shell β
βββββββββββββββββββββββββββββββββΌβββββββββββββΌβββββββββββββββββ
β β
βββββββββββββββββββββββββββββββββΌβββββββββββββΌβββββββββββββββββ
β Go HTTP Server (:8080) β
β ββββββββββββ ββββββββββββββ ββββββββββββ ββββββββββββ β
β β REST β β SSE β β WS β β Shell β β
β β handlers β β pub/sub β β shell β β (20 cmds)β β
β ββββββ¬ββββββ ββββββββββββββ ββββββββββββ ββββββββββββ β
β β β
β ββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Filesystem Core β β
β β ββββββββββββ βββββββββββ ββββββββββββ β β
β β β Buffer β β Inode β β Journal β β β
β β β Cache β β Cache β β (JBD2) β β β
β β β LRU 256 β β LRU 512 β β 128 blks β β β
β β ββββββ¬ββββββ ββββββ¬βββββ ββββββ¬ββββββ β β
β β βββββββββββββββ΄βββββββββββββ β β
β β POSIX ops layer β β
β β open/read/write/stat/mkdir/link/symlink/chmod/... β β
β βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββ β
β β Block Device (disk.img) β β
β β 64 MB binary file Β· 16384 blocks Β· 4096 bytes/block β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Block(s) | Purpose |
|---|---|
| 0 | Boot block (reserved) |
| 1 | Superblock (0xEF53) |
| 2 | Inode bitmap |
| 3 | Block bitmap |
| 4 β 131 | Inode table (4096 inodes Γ 128 bytes) |
| 132 β 2179 | Journal (2048 blocks, JBD2-style) |
| 2180 β 16383 | Data blocks |
| Page | Route | What it shows |
|---|---|---|
| Block Map | / |
128Γ128 SVG grid of all 16384 blocks, color-coded by type (boot, superblock, inode bitmap, block bitmap, inode table, journal, data-used, data-free, cache-dirty). Animates in real-time via SSE. |
| Explorer | /explorer |
File tree browser. Click directories to expand, click files to view content. |
| Inode Inspector | /inode |
Enter any inode number to see mode, UID/GID, size, timestamps, direct/indirect block pointers, and data block list. |
| Journal Viewer | /journal |
Ring buffer of the last 64 committed journal transactions: sequence ID, timestamp, block list, operation tags. |
| Stats | /stats |
Live Recharts graphs of cache hits/misses/evictions, filesystem df (total/used/free), and per-operation metrics counters. |
| Terminal | /terminal |
Full xterm.js terminal connected to the built-in shell over WebSocket. Supports 20 commands (see help). |
ls [-l] [path] ll [path] cd [path] pwd
mkdir <path> rmdir <path> touch <path> rm [-r] <path>
cat <path> write <path> <data> mv <src> <dst>
cp <src> <dst> ln <src> <dst> stat <path>
chmod <mode> <path> chown <uid>:<gid> <path>
df find <path> [-name <pat>]
echo [args...] help
All REST endpoints are prefixed with /api.
| Method | Path | Description |
|---|---|---|
| GET | /api/health |
Liveness check |
| GET | /api/fs/stat?path= |
Stat a path |
| GET | /api/fs/lstat?path= |
Lstat a path |
| GET | /api/fs/ls?path= |
List directory |
| POST | /api/fs/mkdir |
Create directory |
| DELETE | /api/fs/rmdir?path= |
Remove empty directory |
| POST | /api/fs/write |
Write file |
| POST | /api/fs/create |
Create file |
| GET | /api/fs/read?path= |
Read file |
| DELETE | /api/fs/unlink?path= |
Delete file |
| POST | /api/fs/rename |
Rename/move |
| POST | /api/fs/symlink |
Create symlink |
| POST | /api/fs/link |
Create hard link |
| POST | /api/fs/chmod |
Change mode |
| POST | /api/fs/chown |
Change owner/group |
| POST | /api/fs/utimes |
Update timestamps |
| GET | /api/fs/readlink?path= |
Read symlink target |
| POST | /api/fs/truncate |
Truncate file |
| GET | /api/inspect/superblock |
Superblock fields |
| GET | /api/inspect/inode/{num} |
Inode detail |
| GET | /api/inspect/bitmap/blocks |
Bitmap counts |
| GET | /api/inspect/bitmap/inodes |
Bitmap counts |
| GET | /api/inspect/cache |
Buffer cache stats |
| GET | /api/inspect/disk |
Full 16384-block map |
| GET | /api/inspect/block/{addr} |
Hex dump + parse |
| GET | /api/inspect/journal |
Recent transactions |
| GET | /api/inspect/tree |
Recursive dir tree |
| GET | /api/inspect/fragmentation |
Free-run analysis |
| GET | /api/inspect/df |
Disk usage |
| GET | /api/metrics |
Op counters |
| POST | /api/fs/format |
Reformat (destructive) |
| POST | /api/fs/fsck |
Consistency check |
| POST | /api/fs/crash |
Simulate crash + replay |
| POST | /api/shell |
Run one shell command |
| GET/WS | /ws/shell |
Interactive shell (WebSocket) |
| GET | /sse |
Server-sent filesystem events |
# All backend packages with race detector
make race
# Integration smoke tests only
go test ./internal/integration/... -race -v
# API tests only
go test ./api/... -race -vGitHub Actions CI enforces four required checks:
Backend Testrunsgo test ./... -timeout 8mBackend Raceruns the backend race suite in two slicesFrontend Buildrunsnpm ci && npm run buildBrowser E2Eruns Playwright against the live Go server and Vite frontend
For local parity with CI:
go test ./... -timeout 8m
go test ./api ./internal/fs ./internal/cache ./internal/disk -race -timeout 10m
go test ./internal/integration ./internal/journal ./internal/ops ./internal/shell -race -timeout 10m
cd web && npm ci && npm run build && npm run test:e2eAll 60+ tests pass, including:
- 10 integration smoke tests (1 MB write/read, 100 files, hard links, symlink chains, ELOOP, journal recovery, permission denied, disk full, 5 MB file, fsck clean)
- 19 API handler tests
- Cache, disk, fs, journal, ops, shell unit tests β all with
-race
- No OS filesystem calls β the entire stack (block device β inode β directory β ops) is pure Go;
disk.imgis a flat[]bytein memory, synced to file on demand. - Import-cycle-free inode cache β
InodeCachestores raw[128]byteblobs; encoding/decoding happens at thefslayer boundary. - Ordered journaling (JBD2-style) β descriptor block β data blocks β CRC32 commit block β real block writes β fsync. Crash recovery replays any committed-but-not-applied transactions.
- LRU buffer cache with dirty tracking β 256-slot write-back cache;
CacheStats.DirtyAddrsexposes current dirty set for the block map visualizer. - Fast symlinks β targets < 60 bytes stored inline in the inode's block pointer fields (
Blocks512 = 0); no data block allocated. - Sticky bit β
/tmpis created with mode01777;Unlinkenforces sticky-bit semantics.
The API and WebSocket shell no longer accept every browser origin by default.
Use FS_ENGINE_ALLOWED_ORIGINS to set a comma-separated allowlist:
export FS_ENGINE_ALLOWED_ORIGINS="https://app.example.com,https://admin.example.com"Default local development allowlist:
http://localhost:5173http://127.0.0.1:5173http://localhost:8080http://127.0.0.1:8080
Use the repo tasks instead of raw go test ./... so frontend dependency code under web/node_modules is excluded from backend package discovery:
make test
make race
make lint- CI workflows live in
.github/workflows - Issue intake is standardized with issue forms in
.github/ISSUE_TEMPLATE - Pull requests use
.github/PULL_REQUEST_TEMPLATE.md - Branch protection can be applied with
.github/scripts/apply-branch-protection.sh <owner/repo> [branch]
Release and versioning policy is documented in VERSIONING.md.
- Semantic tags use
vX.Y.Z .github/workflows/release.ymlbuilds backend binaries, frontend bundles, and checksums
- Backend container: Dockerfile
- Frontend container: web/Dockerfile
- Local production-like compose: docker-compose.prod.yml
- Kubernetes manifests: deploy/kubernetes
- Prometheus config: deploy/observability/prometheus/prometheus.yml
- Grafana provisioning and dashboard: deploy/observability/grafana
- Local observability compose stack: docker-compose.observability.yml
Metrics scrape target:
GET /metrics?format=prometheus
- Security reporting and policy: SECURITY.md
- Dependency update automation: .github/dependabot.yml
- Security workflow: .github/workflows/security.yml
- Go runtime baseline: patch-level updates should stay at or above the latest fixed release line required by
govulncheck
- Benchmark workflow: .github/workflows/perf.yml
- Local benchmark docs: perf/README.md
- Make targets:
make perf,make perf-go,make perf-k6