diff --git a/SESSION_LOG/2026-02-23-modrinth-install-integration.md b/SESSION_LOG/2026-02-23-modrinth-install-integration.md index 30d3cfb..8fe2a37 100644 --- a/SESSION_LOG/2026-02-23-modrinth-install-integration.md +++ b/SESSION_LOG/2026-02-23-modrinth-install-integration.md @@ -1,112 +1,92 @@ -# Session Summary – Modrinth Mod Install Integration +# 2026-02-23 — Modrinth Install + Full Mod Lifecycle -**Date:** 2026-02-23 **Type:** Foundational Architecture + Implementation -**Status:** Core flow complete, polish pending +**Status:** Full mod lifecycle complete, file browser next --- -## Objective +## Completed -Implement automated mod installation from Modrinth into Minecraft game containers via: +### API + +Implemented full mod lifecycle routes: ``` -Portal → API → Agent → Filesystem +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 ``` -With security controls, validation, and production-grade guardrails. +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 --- -## What Was Completed +## Current System Behavior -### 1. Modrinth Resolver (API Side) +Filesystem is source of truth. -Implemented `resolveCompatibleVersion()`: +No database persistence for: +- Mod install history +- Modrinth project ID mapping -- Fetches project versions from Modrinth API -- Filters by loader (`neoforge`, `fabric`, `quilt`, etc.) and game version (e.g. `1.21.4`) -- Excludes alpha builds -- Sorts by publish date (newest first) -- Selects primary file and extracts `filename`, `downloadUrl`, `sha512` (preferred), `sha1` (fallback), `size` +Installed matching is heuristic based on: +- Slug +- Filename +- Name -Hardened: -- `.jar` only enforcement -- Host allowlist (`cdn.modrinth.com`) -- Required hash presence validation -- Proper `400` mapping for "not found" and "no compatible version" - -### 2. Engine Metadata Bug Fix - -**Before (broken):** -``` -engineType = "minecraft" -engineVersion = "neoforge-1.21.4" -``` - -**After (correct):** -``` -engineType = "neoforge" -engineVersion = "1.21.4" -``` - -Loader filtering now works correctly. The version string no longer includes the loader prefix. - -### 3. API → Agent Payload Contract Fix - -**API was sending:** -```json -{ - "fileName": "...", - "downloadUrl": "...", - "size": 12345, - "sha512": "..." -} -``` - -**Agent expected:** -```json -{ - "source": "modrinth", - "download_url": "...", - "filename": "...", - "sha512": "..." -} -``` - -Mismatch caused `invalid mod_id` errors. API payload corrected to match agent contract. - -### 4. Agent Host Allowlist Expanded - -Added `cdn.modrinth.com` alongside existing `artifacts.zerolaghub.com`. - -Redirect policy still enforced: -- HTTPS only -- Must resolve to allowed host -- Max 3 redirects - -### 5. Agent Filename Validation Updated - -Modrinth filenames contain `+` (e.g. `sodium-neoforge-0.6.13+mc1.21.4.jar`). - -Updated allowed character set from `[a-zA-Z0-9._-]` to `[a-zA-Z0-9._+-]`. - -Still blocks: `/`, `\`, whitespace, path traversal, control characters. - -### 6. End-to-End Install Verified - -Mod successfully: -- Downloaded from Modrinth CDN -- SHA verified -- Written to `/tmp/zlh-agent/mods` -- Moved to `/opt/zlh/minecraft/neoforge/world/mods/` -- Ownership set to `minecraft`, permissions `0644` - -Second install correctly returned `mod already exists` — duplicate protection confirmed working. +Soft delete retains file permanently in `/mods-removed`. No retention policy implemented (intentional). --- -## Security Controls Now In Place +## 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 @@ -114,12 +94,12 @@ Second install correctly returned `mod already exists` — duplicate protection - Redirect limit (max 3) - 200MB hard cap - SHA256 verification -- Filename sanitization +- 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 +- Cache invalidation after every mutation **API level:** - Auth + ownership enforcement @@ -130,16 +110,23 @@ Second install correctly returned `mod already exists` — duplicate protection --- -## Current System State +## Payload Contract (API → Agent) -| Component | Status | -|-----------|--------| -| Modrinth resolver | ✅ Stable | -| API route | ✅ Working | -| Agent integration | ✅ Working | -| Install flow | ✅ Confirmed | -| Duplicate protection | ✅ Working | -| Security model | ✅ Intact | +```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") +``` --- @@ -147,54 +134,21 @@ Second install correctly returned `mod already exists` — duplicate protection ### Response Corruption (Needs Verification) -One early `curl` output appeared corrupted — part of the curl command echoed in the response. Needs clean reproduction to determine root cause: +One early `curl` output appeared corrupted. Reproduce before wiring portal: ```bash curl -sS -D headers.txt -o body.txt ... ``` -Could be: terminal wrap artifact, API returning raw non-JSON, agent returning malformed error, or logging leaking into response body. - -**Reproduce before wiring portal.** - ### API Error Mapping Refinement -Currently all agent non-2xx responses return `502`. Should improve: - -- `"mod already exists"` → `409 Conflict` - -Small API enhancement, low effort. +- `"mod already exists"` should return `409` (currently `502`) --- ## Next Steps -1. **Reproduce and resolve** response corruption issue with clean curl test -2. **Tighten API error mapping** (`409` for duplicate, etc.) -3. **Wire install endpoint to portal UI** — display `Installing`, `Already installed`, `Failed (reason)` -4. **Begin dev-server linking system** (see `docs/architecture/dev-to-game-artifact-pipeline.md`) - ---- - -## Future Work (Not Blocked On) - -- Automatic server restart after install -- Update detection and mod version upgrade logic -- Rollback integration (snapshot + shadow — see `docs/architecture/mod-deployment-safety.md`) -- Mod state tracking in DB -- Curated artifact coordinator - ---- - -## Strategic Note - -This session was not routine bug fixing. The work done today: - -- Aligned contract layers across API and agent -- Corrected the loader/version metadata architecture -- Established a secure mod ingestion path -- Created the foundation for the full mod management system - -The architecture decisions locked here (host allowlist, filename validation, channel separation, payload contract) will apply to every future mod install path including dev promotion, curated updates, and self-healing rollback. - -**This was a foundational milestone.** +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`)