chore(agent): phase roadmap from TODO.md, 5 phases
The five UI items in TODO.md become an executable pipeline. Planning files only — no application code changed. - 01_severity_language: one Python threshold table + `severity` on the values the pages colour, plus the .hero style. Today the thresholds are magic numbers at six JS call sites (75/90 here, 80/92 there), which is exactly how "same colour, four meanings" happens. - 02_overview_fleet_and_alerts: /api/summary gains a derived `alerts` list; the page drops to fleet + warnings. Unconfigured sources must not alert — that is invariant 3, tested. - 03_system_per_host_panels: per-host hero cards, no per-core, shared multi-host charts and the detail picker deleted. The six card metrics already exist in the payload, so the first task pins them with tests instead of inventing fields. - 04_llm_chart_readability: charts.js already exports sparkline() and no page calls it — that is why the page reads flat. Port llm_monitor's drawSpark behaviour (fixed max, right growth, p95 foots) and keep the provenance tags. - 05_ups_hero_battery_glyph: charge/load/runtime hero + an SVG battery whose fill is the charge, verified against both real UPSes and the low-charge/on-battery states only the simulator can produce. PLAN.md records the ratified anchors and the deviation worth knowing: severity vocabulary is ok|warn|fail, matching dash.css and nut.py, not the "danger" wording in the proposal — one synonym for one colour. TODO.md cleared to a bare heading; the items are now phases. .gitignore stops ignoring all of .agents/ (the roadmap is project history) while keeping phase-sessions/ and pipeline.log out. deploy/nut-sudoers rewritten: its header still blamed a port mystery that is solved; the remaining need is reading upsd.users so adding a scoped dashboards user does not delete the accounts upsmon authenticates as. LLM-Generated: true
This commit is contained in:
+42
-43
@@ -1,55 +1,54 @@
|
||||
# driveripper-ops sudo rules for the UPS dashboard work
|
||||
# driveripper-ops sudo rules for the homelab dashboards UPS work.
|
||||
#
|
||||
# Install as /etc/sudoers.d/dashboards-nut (visudo -cf it first):
|
||||
# Install (from the repo checkout, as a user with sudo):
|
||||
#
|
||||
# sudo visudo -cf - <<'EOF' ... EOF # syntax check
|
||||
# install -m 0440 deploy/nut-sudoers /tmp/dashboards-nut
|
||||
# sudo mv /tmp/dashboards-nut /etc/sudoers.d/dashboards-nut
|
||||
# sudo visudo -cf deploy/nut-sudoers # syntax check FIRST
|
||||
# sudo install -m 0440 -o root -g root deploy/nut-sudoers /etc/sudoers.d/dashboards-nut
|
||||
# sudo visudo -cf /etc/sudoers.d/dashboards-nut # and check the installed copy
|
||||
#
|
||||
# Keep it scoped: paths are absolute (sudoers uses `secure_path`), arguments are wildcards
|
||||
# only where the command is read-only or idempotent, and nothing here allows a shell,
|
||||
# `EDITOR=`, `dnf install` of arbitrary code, or `upsd -c` with an attacker-chosen file.
|
||||
# Scope: absolute paths only (`secure_path` is in effect), wildcards only where the command
|
||||
# is read-only or idempotent, and nothing here grants a shell, `sh -c`, an editor, or a
|
||||
# client that can send instant commands.
|
||||
#
|
||||
# WHY THESE ARE NEEDED
|
||||
# Reads already work with zero privileges — upsd on driveripper serves `LIST UPS` /
|
||||
# `LIST VAR` unauthenticated (on port 3051, not 3493). What is missing is a upsd.users
|
||||
# entry with `instcmds`, which is the only thing standing between the dashboard and its
|
||||
# battery-test buttons. `tee /etc/ups/*` is already granted, but writing upsd.users blind
|
||||
# would drop the accounts upsmon authenticates as — upsmon must run as a user with
|
||||
# `shutdown`, so an overwritten file means no orderly power-down. So: read first.
|
||||
|
||||
# ---- already present on driveripper (this is what the dashboard setup used) ---------- #
|
||||
# (root) NOPASSWD: /usr/bin/tee /etc/ups/* # write upsd.conf / upsd.users
|
||||
# (root) NOPASSWD: /usr/bin/cp /etc/ups/* /home/ops/* # back up a config before editing
|
||||
# ---- already present on driveripper (used during setup) ------------------------------ #
|
||||
# (root) NOPASSWD: /usr/bin/tee /etc/ups/* # write a config file
|
||||
# (root) NOPASSWD: /usr/bin/cp /etc/ups/* /home/ops/* # copy one out (root-owned)
|
||||
# (root) NOPASSWD: /usr/bin/systemctl start|restart|reload|enable *
|
||||
# (root) NOPASSWD: /usr/bin/udevadm * # USB device discovery/replug
|
||||
# (root) NOPASSWD: /usr/bin/udevadm * # USB device discovery
|
||||
# (root) NOPASSWD: /usr/bin/firewall-cmd * # only if upsd must serve the LAN
|
||||
# (root) NOPASSWD: /usr/bin/dnf install * # nut, nut-client, nut-dev
|
||||
# (root) NOPASSWD: /usr/bin/dnf install * # nut, nut-client
|
||||
|
||||
# ---- still needed: diagnose why upsd runs but binds nothing -------------------------- #
|
||||
# Reading is the blocker. `upsd` is active, both drivers are up, and 3493 has no listener,
|
||||
# but /etc/ups/* is 0640 root:nut and the journal needs group membership:
|
||||
# $ sudo cat /etc/ups/upsd.conf -> Permission denied
|
||||
# $ journalctl -u nut-server -n 50 -> No entries (not in systemd-journal/wheel)
|
||||
# $ upsc -l -> Error: Connection failure: Connection refused
|
||||
|
||||
Cmnd_Alias NUT_DIAGNOSE = \
|
||||
# ---- read: what is actually configured, before anything is rewritten ----------------- #
|
||||
Cmnd_Alias NUT_READ = \
|
||||
/usr/bin/cat /etc/ups/*, \
|
||||
/usr/bin/systemctl status *, \
|
||||
/usr/bin/journalctl -u nut-server *, /usr/bin/journalctl -u nut-driver@*, \
|
||||
/usr/bin/journalctl -u nut*, \
|
||||
/usr/sbin/ss -ltnp, \
|
||||
/usr/sbin/getenforce, \
|
||||
/usr/bin/ausearch *, /usr/sbin/sealert -l *, \
|
||||
/usr/sbin/semanage port -l, \
|
||||
/usr/bin/upsdrvctl status, /usr/bin/upsdrvctl status *, \
|
||||
/usr/bin/systemctl status nut*, \
|
||||
/usr/bin/upsdrvctl status, \
|
||||
/usr/bin/upsc *, \
|
||||
/usr/bin/lsusb
|
||||
/usr/sbin/ss -ltnp, \
|
||||
/usr/bin/lsusb, \
|
||||
/usr/sbin/getenforce, \
|
||||
/usr/bin/ausearch -m avc *, /usr/sbin/sealert -l *
|
||||
|
||||
# ---- still needed: apply the fix and verify ------------------------------------------ #
|
||||
# `tee /etc/ups/*` already writes configs; these cover the reload path, SELinux booleans,
|
||||
# and a bounded cleanup of the .bak files `tee`-based edits leave behind.
|
||||
Cmnd_Alias NUT_APPLY = \
|
||||
/usr/bin/tee /etc/ups/upsd.conf, /usr/bin/tee /etc/ups/upsd.users, \
|
||||
/usr/bin/tee /etc/ups/ups.conf, /usr/bin/tee /etc/ups/nut.conf, \
|
||||
/usr/bin/rm -f /etc/ups/*.bak-*, \
|
||||
/usr/bin/upsdrvctl restart, /usr/bin/upsdrvctl restart *, \
|
||||
/usr/sbin/setsebool -P allow_upsd_tcp_server 1, \
|
||||
/usr/sbin/setsebool -P nis_enabled 0
|
||||
# ---- write: add/replace a scoped user, then reload ----------------------------------- #
|
||||
Cmnd_Alias NUT_WRITE = \
|
||||
/usr/bin/tee /etc/ups/upsd.users, \
|
||||
/usr/bin/tee /etc/ups/upsd.conf, \
|
||||
/usr/bin/systemctl restart nut-server, \
|
||||
/usr/bin/systemctl reload nut-server
|
||||
|
||||
# NOTE: deliberately NOT allowed, so an agent cannot power-cycle anything by accident:
|
||||
# /usr/bin/upscmd (any form) — the dashboard's own gated POST is the only command path
|
||||
# systemctl stop * — stopping nut-server would blind the UPS view
|
||||
# editors, shells, `sh -c` — no escape hatches
|
||||
# ---- deliberately NOT granted -------------------------------------------------------- #
|
||||
# /usr/bin/upscmd, /usr/bin/upsrw — the dashboard's gated POST /api/ups/command is the
|
||||
# ONLY command path in this project, by design
|
||||
# systemctl stop * — stopping nut-server blinds the UPS view
|
||||
# tee /etc/ups/ups.conf, upsmon.conf — driver instances and shutdown policy are the
|
||||
# operator's, not the dashboard's
|
||||
# sh, bash, vim, tee to anything else — no escape hatches
|
||||
|
||||
Reference in New Issue
Block a user