chore: rename project Roundtable → Steward

Renames the Python package directory, CLI command, env var prefix,
docker-compose service/container/image, Postgres role/db, and all
visible branding. Marketing form is "Fabled Steward".

Clean break from the previous rebrand: drops the fabledscryer→roundtable
import shim in __init__.py and the FABLEDSCRYER_* env var fallback in
config.py and migrations/env.py. Env vars are now STEWARD_* only.

Heads-up for existing deployments:
- Postgres user/db renamed fabledscryer → steward in docker-compose.yml.
  Existing volumes need the role/db renamed inside Postgres, or override
  POSTGRES_USER/POSTGRES_DB to keep the old names.
- Host-agent systemd unit is now steward-agent.service. Existing agents
  keep running under the old name; reinstall to switch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 16:20:14 -04:00
parent e118543b2e
commit 88ab5b917e
148 changed files with 570 additions and 619 deletions
+36 -36
View File
@@ -2,13 +2,13 @@
**Date:** 2026-04-14
**Status:** Approved, ready for implementation planning
**Scope:** Roundtable plugin for remote host resource monitoring (CPU, memory, storage, load, uptime) via a lightweight Python push agent.
**Scope:** Steward plugin for remote host resource monitoring (CPU, memory, storage, load, uptime) via a lightweight Python push agent.
---
## Goal
Give Roundtable a fleet-glance view of remote Linux hosts — current resource usage, history, and alert integration — without requiring agentless SSH/Ansible round-trips, a new toolchain, or SNMP gymnastics on every target.
Give Steward a fleet-glance view of remote Linux hosts — current resource usage, history, and alert integration — without requiring agentless SSH/Ansible round-trips, a new toolchain, or SNMP gymnastics on every target.
## Non-goals
@@ -24,7 +24,7 @@ Give Roundtable a fleet-glance view of remote Linux hosts — current resource u
```
┌──────────────┐ POST /plugins/host_agent/ingest ┌────────────────────────┐
│ Agent │ ──────────────────────────────────────────→ │ Roundtable
│ Agent │ ──────────────────────────────────────────→ │ Steward
│ (Python) │ Authorization: Bearer <per-host-token> │ host_agent plugin │
│ on target │ JSON body: metrics snapshot │ │
│ host │ │ routes.py (ingest + │
@@ -48,8 +48,8 @@ Give Roundtable a fleet-glance view of remote Linux hosts — current resource u
- **Agent** is a single Python file. No external dependencies beyond Python 3.8+ stdlib.
- **Plugin** is self-contained under `plugins/host_agent/`. Writes to the core `PluginMetric` time-series bus (the designed cross-plugin integration point) and its own private `host_agent_registrations` table. Writes to the core `Host` model for identity, but adds no new columns to it.
- **Auth** is per-host bearer tokens, minted on "Add host" in the plugin's settings page.
- **Install** is a one-line `curl | sh` command rendered per-host by Roundtable with the token already baked in.
- **Failure** is handled at the agent (in-memory ring buffer + exponential backoff) so Roundtable outages don't lose brief-window data.
- **Install** is a one-line `curl | sh` command rendered per-host by Steward with the token already baked in.
- **Failure** is handled at the agent (in-memory ring buffer + exponential backoff) so Steward outages don't lose brief-window data.
---
@@ -58,10 +58,10 @@ Give Roundtable a fleet-glance view of remote Linux hosts — current resource u
### File layout on the target host
```
/usr/local/lib/roundtable-agent/agent.py # the script, target ~300 lines
/etc/roundtable-agent.conf # key=value config, 0640 root:roundtable-agent
/etc/systemd/system/roundtable-agent.service # unit file
# dedicated system user: roundtable-agent
/usr/local/lib/steward-agent/agent.py # the script, target ~300 lines
/etc/steward-agent.conf # key=value config, 0640 root:steward-agent
/etc/systemd/system/steward-agent.service # unit file
# dedicated system user: steward-agent
```
### Config file format
@@ -69,7 +69,7 @@ Give Roundtable a fleet-glance view of remote Linux hosts — current resource u
Flat `key = value`, parsed by a ~20-line homegrown parser (no TOML/YAML dependency):
```
url = https://roundtable.home.lan
url = https://steward.home.lan
token = a1b2c3d4...
interval_seconds = 30
hostname = myhost # optional; defaults to uname -n
@@ -111,7 +111,7 @@ To stderr only (systemd captures to journal). No file logging, no log rotation.
### Identity
The agent's self-reported hostname in the payload is advisory. The real identity is the bearer token — Roundtable looks up the `Host` row via `HostAgentRegistration.token_hash`, not via hostname. Changing a host's hostname does not break its identity, and two hosts accidentally sharing a hostname can't collide.
The agent's self-reported hostname in the payload is advisory. The real identity is the bearer token — Steward looks up the `Host` row via `HostAgentRegistration.token_hash`, not via hostname. Changing a host's hostname does not break its identity, and two hosts accidentally sharing a hostname can't collide.
### Failure behavior
@@ -194,7 +194,7 @@ One `ScheduledTask` running every 60 seconds that flags `HostAgentRegistration`
### `METRIC_CATALOG` registration
Add to `roundtable/alerts/routes.py`:
Add to `steward/alerts/routes.py`:
```python
"host_agent": ["cpu_pct", "mem_used_pct", "mem_available_bytes",
@@ -202,7 +202,7 @@ Add to `roundtable/alerts/routes.py`:
"load_5m", "load_15m", "uptime_secs"],
```
This is the one edit outside the plugin directory, matching the pattern every other plugin already follows. Not ideal from a pure-plugin-independence standpoint but unavoidable until Roundtable core grows a plugin-registered catalog API — deferred as future core work.
This is the one edit outside the plugin directory, matching the pattern every other plugin already follows. Not ideal from a pure-plugin-independence standpoint but unavoidable until Steward core grows a plugin-registered catalog API — deferred as future core work.
---
@@ -212,7 +212,7 @@ This is the one edit outside the plugin directory, matching the pattern every ot
```http
POST /plugins/host_agent/ingest HTTP/1.1
Host: roundtable.home.lan
Host: steward.home.lan
Authorization: Bearer a1b2c3d4e5f6...
Content-Type: application/json
```
@@ -311,7 +311,7 @@ Agent treats anything non-2xx as failure → ring buffer. Agent treats 401 speci
### The one-liner (what the UI shows)
```
curl -sSL 'https://roundtable.home.lan/plugins/host_agent/install.sh?token=a1b2c3...' | sudo sh
curl -sSL 'https://steward.home.lan/plugins/host_agent/install.sh?token=a1b2c3...' | sudo sh
```
The UI also offers a "review script before running" link that opens a modal showing the two-step form:
@@ -326,19 +326,19 @@ sudo sh install.sh
```sh
#!/bin/sh
# Roundtable host agent installer
# Steward host agent installer
# Generated for: {{ host_name }} ({{ host_address }})
# Roundtable URL: {{ url }}
# Steward URL: {{ url }}
set -e
ROUNDTABLE_URL="{{ url }}"
STEWARD_URL="{{ url }}"
AGENT_TOKEN="{{ token }}"
AGENT_VERSION="{{ agent_version }}"
AGENT_USER="roundtable-agent"
AGENT_DIR="/usr/local/lib/roundtable-agent"
CONF_FILE="/etc/roundtable-agent.conf"
UNIT_FILE="/etc/systemd/system/roundtable-agent.service"
AGENT_USER="steward-agent"
AGENT_DIR="/usr/local/lib/steward-agent"
CONF_FILE="/etc/steward-agent.conf"
UNIT_FILE="/etc/systemd/system/steward-agent.service"
# ── preflight ────────────────────────────────────────────────────────────────
[ "$(id -u)" = "0" ] || { echo "must run as root (use sudo)"; exit 1; }
@@ -347,7 +347,7 @@ command -v python3 >/dev/null 2>&1 || { echo "python3 not found — install py
# Handle --uninstall
if [ "${1:-}" = "--uninstall" ]; then
systemctl disable --now roundtable-agent.service 2>/dev/null || true
systemctl disable --now steward-agent.service 2>/dev/null || true
rm -f "$UNIT_FILE" "$CONF_FILE"
rm -rf "$AGENT_DIR"
systemctl daemon-reload
@@ -363,13 +363,13 @@ fi
# ── drop the agent file ──────────────────────────────────────────────────────
mkdir -p "$AGENT_DIR"
curl -sSL "${ROUNDTABLE_URL}/plugins/host_agent/agent.py" -o "$AGENT_DIR/agent.py"
curl -sSL "${STEWARD_URL}/plugins/host_agent/agent.py" -o "$AGENT_DIR/agent.py"
chmod 0755 "$AGENT_DIR/agent.py"
chown root:root "$AGENT_DIR/agent.py"
# ── write config ─────────────────────────────────────────────────────────────
cat > "$CONF_FILE" <<EOF
url = $ROUNDTABLE_URL
url = $STEWARD_URL
token = $AGENT_TOKEN
interval_seconds = 30
EOF
@@ -379,7 +379,7 @@ chmod 0640 "$CONF_FILE"
# ── write systemd unit ───────────────────────────────────────────────────────
cat > "$UNIT_FILE" <<EOF
[Unit]
Description=Roundtable host agent
Description=Steward host agent
After=network-online.target
Wants=network-online.target
@@ -400,17 +400,17 @@ WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now roundtable-agent.service
systemctl enable --now steward-agent.service
echo
echo "Roundtable host agent $AGENT_VERSION installed and running."
echo "Check status: systemctl status roundtable-agent"
echo "Logs: journalctl -u roundtable-agent -f"
echo "Steward host agent $AGENT_VERSION installed and running."
echo "Check status: systemctl status steward-agent"
echo "Logs: journalctl -u steward-agent -f"
```
### Design points
- **Agent binary served by Roundtable itself.** `GET /plugins/host_agent/agent.py` serves the script bundled with the plugin, so version drift between install-script and agent is impossible — re-running the installer picks up whatever agent the currently-running Roundtable ships.
- **Agent binary served by Steward itself.** `GET /plugins/host_agent/agent.py` serves the script bundled with the plugin, so version drift between install-script and agent is impossible — re-running the installer picks up whatever agent the currently-running Steward ships.
- **Systemd hardening is cheap and correct.** `NoNewPrivileges`, `ProtectSystem=strict`, `ProtectHome`, `PrivateTmp`, `ReadOnlyPaths=/proc /sys`. The agent only needs read access to `/proc`, `/sys`, and mount points; denying everything else narrows blast radius.
- **Uninstall is a first-class flag**, not a separate script. Same one-liner with `--uninstall` appended.
- **Fail-fast on preflight.** Missing systemd or python3 → clear error, exit. No half-installed agent.
@@ -421,7 +421,7 @@ echo "Logs: journalctl -u roundtable-agent -f"
## UI surfaces
### Dashboard widgets (`roundtable/core/widgets.py`)
### Dashboard widgets (`steward/core/widgets.py`)
- **`host_resources`** — table widget. One row per monitored host: name, CPU %, mem %, disk % (worst mount), load 1m, "last seen Xs ago" with red/yellow/green coloring. Fleet glance.
- **`host_resource_history`** — chart widget for one host. CPU / mem / disk over a selectable time range (1h, 6h, 24h, 7d). Same pattern as every other history widget — Chart.js.
@@ -447,9 +447,9 @@ List of registered hosts with their enable flags, an "Add host" button that open
| Failure | Who handles it | Response |
|---|---|---|
| Agent can't reach Roundtable | Agent | Push to ring buffer, exponential backoff (30→60→120→300s cap), log WARN. Resets on success. |
| Roundtable rejects with 401 | Agent | Log ERROR with remediation hint, continue retry loop. Admin fixes via UI + conf edit; no restart needed. |
| Roundtable rejects with 400 | Agent | Log ERROR, drop the sample (don't re-buffer), continue. Indicates agent/server version skew. |
| Agent can't reach Steward | Agent | Push to ring buffer, exponential backoff (30→60→120→300s cap), log WARN. Resets on success. |
| Steward rejects with 401 | Agent | Log ERROR with remediation hint, continue retry loop. Admin fixes via UI + conf edit; no restart needed. |
| Steward rejects with 400 | Agent | Log ERROR, drop the sample (don't re-buffer), continue. Indicates agent/server version skew. |
| Ring buffer full | Agent | Drop oldest, keep newest. DEBUG log (expected during outages). |
| Config file missing or malformed | Agent | Log ERROR, exit non-zero. Systemd restarts after 10s. Repeated restarts are visible in journal. |
| `/proc/stat` read fails between samples | Agent | Skip CPU for this cycle, still POST the rest. Partial samples allowed. |
@@ -528,7 +528,7 @@ These are not blockers; they are feature boundaries inherent to the design.
6. Settings page: list, add-host flow, rotate-token, delete.
7. Dashboard widgets: table + history chart. Register in `core/widgets.py`.
8. Per-host detail page.
9. `METRIC_CATALOG` entry in `roundtable/alerts/routes.py`.
9. `METRIC_CATALOG` entry in `steward/alerts/routes.py`.
10. Scheduler: stale-agent marker.
11. Tests at every stage; final integration test to tie it together.