docs: fold the monitor service into the stack-level docs

plan/23-monitor.md already covered the design; this makes the monitor
discoverable from the places someone actually starts reading.

- README: stack table, vhost list, DNS names, layout, and a setup section
  covering the bcrypt credential, the no-published-port model, and the
  --hard-on-first-deploy requirement.
- plan/00-overview.md: drop (PLANNED), add to the always-on service list, and
  note that it starts fine with Minecraft down (missing log and refused RCON
  are reported states, not fatal).
- plan/19-routes.md: vhost row, the four GET routes with their contracts, and
  the internal mcnet edges (mc-status for rosters so it needs no Drasl creds,
  RCON for TPS, Kuma probing healthz around caddy).
- caddy/Caddyfile: the snippet index had drifted — it listed five of nine
  vhosts. Added the four missing ones.

Two gotchas promoted to CLAUDE.md because they cost real time and will recur:

- Compose interpolates $-sequences in .env VALUES, so a bare bcrypt hash is
  silently corrupted before reaching the container, and the same bug bites
  again when writing one to a remote .env over ssh. Also records that caddy
  and filestash want opposite hash formats.
- The monitor container runs as root deliberately: /data is drwxr-x--- owned
  by uid 1000, so a USER directive would make the tailer silently read nothing
  while still returning 200.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
oierandClaude Opus 5 committed 2026-08-01 23:30:42 +02:00
1 parent bc99427ef9
commit bac2372c95
5 files changed
+119 -4

No files matched your search

+34
View File
@@ -37,6 +37,8 @@ Internet ── host nginx (TLS, LE certs, HSTS) ──► caddy (127.0.0.1:8880
upload.${BASE_DOMAIN} ─► filestash (player file share, ONE shared login, writable)
files.${BASE_DOMAIN} ─► caddy (read-only, no-auth browse of the same share dir;
per-host on/off via FILES_BROWSE — off on cochi)
monitor.${BASE_DOMAIN}─► monitor (ADMIN ONLY: latest.log stream + dashboard tiles,
caddy basic_auth; per-host on/off MONITOR_ENABLED)
distribution.${BASE} ─► static site from ANOTHER repo (DISTRIBUTION_WEB_ROOT)
minecraft :25565 (+ 24454/udp Simple Voice Chat)
@@ -153,6 +155,34 @@ works. After any filestash config change, check the browser-facing values
the broken redirect announced itself there (`msg=Redirecting to http://https://`)
while every other check passed.
### Compose eats `$` in `.env` values — quote every hash
`docker compose` interpolates `$`-sequences inside `.env` **values**, so a bare
bcrypt hash is silently corrupted before it ever reaches a container:
```
MONITOR_PASSWORD_HASH=$2a$14$abcdefgHIJKLmnop.qrstuv # WRONG
→ container sees $2a$14.qrstuv # $abcdefg… eaten as an unset var
MONITOR_PASSWORD_HASH='$2a$14$abcdefgHIJKLmnop.qrstuv' # single quotes fix it
```
Verified, not folklore. Every login fails with no hint as to why — the hash
looks fine in `.env` and `docker compose config` re-escapes it to `$$…`, which
looks wrong but is correct. **The same bug bites over SSH**: writing a hash to a
remote `.env` through `ssh host "...$HASH..."` lets the remote shell expand it.
Transport it base64-encoded and verify with `md5sum` on both ends.
Note the two services want *opposite* formats: caddy `basic_auth` needs
**bcrypt** (`caddy hash-password`), filestash needs **`$6$`** (`openssl passwd
-6`) and silently rejects bcrypt. Never copy a hash between them.
### Monitor runs as root on purpose
`/data` is `drwxr-x---` owned by uid 1000 (itzg). A non-root container user
cannot traverse it, so the log tailer would read nothing — **silently**, while
still answering HTTP 200. Adding a `USER` directive to `docker/monitor/` as
"hardening" breaks it. What actually bounds the risk there: every mount is `:ro`,
no port is published, and the one credential it holds (RCON) can only send
commands from a compile-time allowlist.
### Memory sizing
~50–100 mods, 8 players, 1.21.1: `INIT_MEMORY: 4G`, `MAX_MEMORY: 10G`,
`USE_AIKAR_FLAGS: TRUE`. **The pack is now 164 jars / 133 server-side** (Create
@@ -219,6 +249,10 @@ Full design settled across WS1–WS6; all tasks unchecked, ready to execute.
htpasswd login, writable, rendered `:ro` config. See `plan/20-files.md`.
Follow-ups: add the Uptime Kuma HTTP monitor for `files.`; the share is
**outside mc-backup** and unquota'd (accepted — eyeballed).
- [x] **Monitor** (`monitor.`, 2026-08-01) — admin-only `latest.log` stream +
dashboard tiles (status, TPS via allowlisted RCON, players, disk, backups,
joins/leaves). Live on cochi, all three phases done. See `plan/23-monitor.md`.
Follow-up: add the Uptime Kuma monitor for `http://monitor:8080/healthz`.
- [ ] **Verify NeoForge version** against current stable before first deploy.
- [x] **Bootstrap Drasl admin** account (`admin`, 2026-06-10). Key is
`DefaultAdmins` (not `DefaultAdminUsernames`); first admin needs an invite-flip.