Commit Graph
3 Commits
Author SHA1 Message Date
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
ducoterra 87042f8855 Add AGENTS.md and align the docs with the code
The brief asked for an AGENTS.md capturing the overall plan and it never
landed, while two files already pointed readers at it. It now holds the
invariants (read-only by construction, never invent a number,
unconfigured is not broken, no CDN, no guessed metric names), the locked
decisions with their reasons, the verified fleet facts, the commands, and
the four known gaps.

Also corrected the docs that had drifted:
- upsd config lives in /etc/ups on driveripper, not /etc/nut, and the
  real blocker is that upsd binds nothing on 3493 — with the sudo rules
  still needed written down in deploy/nut-sudoers.
- .env.example told operators to set `LISTEN 0.0.0.0` and `actions = -`;
  both are worse than needed. Loopback + ssh forward, and per-command
  instcmds instead of granting load.off.
- nut.py claimed the command path is gated three times and that the
  confirmation asks for the UPS name; it is gated four times and asks
  for DANGER.
- .gitignore described a tracked .agents/ roadmap that does not exist.

LLM-Generated: true
2026-10-16 18:54:35 -04:00
ducoterra 426682a999 Add the deployment path: agent systemd unit and container image
The README referenced `deploy/install-agent.sh` and a `Containerfile` that
did not exist, so a fresh clone had no way to actually run this anywhere.

* agent: `--token-file` (and AGENT_TOKEN_FILE). The token guards hostname,
  mounts, disk and GPU models and process names, and argv is world-readable
  through `ps`, so a file has to win over the flag. Missing or empty token
  files are fatal — falling back to "no token" would expose the host.
* deploy/install-agent.sh: creates a system user, generates the token once
  (re-runs reuse it, --rotate-token for a new one), stores it 0600
  root-owned, and runs the agent under ProtectSystem=strict with no
  capabilities; --status and --uninstall included.
* Containerfile: two-stage, uv.lock resolved in the builder and slim Python
  in the runtime. Verified with podman — the image serves /api/health as an
  unprivileged user and writes its history under /var/lib/dashboards.
  UV_PROJECT_ENVIRONMENT and --no-editable are what make the venv survive
  being copied into the runtime stage.
* .containerignore keeps .git, .venv, data/ and .env out of the build
  context.

258 tests, 94% coverage; ruff and pyright clean.

LLM-Generated: true
2026-10-16 17:33:24 -04:00