Drop stale Keycloak/OIDC, NetBird, AdGuard/.home.local, airgap, and Prism-specific content. Reflect the current reality: single docker-compose.yml (drasl, minecraft, mc-backup, caddy, nmsr, uptime-kuma), Drasl password-login auth (no OIDC), public nginx+LE -> caddy ingress, DNS handled outside the repo, FjordLauncher for guests. Point to plan/ as the source of truth; keep the still-valid gotchas (secure-profile, authlib-injector, NeoForge pinning, mod source) and carry-over comms prefs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
144 lines
6.9 KiB
Markdown
144 lines
6.9 KiB
Markdown
# 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
|
||
> 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)
|
||
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
|
||
mc-backup ── RCON → minecraft (6h interval, prune 14d, world-only)
|
||
```
|
||
|
||
Bring-up: render configs → 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.
|
||
- **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`.
|
||
|
||
## 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.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.
|
||
|
||
### 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`, 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).
|
||
|
||
## 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. Import the pack `https://pack.${BASE_DOMAIN}/pack.toml`.
|
||
4. Connect to `${BASE_DOMAIN}` (`:25565`).
|
||
|
||
## Open / Pending Work
|
||
|
||
- [ ] **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.
|