online-mode=false made the server skip the online-auth handshake entirely, so authlib-injector never validated sessions against Drasl: players got offline-hash UUIDs and no skin textures (everyone Steve). authlib-injector's whole purpose is to redirect the *online* handshake from Mojang to Drasl, so online-mode must be true; only the 1.21+ secure profile stays off (ENFORCE_SECURE_PROFILE=false + Drasl SignPublicKeys=false). Correct the conflation in CLAUDE.md and plan/05-minecraft.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.6 KiB
Minecraft server — itzg NeoForge
Summary
itzg/minecraft-server:java21, NeoForge 1.21.1, offline-mode + authlib-injector
pointing at drasl. Mods come from a local ./server-mods volume (mounted at
/mods), NOT packwiz — see 04-mods.md. itzg installs MC + NeoForge + libraries
on first boot and auto-syncs /mods → /data/mods every boot; the stack assumes
the internet is present (for the NeoForge installer), so TYPE=NEOFORGE boots
normally every time.
Server config
TYPE=NEOFORGE, VERSION=1.21.1, NEOFORGE_VERSION=21.1.x
REMOVE_OLD_MODS=TRUE # ./server-mods is the authoritative mod set
volume ./server-mods:/mods:ro
./server-mods is populated by tooling/sync-server-mods.sh (the both/server
subset of mods-sides.json, copied from $DISTRIBUTION_WEB_ROOT). First boot
installs MC + NeoForge + libraries into the mc_data volume; every boot itzg
mirrors /mods into /data/mods and prunes removed ones (REMOVE_OLD_MODS).
Auth / config
ONLINE_MODE=TRUE
ENFORCE_SECURE_PROFILE=FALSE # pairs with drasl SignPublicKeys=false
JVM_OPTS=-javaagent:/extras/authlib-injector.jar=http://auth.${BASE_DOMAIN}/authlib-injector
authlib URL uses the auth subdomain; resolves internally via the caddy alias.
(Server still needs the agent even though Fjord clients have it built in.)
ONLINE_MODE MUST be true. authlib-injector works by redirecting the
server's normal online-auth handshake from Mojang to Drasl. With
ONLINE_MODE=FALSE the server skips that handshake entirely → players get
offline-hash UUIDs, no session validation, and no skins (the server never
fetches textures from Drasl). The thing that must stay off for 1.21+ is the
secure profile (ENFORCE_SECURE_PROFILE=FALSE + Drasl SignPublicKeys=false),
not online-mode. Symptom of getting this wrong: everyone can join but everyone is
Steve, and usercache.json shows offline-hash UUIDs.
Memory: INIT_MEMORY 4G, MAX_MEMORY 10G, USE_AIKAR_FLAGS TRUE.
RCON enabled for mc-backup.
Server operators (OP)
Do NOT use the itzg OPS env var. It resolves usernames to UUIDs via a
Mojang lookup, but this server is ONLINE_MODE=TRUE validated against Drasl —
Drasl assigns its own UUIDs (not Mojang, not the offline-hash). A Mojang-resolved UUID
would not match the player's real session UUID, so the op entry would be inert
(and could clobber a correct ops.json on reboot).
Op via RCON instead. The player must have connected at least once so their
real Drasl UUID is cached in /data/usercache.json; then op <name> writes that
cached UUID to ops.json. On the host:
ssh cochi
docker exec minecraft rcon-cli list # confirm cache / who's on
docker exec minecraft sh -c 'cat /data/usercache.json' # verify name→UUID cached
docker exec minecraft rcon-cli op <name>
docker exec minecraft sh -c 'cat /data/ops.json' # verify level 4 + right UUID
ops.json lives in the mc_data volume → persists across restarts; no reboot
needed (takes effect live). Current op: oier
(7fbc9adb-bcf4-3913-9a99-46d46acd1009), level 4, set 2026-06-21.
Tasks
- Compose: authlib URL
http://auth.${BASE_DOMAIN}/authlib-injector - Compose: mount
./server-mods:/mods:ro+REMOVE_OLD_MODS=TRUE - Run
tooling/sync-server-mods.shbefore bring-up (populates./server-mods) - Mount
./runtimefor authlib-injector.jar at/extras depends_on: drasl (authlib must resolve at boot)- Confirm
ENFORCE_SECURE_PROFILE=FALSE↔ draslSignPublicKeys=false - Verify a client joins with their drasl skin