feat: host_agent plugin — push-model host resource monitoring #1

Merged
bvandeusen merged 0 commits from feat/host-agent into main 2026-04-15 09:43:58 -04:00
bvandeusen commented 2026-04-15 07:11:16 -04:00 (Migrated from git.fabledsword.com)

Summary

Adds the host_agent Roundtable plugin: a lightweight stdlib-only Python agent that runs on each monitored Linux host, collects CPU / memory / disk / load / uptime from /proc every 30s, and POSTs signed payloads to a bearer-token-authed ingest endpoint. Admin UI handles host registration, token rotation, and widget / alert integration.

Spec: docs/plugins/host-agent-design.md
Plan: docs/plugins/host-agent-plan.md

What's in this PR

Main repo (this branch, 10 commits ahead of main):

  • docs/plugins/host-agent-design.md — design spec
  • docs/plugins/host-agent-plan.md — 15-task implementation plan + path-1 note
  • docs/plugins/index.yaml.example — catalog entry
  • roundtable/core/widgets.pyhost_resources + host_resource_history widget registrations
  • roundtable/alerts/routes.pyhost_agent entry in METRIC_CATALOG (9 metrics)
  • tests/plugins/host_agent/ — 42 pure-Python unit tests (config parser, collectors, ring buffer, payload builder, agent POST + backoff, metric expander, install.sh template render, stale-agent filter)

Plugins repo (Roundtable-plugins@7468806, already pushed to main):

  • host_agent/agent.py (370 LoC single-file daemon)
  • host_agent/routes.py — ingest / install.sh / agent.py / widgets / settings
  • host_agent/scheduler.py — stale-agent check
  • host_agent/models.pyHostAgentRegistration (token hash, last_seen, metadata)
  • host_agent/migrations/versions/host_agent_001_initial.py
  • host_agent/templates/install.sh.j2, settings_list.html, widget_table.html, widget_history.html
  • host_agent/plugin.yaml, __init__.py

Test plan

  • pytest tests/plugins/host_agent — 42/42 passing locally
  • Full repo pytest tests/ — no regressions beyond pre-existing RuntimeWarnings
  • Manual end-to-end smoke test on 2026-04-15:
    • Admin settings page renders, add-host flow creates registration + returns one-time install URL
    • /plugins/host_agent/install.sh?token=… renders templated systemd installer, passes sh -n
    • /plugins/host_agent/agent.py serves the agent source
    • Agent running locally against http://localhost:5000 successfully posts samples every 30s
    • plugin_metrics populated with cpu_pct, mem_used_pct, mem_available_bytes, swap_used_bytes, load_1m/5m/15m, uptime_secs, disk_used_pct_worst, and per-mount disk_used_pct/disk_used_bytes/disk_total_bytes
    • registrations.last_seen_at / agent_version / kernel / distro / arch updated on each ingest
    • Dashboard Host Agent — Resources widget renders fleet-glance table with live values
  • End-to-end install against a real remote Ubuntu VM (one-liner curl|sh, systemd unit, service running)

Deviations from the plan

Documented in docs/plugins/host-agent-plan.md "Execution note — path 1":

  1. No DB-backed tests. The Roundtable test harness mocks db_sessionmaker under testing=True and has no existing DB fixture. Rather than invent one mid-plan (scope creep), HTTP-level route tests were dropped and everything testable was factored into pure functions (config parser, collectors, ring buffer, metric expander, staleness filter, template render). Tasks 7, 9, 11, 13 became manual-verification only — all smoke-tested above.
  2. roundtable.core.db.get_session does not exist. Plan assumed a session helper that isn't in the codebase. Real pattern is async with current_app.db_sessionmaker() as session: — used throughout routes.py and scheduler.py.
  3. Admin decorator. Plan left the admin auth mechanism as a TODO. Resolved to from roundtable.auth.middleware import require_role with @require_role(UserRole.admin) (matches roundtable/settings/routes.py convention).
  4. make_stale_task renamed to make_task returning a real ScheduledTask instead of a bare dict (matches plugins/docker/scheduler.py convention).
  5. agent.py is 370 lines vs. the plan's ~350 target — 20 lines over because the appended code matches the plan verbatim. Not reworked.

Known caveats

  • 401 response drops buffered samples. On 401 invalid_token, the agent re-buffers the current sample but silently drops whatever was already in the ring buffer from prior failed POSTs. Behavior matches the approved plan text (Task 5, Step 3) but is asymmetric with the generic-failure path. Fine for now; worth revisiting if you want stricter at-least-once semantics.
  • UI polish followups tracked as Fable task 266 — settings table cells collide on Host / Agent version, rotate/delete buttons need a confirm() guard, widget header wrap is awkward.

🤖 Generated with Claude Code

## Summary Adds the `host_agent` Roundtable plugin: a lightweight stdlib-only Python agent that runs on each monitored Linux host, collects CPU / memory / disk / load / uptime from `/proc` every 30s, and POSTs signed payloads to a bearer-token-authed ingest endpoint. Admin UI handles host registration, token rotation, and widget / alert integration. Spec: `docs/plugins/host-agent-design.md` Plan: `docs/plugins/host-agent-plan.md` ## What's in this PR **Main repo** (this branch, 10 commits ahead of main): - `docs/plugins/host-agent-design.md` — design spec - `docs/plugins/host-agent-plan.md` — 15-task implementation plan + path-1 note - `docs/plugins/index.yaml.example` — catalog entry - `roundtable/core/widgets.py` — `host_resources` + `host_resource_history` widget registrations - `roundtable/alerts/routes.py` — `host_agent` entry in `METRIC_CATALOG` (9 metrics) - `tests/plugins/host_agent/` — 42 pure-Python unit tests (config parser, collectors, ring buffer, payload builder, agent POST + backoff, metric expander, install.sh template render, stale-agent filter) **Plugins repo** (`Roundtable-plugins@7468806`, already pushed to `main`): - `host_agent/agent.py` (370 LoC single-file daemon) - `host_agent/routes.py` — ingest / install.sh / agent.py / widgets / settings - `host_agent/scheduler.py` — stale-agent check - `host_agent/models.py` — `HostAgentRegistration` (token hash, last_seen, metadata) - `host_agent/migrations/versions/host_agent_001_initial.py` - `host_agent/templates/` — `install.sh.j2`, `settings_list.html`, `widget_table.html`, `widget_history.html` - `host_agent/plugin.yaml`, `__init__.py` ## Test plan - [x] `pytest tests/plugins/host_agent` — 42/42 passing locally - [x] Full repo `pytest tests/` — no regressions beyond pre-existing RuntimeWarnings - [x] Manual end-to-end smoke test on 2026-04-15: - Admin settings page renders, add-host flow creates registration + returns one-time install URL - `/plugins/host_agent/install.sh?token=…` renders templated systemd installer, passes `sh -n` - `/plugins/host_agent/agent.py` serves the agent source - Agent running locally against `http://localhost:5000` successfully posts samples every 30s - `plugin_metrics` populated with `cpu_pct`, `mem_used_pct`, `mem_available_bytes`, `swap_used_bytes`, `load_1m/5m/15m`, `uptime_secs`, `disk_used_pct_worst`, and per-mount `disk_used_pct`/`disk_used_bytes`/`disk_total_bytes` - `registrations.last_seen_at` / `agent_version` / `kernel` / `distro` / `arch` updated on each ingest - Dashboard `Host Agent — Resources` widget renders fleet-glance table with live values - [ ] End-to-end install against a real remote Ubuntu VM (one-liner curl|sh, systemd unit, service running) ## Deviations from the plan Documented in `docs/plugins/host-agent-plan.md` "Execution note — path 1": 1. **No DB-backed tests.** The Roundtable test harness mocks `db_sessionmaker` under `testing=True` and has no existing DB fixture. Rather than invent one mid-plan (scope creep), HTTP-level route tests were dropped and everything testable was factored into pure functions (config parser, collectors, ring buffer, metric expander, staleness filter, template render). Tasks 7, 9, 11, 13 became manual-verification only — all smoke-tested above. 2. **`roundtable.core.db.get_session` does not exist.** Plan assumed a session helper that isn't in the codebase. Real pattern is `async with current_app.db_sessionmaker() as session:` — used throughout `routes.py` and `scheduler.py`. 3. **Admin decorator.** Plan left the admin auth mechanism as a TODO. Resolved to `from roundtable.auth.middleware import require_role` with `@require_role(UserRole.admin)` (matches `roundtable/settings/routes.py` convention). 4. **`make_stale_task` renamed to `make_task`** returning a real `ScheduledTask` instead of a bare dict (matches `plugins/docker/scheduler.py` convention). 5. **`agent.py` is 370 lines vs. the plan's ~350 target** — 20 lines over because the appended code matches the plan verbatim. Not reworked. ## Known caveats - **401 response drops buffered samples.** On `401 invalid_token`, the agent re-buffers the current sample but silently drops whatever was already in the ring buffer from prior failed POSTs. Behavior matches the approved plan text (Task 5, Step 3) but is asymmetric with the generic-failure path. Fine for now; worth revisiting if you want stricter at-least-once semantics. - **UI polish followups** tracked as Fable task 266 — settings table cells collide on `Host` / `Agent version`, rotate/delete buttons need a `confirm()` guard, widget header wrap is awkward. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/FabledSteward#1