# 2026-02-23 — Modrinth Install + Full Mod Lifecycle **Type:** Foundational Architecture + Implementation **Status:** Full mod lifecycle complete, file browser next --- ## Completed ### API Implemented full mod lifecycle routes: ``` GET /api/game/mods/search?q=&vmid= GET /api/game/servers/:id/mods POST /api/game/servers/:id/mods/install PATCH /api/game/servers/:id/mods/:modId DELETE /api/game/servers/:id/mods/:modId ``` All routes: - `requireAuth` - Enforce container ownership - Enforce `ctype === "game"` - Forward to agent with timeout + `502`/`504` mapping ### Resolver - Loader normalization fixed (`neoforge` / `fabric` / `forge` / `quilt`) - `.jar`-only enforcement - Host allowlist: `cdn.modrinth.com` - Publish-date safe sorting - SHA512 preferred (fallback to SHA1) ### Agent Mod install flow: - Accepts direct Modrinth artifact URL - Validates: - HTTPS only - Allowed host (`cdn.modrinth.com`, `artifacts.zerolaghub.com`) - Max size 200MB - SHA256 hash - Writes to `/mods` - Enables via filename convention - Soft delete moves to `/mods-removed` - Enable/disable via rename: `.jar` ↔ `.jar.disabled` ### Frontend - Mod search drawer implemented - Installed mods panel implemented - Installed flag merged into search results (heuristic matching) - Install button functional - Enable / disable functional - Delete functional - Toast notifications added --- ## Current System Behavior Filesystem is source of truth. No database persistence for: - Mod install history - Modrinth project ID mapping Installed matching is heuristic based on: - Slug - Filename - Name Soft delete retains file permanently in `/mods-removed`. No retention policy implemented (intentional). --- ## Known Architectural Tradeoffs - No deterministic Modrinth project ID persistence yet - Installed detection is best-effort heuristic - No install queue - No auto-update logic --- ## Security Controls In Place **Agent level:** - HTTPS-only downloads - Host allowlist enforcement - Redirect limit (max 3) - 200MB hard cap - SHA256 verification - Filename sanitization (`[a-zA-Z0-9._+-]` — `+` added for Modrinth filenames) - Duplicate prevention - Ownership enforcement (`minecraft:minecraft`) - Temp file cleanup on failure - Controlled mod directory write path - Cache invalidation after every mutation **API level:** - Auth + ownership enforcement - VMID validation - Timeout protection with `AbortController` - Resolver filtering (loader + version + stability) - Correct payload contract to agent --- ## Payload Contract (API → Agent) ```json { "source": "modrinth", "download_url": "...", "filename": "...", "sha512": "..." } ``` Engine metadata format (corrected this session): ``` engineType = "neoforge" (not "minecraft") engineVersion = "1.21.4" (not "neoforge-1.21.4") ``` --- ## Remaining Issues ### Response Corruption (Needs Verification) One early `curl` output appeared corrupted. Reproduce before wiring portal: ```bash curl -sS -D headers.txt -o body.txt ... ``` ### API Error Mapping Refinement - `"mod already exists"` should return `409` (currently `502`) --- ## Next Steps 1. Reproduce and resolve response corruption issue 2. Tighten API error mapping (`409` for duplicate) 3. Wire install endpoint to portal UI 4. Begin file browser (see `OPEN_THREADS.md`)