Files
ulicraft-server-v1/CLAUDE.md
2026-05-24 04:03:42 +02:00

13 KiB
Raw Permalink Blame History

Minecraft LAN Party 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.

Goals & Constraints

  • Players: 410 friends, LAN-based
  • Modpack vibe: Kitchen-sink (tech + magic + exploration + QoL)
  • Pack approach: Manually curated (~50100 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.

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

Clients (LAN party guests):
    Prism Launcher  →  authlib-injector account pointing at Drasl
                   →  joins minecraft:25565

Auth Flow (end-to-end)

  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.

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+."

authlib-injector JVM agent

  • Syntax: -javaagent:/path/to/authlib-injector.jar=<yggdrasil-api-url>
  • The URL after = is the API root, which for Drasl is <BaseURL>/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.

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.

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

Memory sizing

With ~50100 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

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.

Keycloak Client Configuration

In the homelab realm (or whatever the realm is called):

  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)

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

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

Reference URLs

Communication Preferences (carry-over from past sessions)

  • 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