6.5 KiB
Filesystem and File Browser Architecture
Updated: 2026-03-01 Status: Active — reflects implemented upload model
Overview
ZeroLagHub uses a strict, runtime-aligned filesystem model for game containers. The file browser reflects the real runtime layout and does not use staging directories, shadow folders, or symlink-based indirection.
Uploads are written directly into the runtime directory tree with strict policy enforcement and provenance tracking.
Runtime Root
Each game container has a single runtime root:
/opt/zlh/minecraft/<runtime>/<world>/
All file operations are resolved relative to this root.
The agent is the only component allowed to perform filesystem mutations.
Hidden Internal Files
The following files are internal and never exposed through the file API:
.zlh-shadow.zlh_metadata.json
These are filtered centrally inside the agent internal/files package, not in route handlers.
Upload Model
No Staging
Uploads are written directly into their final runtime location.
There is:
- ❌ No temporary upload folder
- ❌ No symlink-based deployment
- ❌ No background relocation job
Uploads are atomic:
- Write to temp file in same directory
os.Rename()to final path
Upload Allowlist
Uploads are restricted to:
| Type | Path | Extension |
|---|---|---|
| Mods | mods/<file>.jar |
.jar |
| Datapacks | world/datapacks/<file>.zip |
.zip |
All other upload paths are rejected.
Parent directory must already exist. No directory creation is performed.
Security Constraints
Upload is rejected if:
- Path traversal (
../) - Absolute path
- Control characters
- Target exists as directory
- Target is a symlink
- Target resolves outside runtime root
- Path not allowlisted
- Parent directory missing
Provenance Metadata
Uploads create or update .zlh_metadata.json, stored at runtime root.
Example:
{
"mods/sodium.jar": {
"source": "user",
"uploaded_at": "2026-03-01T22:37:01Z"
}
}
stat Response Extension
File stat now includes:
"source": "user" | null
Only "user" is currently written by the agent. No curated inference is performed.
File Browser Responsibilities
Portal (Frontend)
- Displays files
- Displays
source: "user"badge - Enforces extension pre-validation
- Sends multipart upload
API
- Auth + ownership enforcement
- Streams upload to agent
- Does NOT inspect file contents
- Does NOT re-implement upload policy
Agent
- Enforces filesystem rules
- Writes metadata
- Performs atomic writes
- Returns authoritative status
Why No Symlinks
Symlink-based deployment was rejected because:
- It complicates mod loading behavior
- Breaks compatibility with server-side mod loaders
- Introduces unexpected runtime indirection
- Makes provenance ambiguous
Direct writes + metadata is simpler and safer.
Data Flow
Browser → API → Agent → Filesystem
The API uses raw http.request piping to stream uploads. It does not buffer large files in memory.
API Transport Considerations
The API acts strictly as a streaming proxy.
Implementation uses raw http.request piping:
req.pipe(proxyReq)
proxyRes.pipe(res)
This avoids:
- Fetch streaming incompatibilities
- Duplex locking issues
- Multipart buffering problems
Upload timeout for this route should be substantially larger than normal file routes.
Frontend File Browser Direction
Layout
Split-pane panel — directory tree left, file detail/actions right. Slides in as a panel, does not replace the console view. Server console remains visible.
Navigation
Breadcrumb-based. Flat navigation within each directory rather than deep tree expansion. Click folder → replace view. Not expand-in-place.
File Listing
Columns: name, size, modified date, type icon, source badge (user for user-uploaded files). For .jar files: enabled / disabled / removed status.
Actions
Context menu or per-row three-dot menu. Actions: download, delete, rename. For .jar files: enable / disable toggle. Drag-to-upload supported, file picker fallback.
In-Browser Editor
Plain textarea or Monaco for text files (server.properties, .json, .toml, .yml, .txt). Binary files get download link only. Not required for launch.
mods-removed Surface
"Recently removed" section or toggle to show /mods-removed alongside active mods. Makes soft delete visible and gives users a restore path without knowing the underlying filesystem layout.
What to Avoid
- Deep expand/collapse tree for mod directory (use flat list + filter)
- In-browser zip/unzip
- Making the file browser the primary surface — mod manager stays primary, file browser is secondary/advanced
Per-Container Web Server Decision
Do not run Nginx or Caddy per container.
The agent already runs an HTTP server. Serving static file browser assets from the agent directly keeps per-container footprint minimal. No additional process, no config management, no extra memory overhead.
zlh-proxy handles SSL at the edge. A local web server per container adds a layer without adding capability.
Dev Container File Access
Handled via WebSSH2 + SFTP, proxied through the API.
Developers get a real SSH2 session with SFTP channel. No direct container access from the browser — API proxy maintains the security boundary (DEC-008). SFTP warrants its own separate process from day one due to SSH server complexity. It does not belong in the main game agent.
Resilience Notes
The file agent (when extracted) should follow the same binary resilience pattern as the main agent:
- Versioned release layout (
releases/<version>/) currentsymlink pointing to active binary- Previous version retained on disk
- Systemd watchdog flips
currentback to previous on health check failure - No dependency on artifact server for rollback — local fallback only
Design Philosophy
The runtime directory is the source of truth.
No abstraction layer should diverge from Minecraft's real file structure.
Related Documents
docs/architecture/mod-deployment-safety.md— mod lifecycle, upload safety, rollback modeldocs/architecture/dev-to-game-artifact-pipeline.md— dev container promotion pipelinedocs/reference/minecraft-file-locations.md— known files and directories by loaderOPEN_THREADS.md— file browser listed as next major feature- WebSSH2:
https://github.com/billchurch/webssh2— SFTP + SSH2 for dev containers