docs: replace session log with full mod lifecycle summary
This commit is contained in:
parent
751684fb53
commit
be79b5e43c
@ -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
|
**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
|
||||||
|
|
||||||
## What Was Completed
|
- Loader normalization fixed (`neoforge` / `fabric` / `forge` / `quilt`)
|
||||||
|
- `.jar`-only enforcement
|
||||||
|
- Host allowlist: `cdn.modrinth.com`
|
||||||
|
- Publish-date safe sorting
|
||||||
|
- SHA512 preferred (fallback to SHA1)
|
||||||
|
|
||||||
### 1. Modrinth Resolver (API Side)
|
### Agent
|
||||||
|
|
||||||
Implemented `resolveCompatibleVersion()`:
|
Mod install flow:
|
||||||
|
|
||||||
- Fetches project versions from Modrinth API
|
- Accepts direct Modrinth artifact URL
|
||||||
- Filters by loader (`neoforge`, `fabric`, `quilt`, etc.) and game version (e.g. `1.21.4`)
|
- Validates:
|
||||||
- Excludes alpha builds
|
|
||||||
- Sorts by publish date (newest first)
|
|
||||||
- Selects primary file and extracts `filename`, `downloadUrl`, `sha512` (preferred), `sha1` (fallback), `size`
|
|
||||||
|
|
||||||
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
|
- HTTPS only
|
||||||
- Must resolve to allowed host
|
- Allowed host (`cdn.modrinth.com`, `artifacts.zerolaghub.com`)
|
||||||
- Max 3 redirects
|
- 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`
|
||||||
|
|
||||||
### 5. Agent Filename Validation Updated
|
### Frontend
|
||||||
|
|
||||||
Modrinth filenames contain `+` (e.g. `sodium-neoforge-0.6.13+mc1.21.4.jar`).
|
- Mod search drawer implemented
|
||||||
|
- Installed mods panel implemented
|
||||||
Updated allowed character set from `[a-zA-Z0-9._-]` to `[a-zA-Z0-9._+-]`.
|
- Installed flag merged into search results (heuristic matching)
|
||||||
|
- Install button functional
|
||||||
Still blocks: `/`, `\`, whitespace, path traversal, control characters.
|
- Enable / disable functional
|
||||||
|
- Delete functional
|
||||||
### 6. End-to-End Install Verified
|
- Toast notifications added
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Security Controls Now In Place
|
## 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:**
|
**Agent level:**
|
||||||
- HTTPS-only downloads
|
- HTTPS-only downloads
|
||||||
@ -114,12 +94,12 @@ Second install correctly returned `mod already exists` — duplicate protection
|
|||||||
- Redirect limit (max 3)
|
- Redirect limit (max 3)
|
||||||
- 200MB hard cap
|
- 200MB hard cap
|
||||||
- SHA256 verification
|
- SHA256 verification
|
||||||
- Filename sanitization
|
- Filename sanitization (`[a-zA-Z0-9._+-]` — `+` added for Modrinth filenames)
|
||||||
- Duplicate prevention
|
- Duplicate prevention
|
||||||
- Ownership enforcement (`minecraft:minecraft`)
|
- Ownership enforcement (`minecraft:minecraft`)
|
||||||
- Temp file cleanup on failure
|
- Temp file cleanup on failure
|
||||||
- Controlled mod directory write path
|
- Controlled mod directory write path
|
||||||
- Cache invalidation
|
- Cache invalidation after every mutation
|
||||||
|
|
||||||
**API level:**
|
**API level:**
|
||||||
- Auth + ownership enforcement
|
- 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 |
|
```json
|
||||||
|-----------|--------|
|
{
|
||||||
| Modrinth resolver | ✅ Stable |
|
"source": "modrinth",
|
||||||
| API route | ✅ Working |
|
"download_url": "...",
|
||||||
| Agent integration | ✅ Working |
|
"filename": "...",
|
||||||
| Install flow | ✅ Confirmed |
|
"sha512": "..."
|
||||||
| Duplicate protection | ✅ Working |
|
}
|
||||||
| Security model | ✅ Intact |
|
```
|
||||||
|
|
||||||
|
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)
|
### 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
|
```bash
|
||||||
curl -sS -D headers.txt -o body.txt ...
|
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
|
### API Error Mapping Refinement
|
||||||
|
|
||||||
Currently all agent non-2xx responses return `502`. Should improve:
|
- `"mod already exists"` should return `409` (currently `502`)
|
||||||
|
|
||||||
- `"mod already exists"` → `409 Conflict`
|
|
||||||
|
|
||||||
Small API enhancement, low effort.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
1. **Reproduce and resolve** response corruption issue with clean curl test
|
1. Reproduce and resolve response corruption issue
|
||||||
2. **Tighten API error mapping** (`409` for duplicate, etc.)
|
2. Tighten API error mapping (`409` for duplicate)
|
||||||
3. **Wire install endpoint to portal UI** — display `Installing`, `Already installed`, `Failed (reason)`
|
3. Wire install endpoint to portal UI
|
||||||
4. **Begin dev-server linking system** (see `docs/architecture/dev-to-game-artifact-pipeline.md`)
|
4. Begin file browser (see `OPEN_THREADS.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.**
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user