Files
ulicraft-server-v1/plan/05-minecraft.md
Oier Bravo Urtasun d12071df04 fix(mc): ONLINE_MODE=true — authlib-injector needs online handshake for skins
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>
2026-06-24 22:34:47 +02:00

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.sh before bring-up (populates ./server-mods)
  • Mount ./runtime for authlib-injector.jar at /extras
  • depends_on: drasl (authlib must resolve at boot)
  • Confirm ENFORCE_SECURE_PROFILE=FALSE ↔ drasl SignPublicKeys=false
  • Verify a client joins with their drasl skin