Files
dashboards/.agents/phases/todo/10_dev_direct_dial/00_phase.md
T
ducoterra 71900d903a chore(agent): add phase 10 — dev dials the fleet directly
Six tasks that make dev the prod topology: the documented `ops`-installable agent sudoers
grant, installing the agents as systemd services, the upsd ACL for the host and laptop
ranges, `.env.dev` pointing at the real hosts (token in the URL userinfo, `NUT_PORT=3051`),
retiring `scripts/dev-tunnels.sh`, and a live + browser verification pass.

No dashboard Python changes — deployment, config, scripts and docs only.

LLM-Generated: true
2026-10-17 12:27:27 -04:00

6.3 KiB

Phase 10 — Dev dials the fleet directly (no more SSH tunnels)

Source: user request (2026-10) — "I don't want this separation of architectures. My laptop can talk to these hosts directly via local network or vpn already. Just have dev work like prod." Story: n/a Context: AGENTS.md (dev workflow: scripts/dev-tunnels.sh, DEV_TUNNELS/DEV_FORWARDS), deploy/install-agent.sh, deploy/nut-sudoers, scripts/dev-server.sh, scripts/capture-fixtures.sh, tests/test_live_fleet.py, .env.dev.example, .env.example, README.md

Objective

Dev becomes the prod topology: each host's agent runs as a systemd service (installed by ops through a documented minimal sudoers grant), bound to the address the dashboard dials, token in the URL userinfo; driveripper's upsd ACLs the host and laptop IP ranges on port 3051; .env.dev points at the real hosts with no DEV_TUNNELS/DEV_FORWARDS; scripts/dev-tunnels.sh is retired. The dashboard's Python is untouched — this phase changes deployment, config, scripts and docs, and proves parity with the live suite plus browser screenshots.

Decisions (user, 2026-10)

  • The agent is a systemd service, installed by the ops user (not root). The repo documents the simplest possible working sudoers addition in deploy/agent-sudoers, and the user adds it to each host.
  • The upsd ACL allows the host's IP range and the laptop's IP range — both real ranges, derived at install time and commented in .env.dev so they can be re-derived when the laptop's IP changes.

Precondition (user action, before task 02)

The sudoers line needs root, and the command-scoped sudo on driveripper does not include visudo. The user adds the one line from deploy/agent-sudoers on each host. If it is missing when task 02 runs, that task must stop cleanly and print the exact instructions — never loop, never wait, never attempt to edit sudoers itself.

Dependencies

  • 05_ups_hero_battery_glyph (complete) — the last UI phase; its pages are what this phase re-verifies against live data after the transport change.
  • 06_system_page_gpu_verdicts, 07_llm_monitor_style_pages, 08_events_paging_and_copy, 09_disks_smartctl (todo) — independent: this phase changes transport and ops, not payloads or page behaviour, and they do not depend on the transport.

Tasks

  1. 01_sudoers_grant_and_installer_hint.md — deploy/agent-sudoers (the minimal working grant
    • install instructions, in the nut-sudoers house style); install-agent.sh run without root prints the exact line to add.
  2. 02_install_agents_on_fleet.md — on all three hosts: kill the ad-hoc agent, stage the installer, install the systemd service bound to the dialed address, capture the printed tokens, verify from the laptop.
  3. 03_upsd_acl_for_laptop.md — driveripper: append ACL lines for the host range and the laptop range to upsd.conf (read-first, cmp-after, never blind-write), restart nut-server, verify the NUT wire from the laptop.
  4. 04_env_dev_direct_dial.md — stop the tunnels; rewrite the local .env.dev to direct targets (userinfo tokens, NUT_HOST/NUT_PORT=3051); restart the dev server; sources ok.
  5. 05_retire_tunnels_update_scripts.md — delete scripts/dev-tunnels.sh; rewrite .env.dev.example direct-shaped; fix capture-fixtures.sh, the live suite's docstring and skip messages, ups_contract.py, the alerts.py docstring, test_config.py, .env.example's NUT note, and the dev-workflow sections of README.md/AGENTS.md.
  6. 06_verify_direct_dial_live.md — full live verification: /api/health ok over direct dial, uv run pytest -m live --live green, browser screenshots of / + /system + /ups + /llm on live data, no tunnel port needed on the laptop.

Testing & Quality

  • The Python changes in this phase are test-file strings and one docstring — no new logic. The full suite must stay 100% green and overall coverage must not drop (gate: --cov-fail-under=94).
  • bash -n on deploy/install-agent.sh and scripts/capture-fixtures.sh after their edits.
  • The real proof of this phase is the live path: uv run pytest -m live --live against the directly-dialed fleet (the same production code paths over the prod-shaped transport) plus a browser screenshot per AGENTS.md rule 10.
  • The phase is "Complete" only when the entire project test suite passes 100% (every test in the project, not just the phase's — pre-existing failures included) and the coverage gate in .agents/validate.sh holds.

Documentation & Consistency

  • The final pass reviews README.md and AGENTS.md in full against the project as it stands after this phase — fixing every inconsistency found, including pre-existing ones not caused by the phase (including any remaining mention of dev-tunnels.sh, DEV_TUNNELS, DEV_FORWARDS, 9121/9122/9123 or 3494).
  • Task 05 updates README.md/AGENTS.md immediately for the user-visible dev-workflow change.
  • PLAN.md is locked and must not be edited: its Data-Model note ("forwarded to 127.0.0.1:3494 in dev") goes stale after this phase — flag it in the final pass's report to the user, and leave the file untouched.

Completion Criteria

  • systemctl status dashboards-agent is active on all three hosts, each bound to the address the dashboard dials, answering /health (unauthenticated) and /snapshot (bearer) from the laptop
  • driveripper's upsd answers LIST UPS from the laptop on 3051 — not via the host's loopback
  • git grep -n "dev-tunnels\|DEV_TUNNELS\|DEV_FORWARDS\|9121\|9122\|9123\|3494" matches nothing outside .agents/phases/complete/
  • ./scripts/dev-server.sh status shows every source ok over direct dial
  • uv run pytest -m live --live (with .env.dev sourced) is green
  • data/logs/ holds phase10-*.png screenshots of /, /system, /ups, /llm on live data over direct dial
  • full test suite 100% passing, coverage gate (≥94%) holds, ruff + pyright clean
  • README.md and AGENTS.md describe the direct-dial dev workflow and mention no tunnels (verified in the final pass)
  • no behavior change in completed phases: the four pages render the same payloads as before the transport change