diff --git a/plan/23-monitor.md b/plan/23-monitor.md index 7e0c8b9..3738ed6 100644 --- a/plan/23-monitor.md +++ b/plan/23-monitor.md @@ -247,16 +247,17 @@ tail-across-rotation. None of it is in the tiles. Once a password-gated page shows raw log lines, everything after is a small additive commit with a rolling one-container restart. -### Phase 1 — thin slice: logs only +### Phase 1 — thin slice: logs only — **DONE, live on cochi 2026-08-01** -- [ ] DNS A record for `monitor.${BASE_DOMAIN}`; add to `LE_SUBDOMAINS`; issue cert. -- [ ] `docker/monitor/` — Go service: ring buffer, tailer (inode/shrink reopen), +- [x] DNS A record for `monitor.${BASE_DOMAIN}`; add to `LE_SUBDOMAINS`; issue cert. + (DNS already resolved — the zone has a wildcard, so no record was needed.) +- [x] `docker/monitor/` — Go service: ring buffer, tailer (inode/shrink reopen), cold-start backfill, `GET /api/log?since=`, `GET /healthz`, embedded UI. -- [ ] `ui/index.html` — log pane, sticky autoscroll, filter, level chips. -- [ ] compose service (no `ports:`, `mc_data:/data:ro`). -- [ ] `caddy/conf.d/80-monitor.caddy` + `MONITOR_USER`/`MONITOR_PASSWORD_HASH`. -- [ ] nginx vhost + `MONITOR_ENABLED` gate; render + reload. -- [ ] Deploy with `--hard` (world downtime). Verify below. +- [x] `ui/index.html` — log pane, sticky autoscroll, filter, level chips. +- [x] compose service (no `ports:`, `mc_data:/data:ro`). +- [x] `caddy/conf.d/80-monitor.caddy` + `MONITOR_USER`/`MONITOR_PASSWORD_HASH`. +- [x] nginx vhost + `MONITOR_ENABLED` gate; render + reload. +- [x] Deploy with `--hard` (world downtime, 0 players online). Verified below. ### Phase 2 — tiles @@ -301,3 +302,31 @@ Then the one that actually matters, because it is the failure this design is most likely to hit: **restart the Minecraft server and confirm the stream keeps moving.** That is the rotation path, it fires on every deploy, and a broken tailer stays HTTP-200 while going silent. + +### Phase 1 results (cochi, 2026-08-01) + +The deploy itself exercised the rotation path: monitor came up while the old +`latest.log` still existed, Minecraft then created a new one, and the tailer +reported `rotations: 1` and kept streaming. Verified in prod: + +| Check | Result | +|---|---| +| `GET /` no creds / wrong creds / right creds | 401 / 401 / 200 | +| TLS | `CN=monitor.ulicraft.net`, Let's Encrypt | +| Buffer after boot | 908 lines, `tail.ok: true` | +| Stream advancing | +109 lines in 12s, `dropped: 0`, `reset: false` | +| Rotation across the MC restart | `rotations: 1`, stream uninterrupted | +| Port 8080 from the internet | connection refused | +| `docker compose port monitor 8080` | `:0` (nothing published) | +| Host listeners on :8080 | none | + +Also exercised locally before deploy, against a synthetic log: backfill capped +to buffer size, partial (unterminated) lines withheld until their newline lands, +`mv`-style rotation, in-place truncation, delete-and-recreate, ring overflow +(`dropped: 15, reset: true`), a cursor from the future, malformed `since` (400), +unknown path (404). + +**Known cosmetic gap:** a rotation done as delete-then-recreate (rather than +`mv`) does not increment the `rotations` counter — the tailer takes the +"file was gone, reopen" branch instead of the rotation branch. Streaming is +unaffected; only the displayed count is.