docs: replace session log with full mod lifecycle summary

This commit is contained in:
jester 2026-02-28 22:25:39 +00:00
parent 751684fb53
commit be79b5e43c

View File

@ -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 `<serverRoot>/mods`
- Enables via filename convention
- Soft delete moves to `<serverRoot>/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`)