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

+43 -1
View File
@@ -19,6 +19,7 @@ One compose file, one `docker compose up -d`:
| `uptime-kuma` | louislam/uptime-kuma | status page at `status.${BASE_DOMAIN}` |
| `filestash` | machines/filestash (digest-pinned) | player file share (upload) at `upload.${BASE_DOMAIN}` |
| `mc-backup` | itzg/mc-backup | world backups every 6h via RCON |
| `monitor` | built from source | admin-only log stream + dashboard at `monitor.${BASE_DOMAIN}` |
Vhosts (all proxied through the host nginx → caddy):
@@ -27,6 +28,7 @@ Vhosts (all proxied through the host nginx → caddy):
- `avatar.` — NMSR
- `status.` — Uptime Kuma
- `files.` — Filestash player file share (one shared login, writable)
- `monitor.` — **admin only**, log stream + dashboard (caddy basic_auth)
- `distribution.` — static site from another repo (`DISTRIBUTION_WEB_ROOT`)
Config lives in `.env`: `BASE_DOMAIN`, `RCON_PASSWORD`, `CADDY_HTTP_PORT` /
@@ -35,7 +37,8 @@ Config lives in `.env`: `BASE_DOMAIN`, `RCON_PASSWORD`, `CADDY_HTTP_PORT` /
## DNS
Handled **outside this repo**. Point `${BASE_DOMAIN}` and every subdomain
(`auth. avatar. status. files. distribution. www.`) at the host running nginx.
(`auth. avatar. status. files. monitor. distribution. www.`) at the host running
nginx.
## Prerequisites
@@ -158,6 +161,43 @@ curl -H "Host: ulicraft.net" \
Wired in `landing/src/data/site.ts` (`statusApi`) and `caddy/conf.d/10-static.caddy`.
## Monitor (admin log stream + dashboard)
`monitor.${BASE_DOMAIN}` — a small Go service (`docker/monitor/`) that tails the
Minecraft server's `latest.log` and shows it live with autoscroll, a filter and
ERROR/WARN/INFO chips, above a row of tiles: server status, TPS, players online
(with NMSR heads), registered accounts, disk free, backup freshness and recent
joins/leaves.
**Admin only, and read-only.** The log carries player IPs, chat and admin
commands verbatim and nothing sanitizes it, so the whole vhost sits behind caddy
`basic_auth`. There are no controls in the UI — every route is a `GET`.
```bash
# generate the credential (BCRYPT — the opposite of FILES_PASSWORD_HASH, which needs $6$)
docker run --rm caddy:alpine caddy hash-password --plaintext 'your-password'
```
Then in `.env` — **single-quote the hash**, Compose mangles bare `$`-sequences:
```sh
MONITOR_ENABLED=on # per-host on|off, like FILES_BROWSE
MONITOR_USER=admin
MONITOR_PASSWORD_HASH='$2a$14$...'
```
The container publishes **no port**: caddy is the only way in, so there is no
host listener to firewall. It holds one credential (`RCON_PASSWORD`, for the TPS
tile) and can only send commands from a compile-time allowlist that no HTTP
input can reach.
Adding it needs a cert (`monitor` in `LE_SUBDOMAINS` →
`tooling/issue-letsencrypt.sh`) and an nginx vhost (`tooling/render-nginx.sh
--install`). Because it adds a caddy conf file, the **first** deploy needs
`./update-all.sh --hard` (world downtime); later changes are rolling.
Full design, gotchas and verification: **`plan/23-monitor.md`**.
## Player file share (Filestash)
`upload.${BASE_DOMAIN}` is a web file share for player media — screenshots,
@@ -224,6 +264,8 @@ caddy/Caddyfile + conf.d/*.caddy # ingress vhost snippets
drasl/config/config.toml.tmpl # auth config (rendered)
nmsr/config.toml.tmpl # avatar renderer config, Drasl-backed (rendered)
docker/nmsr/ # built-from-source NMSR image
docker/mc-status/ # built-from-source SLP→JSON pinger
docker/monitor/ # built-from-source admin log stream + dashboard
landing/ # Astro site → www/
launcher/ # FjordLauncher assets (gitignored)
server-mods/ # filtered server mod jars (synced, gitignored)