Two follow-ups from the packwiz→04-mods migration: curate cosmetic-client entries in mods-sides.json, and rework the landing join flow off the dead packwiz pack.toml URL onto the HeliosLauncher/distribution.json flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
173 lines
9.0 KiB
Markdown
173 lines
9.0 KiB
Markdown
# Ulicraft Server — Project Context
|
||
|
||
> Self-hosted modded Minecraft (NeoForge 1.21.1) with self-hosted auth/skins
|
||
> (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`.
|
||
This file is a fast orientation + the durable decisions/gotchas; when it disagrees
|
||
with `plan/` or the code, those win.
|
||
|
||
## Goals & Constraints
|
||
|
||
- **Players**: 4–10 friends
|
||
- **Modpack vibe**: Kitchen-sink (tech + magic + exploration + QoL)
|
||
- **Pack approach**: Manually curated (~50–100 mods), NOT a forked existing pack
|
||
- **Minecraft version**: 1.21.1, **NeoForge** (not classic Forge)
|
||
- **Auth/skins**: Drasl (Yggdrasil-compatible), **password login** — no OIDC
|
||
- **DNS**: handled OUTSIDE this repo; point `${BASE_DOMAIN}` + subdomains at the host
|
||
- **Persistence horizon**: long-term service, not disposable
|
||
|
||
## Architecture
|
||
|
||
One unified `docker-compose.yml`. The host's own nginx terminates TLS (Let's
|
||
Encrypt) and reverse-proxies every vhost to caddy (published localhost-only) by
|
||
Host header. caddy is the internal router; containers reach the stack's own names
|
||
via caddy's `mcnet` aliases (`auth.`, `pack.`).
|
||
|
||
```
|
||
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)
|
||
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 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 → 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.
|
||
|
||
## Key Decisions
|
||
|
||
- **NeoForge over Forge** — the 1.21.x kitchen-sink ecosystem is overwhelmingly
|
||
NeoForge; the Forge team migrated. `itzg/minecraft-server` via `TYPE=NEOFORGE`.
|
||
- **Drasl over Ely.by / vanilla offline** — self-hosted Go service, Yggdrasil
|
||
drop-in, skins + capes, actively maintained. Run with **password login**
|
||
(no OIDC/Keycloak in this stack).
|
||
- **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.
|
||
- **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
|
||
|
||
### Minecraft 1.21+ secure profile
|
||
- Server: `ENFORCE_SECURE_PROFILE=FALSE` (`enforce-secure-profile=false`)
|
||
- Drasl: `SignPublicKeys = false`
|
||
- Linked. Drasl docs: *"Mixed authentication does not work with
|
||
`SignPublicKeys = true` on Minecraft 1.21+."*
|
||
|
||
### authlib-injector JVM agent
|
||
- Syntax: `-javaagent:/extras/authlib-injector.jar=<yggdrasil-api-url>`
|
||
- URL after `=` is the **API root** = `<BaseURL>/authlib-injector`. For this
|
||
stack: `http://auth.${BASE_DOMAIN}/authlib-injector` (internal, over mcnet).
|
||
- **Must match between server and client** (clients use the public
|
||
`https://auth.${BASE_DOMAIN}/authlib-injector`), else session validation fails
|
||
silently with "Invalid session".
|
||
- Releases: https://github.com/yushijinhun/authlib-injector — jar lives in
|
||
`runtime/` (mounted at `/extras/authlib-injector.jar`).
|
||
|
||
### NeoForge version pinning
|
||
Mods are picky about exact minor versions. Compose pins `NEOFORGE_VERSION:
|
||
"21.1.233"` — **must match the distribution's NeoForge** (`distribution.json`),
|
||
since mods are built against it (e.g. ferritecore≥218, farmersdelight≥219).
|
||
**Verify against https://projects.neoforged.net/neoforged/neoforge before
|
||
deploying.** Never `"latest"` for a stable server.
|
||
|
||
### 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`,
|
||
`USE_AIKAR_FLAGS: TRUE`.
|
||
|
||
## Config (.env)
|
||
|
||
`BASE_DOMAIN`, `RCON_PASSWORD`, `CADDY_HTTP_PORT`/`CADDY_HTTP_BIND`,
|
||
`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)
|
||
|
||
Launcher: **FjordLauncherUnlocked** (Prism fork, first-class authlib-injector),
|
||
mirrored at apex `/launcher/`. Per guest:
|
||
1. Download from `https://${BASE_DOMAIN}` → `/launcher/`.
|
||
2. Add an authlib-injector account, URL
|
||
`https://auth.${BASE_DOMAIN}/authlib-injector` (register/login at
|
||
`https://auth.${BASE_DOMAIN}`).
|
||
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
|
||
|
||
### Roadmap — mod migration follow-ups (post packwiz→04-mods)
|
||
- [ ] **Curate `mods-sides.json` cosmetic-client mods.** tomllib auto-flagged 11
|
||
hard client-only mods; the other 65 default `both`, including client-cosmetic
|
||
ones (BetterF3, entityculling, MouseTweaks, ponderjs, fancymenu, drippy, melody,
|
||
welcomescreen, Searchables, JustEnoughResources, TravelersTitles, yeetus…) that
|
||
load harmlessly on the server but waste RAM. Hand-edit them to `client` so
|
||
`sync-server-mods.sh` drops them from `./server-mods`. (Also review `appleskin`,
|
||
flagged `client` — flip to `both` if server-side food data wanted.)
|
||
- [ ] **Rework the landing join flow off packwiz.** `pack.` vhost is gone;
|
||
`site.ts packwizUrl` is a dead-URL placeholder (kept w/ TODO so the build
|
||
passes) and `i18n/ui.ts` join steps (all 3 langs) still tell guests to import
|
||
`pack.toml`. Redesign step 3 around the HeliosLauncher/`distribution.json`
|
||
install flow. See `plan/04-mods.md` + `site.ts` TODO.
|
||
|
||
- [ ] **Mod shortlist** for the kitchen-sink pack: Performance (Embeddium,
|
||
FerriteCore, ModernFix), Tech (Mekanism, Create, IE, AE2), Magic (Ars Nouveau,
|
||
Botania, Iron's Spells), Storage (Sophisticated, Functional), Exploration
|
||
(YUNG's, Repurposed Structures), QoL (JEI, Jade, IPN, AppleSkin), World gen
|
||
(Tectonic, Terralith — verify NeoForge 1.21.1), Compat (Polymorph), Map (Xaero),
|
||
Voice (Simple Voice Chat — 24454/udp already in compose).
|
||
- [ ] Server-side perf mods (C2ME, etc.).
|
||
- [ ] **Verify NeoForge version** against current stable before first deploy.
|
||
- [ ] **Bootstrap Drasl admin** account.
|
||
|
||
## Reference URLs
|
||
|
||
- Drasl: https://github.com/unmojang/drasl — config docs:
|
||
https://github.com/unmojang/drasl/blob/master/doc/configuration.md
|
||
- authlib-injector: https://github.com/yushijinhun/authlib-injector
|
||
- itzg/minecraft-server: https://github.com/itzg/docker-minecraft-server
|
||
(NeoForge: …/docs/types-and-platforms/server-types/forge.md)
|
||
- itzg/mc-backup: https://github.com/itzg/docker-mc-backup
|
||
- NeoForge versions: https://projects.neoforged.net/neoforged/neoforge
|
||
- NMSR: https://github.com/NickAcPT/nmsr-rs
|
||
- Uptime Kuma: https://github.com/louislam/uptime-kuma
|
||
- FjordLauncherUnlocked: https://github.com/hero-persson/FjordLauncherUnlocked
|
||
- Reference packs (inspiration, NOT forking): ATM10, Leaking Kitchen Sink.
|
||
|
||
## Communication Preferences (carry-over)
|
||
|
||
- Concise and direct; diagnose + resolve without demanding repro steps.
|
||
- Prefer iterative single-change updates over large rewrites.
|
||
- Cautious scripts that halt on ambiguity rather than proceeding silently.
|
||
- Avoid over-engineering when a simple answer suffices.
|
||
- Spanish or English both fine.
|