feat(monitor): dashboard tiles + TPS via allowlisted RCON
Phases 2 and 3 of plan/23-monitor.md. /api/summary adds: server online/MOTD/ version, players online with NMSR heads, registered-account count, disk free on the world volume, backup freshness, join/leave history, and TPS. Each tile has its own TTL cache and its own failure mode. A failed collector keeps serving its last good value and reports the reason in a sibling *_err field, so the page goes stale-with-a-warning rather than blank. The five collectors refresh concurrently: sequentially, one request during an outage would cost the sum of every timeout (~15s), and an outage is exactly when someone is looking at this page. Measured with a dead dependency: 0.24s. TPS needs RCON, so the monitor now holds RCON_PASSWORD. Read-only survives because the allowlist is a compile-time constant and no HTTP input reaches a command string — the UI cannot run commands because the server cannot be asked to. The Source RCON client is implemented in-repo rather than added as a dependency precisely to keep that property local and auditable. Two things the real server corrected: - `neoforge tps` prints "Overall: 20.000 TPS (0.357 ms/tick)", not the older Forge "Mean tick time / Mean TPS" wording the plan assumed. main_test.go pins the real captured output so a format change fails a test instead of silently zeroing the tile. - Rosters come from mc-status, which already holds the Drasl admin credentials and exposes a sanitized list, so this container does not get a second copy. Also fixes a phase-1 bug: the UI's level regex assumed itzg's console timestamp ([12:34:56]) but the file uses [01Aug2026 22:55:34.358], so every line was classified INFO and the ERROR/WARN chips did nothing. It now matches on shape. The container runs as root deliberately — /data is drwxr-x--- owned by uid 1000, so a non-root user cannot traverse it and the tailer would silently read nothing. The :ro mounts and absent port are what bound the damage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
53604a6127
commit
00984e1a70
7 files changed
+958
-20
No files matched your search
@@ -345,11 +345,31 @@ services:
|
||||
environment:
|
||||
MONITOR_LOG_PATH: "/data/logs/latest.log"
|
||||
MONITOR_BUFFER_LINES: "5000"
|
||||
# Dashboard tiles. Server status + rosters are read from mc-status over
|
||||
# mcnet rather than from Drasl directly — mc-status already holds the
|
||||
# admin credentials and exposes a sanitized list, so this container does
|
||||
# not need a second copy of that secret.
|
||||
MONITOR_MCSTATUS_URL: "http://mc-status:8080"
|
||||
# Player heads. Rendered by NMSR on its own PUBLIC origin, so the browser
|
||||
# fetches them cross-origin (no auth involved, avatars are public anyway).
|
||||
MONITOR_AVATAR_URL: "https://avatar.${BASE_DOMAIN}"
|
||||
MONITOR_AVATAR_MODE: "${AVATAR_MODE:-headiso}"
|
||||
# TPS via RCON. This is the ONLY credential the monitor holds, and the
|
||||
# only command it can send is the compile-time allowlist in rcon.go
|
||||
# (`neoforge tps`, `list`) — no HTTP input reaches a command string, so
|
||||
# the UI stays incapable of running anything. NeoForge 1.21.1 uses
|
||||
# `neoforge tps`; `forge tps` does not exist here.
|
||||
MONITOR_RCON_ADDR: "minecraft:25575"
|
||||
MONITOR_RCON_PASSWORD: ${RCON_PASSWORD}
|
||||
MONITOR_TPS_INTERVAL: "30s"
|
||||
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.
|
||||
# It is also what the disk-free tile statfs's.
|
||||
- mc_data:/data:ro
|
||||
# Backup freshness tile. Same dir mc-backup writes to, read-only here.
|
||||
- ./backups:/backups:ro
|
||||
networks:
|
||||
- mcnet
|
||||
|
||||
|
||||
Reference in new issue
Block a user