From 1709bcd34039338f5d9d893595fbf95e8b89526e Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Tue, 9 Jun 2026 02:16:42 +0200 Subject: [PATCH] docs(claude): rewrite project context for unified stack 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) --- CLAUDE.md | 343 ++++++++++++++++-------------------------------------- 1 file changed, 103 insertions(+), 240 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f7cf079..39a45f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,280 +1,143 @@ -# Minecraft LAN Party Server — Project Context +# Ulicraft Server — Project Context -> Self-hosted modded Minecraft server for a LAN party (~8 players), with -> persistent auth/skin infrastructure designed to outlive the LAN weekend -> and integrate with the existing homelab. +> 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, LAN-based +- **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 -- **Mod loader**: NeoForge (NOT classic Forge — see Decisions below) -- **Network**: LAN party context, but stack is persistent (lives past the weekend) -- **Auth**: Self-hosted Yggdrasil-compatible (Drasl) + Keycloak OIDC -- **Skins**: Handled by Drasl -- **Persistence horizon**: Long-term homelab service, not disposable - -## Existing Homelab Context (relevant to this project) - -- **Keycloak 26.0.7** running in Docker Compose (PostgreSQL 17, production mode, - bind-mounted secrets, xforwarded proxy headers). This is the IdP for everything. -- **NetBird** as the proxy/networking layer for remote access (NOT used for the - LAN party itself — see Decisions). -- **AdGuard Home** as recursive DNS resolver — used here for `*.home.local` LAN records. -- **Multi-VPS topology**: this Minecraft project likely runs on a local box or - beefier homelab node, not a VPS (RAM/CPU requirements). -- Operator timezone: Europe/Madrid. - -## Key Decisions (with reasoning) - -### NeoForge over classic Forge -Despite the user originally saying "Forge", the 1.21.x kitchen-sink ecosystem -(ATM10, Leaking Kitchen Sink, etc.) is overwhelmingly NeoForge in 2025/2026. -The original Forge team essentially migrated to NeoForge. `itzg/minecraft-server` -supports both via `TYPE=NEOFORGE`. Going with NeoForge 1.21.1. - -### Drasl over Ely.by or vanilla offline mode -- **Ely.by**: not self-hosted, Russian-hosted, no control over identity layer. -- **Vanilla offline + no auth server**: works for one weekend but skins break, - no persistent identity, doesn't match homelab philosophy. -- **Drasl**: self-hosted Go service, Yggdrasil-compatible, drop-in Mojang - replacement, supports skins + capes, **has first-class OIDC support with - PKCE** (perfect for Keycloak), actively maintained (current version 3.4.2+). - GPLv3 licensed. - -### LAN (not NetBird) for the party itself -NetBird is the homelab's remote access layer, but adding a mesh VPN on top -of a LAN party introduces complexity for guests with no upside. Drasl and -Minecraft live on the LAN; clients reach them via `drasl.home.local` resolved -through AdGuard. - -### Manual curation (user choice — flagged as expensive) -User insisted on manual curation despite my pushback that forking ATM10 or -Leaking Kitchen Sink would save weeks of crash-log triage. **Note for future -sessions**: if curation gets painful, the fork-and-trim option is still on -the table — Leaking Kitchen Sink is the closest match to the requested vibe. - -### itzg/minecraft-server as base image -The de facto standard. Handles NeoForge installation automatically via -`TYPE=NEOFORGE` + `VERSION` + `NEOFORGE_VERSION`. Supports Modrinth and -CurseForge mod auto-download via `MODRINTH_PROJECTS` and `CURSEFORGE_FILES`. +- **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 -``` -LAN segment - │ - ├── AdGuard Home → resolves drasl.home.local, keycloak.home.local - │ to the Docker host's LAN IP - │ - ├── Keycloak (existing, separate compose) - │ realm: homelab - │ client: drasl (confidential, PKCE S256) - │ - └── Minecraft host (this project's compose) - │ - ├── drasl :25585 (Yggdrasil API + web UI) - │ └── OIDC → Keycloak - │ - ├── minecraft :25565 (server) - │ :24454/udp (Simple Voice Chat, optional) - │ └── -javaagent:authlib-injector.jar=http://drasl.home.local:25585/authlib-injector - │ └── ONLINE_MODE=false (required for authlib-injector) - │ - └── mc-backup (itzg/mc-backup, 6h interval) - └── RCON → minecraft +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.`). -Clients (LAN party guests): - Prism Launcher → authlib-injector account pointing at Drasl - → joins minecraft:25565 +``` +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) ``` -## Auth Flow (end-to-end) +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. -1. Guest opens `http://drasl.home.local:25585` in browser. -2. Clicks "Register with Keycloak" → redirected to Keycloak login. -3. Logs in with homelab Keycloak credentials. -4. Returned to Drasl, picks a player name, uploads a skin. -5. Drasl shows them a "Minecraft Token" on their profile page. -6. In Prism Launcher: Settings → Accounts → Add authlib-injector account - with URL `http://drasl.home.local:25585/authlib-injector` and the - Minecraft Token as password. -7. Joins Minecraft server. Server validates session against Drasl (via - authlib-injector JVM agent), Drasl confirms, player enters with their skin. +## 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 incompatibility -- Server: must set `enforce-secure-profile=false` (compose: `ENFORCE_SECURE_PROFILE=FALSE`) -- Drasl: must set `SignPublicKeys = false` -- These are linked. The Drasl docs explicitly warn: *"Mixed authentication does - not work with `SignPublicKeys = true` on Minecraft 1.21+."* +### 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:/path/to/authlib-injector.jar=` -- The URL after `=` is the **API root**, which for Drasl is - `/authlib-injector`. -- **Must match between server and client.** If server uses - `http://drasl.home.local:25585/authlib-injector` and client uses anything - else (e.g. an IP), session validation fails silently with "Invalid session". -- Download from: https://github.com/yushijinhun/authlib-injector/releases -- Mount into the itzg container at `/extras/authlib-injector.jar`. - -### Keycloak reachability on LAN day -If Keycloak is only reachable via NetBird or only from the public internet, -**Drasl OIDC registration breaks on LAN-only day**. Options: -1. Expose Keycloak on the LAN (simplest) -2. Pre-register all guests before the party -3. Temporarily disable OIDC and allow password registration during the LAN - (`AllowPasswordLogin = true`, comment out the OIDC block) - -### HTTP vs HTTPS for OIDC -Modern browsers warn on plain HTTP for OIDC flows but they work. For a -production-grade setup, front Drasl with Caddy or Traefik using a local CA -cert (smallstep/step-ca pairs well with the existing homelab). For LAN-only -HTTP is fine. +- Syntax: `-javaagent:/extras/authlib-injector.jar=` +- URL after `=` is the **API root** = `/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 NeoForge minor versions. The compose currently -pins `NEOFORGE_VERSION: "21.1.209"` as a placeholder. **Verify against -https://projects.neoforged.net/neoforged/neoforge before deploying.** Don't -use `"latest"` for a stable server. - -### Drasl username vs player name -- **Drasl username** = OIDC user's email (used for web UI login) -- **Player name** = `preferred_username` from OIDC, or user-chosen if - `AllowChoosingPlayerName = true` -- Keycloak users must have email set. Verify the realm's email-as-username - setting and the `preferred_username` mapper are configured. +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** for itzg auto-download. CurseForge -requires an API key (`CF_API_KEY`) and has been historically flakier with -the itzg image. Use CF only for mods that are exclusive to it. - -## Compose Stack - -The working compose file is at `./docker-compose.yml`. Key environment vars -documented inline. Adjacent files: - -``` -. -├── docker-compose.yml -├── .env # RCON_PASSWORD, CF_API_KEY (if needed) -├── drasl/ -│ └── config/ -│ ├── config.toml # see ./drasl-config.toml example -│ └── keycloak-client-secret # single line, no trailing newline -├── extras/ -│ ├── authlib-injector.jar # downloaded from yushijinhun's releases -│ ├── modrinth-mods.txt # one mod slug per line -│ └── cf-mods.txt # only if using CurseForge mods -├── backups/ # mc-backup writes here -└── CLAUDE.md # this file -``` +Prefer **Modrinth over CurseForge** (`packwiz modrinth add `). 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 -With ~50–100 mods and 8 concurrent players on 1.21.1: -- `INIT_MEMORY: 4G`, `MAX_MEMORY: 10G` is a safe starting point -- Use `USE_AIKAR_FLAGS: TRUE` for the well-known GC tuning -- Monitor with `mc-monitor` (itzg makes one) if you want metrics +~50–100 mods, 8 players, 1.21.1: `INIT_MEMORY: 4G`, `MAX_MEMORY: 10G`, +`USE_AIKAR_FLAGS: TRUE`. -### Backups -`itzg/mc-backup` runs alongside, talks to the server via RCON, takes -snapshots every 6h, prunes after 14 days. Backups are world-only (no mod -jars), which is correct — mods are reproducible from the mod list files. +## Config (.env) -## Keycloak Client Configuration +`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). -In the `homelab` realm (or whatever the realm is called): +## Client Distribution (guests) -1. **Clients → Create client** - - Client type: `OpenID Connect` - - Client ID: `drasl` - - Name: `Drasl Minecraft Auth` - -2. **Capability config** - - Client authentication: **ON** (confidential client) - - Authentication flow: **Standard flow** only - - Disable: Direct access grants, Implicit, Service accounts - -3. **Login settings** - - Root URL: `http://drasl.home.local:25585` - - Valid redirect URIs: `http://drasl.home.local:25585/web/oidc-callback/Keycloak` - (the `Keycloak` at the end MUST match the `Name = "Keycloak"` in Drasl's - `[[RegistrationOIDC]]` block — case sensitive) - - Web origins: `http://drasl.home.local:25585` - -4. **Credentials tab** → copy Client Secret to - `./drasl/config/keycloak-client-secret` (no trailing newline). - -5. **Advanced tab** → PKCE Code Challenge Method: `S256` - -## Client Distribution (LAN guests) - -### Recommended launcher -**Prism Launcher** — open source, cross-platform (Win/Mac/Linux), first-class -authlib-injector support, can import/export instance ZIPs. - -### Per-guest one-time setup -1. Install Prism Launcher. -2. Add authlib-injector account: - - URL: `http://drasl.home.local:25585/authlib-injector` - - Username/password: their Drasl credentials (or Minecraft Token if - registered via Keycloak OIDC) -3. Import the modpack instance ZIP. - -### What to ship guests -- A Prism instance ZIP (right-click instance → Export Instance) -- A one-page README with the auth URL and import steps -- Optionally `authlib-injector.jar` for guests who refuse Prism +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 -These were on the roadmap when we ended the brainstorm: - -- [ ] **Mod shortlist for the kitchen-sink pack.** Categories to fill: - - Performance (Embeddium/Sodium-equivalent for NeoForge, FerriteCore, ModernFix) - - Tech/automation (Mekanism, Create, Immersive Engineering, AE2) - - Magic (Ars Nouveau, Botania, Iron's Spells 'n Spellbooks) - - Storage (Sophisticated Storage/Backpacks, Functional Storage) - - Exploration (YUNG's structures, Repurposed Structures, biome packs) - - QoL (JEI, Jade, JEI Resources, Inventory Profiles Next, AppleSkin) - - World gen (Tectonic, Terralith — check NeoForge 1.21.1 compat) - - Compat glue (Polymorph for recipe conflicts) - - Map (XaeroMinimap + Xaero World Map) - - Voice (Simple Voice Chat — port 24454/udp already in compose) -- [ ] **Decide on dimension/server-side performance mods** (C2ME, Lithium-equivalent) -- [ ] **Reverse proxy + local TLS** for Drasl (Caddy + step-ca recommended, - given the existing homelab) -- [ ] **DNS record** in AdGuard for `drasl.home.local` → Docker host LAN IP -- [ ] **Keycloak client** creation per the section above -- [ ] **Verify NeoForge version** against current stable before first deploy -- [ ] **Bootstrap admin in Drasl**: leave `AllowPasswordLogin = true` initially, - create the admin account, then optionally disable password login to force OIDC +- [ ] **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 repo: https://github.com/unmojang/drasl -- Drasl configuration docs: https://github.com/unmojang/drasl/blob/master/doc/configuration.md -- Drasl recipes (example configs): https://github.com/unmojang/drasl/blob/master/doc/recipes.md +- 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 -- itzg NeoForge docs: https://github.com/itzg/docker-minecraft-server/blob/master/docs/types-and-platforms/server-types/forge.md + (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 -- Prism Launcher: https://prismlauncher.org/ -- Reference modpacks for inspiration (NOT forking): - - ATM10 (Modrinth/CurseForge — ~500 mods, NeoForge 1.21.1) - - Leaking Kitchen Sink (CurseForge — trimmed ~150 mods, NeoForge 1.21.1) +- 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 from past sessions) +## Communication Preferences (carry-over) -- Concise and direct -- Diagnose and resolve without demanding detailed reproduction steps -- Prefer iterative single-change updates over large rewrites -- Cautious scripts that halt on ambiguity rather than proceeding silently -- Avoid over-engineered responses when a simple answer suffices -- Spanish or English fine; user is comfortable in both +- 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.