feat(mods): replace packwiz with distribution-fed custom mod volume
Drop packwiz entirely. The server now loads a filtered mod subset from a local ./server-mods volume instead of fetching a packwiz pack at boot. New flow: - tooling/classify-mods.py (tomllib only, no network) reads each jar's META-INF/neoforge.mods.toml and seeds mods-sides.json with a side (client|server|both; default both). Existing entries are preserved. - mods-sides.json: committed, keyed by jar filename, hand-editable override surface. Initial seed: 65 both, 11 client, 0 server (76 jars). - tooling/sync-server-mods.sh mirrors the both/server jars from $DISTRIBUTION_WEB_ROOT into ./server-mods/ (authoritative; prunes strays; halts on a missing jar). Replaces render-pack.sh. - compose: minecraft mounts ./server-mods:/mods:ro with REMOVE_OLD_MODS=TRUE (itzg auto-syncs /mods -> /data/mods). Removed PACKWIZ_URL, the pack. extra_host (auth. kept for authlib), and depends_on caddy (drasl kept). Both classify-mods.py and sync-server-mods.sh resolve their source dir as: CLI arg / MODS_DIST_DIR env / $DISTRIBUTION_WEB_ROOT (in that order). Removed: tooling/render-pack.sh + add-custom-mod.sh (packwiz tooling), pack/ (packwiz source), custom/ (76 jars now sourced from the distribution), the pack. caddy vhost + its mounts/alias, and the packwiz .gitignore block. Client distribution is UNCHANGED: HeliosLauncher still installs the full modset from distribution.json. Docs (CLAUDE.md, plan/04/05/14, runtime) updated; plan/04-packwiz.md stubbed as superseded by plan/04-mods.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
41
CLAUDE.md
41
CLAUDE.md
@@ -1,7 +1,7 @@
|
||||
# Ulicraft Server — Project Context
|
||||
|
||||
> Self-hosted modded Minecraft (NeoForge 1.21.1) with self-hosted auth/skins
|
||||
> (Drasl), a packwiz modpack, avatar rendering, a guest landing page, and uptime
|
||||
> (Drasl), a distribution-fed modpack, avatar rendering, a guest landing page, and uptime
|
||||
> monitoring. Public, internet-present deployment behind the host's nginx + TLS.
|
||||
|
||||
**`plan/` is the source of truth for the design.** Start at `plan/00-overview.md`.
|
||||
@@ -30,18 +30,17 @@ Internet ── host nginx (TLS, LE certs, HSTS) ──► caddy (127.0.0.1:8880
|
||||
│ routes by Host:
|
||||
${BASE_DOMAIN} ─► /srv/www landing + /launcher/ downloads
|
||||
auth.${BASE_DOMAIN} ─► drasl (Yggdrasil API + web UI)
|
||||
pack.${BASE_DOMAIN} ─► packwiz files + /custom/ jars
|
||||
avatar.${BASE_DOMAIN} ─► nmsr (skin/avatar renderer, Drasl-backed)
|
||||
status.${BASE_DOMAIN} ─► uptime-kuma (status page + monitors)
|
||||
distribution.${BASE} ─► static site from ANOTHER repo (DISTRIBUTION_WEB_ROOT)
|
||||
|
||||
minecraft :25565 (+ 24454/udp Simple Voice Chat)
|
||||
└ ONLINE_MODE=false, -javaagent authlib-injector → http://auth.${BASE_DOMAIN}/authlib-injector
|
||||
└ mods via PACKWIZ_URL → http://pack.${BASE_DOMAIN}/pack.toml
|
||||
└ mods from ./server-mods volume (/mods, REMOVE_OLD_MODS) — see plan/04-mods.md
|
||||
mc-backup ── RCON → minecraft (6h interval, prune 14d, world-only)
|
||||
```
|
||||
|
||||
Bring-up: render configs → build landing → fetch launcher → `docker compose up -d --build`.
|
||||
Bring-up: render configs → sync server mods → build landing → fetch launcher → `docker compose up -d --build`.
|
||||
See `plan/12-build-order.md` and `plan/14-deploy.md`. Deploy to prod host `cochi`
|
||||
via the `deploy` skill.
|
||||
|
||||
@@ -55,8 +54,11 @@ via the `deploy` skill.
|
||||
- **Manual curation** (user choice, flagged expensive) — if it gets painful,
|
||||
fork-and-trim Leaking Kitchen Sink is still on the table (closest vibe match).
|
||||
- **itzg/minecraft-server** base image — de-facto standard, auto-installs NeoForge.
|
||||
- **packwiz** for the modpack — `PACKWIZ_URL` drives server mod install; clients
|
||||
import the same `pack.toml`. Jars come from the upstream CDN per `.pw.toml`.
|
||||
- **Distribution-fed mod volume (replaced packwiz)** — the modset's source of
|
||||
truth is the HeliosLauncher distribution repo (`$DISTRIBUTION_WEB_ROOT`).
|
||||
Clients install the full set via `distribution.json`. The SERVER loads a
|
||||
filtered subset (`both`/`server`) from a mounted `./server-mods` volume
|
||||
(`REMOVE_OLD_MODS=TRUE`). See `plan/04-mods.md`.
|
||||
|
||||
## Critical Gotchas
|
||||
|
||||
@@ -81,10 +83,17 @@ Mods are picky about exact minor versions. Compose pins `NEOFORGE_VERSION:
|
||||
"21.1.209"`. **Verify against https://projects.neoforged.net/neoforged/neoforge
|
||||
before deploying.** Never `"latest"` for a stable server.
|
||||
|
||||
### Mod source preference
|
||||
Prefer **Modrinth over CurseForge** (`packwiz modrinth add <slug>`). CF needs
|
||||
`CF_API_KEY` and is flakier. Tag client-only mods `side = "client"` so the server
|
||||
doesn't pull and crash on them.
|
||||
### Mod source + server filtering
|
||||
The full modset lives in the distribution repo (`$DISTRIBUTION_WEB_ROOT/servers/
|
||||
ulicraft-1.21.1/forgemods/required/*.jar`), managed by Nebula. Clients get
|
||||
everything via `distribution.json`. The server runs a **filtered subset**:
|
||||
`tooling/classify-mods.py` reads each jar's `neoforge.mods.toml` with tomllib and
|
||||
seeds `mods-sides.json` (`client|server|both`, default `both`), then
|
||||
`tooling/sync-server-mods.sh` copies the `both`/`server` jars into `./server-mods`
|
||||
(mounted at `/mods`). Hard client-only mods (sodium/iris) classify as `client`
|
||||
automatically; cosmetic-client mods that declare `BOTH` stay `both` until you
|
||||
hand-edit `mods-sides.json` to `client`. No Modrinth/packwiz, no network at
|
||||
classify/sync time. See `plan/04-mods.md`.
|
||||
|
||||
### Memory sizing
|
||||
~50–100 mods, 8 players, 1.21.1: `INIT_MEMORY: 4G`, `MAX_MEMORY: 10G`,
|
||||
@@ -93,9 +102,11 @@ doesn't pull and crash on them.
|
||||
## Config (.env)
|
||||
|
||||
`BASE_DOMAIN`, `RCON_PASSWORD`, `CADDY_HTTP_PORT`/`CADDY_HTTP_BIND`,
|
||||
`DISTRIBUTION_WEB_ROOT`, the Let's Encrypt block (`LE_EMAIL`, `LE_SUBDOMAINS`,
|
||||
`OVH_*`), optional `CF_API_KEY`. Rendered configs (drasl, nmsr, packwiz) come
|
||||
from `*.tmpl` via `tooling/render-config.sh` (substitutes `${BASE_DOMAIN}` only).
|
||||
`DISTRIBUTION_WEB_ROOT` (source of the modset + caddy `distribution.` mount), the
|
||||
Let's Encrypt block (`LE_EMAIL`, `LE_SUBDOMAINS`, `OVH_*`). Rendered configs
|
||||
(drasl, nmsr) come from `*.tmpl` via `tooling/render-config.sh` (substitutes
|
||||
`${BASE_DOMAIN}` only). Server mods are synced (not rendered) by
|
||||
`tooling/sync-server-mods.sh`.
|
||||
|
||||
## Client Distribution (guests)
|
||||
|
||||
@@ -105,7 +116,9 @@ mirrored at apex `/launcher/`. Per guest:
|
||||
2. Add an authlib-injector account, URL
|
||||
`https://auth.${BASE_DOMAIN}/authlib-injector` (register/login at
|
||||
`https://auth.${BASE_DOMAIN}`).
|
||||
3. Import the pack `https://pack.${BASE_DOMAIN}/pack.toml`.
|
||||
3. The launcher installs the modpack from the distribution (`distribution.json`).
|
||||
(NOTE: packwiz/`pack.` removed — the landing join step still references a
|
||||
packwiz URL and needs reworking around the launcher/distribution flow.)
|
||||
4. Connect to `${BASE_DOMAIN}` (`:25565`).
|
||||
|
||||
## Open / Pending Work
|
||||
|
||||
Reference in New Issue
Block a user