phase: 10_dev_direct_dial
**Phase 10 final pass — repo green; live criteria blocked off-repo.**
- Verified: `gateway` runs `dashboards-agent` (active/enabled, `10.1.1.27:9120` → `/health` 200, `/snapshot` 401 no-token / 200 bearer on-host, token matches `/etc/dashboards-agent.token`); `driveripper`/`deskwork` inactive, `sudo -n` → "a password is required", `:9120` empty.
- New measurement (transient runtime rule on gateway `firewall-cmd --add-port=9120/tcp`, **reverted** — runtime+permanent port lists verified back to the original set, no rich rules): laptop still gets no SYN through, while `:22`/`:80` on the same address answer in ms ⇒ the block is the lab gateway `10.2.0.1` port filter, not this app/host firewall/agent. No other live change made (no sudoers, no upsd, no UPS command).
- Tests/lint (exact): `bash .agents/validate.sh` → **EXIT 0** — `uv run pytest --cov --cov-fail-under=94` **793 passed, 11 skipped, coverage 98.33 %**; `ruff check src tests agent` clean; `pyright` 0 errors; `node --check` clean; hygiene clean; `uv pip list --outdated` empty (bumped fastapi 0.143.3→0.143.5, `uv.lock` only).
- `uv run pytest -m live --live` (`.env.dev` sourced): **4 passed, 1 skipped, 6 failed** — all six are `:9120`/`:3051` connect timeouts; zero parse/contract failures.
- Dev server restarted on current code: engines `ok`; 3×`host` + 3×`disks` + `nut` `DOWN` with real reasons (designed degradation, stub rows intact, no credential leakage).
- Completion criteria: screenshots ✓ (`data/logs/phase10-{index,system,disks,llm,ups}-live.png`); tunnel-vocabulary grep ✓ outside `.agents/` history (PLAN.md locked line, phase overviews, past reports; fixtures/`uv.lock` hits are digit substrings — `tests/test_deploy_scripts.py` guards operational files); no page-payload change ✓ (contract tests). **Not met, operator-side:** agents on all three hosts, `LIST UPS` from the laptop, `dev-server.sh status` all-ok, live suite green.
- Fixed in this pass: `sources/nut.py` unreachable-upsd message no longer prints a bare `(TimeoutError: )` (now `(timed out after 4s)` / `(ConnectionRefusedError: …)`), pinned by a new test + extended refused-port test.
- Docs: README verified consistent (every env var in `config.py` documented, routes/flags/`--with-llm`/pager sizes checked) — no changes needed. AGENTS.md fixed 4 inconsistencies: per-host sudo reality (gateway grants `NOPASSWD ALL`, which is why it installed without `agent-sudoers`; driveripper scoped; deskwork unrelated), the now-proven firewall finding, `/system` table typo ("agent per host"), gap-4 DB/WAL sizes + test count. `.env.example` `upsd.users` sample now one `instcmds` per line (it contradicted README and the in-app copy). `.env.dev` (gitignored) comment corrected to match the evidence.
- Flagged, untouched (locked): `.agents/PLAN.md` L92–95 still says upsd is "forwarded to `127.0.0.1:3494` in dev" — false after this phase; evidence + exact operator commands appended to `data/logs/NOTES-verify.md` §11.
- Next pending phase: `11_admin_login`.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
# sudoers grant that lets the unprivileged `ops` account install the homelab dashboards
|
||||
# host agent on a monitored host. One line, one script path — deploy/nut-sudoers is the
|
||||
# separate NUT grant on driveripper.
|
||||
#
|
||||
# Install (from the staged copy on the host, as root — the command-scoped `ops` sudo on
|
||||
# these hosts does NOT include visudo, which is exactly why this file exists):
|
||||
#
|
||||
# sudo visudo -cf /home/ops/agent-install/deploy/agent-sudoers # syntax check FIRST
|
||||
# sudo install -m 0440 -o root -g root /home/ops/agent-install/deploy/agent-sudoers \
|
||||
# /etc/sudoers.d/dashboards-agent
|
||||
# sudo visudo -cf /etc/sudoers.d/dashboards-agent # and check the installed copy
|
||||
#
|
||||
# Scope: an absolute path (no wildcards, `secure_path` is in effect), no shell, no editor,
|
||||
# no `tee`, nothing that can amend this file. Running install-agent.sh without root prints
|
||||
# this exact grant with the script's own resolved path, so a checkout at another path tells
|
||||
# you what to add instead of saying "run as root" to someone who cannot.
|
||||
#
|
||||
# WHY THIS IS NEEDED
|
||||
# The agent is read-only by construction: it answers GET /snapshot on a socket, writes
|
||||
# nothing anywhere, and every request except /health needs the bearer token it reads from a
|
||||
# file only root and the agent's own service user can read (0640 root:dashboards-agent — the
|
||||
# agent opens the path itself, so the service user must be in that group). What needs root is
|
||||
# the *install*, not the agent: creating the
|
||||
# dashboards-agent system user, writing /usr/local/lib/dashboards-agent/host_agent.py,
|
||||
# dropping /etc/systemd/system/dashboards-agent.service, storing the token, and running
|
||||
# systemctl daemon-reload + enable --now (disable --now for --uninstall).
|
||||
# Phase 10 runs each host's agent as that systemd service bound to the address the
|
||||
# dashboard dials directly, so the account that deploys over ssh — ops — has to be able to
|
||||
# run the installer, and that is the only reason it is granted anything.
|
||||
#
|
||||
# WHY THE WHOLE SCRIPT PATH AND NOT A FLAG LIST
|
||||
# The flags carry no secrets: --bind/--port choose the socket, --status is read-only,
|
||||
# --uninstall removes exactly what this install put in place, --rotate-token mints a token
|
||||
# the operator is about to be shown anyway. sudoers can only match flags with a wildcard,
|
||||
# and a wildcard list has to be edited in step with every flag the script gains — an edit
|
||||
# whose failure mode is a broken install, not a smaller privilege. The one flag whose
|
||||
# *argument* would be sensitive is --token, and no documented flow uses it: the installer
|
||||
# generates the token, prints it once, and hands the agent --token-file, because argv is
|
||||
# world-readable through ps while the file is not.
|
||||
|
||||
# ---- the grant ------------------------------------------------------------------ #
|
||||
# The host has no repo checkout, so deploy/ and agent/ are staged to ~/agent-install/
|
||||
# (phase 10 task 02 does the copy), which keeps this path identical on every host:
|
||||
#
|
||||
# ssh ops@host 'mkdir -p ~/agent-install'
|
||||
# scp -r deploy agent ops@host:~/agent-install/
|
||||
#
|
||||
# `ops` is the fleet's monitored/deploy account; change only that field if the agent is
|
||||
# installed by a different account. Nothing follows the path, which in sudoers means the
|
||||
# script may be run with any of its own flags — see the block above.
|
||||
ops ALL=(root) NOPASSWD: /home/ops/agent-install/deploy/install-agent.sh
|
||||
|
||||
# ---- making the grant mean something (one root command, recommended) ------------ #
|
||||
# A sudo grant on a path is only as strong as who can write that path. ops owns
|
||||
# ~/agent-install, so until the staged copy is root-owned this line is a convenience for a
|
||||
# human, not a boundary. After staging, as root:
|
||||
#
|
||||
# sudo chown -R root:root /home/ops/agent-install
|
||||
#
|
||||
# ops can still wipe its own home directory and re-copy (it is ops' home), so this guards
|
||||
# against drift and a stray edit — not against an operator who wants something else.
|
||||
|
||||
# ---- deliberately NOT granted --------------------------------------------------- #
|
||||
# visudo, vim, nano, sh, bash, tee — no self-amendment of this grant, no shell, and no
|
||||
# writing any file as root
|
||||
# systemctl (anything else) — the installer already runs the few unit operations
|
||||
# it needs as itself; a bare systemctl * could stop
|
||||
# anything on the host
|
||||
# cat /etc/dashboards-agent.token — the token is readable by root and the agent user
|
||||
# only; install-agent.sh prints it once on first
|
||||
# install and --rotate-token mints a new one
|
||||
# useradd, usermod, groupmod — the installer creates its own user and its disk
|
||||
# group membership; a bare usermod could add ops to
|
||||
# any group on the box
|
||||
# upscmd, upsrw — NUT instant commands stay the dashboard's gated
|
||||
# POST /api/ups/command; see deploy/nut-sudoers
|
||||
Reference in New Issue
Block a user