# 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**: 4–10 friends, LAN-based
- **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`.

## 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 ~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

### 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)

### 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

## 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

- 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
- 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
- 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)

## 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
