Files
dashboards/deploy/nut-sudoers
T
ducoterra 4b4962d7fb 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
2026-10-16 20:10:23 -04:00

55 lines
2.9 KiB
Plaintext

# driveripper-ops sudo rules for the homelab dashboards UPS work.
#
# Install (from the repo checkout, as a user with sudo):
#
# 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
#
# 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 (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
# (root) NOPASSWD: /usr/bin/firewall-cmd * # only if upsd must serve the LAN
# (root) NOPASSWD: /usr/bin/dnf install * # nut, nut-client
# ---- read: what is actually configured, before anything is rewritten ----------------- #
Cmnd_Alias NUT_READ = \
/usr/bin/cat /etc/ups/*, \
/usr/bin/journalctl -u nut*, \
/usr/bin/systemctl status nut*, \
/usr/bin/upsdrvctl status, \
/usr/bin/upsc *, \
/usr/sbin/ss -ltnp, \
/usr/bin/lsusb, \
/usr/sbin/getenforce, \
/usr/bin/ausearch -m avc *, /usr/sbin/sealert -l *
# ---- 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
# ---- 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