feat(monitor): admin-only latest.log stream at monitor.
Phase 1 of plan/23-monitor.md: a small Go service that tails the Minecraft server's latest.log into an in-memory ring buffer and serves it to an embedded single-page UI (autoscroll, substring/regex filter, level chips). Read-only by construction — /data is mounted :ro, there is no mutating route, and the container holds no credentials. It publishes NO port: caddy's basic_auth vhost is the only way in, which is the point, since the service has no auth of its own and the log carries player IPs, chat and admin commands. Two non-obvious choices, both to avoid prod-only failures: - Polling, not SSE. The chain is host nginx -> caddy -> app, and nginx buffers proxied responses by default, so an EventSource would stall on cochi (whose vhost is an untracked per-host file) and not reproduce locally. Delay was accepted at design time, so polling is strictly better. - The cursor is a sequence number, not a byte offset, and the tailer reopens on inode change or size shrink. itzg rotates latest.log on every server restart, i.e. every deploy — a naive tailer goes silent afterwards while still answering HTTP 200. Verified against rotate, truncate, delete/recreate, partial-line and ring-overflow cases. MONITOR_ENABLED (on|off) gates both layers per host, mirroring FILES_BROWSE. MONITOR_PASSWORD_HASH must be bcrypt AND single-quoted in .env: Compose interpolates $-sequences in .env values, so a bare hash is silently mangled ($2a$14$abc... -> $$2a$$14...) and every login fails with no hint why. Confirmed empirically, and it is the opposite of filestash, which needs $6$. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
8507ffd020
commit
fca6747bcc
13 files changed
+1241
-4
No files matched your search
@@ -164,6 +164,13 @@ services:
|
||||
- "${CADDY_HTTP_BIND:-127.0.0.1}:${CADDY_HTTP_PORT:-8880}:80"
|
||||
environment:
|
||||
BASE_DOMAIN: ${BASE_DOMAIN}
|
||||
# basic_auth credentials for monitor.${BASE_DOMAIN} (80-monitor.on.caddy).
|
||||
# MONITOR_PASSWORD_HASH must be BCRYPT (`caddy hash-password`) and must be
|
||||
# SINGLE-QUOTED in .env — compose interpolates $-sequences in .env values,
|
||||
# so a bare bcrypt hash gets silently mangled ($2a$14$abc… -> $$2a$$14…)
|
||||
# and every login fails. See plan/23-monitor.md.
|
||||
MONITOR_USER: ${MONITOR_USER:-admin}
|
||||
MONITOR_PASSWORD_HASH: ${MONITOR_PASSWORD_HASH:-}
|
||||
volumes:
|
||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./caddy/conf.d/00-core.caddy:/etc/caddy/conf.d/00-core.caddy:ro
|
||||
@@ -181,6 +188,10 @@ services:
|
||||
# reads the same var); this one keeps caddy itself from serving the share.
|
||||
- ./caddy/conf.d/62-files.${FILES_BROWSE:-on}.caddy:/etc/caddy/conf.d/62-files.caddy:ro
|
||||
- ./caddy/conf.d/70-generators.caddy:/etc/caddy/conf.d/70-generators.caddy:ro
|
||||
# monitor.${BASE_DOMAIN} admin log stream, gated by MONITOR_ENABLED
|
||||
# (on|off) — same substitute-into-the-source-path trick as FILES_BROWSE
|
||||
# above, and the same recreate requirement when the value changes.
|
||||
- ./caddy/conf.d/80-monitor.${MONITOR_ENABLED:-on}.caddy:/etc/caddy/conf.d/80-monitor.caddy:ro
|
||||
- ./www:/srv/www:ro
|
||||
- ./launcher:/srv/launcher:ro
|
||||
# Read-only browse of the shared upload dir at files.${BASE_DOMAIN}
|
||||
@@ -313,6 +324,35 @@ services:
|
||||
networks:
|
||||
- mcnet
|
||||
|
||||
# Admin-only log stream at monitor.${BASE_DOMAIN} (built from docker/monitor).
|
||||
# Phase 1 of plan/23-monitor.md: tails the Minecraft server's latest.log into
|
||||
# an in-memory ring buffer and serves it to a small embedded UI by polling.
|
||||
#
|
||||
# Read-only in every sense: /data is mounted :ro, the service exposes no
|
||||
# mutating route, and it holds no credentials. It publishes NO port — the only
|
||||
# way in is caddy's basic_auth vhost, which is exactly the point (the container
|
||||
# has no auth of its own, so an accidental port publish would expose the raw
|
||||
# log — chat, IPs, admin commands — to the internet). Keep it portless.
|
||||
#
|
||||
# Uptime Kuma can probe http://monitor:8080/healthz directly over mcnet,
|
||||
# bypassing caddy, so no auth exemption is needed on the vhost.
|
||||
monitor:
|
||||
build:
|
||||
context: ./docker/monitor
|
||||
image: ulicraft/monitor:local
|
||||
container_name: monitor
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MONITOR_LOG_PATH: "/data/logs/latest.log"
|
||||
MONITOR_BUFFER_LINES: "5000"
|
||||
volumes:
|
||||
# The whole Minecraft data dir, read-only. Only logs/latest.log is read;
|
||||
# mounting /data rather than /data/logs keeps the path stable if itzg ever
|
||||
# moves the log dir, and :ro means it cannot touch the world either way.
|
||||
- mc_data:/data:ro
|
||||
networks:
|
||||
- mcnet
|
||||
|
||||
# Status monitoring + public status page. caddy proxies status. -> :3001.
|
||||
# Joins mcnet so monitors probe the stack by internal service name.
|
||||
uptime-kuma:
|
||||
|
||||
Reference in new issue
Block a user