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>
This commit is contained in:
2026-06-24 22:34:47 +02:00
parent a67b9cf59e
commit d12071df04
3 changed files with 28 additions and 6 deletions

View File

@@ -35,7 +35,7 @@ Internet ── host nginx (TLS, LE certs, HSTS) ──► caddy (127.0.0.1:8880
distribution.${BASE} ─► static site from ANOTHER repo (DISTRIBUTION_WEB_ROOT) distribution.${BASE} ─► static site from ANOTHER repo (DISTRIBUTION_WEB_ROOT)
minecraft :25565 (+ 24454/udp Simple Voice Chat) minecraft :25565 (+ 24454/udp Simple Voice Chat)
└ ONLINE_MODE=false, -javaagent authlib-injector → http://auth.${BASE_DOMAIN}/authlib-injector └ ONLINE_MODE=true (validated against Drasl, NOT offline), -javaagent authlib-injector → http://auth.${BASE_DOMAIN}/authlib-injector
└ mods from ./server-mods volume (/mods, REMOVE_OLD_MODS) — see plan/04-mods.md └ mods from ./server-mods volume (/mods, REMOVE_OLD_MODS) — see plan/04-mods.md
mc-backup ── RCON → minecraft (6h interval, prune 14d, world-only) mc-backup ── RCON → minecraft (6h interval, prune 14d, world-only)
``` ```
@@ -67,6 +67,14 @@ via the `deploy` skill.
- Drasl: `SignPublicKeys = false` - Drasl: `SignPublicKeys = false`
- Linked. Drasl docs: *"Mixed authentication does not work with - Linked. Drasl docs: *"Mixed authentication does not work with
`SignPublicKeys = true` on Minecraft 1.21+."* `SignPublicKeys = true` on Minecraft 1.21+."*
- **This is the ONLY auth flag that goes off. `ONLINE_MODE` stays TRUE** — see
next gotcha. Do not conflate "secure profile off" with "offline mode".
### ONLINE_MODE must be TRUE (authlib-injector ≠ offline)
authlib-injector redirects the server's normal **online**-auth handshake from
Mojang to Drasl. `ONLINE_MODE=FALSE` skips that handshake → offline-hash UUIDs,
no Drasl session validation, and **no skins** (everyone joins but everyone is
Steve; `usercache.json` shows offline-hash UUIDs). Keep `ONLINE_MODE=TRUE`.
### authlib-injector JVM agent ### authlib-injector JVM agent
- Syntax: `-javaagent:/extras/authlib-injector.jar=<yggdrasil-api-url>` - Syntax: `-javaagent:/extras/authlib-injector.jar=<yggdrasil-api-url>`

View File

@@ -58,8 +58,13 @@ services:
USE_AIKAR_FLAGS: "TRUE" USE_AIKAR_FLAGS: "TRUE"
JVM_XX_OPTS: "-XX:+UseG1GC" JVM_XX_OPTS: "-XX:+UseG1GC"
# ── Auth: offline mode + authlib-injector pointing at Drasl # ── Auth: ONLINE mode validated against Drasl via authlib-injector
ONLINE_MODE: "FALSE" # MUST be true: authlib-injector redirects the normal online-auth
# handshake from Mojang to Drasl. online-mode=false skips the handshake
# entirely -> offline UUIDs, no session validation, NO SKINS for anyone.
# Secure-profile is the part that must stay off on 1.21+ (see
# ENFORCE_SECURE_PROFILE + Drasl SignPublicKeys=false), NOT online-mode.
ONLINE_MODE: "TRUE"
# Resolves over mcnet to caddy (alias auth.${BASE_DOMAIN}) -> drasl. # Resolves over mcnet to caddy (alias auth.${BASE_DOMAIN}) -> drasl.
JVM_OPTS: "-javaagent:/extras/authlib-injector.jar=https://auth.${BASE_DOMAIN}/authlib-injector" JVM_OPTS: "-javaagent:/extras/authlib-injector.jar=https://auth.${BASE_DOMAIN}/authlib-injector"

View File

@@ -25,21 +25,30 @@ mirrors `/mods` into `/data/mods` and prunes removed ones (`REMOVE_OLD_MODS`).
## Auth / config ## Auth / config
``` ```
ONLINE_MODE=FALSE ONLINE_MODE=TRUE
ENFORCE_SECURE_PROFILE=FALSE # pairs with drasl SignPublicKeys=false ENFORCE_SECURE_PROFILE=FALSE # pairs with drasl SignPublicKeys=false
JVM_OPTS=-javaagent:/extras/authlib-injector.jar=http://auth.${BASE_DOMAIN}/authlib-injector 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. 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.) (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`. Memory: `INIT_MEMORY 4G`, `MAX_MEMORY 10G`, `USE_AIKAR_FLAGS TRUE`.
RCON enabled for mc-backup. RCON enabled for mc-backup.
## Server operators (OP) ## Server operators (OP)
**Do NOT use the itzg `OPS` env var.** It resolves usernames to UUIDs via a **Do NOT use the itzg `OPS` env var.** It resolves usernames to UUIDs via a
Mojang lookup, but this server is `ONLINE_MODE=FALSE` with Drasl auth — Drasl Mojang lookup, but this server is `ONLINE_MODE=TRUE` validated against Drasl
assigns its own UUIDs (not Mojang, not the offline-hash). A Mojang-resolved UUID 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 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). (and could clobber a correct `ops.json` on reboot).