From 88ab5b917eb424495f1c4ce3d2a432d532a7227e Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 25 Apr 2026 16:20:14 -0400 Subject: [PATCH] =?UTF-8?q?chore:=20rename=20project=20Roundtable=20?= =?UTF-8?q?=E2=86=92=20Steward?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .env.example | 6 +- .gitignore | 2 +- Dockerfile | 6 +- README.md | 10 +- alembic.ini | 2 +- config.example.yaml | 6 +- docker-compose.yml | 16 +- docs/architecture.md | 26 +-- docs/core/alerting.md | 4 +- docs/core/ansible.md | 12 +- docs/core/configuration.md | 22 +- docs/core/monitors.md | 14 +- docs/plugins/host-agent-design.md | 72 +++---- docs/plugins/host-agent-plan.md | 104 +++++----- docs/plugins/index.yaml.example | 50 ++--- docs/plugins/overview.md | 10 +- docs/plugins/writing-a-plugin.md | 32 +-- docs/reference/code-map.md | 118 +++++------ docs/superpowers/plans/2026-04-13-rebrand.md | 190 +++++++++--------- .../specs/2026-04-13-rebrand-design.md | 30 +-- entrypoint.sh | 2 +- pyproject.toml | 6 +- roundtable/__init__.py | 39 ---- roundtable/settings/__init__.py | 1 - steward/__init__.py | 1 + {roundtable => steward}/alerts/__init__.py | 0 {roundtable => steward}/alerts/routes.py | 12 +- {roundtable => steward}/ansible/__init__.py | 0 {roundtable => steward}/ansible/executor.py | 4 +- {roundtable => steward}/ansible/routes.py | 10 +- {roundtable => steward}/ansible/sources.py | 4 +- {roundtable => steward}/app.py | 2 +- {roundtable => steward}/audit/__init__.py | 0 {roundtable => steward}/audit/routes.py | 6 +- {roundtable => steward}/auth/__init__.py | 0 {roundtable => steward}/auth/ldap_auth.py | 2 +- {roundtable => steward}/auth/middleware.py | 2 +- {roundtable => steward}/auth/oidc.py | 2 +- {roundtable => steward}/auth/routes.py | 18 +- {roundtable => steward}/cli.py | 2 +- {roundtable => steward}/config.py | 13 +- {roundtable => steward}/core/__init__.py | 0 {roundtable => steward}/core/alerts.py | 6 +- {roundtable => steward}/core/audit.py | 4 +- {roundtable => steward}/core/cleanup.py | 6 +- .../core/migration_runner.py | 2 +- {roundtable => steward}/core/migrations.py | 0 {roundtable => steward}/core/notifications.py | 4 +- {roundtable => steward}/core/plugin_index.py | 2 +- .../core/plugin_manager.py | 28 +-- {roundtable => steward}/core/reports.py | 22 +- {roundtable => steward}/core/scheduler.py | 0 {roundtable => steward}/core/settings.py | 12 +- {roundtable => steward}/core/time_range.py | 2 +- {roundtable => steward}/core/widgets.py | 2 +- {roundtable => steward}/dashboard/__init__.py | 0 {roundtable => steward}/dashboard/routes.py | 14 +- {roundtable => steward}/database.py | 0 {roundtable => steward}/dns/__init__.py | 0 {roundtable => steward}/dns/routes.py | 8 +- {roundtable => steward}/hosts/__init__.py | 0 {roundtable => steward}/hosts/routes.py | 10 +- .../migrations/__init__.py | 0 {roundtable => steward}/migrations/env.py | 8 +- .../migrations/script.py.mako | 0 .../migrations/versions/0001_core_initial.py | 0 .../migrations/versions/0002_core_monitors.py | 0 .../migrations/versions/0003_ansible_runs.py | 0 .../migrations/versions/0004_app_settings.py | 0 .../migrations/versions/0005_dashboards.py | 0 .../versions/0006_dashboard_is_default.py | 0 .../versions/0007_dashboard_ownership.py | 0 .../migrations/versions/0008_share_tokens.py | 0 .../versions/0009_widget_variants.py | 0 .../versions/0010_maintenance_windows.py | 0 .../migrations/versions/0011_audit_log.py | 0 .../migrations/versions/__init__.py | 0 {roundtable => steward}/models/__init__.py | 0 {roundtable => steward}/models/alerts.py | 0 {roundtable => steward}/models/ansible.py | 0 {roundtable => steward}/models/audit.py | 0 {roundtable => steward}/models/base.py | 0 {roundtable => steward}/models/dashboard.py | 0 {roundtable => steward}/models/hosts.py | 0 {roundtable => steward}/models/metrics.py | 0 {roundtable => steward}/models/monitors.py | 0 {roundtable => steward}/models/settings.py | 2 +- {roundtable => steward}/models/users.py | 0 {roundtable => steward}/monitors/__init__.py | 0 {roundtable => steward}/monitors/dns.py | 6 +- {roundtable => steward}/monitors/ping.py | 6 +- {roundtable => steward}/ping/__init__.py | 0 {roundtable => steward}/ping/routes.py | 12 +- steward/settings/__init__.py | 1 + {roundtable => steward}/settings/routes.py | 42 ++-- .../templates/_time_range.html | 0 .../templates/alerts/_rule_fields.html | 0 .../templates/alerts/list.html | 2 +- .../templates/alerts/maintenance.html | 2 +- .../templates/alerts/maintenance_form.html | 2 +- .../templates/alerts/rules_form.html | 2 +- .../templates/ansible/browse.html | 2 +- .../templates/ansible/index.html | 2 +- .../templates/ansible/run_detail.html | 2 +- .../templates/ansible/run_started.html | 0 .../templates/audit/list.html | 2 +- .../templates/auth/login.html | 2 +- .../templates/auth/setup.html | 2 +- {roundtable => steward}/templates/base.html | 4 +- .../templates/dashboard/_edit_panels.html | 0 .../templates/dashboard/edit.html | 2 +- .../templates/dashboard/index.html | 2 +- .../templates/dashboard/list.html | 2 +- .../templates/dashboard/share.html | 2 +- .../templates/dashboard/share_created.html | 2 +- .../templates/dashboard/share_view.html | 4 +- .../templates/dns/index.html | 2 +- .../templates/dns/rows.html | 0 .../templates/errors/404.html | 2 +- .../templates/errors/500.html | 2 +- .../templates/hosts/form.html | 2 +- .../templates/hosts/list.html | 2 +- .../templates/hosts/uptime.html | 2 +- .../templates/hosts/uptime_widget.html | 0 .../templates/ping/index.html | 2 +- .../templates/ping/rows.html | 0 .../templates/settings/_ansible_sources.html | 0 .../templates/settings/_catalog_partial.html | 0 .../templates/settings/_install_result.html | 0 .../templates/settings/_plugin_repos.html | 0 .../templates/settings/_restart_pending.html | 0 .../templates/settings/_tabs.html | 0 .../templates/settings/ansible.html | 4 +- .../templates/settings/auth.html | 12 +- .../templates/settings/general.html | 6 +- .../templates/settings/index.html | 4 +- .../templates/settings/notifications.html | 4 +- .../templates/settings/plugin_detail.html | 4 +- .../templates/settings/plugins.html | 4 +- .../templates/settings/reports.html | 4 +- tests/conftest.py | 2 +- tests/core/test_settings_public_base_url.py | 2 +- tests/plugins/host_agent/test_agent_config.py | 4 +- .../host_agent/test_install_template.py | 4 +- tests/test_app.py | 2 +- tests/test_auth.py | 12 +- tests/test_config.py | 10 +- tests/test_models.py | 2 +- 148 files changed, 570 insertions(+), 619 deletions(-) delete mode 100644 roundtable/__init__.py delete mode 100644 roundtable/settings/__init__.py create mode 100644 steward/__init__.py rename {roundtable => steward}/alerts/__init__.py (100%) rename {roundtable => steward}/alerts/routes.py (97%) rename {roundtable => steward}/ansible/__init__.py (100%) rename {roundtable => steward}/ansible/executor.py (97%) rename {roundtable => steward}/ansible/routes.py (95%) rename {roundtable => steward}/ansible/sources.py (96%) rename {roundtable => steward}/app.py (99%) rename {roundtable => steward}/audit/__init__.py (100%) rename {roundtable => steward}/audit/routes.py (89%) rename {roundtable => steward}/auth/__init__.py (100%) rename {roundtable => steward}/auth/ldap_auth.py (99%) rename {roundtable => steward}/auth/middleware.py (96%) rename {roundtable => steward}/auth/oidc.py (99%) rename {roundtable => steward}/auth/routes.py (93%) rename {roundtable => steward}/cli.py (92%) rename {roundtable => steward}/config.py (83%) rename {roundtable => steward}/core/__init__.py (100%) rename {roundtable => steward}/core/alerts.py (98%) rename {roundtable => steward}/core/audit.py (94%) rename {roundtable => steward}/core/cleanup.py (87%) rename {roundtable => steward}/core/migration_runner.py (98%) rename {roundtable => steward}/core/migrations.py (100%) rename {roundtable => steward}/core/notifications.py (95%) rename {roundtable => steward}/core/plugin_index.py (98%) rename {roundtable => steward}/core/plugin_manager.py (95%) rename {roundtable => steward}/core/reports.py (94%) rename {roundtable => steward}/core/scheduler.py (100%) rename {roundtable => steward}/core/settings.py (96%) rename {roundtable => steward}/core/time_range.py (97%) rename {roundtable => steward}/core/widgets.py (99%) rename {roundtable => steward}/dashboard/__init__.py (100%) rename {roundtable => steward}/dashboard/routes.py (98%) rename {roundtable => steward}/database.py (100%) rename {roundtable => steward}/dns/__init__.py (100%) rename {roundtable => steward}/dns/routes.py (89%) rename {roundtable => steward}/hosts/__init__.py (100%) rename {roundtable => steward}/hosts/routes.py (96%) rename {roundtable => steward}/migrations/__init__.py (100%) rename {roundtable => steward}/migrations/env.py (89%) rename {roundtable => steward}/migrations/script.py.mako (100%) rename {roundtable => steward}/migrations/versions/0001_core_initial.py (100%) rename {roundtable => steward}/migrations/versions/0002_core_monitors.py (100%) rename {roundtable => steward}/migrations/versions/0003_ansible_runs.py (100%) rename {roundtable => steward}/migrations/versions/0004_app_settings.py (100%) rename {roundtable => steward}/migrations/versions/0005_dashboards.py (100%) rename {roundtable => steward}/migrations/versions/0006_dashboard_is_default.py (100%) rename {roundtable => steward}/migrations/versions/0007_dashboard_ownership.py (100%) rename {roundtable => steward}/migrations/versions/0008_share_tokens.py (100%) rename {roundtable => steward}/migrations/versions/0009_widget_variants.py (100%) rename {roundtable => steward}/migrations/versions/0010_maintenance_windows.py (100%) rename {roundtable => steward}/migrations/versions/0011_audit_log.py (100%) rename {roundtable => steward}/migrations/versions/__init__.py (100%) rename {roundtable => steward}/models/__init__.py (100%) rename {roundtable => steward}/models/alerts.py (100%) rename {roundtable => steward}/models/ansible.py (100%) rename {roundtable => steward}/models/audit.py (100%) rename {roundtable => steward}/models/base.py (100%) rename {roundtable => steward}/models/dashboard.py (100%) rename {roundtable => steward}/models/hosts.py (100%) rename {roundtable => steward}/models/metrics.py (100%) rename {roundtable => steward}/models/monitors.py (100%) rename {roundtable => steward}/models/settings.py (93%) rename {roundtable => steward}/models/users.py (100%) rename {roundtable => steward}/monitors/__init__.py (100%) rename {roundtable => steward}/monitors/dns.py (94%) rename {roundtable => steward}/monitors/ping.py (93%) rename {roundtable => steward}/ping/__init__.py (100%) rename {roundtable => steward}/ping/routes.py (92%) create mode 100644 steward/settings/__init__.py rename {roundtable => steward}/settings/routes.py (95%) rename {roundtable => steward}/templates/_time_range.html (100%) rename {roundtable => steward}/templates/alerts/_rule_fields.html (100%) rename {roundtable => steward}/templates/alerts/list.html (98%) rename {roundtable => steward}/templates/alerts/maintenance.html (97%) rename {roundtable => steward}/templates/alerts/maintenance_form.html (98%) rename {roundtable => steward}/templates/alerts/rules_form.html (99%) rename {roundtable => steward}/templates/ansible/browse.html (98%) rename {roundtable => steward}/templates/ansible/index.html (96%) rename {roundtable => steward}/templates/ansible/run_detail.html (97%) rename {roundtable => steward}/templates/ansible/run_started.html (100%) rename {roundtable => steward}/templates/audit/list.html (98%) rename {roundtable => steward}/templates/auth/login.html (96%) rename {roundtable => steward}/templates/auth/setup.html (93%) rename {roundtable => steward}/templates/base.html (99%) rename {roundtable => steward}/templates/dashboard/_edit_panels.html (100%) rename {roundtable => steward}/templates/dashboard/edit.html (95%) rename {roundtable => steward}/templates/dashboard/index.html (98%) rename {roundtable => steward}/templates/dashboard/list.html (99%) rename {roundtable => steward}/templates/dashboard/share.html (98%) rename {roundtable => steward}/templates/dashboard/share_created.html (95%) rename {roundtable => steward}/templates/dashboard/share_view.html (97%) rename {roundtable => steward}/templates/dns/index.html (90%) rename {roundtable => steward}/templates/dns/rows.html (100%) rename {roundtable => steward}/templates/errors/404.html (89%) rename {roundtable => steward}/templates/errors/500.html (90%) rename {roundtable => steward}/templates/hosts/form.html (98%) rename {roundtable => steward}/templates/hosts/list.html (99%) rename {roundtable => steward}/templates/hosts/uptime.html (98%) rename {roundtable => steward}/templates/hosts/uptime_widget.html (100%) rename {roundtable => steward}/templates/ping/index.html (97%) rename {roundtable => steward}/templates/ping/rows.html (100%) rename {roundtable => steward}/templates/settings/_ansible_sources.html (100%) rename {roundtable => steward}/templates/settings/_catalog_partial.html (100%) rename {roundtable => steward}/templates/settings/_install_result.html (100%) rename {roundtable => steward}/templates/settings/_plugin_repos.html (100%) rename {roundtable => steward}/templates/settings/_restart_pending.html (100%) rename {roundtable => steward}/templates/settings/_tabs.html (100%) rename {roundtable => steward}/templates/settings/ansible.html (86%) rename {roundtable => steward}/templates/settings/auth.html (96%) rename {roundtable => steward}/templates/settings/general.html (91%) rename {roundtable => steward}/templates/settings/index.html (98%) rename {roundtable => steward}/templates/settings/notifications.html (96%) rename {roundtable => steward}/templates/settings/plugin_detail.html (98%) rename {roundtable => steward}/templates/settings/plugins.html (97%) rename {roundtable => steward}/templates/settings/reports.html (97%) diff --git a/.env.example b/.env.example index 16af250..8d36b78 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ # Copy to .env for Docker / local development secrets # These override values in config.yaml -ROUNDTABLE_SECRET_KEY=change-me -ROUNDTABLE_DATABASE__URL=postgresql+asyncpg://roundtable:password@localhost/roundtable -ROUNDTABLE_SMTP__PASSWORD= +STEWARD_SECRET_KEY=change-me +STEWARD_DATABASE__URL=postgresql+asyncpg://steward:password@localhost/steward +STEWARD_SMTP__PASSWORD= diff --git a/.gitignore b/.gitignore index a0290f9..fb2b9af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Planning files docs/superpowers/ -# Plugin directory — managed as a separate repo (bvandeusen/roundtable-plugins) +# Plugin directory — managed as a separate repo (bvandeusen/steward-plugins) # PLUGIN_DIR in config.yaml points here at runtime /plugins/ diff --git a/Dockerfile b/Dockerfile index 6f08158..938501c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN pip install --upgrade pip WORKDIR /app COPY pyproject.toml . -COPY roundtable/ roundtable/ +COPY steward/ steward/ RUN pip install --no-cache-dir . COPY alembic.ini . @@ -22,7 +22,7 @@ RUN chmod +x /entrypoint.sh # Runtime directories — owned by app user. The container starts as root # so the entrypoint can fix up /data permissions if needed, then drops to -# 'app' (uid 1000) via gosu before launching roundtable. +# 'app' (uid 1000) via gosu before launching steward. RUN useradd -m -u 1000 app \ && mkdir -p /data/playbook_cache /data/plugins \ && chown -R app:app /data /app @@ -30,4 +30,4 @@ RUN useradd -m -u 1000 app \ EXPOSE 5000 ENTRYPOINT ["/entrypoint.sh"] -CMD ["roundtable", "--host", "0.0.0.0", "--port", "5000"] +CMD ["steward", "--host", "0.0.0.0", "--port", "5000"] diff --git a/README.md b/README.md index 9e14986..b7a6239 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Roundtable +# Steward -A self-hosted network monitoring and infrastructure management hub for home servers. Roundtable gives you a single pane of glass over your hosts, services, and automation — with live-updating dashboards, alerting, and Ansible integration. +A self-hosted network monitoring and infrastructure management hub for home servers. Steward gives you a single pane of glass over your hosts, services, and automation — with live-updating dashboards, alerting, and Ansible integration. --- @@ -21,7 +21,7 @@ No JavaScript framework. No build step. No external workers or message brokers. ```bash cp .env.example .env -# Set ROUNDTABLE_DATABASE_URL in .env +# Set STEWARD_DATABASE_URL in .env docker compose up -d ``` @@ -41,7 +41,7 @@ pip install -e . cp config.example.yaml config.yaml # Edit config.yaml — set database.url at minimum -roundtable --host 0.0.0.0 --port 5000 +steward --host 0.0.0.0 --port 5000 ``` ICMP ping requires `CAP_NET_RAW` or the `setuid` ping binary. TCP mode (the default) needs no elevated privileges. @@ -54,7 +54,7 @@ Only two things must be set to run the app: | What | How | |---|---| -| Database URL | `ROUNDTABLE_DATABASE_URL` env var or `database.url` in `config.yaml` | +| Database URL | `STEWARD_DATABASE_URL` env var or `database.url` in `config.yaml` | | Secret key | Auto-generated on first run and saved to `/data/secret.key` | Everything else — SMTP, webhooks, monitor intervals, Ansible sources, plugin settings — is configured through the web UI at `/settings/`. diff --git a/alembic.ini b/alembic.ini index 4d7fa0d..1672123 100644 --- a/alembic.ini +++ b/alembic.ini @@ -1,5 +1,5 @@ [alembic] -script_location = roundtable/migrations +script_location = steward/migrations prepend_sys_path = . version_path_separator = os diff --git a/config.example.yaml b/config.example.yaml index 6bace6a..e165f7d 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -1,9 +1,9 @@ -# Roundtable — Bootstrap Configuration Example +# Steward — Bootstrap Configuration Example # # The only REQUIRED setting is the database URL. # Set it via env var (recommended for Docker): # -# ROUNDTABLE_DATABASE_URL=postgresql+asyncpg://user:pass@host/db +# STEWARD_DATABASE_URL=postgresql+asyncpg://user:pass@host/db # # All other settings (SMTP, webhook, ansible, plugins, retention) # are stored in the database and managed via the Settings UI at /settings/. @@ -11,7 +11,7 @@ # config.yaml is optional. Use it only if you prefer file-based bootstrap. database: - url: "postgresql+asyncpg://roundtable:password@localhost/roundtable" + url: "postgresql+asyncpg://steward:password@localhost/steward" # Optional: override the auto-generated secret key. # If not set, a key is auto-generated on first run and saved to /data/secret.key. diff --git a/docker-compose.yml b/docker-compose.yml index 382ec10..faedd9f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,8 @@ services: - roundtable: + steward: build: . - container_name: roundtable - image: roundtable:latest + container_name: steward + image: steward:latest ports: - "5000:5000" volumes: @@ -11,7 +11,7 @@ services: - /mnt/Data/traefik/log:/var/log/traefik:ro - /var/run/docker.sock:/var/run/docker.sock:ro environment: - - ROUNDTABLE_DATABASE_URL=postgresql+asyncpg://fabledscryer:fabledscryer@db/fabledscryer + - STEWARD_DATABASE_URL=postgresql+asyncpg://steward:steward@db/steward depends_on: db: condition: service_healthy @@ -20,13 +20,13 @@ services: db: image: postgres:16-alpine environment: - POSTGRES_USER: fabledscryer - POSTGRES_PASSWORD: fabledscryer - POSTGRES_DB: fabledscryer + POSTGRES_USER: steward + POSTGRES_PASSWORD: steward + POSTGRES_DB: steward volumes: - pgdata:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U fabledscryer"] + test: ["CMD-SHELL", "pg_isready -U steward"] interval: 5s timeout: 5s retries: 5 diff --git a/docs/architecture.md b/docs/architecture.md index 3da5bb2..0bef185 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,12 +1,12 @@ # Architecture -Roundtable is a single Quart (async Python) process. There are no separate worker processes, no message brokers, and no build pipeline for the frontend. All monitoring, scheduling, and request handling runs on a single asyncio event loop. +Steward is a single Quart (async Python) process. There are no separate worker processes, no message brokers, and no build pipeline for the frontend. All monitoring, scheduling, and request handling runs on a single asyncio event loop. --- ## Startup Sequence -`create_app()` in `roundtable/app.py` runs these steps synchronously before the event loop starts: +`create_app()` in `steward/app.py` runs these steps synchronously before the event loop starts: | Step | What happens | |---|---| @@ -32,14 +32,14 @@ All routes are Quart Blueprints registered in `app.py`: | Prefix | Blueprint | Module | |---|---|---| -| `/auth/` | `auth_bp` | `roundtable/auth/routes.py` | -| `/` | `dashboard_bp` | `roundtable/dashboard/routes.py` | -| `/hosts/` | `hosts_bp` | `roundtable/hosts/routes.py` | -| `/ping/` | `ping_bp` | `roundtable/ping/routes.py` | -| `/dns/` | `dns_bp` | `roundtable/dns/routes.py` | -| `/alerts/` | `alerts_bp` | `roundtable/alerts/routes.py` | -| `/ansible/` | `ansible_bp` | `roundtable/ansible/routes.py` | -| `/settings/` | `settings_bp` | `roundtable/settings/routes.py` | +| `/auth/` | `auth_bp` | `steward/auth/routes.py` | +| `/` | `dashboard_bp` | `steward/dashboard/routes.py` | +| `/hosts/` | `hosts_bp` | `steward/hosts/routes.py` | +| `/ping/` | `ping_bp` | `steward/ping/routes.py` | +| `/dns/` | `dns_bp` | `steward/dns/routes.py` | +| `/alerts/` | `alerts_bp` | `steward/alerts/routes.py` | +| `/ansible/` | `ansible_bp` | `steward/ansible/routes.py` | +| `/settings/` | `settings_bp` | `steward/settings/routes.py` | | `/plugins//` | plugin blueprint | `plugins//routes.py` | Plugin blueprints are mounted automatically by `load_plugins()` using the plugin directory name as the URL prefix. @@ -48,7 +48,7 @@ Plugin blueprints are mounted automatically by `load_plugins()` using the plugin ## Scheduler -`roundtable/core/scheduler.py` exports two things: +`steward/core/scheduler.py` exports two things: - `ScheduledTask` dataclass — holds a `name`, `coro_factory` (zero-argument callable returning a coroutine), `interval_seconds`, and optional `run_on_startup` flag - `start_scheduler(tasks)` — async function that loops every second, calling `asyncio.create_task()` for each task whose interval has elapsed @@ -93,9 +93,9 @@ This means the only file you must touch to get the app running is the database U No JavaScript framework, no build step. The frontend is: -- **Jinja2 templates** rendered server-side (`roundtable/templates/`) +- **Jinja2 templates** rendered server-side (`steward/templates/`) - **HTMX** for live-updating fragments (dashboard widgets, ping pills, DNS status) -- A single CSS design system in `roundtable/templates/base.html` using CSS custom properties +- A single CSS design system in `steward/templates/base.html` using CSS custom properties Live-updating widgets use HTMX polling: ```html diff --git a/docs/core/alerting.md b/docs/core/alerting.md index 6d9eef8..b348b2f 100644 --- a/docs/core/alerting.md +++ b/docs/core/alerting.md @@ -14,7 +14,7 @@ When any monitor or plugin calls `record_metric()`: 4. If a state transition occurs, an `AlertEvent` row is written 5. Notification I/O is deferred outside the transaction via `asyncio.create_task()` -**Key function:** `roundtable/core/alerts.py` → `record_metric(session, source_module, resource_name, metric_name, value)` +**Key function:** `steward/core/alerts.py` → `record_metric(session, source_module, resource_name, metric_name, value)` `record_metric()` must always be called inside an active transaction: @@ -130,7 +130,7 @@ Webhook is skipped if `webhook.url` is empty. ## Data Models -Defined in `roundtable/models/alerts.py`: +Defined in `steward/models/alerts.py`: - **`alert_rules`** — one row per configured rule - **`alert_states`** — one row per rule, tracks current state and consecutive failure count diff --git a/docs/core/ansible.md b/docs/core/ansible.md index a0667df..b8366d5 100644 --- a/docs/core/ansible.md +++ b/docs/core/ansible.md @@ -1,6 +1,6 @@ # Ansible Integration -Roundtable can browse, trigger, and stream output from Ansible playbooks directly from the web UI. Runs execute as asyncio tasks inside the same process — no Celery, no external workers. +Steward can browse, trigger, and stream output from Ansible playbooks directly from the web UI. Runs execute as asyncio tasks inside the same process — no Celery, no external workers. --- @@ -115,7 +115,7 @@ Output stored in the DB is capped at 1 MB. If truncated, `[output truncated]` is ## Data Model -`ansible_runs` table (defined in `roundtable/models/ansible.py`): +`ansible_runs` table (defined in `steward/models/ansible.py`): | Column | Type | Description | |---|---|---| @@ -137,7 +137,7 @@ Runs older than `data.retention_days` (default 90) are pruned by the `data_clean | File | Purpose | |---|---| -| `roundtable/ansible/sources.py` | Source discovery, git pull logic | -| `roundtable/ansible/executor.py` | Subprocess execution and output streaming | -| `roundtable/ansible/routes.py` | HTTP routes (browse, trigger, stream, history) | -| `roundtable/models/ansible.py` | `AnsibleRun` model | +| `steward/ansible/sources.py` | Source discovery, git pull logic | +| `steward/ansible/executor.py` | Subprocess execution and output streaming | +| `steward/ansible/routes.py` | HTTP routes (browse, trigger, stream, history) | +| `steward/models/ansible.py` | `AnsibleRun` model | diff --git a/docs/core/configuration.md b/docs/core/configuration.md index 3ab5a17..9b98741 100644 --- a/docs/core/configuration.md +++ b/docs/core/configuration.md @@ -1,6 +1,6 @@ # Configuration -Roundtable uses a two-layer configuration system. Only the bare minimum needed to boot lives in files or environment variables. Everything else is stored in the database and managed through the Settings UI. +Steward uses a two-layer configuration system. Only the bare minimum needed to boot lives in files or environment variables. Everything else is stored in the database and managed through the Settings UI. --- @@ -10,31 +10,29 @@ These three values are read at startup from `config.yaml` and/or environment var | Key | Env var | Default | Description | |---|---|---|---| -| `database.url` | `ROUNDTABLE_DATABASE_URL` | — | PostgreSQL async URL. **Required.** | -| `secret_key` | `ROUNDTABLE_SECRET_KEY` | auto-generated | Flask/Quart session signing key. Auto-generated and saved to `/data/secret.key` if not set. | -| `plugin_dir` | `ROUNDTABLE_PLUGIN_DIR` | `plugins` | Path to the plugins directory. | +| `database.url` | `STEWARD_DATABASE_URL` | — | PostgreSQL async URL. **Required.** | +| `secret_key` | `STEWARD_SECRET_KEY` | auto-generated | Flask/Quart session signing key. Auto-generated and saved to `/data/secret.key` if not set. | +| `plugin_dir` | `STEWARD_PLUGIN_DIR` | `plugins` | Path to the plugins directory. | -**Resolution order for `database_url`:** env var `ROUNDTABLE_DATABASE_URL` → env var `ROUNDTABLE_DATABASE__URL` (legacy double-underscore) → `database.url` in `config.yaml`. +**Resolution order for `database_url`:** env var `STEWARD_DATABASE_URL` → env var `STEWARD_DATABASE__URL` (legacy double-underscore) → `database.url` in `config.yaml`. -**Resolution order for `secret_key`:** env var `ROUNDTABLE_SECRET_KEY` → `secret_key` in `config.yaml` → `/data/secret.key` file → auto-generate and write to `/data/secret.key`. +**Resolution order for `secret_key`:** env var `STEWARD_SECRET_KEY` → `secret_key` in `config.yaml` → `/data/secret.key` file → auto-generate and write to `/data/secret.key`. ### Minimal config.yaml ```yaml database: - url: "postgresql+asyncpg://user:password@localhost/roundtable" + url: "postgresql+asyncpg://user:password@localhost/steward" ``` ### Minimal env-only setup (Docker) ```bash -ROUNDTABLE_DATABASE_URL=postgresql+asyncpg://user:password@db/roundtable +STEWARD_DATABASE_URL=postgresql+asyncpg://user:password@db/steward ``` A `.env` file is loaded automatically if present. -> **Legacy env vars:** `FABLEDSCRYER_*` env vars are still accepted as a fallback for existing deployments. They will be removed in a future release — migrate to `ROUNDTABLE_*` when convenient. - --- ## App Settings (Database-backed) @@ -68,7 +66,7 @@ Default webhook template: ### Reading and Writing Settings in Code ```python -from roundtable.core.settings import get_setting, set_setting +from steward.core.settings import get_setting, set_setting # Read async with current_app.db_sessionmaker() as session: @@ -84,7 +82,7 @@ async with current_app.db_sessionmaker() as session: ### The DEFAULTS Dict -`roundtable/core/settings.py` contains the `DEFAULTS` dict — the canonical list of all recognised settings and their default values. Add new settings here to make them recognised by `get_all_settings()` and the settings UI. +`steward/core/settings.py` contains the `DEFAULTS` dict — the canonical list of all recognised settings and their default values. Add new settings here to make them recognised by `get_all_settings()` and the settings UI. --- diff --git a/docs/core/monitors.md b/docs/core/monitors.md index 0accd47..2345e85 100644 --- a/docs/core/monitors.md +++ b/docs/core/monitors.md @@ -1,13 +1,13 @@ # Core Monitors -Roundtable ships two built-in monitors: Ping and DNS. Both run as asyncio scheduled tasks on the same event loop as the web server, with no separate processes. +Steward ships two built-in monitors: Ping and DNS. Both run as asyncio scheduled tasks on the same event loop as the web server, with no separate processes. --- ## Ping Monitor -**Source:** `roundtable/monitors/ping.py` -**Scheduler task:** `ping_monitor` in `roundtable/app.py` +**Source:** `steward/monitors/ping.py` +**Scheduler task:** `ping_monitor` in `steward/app.py` **Interval:** `monitors.poll_interval_seconds` (default 60s), runs on startup ### How It Works @@ -29,7 +29,7 @@ Each probe writes a `PingResult` row and calls `record_metric()`: ### Data Model -`ping_results` table (defined in `roundtable/models/monitors.py`): +`ping_results` table (defined in `steward/models/monitors.py`): | Column | Type | Description | |---|---|---| @@ -51,8 +51,8 @@ Old rows are pruned by the `data_cleanup` task (default: 90 days). ## DNS Monitor -**Source:** `roundtable/monitors/dns.py` -**Scheduler task:** `dns_monitor` in `roundtable/app.py` +**Source:** `steward/monitors/dns.py` +**Scheduler task:** `dns_monitor` in `steward/app.py` **Interval:** `monitors.poll_interval_seconds` (default 60s), runs on startup ### How It Works @@ -70,7 +70,7 @@ Each check writes a `DnsResult` row and calls `record_metric()`: ### Data Model -`dns_results` table (defined in `roundtable/models/monitors.py`): +`dns_results` table (defined in `steward/models/monitors.py`): | Column | Type | Description | |---|---|---| diff --git a/docs/plugins/host-agent-design.md b/docs/plugins/host-agent-design.md index 49c1fed..ef6611a 100644 --- a/docs/plugins/host-agent-design.md +++ b/docs/plugins/host-agent-design.md @@ -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 │ 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" < "$UNIT_FILE" < **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Ship a `host_agent` Roundtable plugin plus a stdlib-only Python push agent that reports CPU/memory/storage/load/uptime from remote Linux hosts to Roundtable. +**Goal:** Ship a `host_agent` Steward plugin plus a stdlib-only Python push agent that reports CPU/memory/storage/load/uptime from remote Linux hosts to Steward. **Architecture:** Plugin lives under `plugins/host_agent/` (mirrors `plugins/http/`). It owns a private `host_agent_registrations` table, writes time-series data into the core `PluginMetric` bus, serves its own agent source at `GET /plugins/host_agent/agent.py`, and renders a per-host install script at `GET /plugins/host_agent/install.sh`. The agent is a single ~300-line Python script with a 30s collect→POST loop, in-memory ring buffer, exponential backoff, and systemd unit installed by a curl one-liner. @@ -14,7 +14,7 @@ ## Execution note — path 1 (no DB-backed tests) -Mid-execution discovery: the Roundtable test harness runs `create_app(testing=True)`, which mocks `db_sessionmaker` and skips all migrations. There is no existing DB-backed test fixture in this codebase, and no existing plugin ships with tests. The first execution attempt tried to invent a SQLite-backed override fixture, which cascaded into modifying a production migration (`0007_dashboard_ownership.py`) to remove an inline FK — unacceptable. That commit was reverted. +Mid-execution discovery: the Steward test harness runs `create_app(testing=True)`, which mocks `db_sessionmaker` and skips all migrations. There is no existing DB-backed test fixture in this codebase, and no existing plugin ships with tests. The first execution attempt tried to invent a SQLite-backed override fixture, which cascaded into modifying a production migration (`0007_dashboard_ownership.py`) to remove an inline FK — unacceptable. That commit was reverted. **Revised test strategy:** test the agent exhaustively (everything that lives in `plugins/host_agent/agent.py` — pure Python, stdlib only, trivially unit-testable). Test the server-side metric-expansion function as a pure function that takes a sample dict + host name and returns a list of `(metric_name, resource_name, value)` tuples. Everything else — ingest route, install route, settings routes, widgets, detail page, scheduler — is verified manually against the dev server. @@ -43,7 +43,7 @@ Where code blocks in tasks below reference DB-backed tests, treat them as guidan - `plugins/host_agent/scheduler.py` — stale-agent marker task. - `plugins/host_agent/agent.py` — the Python agent script, served to targets. - `plugins/host_agent/migrations/__init__.py` -- `plugins/host_agent/migrations/env.py` — alembic env (copy of http plugin's, but imports `roundtable.models.base`). +- `plugins/host_agent/migrations/env.py` — alembic env (copy of http plugin's, but imports `steward.models.base`). - `plugins/host_agent/migrations/versions/__init__.py` - `plugins/host_agent/migrations/versions/host_agent_001_initial.py` — creates `host_agent_registrations`. - `plugins/host_agent/templates/install.sh.j2` — Jinja install script template. @@ -68,8 +68,8 @@ Where code blocks in tasks below reference DB-backed tests, treat them as guidan **Modified files (core, small edits):** -- `roundtable/alerts/routes.py` — add `"host_agent"` entry to `METRIC_CATALOG`. -- `roundtable/core/widgets.py` — add `host_resources` and `host_resource_history` entries. +- `steward/alerts/routes.py` — add `"host_agent"` entry to `METRIC_CATALOG`. +- `steward/core/widgets.py` — add `host_resources` and `host_resource_history` entries. - `docs/plugins/index.yaml.example` — add catalog entry. --- @@ -102,7 +102,7 @@ pytestmark = pytest.mark.asyncio async def test_host_agent_migration_creates_table(app): # The app fixture runs all plugin migrations on startup. - from roundtable.core.db import get_engine + from steward.core.db import get_engine engine = get_engine() async with engine.connect() as conn: def _check(sync_conn): @@ -127,11 +127,11 @@ Expected: FAIL (plugin not loaded, table missing). name: host_agent version: "1.0.0" description: "Remote Linux host resource monitoring via a lightweight Python push agent (CPU, memory, storage, load, uptime)" -author: "Roundtable" +author: "Steward" license: "MIT" min_app_version: "0.1.0" -repository_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins" -homepage: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/src/branch/main/host_agent" +repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins" +homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/host_agent" tags: - host - monitoring @@ -180,7 +180,7 @@ from __future__ import annotations import uuid from datetime import datetime, timezone from sqlalchemy import Column, String, DateTime, ForeignKey -from roundtable.models.base import Base +from steward.models.base import Base def _uuid() -> str: @@ -269,8 +269,8 @@ from alembic import context sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) -from roundtable.models.base import Base -import roundtable.models # noqa: F401 +from steward.models.base import Base +import steward.models # noqa: F401 from plugins.host_agent.models import HostAgentRegistration # noqa: F401 config = context.config @@ -282,14 +282,14 @@ target_metadata = Base.metadata def _get_url() -> str: import yaml - cfg_path = os.environ.get("ROUNDTABLE_CONFIG", "config.yaml") + cfg_path = os.environ.get("STEWARD_CONFIG", "config.yaml") try: with open(cfg_path) as f: cfg = yaml.safe_load(f) or {} url = cfg.get("database", {}).get("url", "") except FileNotFoundError: url = "" - return os.environ.get("ROUNDTABLE_DATABASE__URL", url) + return os.environ.get("STEWARD_DATABASE__URL", url) def run_migrations_offline() -> None: @@ -413,12 +413,12 @@ from plugins.host_agent.agent import read_config, ConfigError def test_parses_flat_key_value(tmp_path): p = tmp_path / "agent.conf" p.write_text( - "url = https://roundtable.example\n" + "url = https://steward.example\n" "token = abc123\n" "interval_seconds = 45\n" ) cfg = read_config(str(p)) - assert cfg["url"] == "https://roundtable.example" + assert cfg["url"] == "https://steward.example" assert cfg["token"] == "abc123" assert cfg["interval_seconds"] == 45 @@ -466,7 +466,7 @@ Expected: FAIL (`read_config` does not exist). ```python # plugins/host_agent/agent.py -"""Roundtable host agent — pushes resource metrics to a Roundtable instance. +"""Steward host agent — pushes resource metrics to a Steward instance. Python 3.8+ stdlib only. Target ~300 lines. Served to targets at GET /plugins/host_agent/agent.py. @@ -1060,7 +1060,7 @@ def post_payload(url: str, token: str, payload: dict) -> tuple[bool, int | None] headers={ "Content-Type": "application/json", "Authorization": f"Bearer {token}", - "User-Agent": f"roundtable-host-agent/{AGENT_VERSION}", + "User-Agent": f"steward-host-agent/{AGENT_VERSION}", }, method="POST", ) @@ -1112,7 +1112,7 @@ def main_loop(conf_path: str) -> int: buffer = RingBuffer(maxlen=20) backoff = 0 - _log("INFO", f"roundtable-host-agent {AGENT_VERSION} starting " + _log("INFO", f"steward-host-agent {AGENT_VERSION} starting " f"(url={cfg['url']}, interval={cfg['interval_seconds']}s)") while not _shutdown_requested: @@ -1171,7 +1171,7 @@ def main_loop(conf_path: str) -> int: if __name__ == "__main__": - conf = os.environ.get("ROUNDTABLE_AGENT_CONFIG", "/etc/roundtable-agent.conf") + conf = os.environ.get("STEWARD_AGENT_CONFIG", "/etc/steward-agent.conf") sys.exit(main_loop(conf)) ``` @@ -1209,8 +1209,8 @@ git commit -m "feat(host_agent): agent POST, backoff, and main loop" import hashlib import pytest_asyncio -from roundtable.models.hosts import Host -from roundtable.core.db import get_session +from steward.models.hosts import Host +from steward.core.db import get_session from plugins.host_agent.models import HostAgentRegistration @@ -1233,7 +1233,7 @@ async def registered_host(app): return {"host": host, "registration": reg, "token": raw_token} ``` -> **Note:** Verify `Host.__init__` accepts `name=` and `address=` kwargs before relying on this. If the real Host model requires more fields (e.g., `probe_type`), pass them here. Read `roundtable/models/hosts.py` first to confirm. +> **Note:** Verify `Host.__init__` accepts `name=` and `address=` kwargs before relying on this. If the real Host model requires more fields (e.g., `probe_type`), pass them here. Read `steward/models/hosts.py` first to confirm. - [ ] **Step 2: Write failing ingest test** @@ -1244,8 +1244,8 @@ from datetime import datetime, timezone import pytest from sqlalchemy import select -from roundtable.models.metrics import PluginMetric -from roundtable.core.db import get_session +from steward.models.metrics import PluginMetric +from steward.core.db import get_session from plugins.host_agent.models import HostAgentRegistration pytestmark = pytest.mark.asyncio @@ -1352,9 +1352,9 @@ from typing import Any from quart import Blueprint, jsonify, request from sqlalchemy import select -from roundtable.core.db import get_session -from roundtable.models.hosts import Host -from roundtable.models.metrics import PluginMetric +from steward.core.db import get_session +from steward.models.hosts import Host +from steward.models.metrics import PluginMetric from .models import HostAgentRegistration host_agent_bp = Blueprint("host_agent", __name__, template_folder="templates") @@ -1628,7 +1628,7 @@ async def test_install_sh_renders_with_token(client, registered_host): assert resp.status_code == 200 assert resp.content_type.startswith("text/plain") text = (await resp.get_data()).decode() - assert "roundtable-agent" in text + assert "steward-agent" in text assert registered_host["token"] in text assert "systemctl enable --now" in text assert "NoNewPrivileges=yes" in text @@ -1758,11 +1758,11 @@ git commit -m "feat(host_agent): install.sh and agent.py serving routes" - Create: `plugins/host_agent/templates/settings_list.html` - Create: `tests/plugins/host_agent/test_settings_routes.py` -Auth note: the existing Roundtable admin decorator pattern needs to be used here. Read `roundtable/settings/routes.py` (or similar) to find the decorator name — likely something like `@require_admin` or a session check. Use whatever the rest of the codebase uses. **Do not invent a new auth mechanism.** +Auth note: the existing Steward admin decorator pattern needs to be used here. Read `steward/settings/routes.py` (or similar) to find the decorator name — likely something like `@require_admin` or a session check. Use whatever the rest of the codebase uses. **Do not invent a new auth mechanism.** - [ ] **Step 1: Find the admin auth decorator** -Run: `grep -rn "require_admin\|@admin_required\|def admin" roundtable/settings/ roundtable/core/auth.py 2>/dev/null | head -20` +Run: `grep -rn "require_admin\|@admin_required\|def admin" steward/settings/ steward/core/auth.py 2>/dev/null | head -20` Note the decorator name and import path for use in Step 3. - [ ] **Step 2: Write failing test** @@ -1772,8 +1772,8 @@ Note the decorator name and import path for use in Step 3. import pytest from sqlalchemy import select -from roundtable.core.db import get_session -from roundtable.models.hosts import Host +from steward.core.db import get_session +from steward.models.hosts import Host from plugins.host_agent.models import HostAgentRegistration pytestmark = pytest.mark.asyncio @@ -1839,7 +1839,7 @@ from quart import redirect, url_for # TODO: replace _require_admin with the project-wide decorator found in Step 1. # Placeholder below mirrors the shape; swap for real admin auth. -from roundtable.core.auth import require_admin # adjust import to actual path +from steward.core.auth import require_admin # adjust import to actual path def _new_token_pair() -> tuple[str, str]: @@ -1938,7 +1938,7 @@ async def settings_list(): ```html {% extends "base.html" %} -{% block title %}Host Agent — Roundtable{% endblock %} +{% block title %}Host Agent — Steward{% endblock %} {% block content %}

Host Agent — Registered Hosts

@@ -2003,7 +2003,7 @@ async def settings_list(): - [ ] **Step 6: Run tests — iterate on auth decorator if needed** Run: `pytest tests/plugins/host_agent/test_settings_routes.py -v` -Expected: PASS. If admin auth fails, the actual import path from Step 1 is wrong — fix the `from roundtable.core.auth import require_admin` line. +Expected: PASS. If admin auth fails, the actual import path from Step 1 is wrong — fix the `from steward.core.auth import require_admin` line. - [ ] **Step 7: Commit** @@ -2020,8 +2020,8 @@ git commit -m "feat(host_agent): plugin settings page — add, rotate, delete" - Modify: `plugins/host_agent/routes.py` — add `/widget` and `/widget/history` partials. - Create: `plugins/host_agent/templates/widget_table.html` - Create: `plugins/host_agent/templates/widget_history.html` -- Modify: `roundtable/core/widgets.py` -- Modify: `roundtable/alerts/routes.py` +- Modify: `steward/core/widgets.py` +- Modify: `steward/alerts/routes.py` - [ ] **Step 1: Add widget partial routes to `plugins/host_agent/routes.py`** @@ -2165,7 +2165,7 @@ async def widget_history(): ``` -- [ ] **Step 4: Register widgets in `roundtable/core/widgets.py`** +- [ ] **Step 4: Register widgets in `steward/core/widgets.py`** Add to `WIDGET_REGISTRY` (alphabetically by existing convention, or at the end — check the file first): @@ -2206,7 +2206,7 @@ Add to `WIDGET_REGISTRY` (alphabetically by existing convention, or at the end - [ ] **Step 5: Register `host_agent` in `METRIC_CATALOG`** -Read `roundtable/alerts/routes.py`. Locate the `METRIC_CATALOG` dict. Add: +Read `steward/alerts/routes.py`. Locate the `METRIC_CATALOG` dict. Add: ```python "host_agent": [ @@ -2244,7 +2244,7 @@ Expected: PASS. - [ ] **Step 7: Commit** ```bash -git add plugins/host_agent/routes.py plugins/host_agent/templates/widget_table.html plugins/host_agent/templates/widget_history.html roundtable/core/widgets.py roundtable/alerts/routes.py tests/plugins/host_agent/test_ingest_route.py +git add plugins/host_agent/routes.py plugins/host_agent/templates/widget_table.html plugins/host_agent/templates/widget_history.html steward/core/widgets.py steward/alerts/routes.py tests/plugins/host_agent/test_ingest_route.py git commit -m "feat(host_agent): dashboard widgets and alert metric catalog entry" ``` @@ -2409,8 +2409,8 @@ from datetime import datetime, timedelta, timezone import pytest from sqlalchemy import select -from roundtable.core.db import get_session -from roundtable.models.hosts import Host +from steward.core.db import get_session +from steward.models.hosts import Host from plugins.host_agent.models import HostAgentRegistration from plugins.host_agent.scheduler import find_stale_registrations @@ -2459,8 +2459,8 @@ from datetime import datetime, timedelta, timezone from sqlalchemy import select -from roundtable.core.db import get_session -from roundtable.models.hosts import Host +from steward.core.db import get_session +from steward.models.hosts import Host from .models import HostAgentRegistration @@ -2531,8 +2531,8 @@ from unittest.mock import patch import pytest from sqlalchemy import select -from roundtable.core.db import get_session -from roundtable.models.metrics import PluginMetric +from steward.core.db import get_session +from steward.models.metrics import PluginMetric from plugins.host_agent import agent as a pytestmark = pytest.mark.asyncio @@ -2609,12 +2609,12 @@ Append to the `plugins:` list in `docs/plugins/index.yaml.example`: - name: host_agent version: "1.0.0" description: "Remote Linux host resource monitoring via a lightweight Python push agent" - author: "Roundtable" + author: "Steward" license: "MIT" min_app_version: "0.1.0" - repository_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins" - homepage: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/src/branch/main/host_agent" - download_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/releases/download/host_agent-v1.0.0/host_agent.zip" + repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins" + homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/host_agent" + download_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/host_agent-v1.0.0/host_agent.zip" checksum_sha256: "" tags: - host @@ -2643,7 +2643,7 @@ Use `fable_update_task` to set status=done on task 252 ("Implement host_agent pl - [ ] **Step 2: Add a Fable note summarizing what shipped** -One-paragraph `fable_create_note` attached to Roundtable project (id 6) with: +One-paragraph `fable_create_note` attached to Steward project (id 6) with: - Link to spec: `docs/plugins/host-agent-design.md` - Link to plan: `docs/plugins/host-agent-plan.md` - Note any deviations from the spec discovered during implementation (e.g., auth decorator name, fixture adjustments). diff --git a/docs/plugins/index.yaml.example b/docs/plugins/index.yaml.example index 4f934df..839efae 100644 --- a/docs/plugins/index.yaml.example +++ b/docs/plugins/index.yaml.example @@ -1,10 +1,10 @@ -# roundtable-plugins / index.yaml +# steward-plugins / index.yaml # -# This file is the catalog index for the roundtable plugin repository. +# This file is the catalog index for the steward plugin repository. # It is fetched by the app's Settings → Plugins → Plugin Catalog UI. # -# Roundtable reads this file from: -# https://git.fabledsword.com/bvandeusen/Roundtable-plugins/raw/branch/main/index.yaml +# Steward reads this file from: +# https://git.fabledsword.com/bvandeusen/Steward-plugins/raw/branch/main/index.yaml # # After adding or updating a plugin entry, commit and push — the change is # live immediately for anyone whose app fetches the catalog (cache TTL: 5 min). @@ -25,7 +25,7 @@ # # Download URL conventions: # Gitea release assets (recommended): -# https://git.fabledsword.com/bvandeusen/Roundtable-plugins/releases/download/traefik-v1.0.0/traefik.zip +# https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/traefik-v1.0.0/traefik.zip # Upload the zip as a release attachment in Gitea; paste the URL here. # Source archive tarballs work too but release assets are preferred (smaller, plugin-only). @@ -37,12 +37,12 @@ plugins: - name: http version: "1.0.0" description: "Synthetic HTTP endpoint monitoring — status code, response time, content match, TLS expiry" - author: "Roundtable" + author: "Steward" license: "MIT" min_app_version: "0.1.0" - repository_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins" - homepage: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/src/branch/main/http" - download_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/releases/download/http-v1.0.0/http.zip" + repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins" + homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/http" + download_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/http-v1.0.0/http.zip" checksum_sha256: "" tags: - monitoring @@ -53,12 +53,12 @@ plugins: - name: docker version: "1.0.0" description: "Docker container status, resource usage, and restart tracking via Docker socket" - author: "Roundtable" + author: "Steward" license: "MIT" min_app_version: "0.1.0" - repository_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins" - homepage: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/src/branch/main/docker" - download_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/releases/download/docker-v1.0.0/docker.zip" + repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins" + homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/docker" + download_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/docker-v1.0.0/docker.zip" checksum_sha256: "" tags: - containers @@ -68,12 +68,12 @@ plugins: - name: traefik version: "1.0.0" description: "Traefik reverse proxy metrics and access log integration" - author: "Roundtable" + author: "Steward" license: "MIT" min_app_version: "0.1.0" - repository_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins" - homepage: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/src/branch/main/traefik" - download_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/releases/download/traefik-v1.0.0/traefik.zip" + repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins" + homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/traefik" + download_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/traefik-v1.0.0/traefik.zip" checksum_sha256: "" # fill in after running: sha256sum traefik.zip tags: - proxy @@ -83,12 +83,12 @@ plugins: - name: unifi version: "1.0.0" description: "UniFi Network controller integration — WAN health, devices, clients, DPI" - author: "Roundtable" + author: "Steward" license: "MIT" min_app_version: "0.1.0" - repository_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins" - homepage: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/src/branch/main/unifi" - download_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/releases/download/unifi-v1.0.0/unifi.zip" + repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins" + homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/unifi" + download_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/unifi-v1.0.0/unifi.zip" checksum_sha256: "" tags: - network @@ -98,12 +98,12 @@ plugins: - name: host_agent version: "1.0.0" description: "Remote Linux host resource monitoring via a lightweight Python push agent" - author: "Roundtable" + author: "Steward" license: "MIT" min_app_version: "0.1.0" - repository_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins" - homepage: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/src/branch/main/host_agent" - download_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/releases/download/host_agent-v1.0.0/host_agent.zip" + repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins" + homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/host_agent" + download_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/host_agent-v1.0.0/host_agent.zip" checksum_sha256: "" tags: - host diff --git a/docs/plugins/overview.md b/docs/plugins/overview.md index fc27136..8a2cdbc 100644 --- a/docs/plugins/overview.md +++ b/docs/plugins/overview.md @@ -1,12 +1,12 @@ # Plugin System Overview -Plugins extend Roundtable with new data sources, UI pages, scheduled tasks, and dashboard widgets. Only enabled plugins are imported — disabled or unlisted plugins have zero runtime overhead. +Plugins extend Steward with new data sources, UI pages, scheduled tasks, and dashboard widgets. Only enabled plugins are imported — disabled or unlisted plugins have zero runtime overhead. --- ## How Plugins Are Loaded -Plugin loading happens in step 9 of `create_app()`, after all core blueprints and tasks are registered. The entrypoint is `load_plugins(app)` in `roundtable/core/plugin_manager.py`. +Plugin loading happens in step 9 of `create_app()`, after all core blueprints and tasks are registered. The entrypoint is `load_plugins(app)` in `steward/core/plugin_manager.py`. For each plugin listed as `enabled: true` in the `PLUGINS` config: @@ -57,7 +57,7 @@ description: "Does a thing" # Optional author: "Your Name" -min_app_version: "0.1.0" # Minimum Roundtable version required +min_app_version: "0.1.0" # Minimum Steward version required # Default config — merged with user overrides at runtime # Access at runtime via: app.config["PLUGINS"]["myplugin"]["my_setting"] @@ -90,7 +90,7 @@ def setup(app): Returns a list of `ScheduledTask` objects. Return `[]` if the plugin has no background tasks. Called after `setup()`, so any app references set in `setup()` are available. ```python -from roundtable.core.scheduler import ScheduledTask +from steward.core.scheduler import ScheduledTask def get_scheduled_tasks(): app = _app @@ -154,7 +154,7 @@ A plugin can contribute a dashboard widget by: 1. Adding a `GET /widget` route to its blueprint that returns an HTMX HTML fragment 2. The dashboard template polling that endpoint with HTMX -The dashboard (`roundtable/templates/dashboard/index.html`) currently includes the Traefik widget conditionally based on `traefik_enabled`. To add a new plugin widget, the dashboard route and template both need to be updated to detect and render the new widget. See the Traefik widget implementation as a reference. +The dashboard (`steward/templates/dashboard/index.html`) currently includes the Traefik widget conditionally based on `traefik_enabled`. To add a new plugin widget, the dashboard route and template both need to be updated to detect and render the new widget. See the Traefik widget implementation as a reference. --- diff --git a/docs/plugins/writing-a-plugin.md b/docs/plugins/writing-a-plugin.md index 87005c6..4128f08 100644 --- a/docs/plugins/writing-a-plugin.md +++ b/docs/plugins/writing-a-plugin.md @@ -46,7 +46,7 @@ config: ## Step 3: Define Models (if needed) -If your plugin stores data, define SQLAlchemy models using the shared `Base` from `roundtable.models.base`. +If your plugin stores data, define SQLAlchemy models using the shared `Base` from `steward.models.base`. ```python # plugins/myplugin/models.py @@ -55,7 +55,7 @@ import uuid from datetime import datetime from sqlalchemy import String, Float, DateTime from sqlalchemy.orm import Mapped, mapped_column -from roundtable.models.base import Base +from steward.models.base import Base class MyPluginMetric(Base): @@ -79,7 +79,7 @@ Generate the initial migration: # From the project root alembic --config alembic.ini revision \ --autogenerate \ - --head=roundtable@head \ + --head=steward@head \ --branch-label=myplugin \ -m "myplugin initial" ``` @@ -106,8 +106,8 @@ Keep task logic in a separate file so `__init__.py` stays clean. # plugins/myplugin/scheduler.py from __future__ import annotations import logging -from roundtable.core.scheduler import ScheduledTask -from roundtable.core.alerts import record_metric +from steward.core.scheduler import ScheduledTask +from steward.core.alerts import record_metric logger = logging.getLogger(__name__) @@ -174,8 +174,8 @@ async def _fetch_value(url: str) -> float: ```python # plugins/myplugin/routes.py from quart import Blueprint, current_app, render_template -from roundtable.auth.middleware import require_role -from roundtable.models.users import UserRole +from steward.auth.middleware import require_role +from steward.models.users import UserRole from .models import MyPluginMetric myplugin_bp = Blueprint("myplugin", __name__, template_folder="templates") @@ -215,7 +215,7 @@ Templates live in `plugins/myplugin/templates/myplugin/` (the extra nesting avoi ```html {# plugins/myplugin/templates/myplugin/index.html #} {% extends "base.html" %} -{% block title %}My Plugin — Roundtable{% endblock %} +{% block title %}My Plugin — Steward{% endblock %} {% block content %}
My Plugin
{% for row in rows %} @@ -283,7 +283,7 @@ On next startup, the plugin will be loaded, its migrations applied, and its blue `record_metric()` is how plugins feed data into the alert pipeline. Any metric you write here can be the target of an alert rule created in the UI. ```python -from roundtable.core.alerts import record_metric +from steward.core.alerts import record_metric # Must be inside an active transaction async with session.begin(): @@ -302,11 +302,11 @@ async with session.begin(): ## Auth in Routes -Use the `@require_role` decorator from `roundtable.auth.middleware`: +Use the `@require_role` decorator from `steward.auth.middleware`: ```python -from roundtable.auth.middleware import require_role -from roundtable.models.users import UserRole +from steward.auth.middleware import require_role +from steward.models.users import UserRole @myplugin_bp.get("/admin-only") @require_role(UserRole.admin) @@ -325,13 +325,13 @@ Role hierarchy: `admin > operator > viewer`. Requiring `viewer` grants access to ## Publishing to the Catalog -The official plugin catalog is hosted at `https://git.fabledsword.com/bvandeusen/Roundtable-plugins`. Anyone can submit a plugin by opening a pull request — first-party and third-party plugins are treated identically by the catalog system. +The official plugin catalog is hosted at `https://git.fabledsword.com/bvandeusen/Steward-plugins`. Anyone can submit a plugin by opening a pull request — first-party and third-party plugins are treated identically by the catalog system. ### Repo layout ``` -roundtable-plugins/ -├── index.yaml ← catalog index — the only file Roundtable fetches +steward-plugins/ +├── index.yaml ← catalog index — the only file Steward fetches ├── myplugin/ │ ├── plugin.yaml │ ├── __init__.py @@ -381,7 +381,7 @@ myplugin.zip Generate the zip and its checksum: ```bash -cd roundtable-plugins +cd steward-plugins zip -r myplugin.zip myplugin/ sha256sum myplugin.zip # paste this into index.yaml checksum_sha256 ``` diff --git a/docs/reference/code-map.md b/docs/reference/code-map.md index b273351..1bb9282 100644 --- a/docs/reference/code-map.md +++ b/docs/reference/code-map.md @@ -8,15 +8,15 @@ Quick reference for where key functions, models, and entry points live in the co | What | File | Function / Class | |---|---|---| -| App factory | `roundtable/app.py` | `create_app()` | -| CLI entry point | `roundtable/cli.py` | `main()` | -| Bootstrap config loading | `roundtable/config.py` | `load_bootstrap()` | -| Secret key resolution | `roundtable/config.py` | `_resolve_secret_key()` | -| DB engine init | `roundtable/database.py` | `init_db()` | -| Core migrations | `roundtable/core/migration_runner.py` | `run_core_migrations()` | -| Plugin migrations | `roundtable/core/migration_runner.py` | `run_plugin_migrations()` | -| Core task registration | `roundtable/app.py` | `_register_core_tasks()` | -| Scheduler loop | `roundtable/core/scheduler.py` | `start_scheduler()` | +| App factory | `steward/app.py` | `create_app()` | +| CLI entry point | `steward/cli.py` | `main()` | +| Bootstrap config loading | `steward/config.py` | `load_bootstrap()` | +| Secret key resolution | `steward/config.py` | `_resolve_secret_key()` | +| DB engine init | `steward/database.py` | `init_db()` | +| Core migrations | `steward/core/migration_runner.py` | `run_core_migrations()` | +| Plugin migrations | `steward/core/migration_runner.py` | `run_plugin_migrations()` | +| Core task registration | `steward/app.py` | `_register_core_tasks()` | +| Scheduler loop | `steward/core/scheduler.py` | `start_scheduler()` | --- @@ -24,15 +24,15 @@ Quick reference for where key functions, models, and entry points live in the co | What | File | Function | |---|---|---| -| All defaults | `roundtable/core/settings.py` | `DEFAULTS` dict | -| Read a setting | `roundtable/core/settings.py` | `get_setting(session, key)` | -| Write a setting | `roundtable/core/settings.py` | `set_setting(session, key, value)` | -| Read all settings | `roundtable/core/settings.py` | `get_all_settings(session)` | -| Sync load at startup | `roundtable/core/settings.py` | `load_settings_sync(db_url)` | -| Extract SMTP dict | `roundtable/core/settings.py` | `to_smtp_cfg(settings)` | -| Extract webhook dict | `roundtable/core/settings.py` | `to_webhook_cfg(settings)` | -| Extract Ansible dict | `roundtable/core/settings.py` | `to_ansible_cfg(settings)` | -| Extract plugins dict | `roundtable/core/settings.py` | `to_plugins_cfg(settings)` | +| All defaults | `steward/core/settings.py` | `DEFAULTS` dict | +| Read a setting | `steward/core/settings.py` | `get_setting(session, key)` | +| Write a setting | `steward/core/settings.py` | `set_setting(session, key, value)` | +| Read all settings | `steward/core/settings.py` | `get_all_settings(session)` | +| Sync load at startup | `steward/core/settings.py` | `load_settings_sync(db_url)` | +| Extract SMTP dict | `steward/core/settings.py` | `to_smtp_cfg(settings)` | +| Extract webhook dict | `steward/core/settings.py` | `to_webhook_cfg(settings)` | +| Extract Ansible dict | `steward/core/settings.py` | `to_ansible_cfg(settings)` | +| Extract plugins dict | `steward/core/settings.py` | `to_plugins_cfg(settings)` | --- @@ -40,9 +40,9 @@ Quick reference for where key functions, models, and entry points live in the co | What | File | Function | |---|---|---| -| Plugin loading | `roundtable/core/plugin_manager.py` | `load_plugins(app)` | -| ScheduledTask dataclass | `roundtable/core/scheduler.py` | `ScheduledTask` | -| Task runner | `roundtable/core/scheduler.py` | `start_scheduler(tasks)` | +| Plugin loading | `steward/core/plugin_manager.py` | `load_plugins(app)` | +| ScheduledTask dataclass | `steward/core/scheduler.py` | `ScheduledTask` | +| Task runner | `steward/core/scheduler.py` | `start_scheduler(tasks)` | --- @@ -50,11 +50,11 @@ Quick reference for where key functions, models, and entry points live in the co | What | File | Function | |---|---|---| -| Write metric + evaluate alerts | `roundtable/core/alerts.py` | `record_metric(session, source_module, resource_name, metric_name, value)` | -| Init alert pipeline | `roundtable/core/alerts.py` | `init_alerts(app)` | -| Rule evaluation | `roundtable/core/alerts.py` | `_evaluate_rule()` (internal) | -| Notification dispatch | `roundtable/core/alerts.py` | `_dispatch_notification()` (internal) | -| Email + webhook send | `roundtable/core/notifications.py` | `dispatch_notifications()` | +| Write metric + evaluate alerts | `steward/core/alerts.py` | `record_metric(session, source_module, resource_name, metric_name, value)` | +| Init alert pipeline | `steward/core/alerts.py` | `init_alerts(app)` | +| Rule evaluation | `steward/core/alerts.py` | `_evaluate_rule()` (internal) | +| Notification dispatch | `steward/core/alerts.py` | `_dispatch_notification()` (internal) | +| Email + webhook send | `steward/core/notifications.py` | `dispatch_notifications()` | --- @@ -62,9 +62,9 @@ Quick reference for where key functions, models, and entry points live in the co | What | File | Function | |---|---|---| -| Ping a host | `roundtable/monitors/ping.py` | `ping_check(host, session)` | -| DNS check a host | `roundtable/monitors/dns.py` | `dns_check(host, session)` | -| Data cleanup | `roundtable/core/cleanup.py` | `run_cleanup(app)` | +| Ping a host | `steward/monitors/ping.py` | `ping_check(host, session)` | +| DNS check a host | `steward/monitors/dns.py` | `dns_check(host, session)` | +| Data cleanup | `steward/core/cleanup.py` | `run_cleanup(app)` | --- @@ -72,9 +72,9 @@ Quick reference for where key functions, models, and entry points live in the co | What | File | Function / Class | |---|---|---| -| Role-based access decorator | `roundtable/auth/middleware.py` | `@require_role(UserRole.X)` | -| Login / session handling | `roundtable/auth/middleware.py` | `login_user()`, `logout_user()` | -| User count (for first-run) | `roundtable/auth/middleware.py` | `get_user_count(app)` | +| Role-based access decorator | `steward/auth/middleware.py` | `@require_role(UserRole.X)` | +| Login / session handling | `steward/auth/middleware.py` | `login_user()`, `logout_user()` | +| User count (for first-run) | `steward/auth/middleware.py` | `get_user_count(app)` | --- @@ -82,18 +82,18 @@ Quick reference for where key functions, models, and entry points live in the co | Model | File | Table | |---|---|---| -| `Host` | `roundtable/models/hosts.py` | `hosts` | -| `PingResult` | `roundtable/models/monitors.py` | `ping_results` | -| `DnsResult` | `roundtable/models/monitors.py` | `dns_results` | -| `AlertRule` | `roundtable/models/alerts.py` | `alert_rules` | -| `AlertState` | `roundtable/models/alerts.py` | `alert_states` | -| `AlertEvent` | `roundtable/models/alerts.py` | `alert_events` | -| `PluginMetric` | `roundtable/models/metrics.py` | `plugin_metrics` | -| `AnsibleRun` | `roundtable/models/ansible.py` | `ansible_runs` | -| `User` | `roundtable/models/users.py` | `users` | -| `AppSetting` | `roundtable/models/settings.py` | `app_settings` | +| `Host` | `steward/models/hosts.py` | `hosts` | +| `PingResult` | `steward/models/monitors.py` | `ping_results` | +| `DnsResult` | `steward/models/monitors.py` | `dns_results` | +| `AlertRule` | `steward/models/alerts.py` | `alert_rules` | +| `AlertState` | `steward/models/alerts.py` | `alert_states` | +| `AlertEvent` | `steward/models/alerts.py` | `alert_events` | +| `PluginMetric` | `steward/models/metrics.py` | `plugin_metrics` | +| `AnsibleRun` | `steward/models/ansible.py` | `ansible_runs` | +| `User` | `steward/models/users.py` | `users` | +| `AppSetting` | `steward/models/settings.py` | `app_settings` | | `TraefikMetric` | `plugins/traefik/models.py` | `traefik_metrics` | -| SQLAlchemy `Base` | `roundtable/models/base.py` | (shared declarative base) | +| SQLAlchemy `Base` | `steward/models/base.py` | (shared declarative base) | --- @@ -101,16 +101,16 @@ Quick reference for where key functions, models, and entry points live in the co | URL pattern | Blueprint | File | |---|---|---| -| `/` (dashboard) | `dashboard_bp` | `roundtable/dashboard/routes.py` | -| `/auth/login`, `/auth/logout` | `auth_bp` | `roundtable/auth/routes.py` | -| `/hosts/` | `hosts_bp` | `roundtable/hosts/routes.py` | -| `/ping/`, `/ping/rows`, `/ping/settings` | `ping_bp` | `roundtable/ping/routes.py` | -| `/dns/`, `/dns/rows` | `dns_bp` | `roundtable/dns/routes.py` | -| `/alerts/` | `alerts_bp` | `roundtable/alerts/routes.py` | -| `/ansible/` | `ansible_bp` | `roundtable/ansible/routes.py` | -| `/settings/` | `settings_bp` | `roundtable/settings/routes.py` | +| `/` (dashboard) | `dashboard_bp` | `steward/dashboard/routes.py` | +| `/auth/login`, `/auth/logout` | `auth_bp` | `steward/auth/routes.py` | +| `/hosts/` | `hosts_bp` | `steward/hosts/routes.py` | +| `/ping/`, `/ping/rows`, `/ping/settings` | `ping_bp` | `steward/ping/routes.py` | +| `/dns/`, `/dns/rows` | `dns_bp` | `steward/dns/routes.py` | +| `/alerts/` | `alerts_bp` | `steward/alerts/routes.py` | +| `/ansible/` | `ansible_bp` | `steward/ansible/routes.py` | +| `/settings/` | `settings_bp` | `steward/settings/routes.py` | | `/plugins/traefik/`, `/plugins/traefik/widget` | `traefik_bp` | `plugins/traefik/routes.py` | -| `/health` | (inline) | `roundtable/app.py` | +| `/health` | (inline) | `steward/app.py` | --- @@ -118,12 +118,12 @@ Quick reference for where key functions, models, and entry points live in the co | Template | Purpose | |---|---| -| `roundtable/templates/base.html` | Layout, navigation, full CSS design system | -| `roundtable/templates/dashboard/index.html` | Dashboard with stat strip and widget grid | -| `roundtable/templates/ping/rows.html` | HTMX fragment: ping pill rows (shared by dashboard and /ping/) | -| `roundtable/templates/ping/index.html` | Full /ping/ page | -| `roundtable/templates/dns/rows.html` | HTMX fragment: DNS status rows | -| `roundtable/templates/dns/index.html` | Full /dns/ page | +| `steward/templates/base.html` | Layout, navigation, full CSS design system | +| `steward/templates/dashboard/index.html` | Dashboard with stat strip and widget grid | +| `steward/templates/ping/rows.html` | HTMX fragment: ping pill rows (shared by dashboard and /ping/) | +| `steward/templates/ping/index.html` | Full /ping/ page | +| `steward/templates/dns/rows.html` | HTMX fragment: DNS status rows | +| `steward/templates/dns/index.html` | Full /dns/ page | | `plugins/traefik/templates/traefik/widget.html` | HTMX fragment: Traefik dashboard widget | | `plugins/traefik/templates/traefik/index.html` | Full Traefik detail page | @@ -133,6 +133,6 @@ Quick reference for where key functions, models, and entry points live in the co | Location | Covers | |---|---| -| `roundtable/migrations/versions/` | Core schema (hosts, users, monitors, alerts, app_settings) | +| `steward/migrations/versions/` | Core schema (hosts, users, monitors, alerts, app_settings) | | `plugins/traefik/migrations/versions/` | `traefik_metrics` table | | `alembic.ini` | Alembic config; `version_locations` lists all migration directories | diff --git a/docs/superpowers/plans/2026-04-13-rebrand.md b/docs/superpowers/plans/2026-04-13-rebrand.md index 89a0ff9..243fe04 100644 --- a/docs/superpowers/plans/2026-04-13-rebrand.md +++ b/docs/superpowers/plans/2026-04-13-rebrand.md @@ -1,10 +1,10 @@ -# Roundtable Rebrand Implementation Plan +# Steward Rebrand Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > > **Testing:** Per project feedback, skip all test authoring and test runs during this work. Verify manually by running the app. -**Goal:** Rebrand FabledScryer to Roundtable: full visual reskin plus full rename of package, config, containers, and docs. +**Goal:** Rebrand FabledScryer to Steward: full visual reskin plus full rename of package, config, containers, and docs. **Architecture:** Staged as six sequential PRs. PR 1 is a pure visual/copy reskin that still ships under the FabledScryer name. PRs 2–4 perform the mechanical rename in order (package → config → container + user-visible strings) with a fallback shim in PR 3 so self-hosters don't break mid-upgrade. PR 5 updates docs. PR 6 removes the shim after one release cycle. @@ -25,29 +25,29 @@ - Any template with an empty-state message — themed line **Renamed (PR 2 — package):** -- `fabledscryer/` → `roundtable/` (directory + every `from fabledscryer` / `import fabledscryer` reference) +- `fabledscryer/` → `steward/` (directory + every `from fabledscryer` / `import fabledscryer` reference) - `pyproject.toml` — name, entry point, hatch packages - `alembic.ini` — `script_location` - `tests/**` — imports (tests not executed, but must not break collection; update imports mechanically) **Modified (PR 3 — config & env):** -- `fabledscryer/config.py` → `roundtable/config.py` (already moved in PR 2) — read both `ROUNDTABLE_*` and `FABLEDSCRYER_*` with deprecation warning +- `fabledscryer/config.py` → `steward/config.py` (already moved in PR 2) — read both `STEWARD_*` and `FABLEDSCRYER_*` with deprecation warning - `.env.example`, `config.example.yaml` — new names -- First-boot migration of `~/.config/fabledscryer` → `~/.config/roundtable` (if the app uses it — verify during task) +- First-boot migration of `~/.config/fabledscryer` → `~/.config/steward` (if the app uses it — verify during task) **Modified (PR 4 — container + user strings):** - `Dockerfile` — COPY paths, CMD, image labels - `docker-compose.yml` — service name, image, container_name, volumes, env - `entrypoint.sh` — package path for `nut_setup.py` invocation -- `roundtable/templates/base.html` — wordmark text "Fabled Scryer" → "Roundtable", `` -- `roundtable/templates/auth/login.html` — any residual "Fabled Scryer" text +- `steward/templates/base.html` — wordmark text "Fabled Scryer" → "Steward", `<title>` +- `steward/templates/auth/login.html` — any residual "Fabled Scryer" text - `README.md` — first-page references (full doc sweep is PR 5) **Modified (PR 5 — docs):** - `README.md`, `docs/**/*.md` **Modified (PR 6 — cleanup):** -- `roundtable/config.py` — remove fallback shim +- `steward/config.py` — remove fallback shim --- @@ -197,7 +197,7 @@ git commit -m "style: candlelit glow background replaces star field" - [ ] **Step 1: Change the title block** ```html -<title>{% block title %}Roundtable{% endblock %} +{% block title %}Steward{% endblock %} ``` Note: the visible wordmark text still reads "Fabled Scryer" — that flips in PR 4. This only changes the browser tab title, which is acceptable to flip early since it's not visible inside the UI. @@ -263,7 +263,7 @@ Content for `fabledscryer/templates/errors/404.html`: ```html {% extends "base.html" %} -{% block title %}Not Found — Roundtable{% endblock %} +{% block title %}Not Found — Steward{% endblock %} {% block content %}

404

@@ -278,7 +278,7 @@ Content for `fabledscryer/templates/errors/500.html`: ```html {% extends "base.html" %} -{% block title %}Error — Roundtable{% endblock %} +{% block title %}Error — Steward{% endblock %} {% block content %}

500

@@ -361,7 +361,7 @@ git add -A git commit -m "style: visual polish after manual review" ``` -**PR 1 done.** Open PR with title `feat: roundtable visual reskin (pewter & gold)`. +**PR 1 done.** Open PR with title `feat: steward visual reskin (pewter & gold)`. --- @@ -370,39 +370,39 @@ git commit -m "style: visual polish after manual review" ### Task 10: Rename the package directory **Files:** -- Rename: `fabledscryer/` → `roundtable/` +- Rename: `fabledscryer/` → `steward/` - [ ] **Step 1: Rename the directory** ```bash -git mv fabledscryer roundtable +git mv fabledscryer steward ``` - [ ] **Step 2: Confirm the move** ```bash -ls roundtable/ | head +ls steward/ | head git status | head ``` -Expected: see `__init__.py`, `app.py`, `cli.py`, etc. inside `roundtable/`. +Expected: see `__init__.py`, `app.py`, `cli.py`, etc. inside `steward/`. - [ ] **Step 3: Commit** ```bash -git commit -m "refactor: rename package directory fabledscryer → roundtable" +git commit -m "refactor: rename package directory fabledscryer → steward" ``` -### Task 11: Rewrite all `fabledscryer` imports to `roundtable` +### Task 11: Rewrite all `fabledscryer` imports to `steward` **Files:** -- Modify: every file under `roundtable/`, `tests/`, `alembic.ini`, `entrypoint.sh`, `Dockerfile` that imports from the old package +- Modify: every file under `steward/`, `tests/`, `alembic.ini`, `entrypoint.sh`, `Dockerfile` that imports from the old package - [ ] **Step 1: Find every remaining `fabledscryer` reference in Python code** Run: ```bash -grep -rn "fabledscryer" roundtable/ tests/ alembic.ini entrypoint.sh Dockerfile pyproject.toml +grep -rn "fabledscryer" steward/ tests/ alembic.ini entrypoint.sh Dockerfile pyproject.toml ``` Keep the output visible as a checklist. @@ -410,24 +410,24 @@ Keep the output visible as a checklist. - [ ] **Step 2: Run a safe codemod across Python files** ```bash -grep -rl "fabledscryer" roundtable/ tests/ | xargs sed -i 's/fabledscryer/roundtable/g' +grep -rl "fabledscryer" steward/ tests/ | xargs sed -i 's/fabledscryer/steward/g' ``` -This touches only files under `roundtable/` and `tests/`. Do NOT run it across the whole repo yet — `docs/`, `.env.example`, `docker-compose.yml`, `config.example.yaml` and `README.md` are handled in later PRs. +This touches only files under `steward/` and `tests/`. Do NOT run it across the whole repo yet — `docs/`, `.env.example`, `docker-compose.yml`, `config.example.yaml` and `README.md` are handled in later PRs. - [ ] **Step 3: Spot-check a few critical files** ```bash -grep -n "roundtable\|fabledscryer" roundtable/app.py roundtable/cli.py roundtable/config.py roundtable/migrations/env.py +grep -n "steward\|fabledscryer" steward/app.py steward/cli.py steward/config.py steward/migrations/env.py ``` -Expected: only `roundtable` references remain; no stray `fabledscryer`. +Expected: only `steward` references remain; no stray `fabledscryer`. - [ ] **Step 4: Commit** ```bash git add -A -git commit -m "refactor: update imports fabledscryer → roundtable" +git commit -m "refactor: update imports fabledscryer → steward" ``` ### Task 12: Update `pyproject.toml` @@ -439,15 +439,15 @@ git commit -m "refactor: update imports fabledscryer → roundtable" ```toml [project] -name = "roundtable" +name = "steward" version = "0.1.0" # ... dependencies unchanged ... [project.scripts] -roundtable = "roundtable.cli:main" +steward = "steward.cli:main" [tool.hatch.build.targets.wheel] -packages = ["roundtable"] +packages = ["steward"] ``` Leave `[project.optional-dependencies]`, `[tool.pytest.ini_options]`, and all dependency pins untouched. @@ -456,7 +456,7 @@ Leave `[project.optional-dependencies]`, `[tool.pytest.ini_options]`, and all de ```bash git add pyproject.toml -git commit -m "build: rename package to roundtable in pyproject" +git commit -m "build: rename package to steward in pyproject" ``` ### Task 13: Update `alembic.ini` @@ -468,7 +468,7 @@ git commit -m "build: rename package to roundtable in pyproject" ```ini [alembic] -script_location = roundtable/migrations +script_location = steward/migrations prepend_sys_path = . version_path_separator = os ``` @@ -477,7 +477,7 @@ version_path_separator = os ```bash git add alembic.ini -git commit -m "build: point alembic at roundtable/migrations" +git commit -m "build: point alembic at steward/migrations" ``` ### Task 14: Verify the package installs and imports cleanly @@ -487,8 +487,8 @@ git commit -m "build: point alembic at roundtable/migrations" ```bash python -m venv /tmp/rt-verify /tmp/rt-verify/bin/pip install -e . -/tmp/rt-verify/bin/python -c "import roundtable; import roundtable.app; import roundtable.cli; print('ok')" -/tmp/rt-verify/bin/roundtable --help +/tmp/rt-verify/bin/python -c "import steward; import steward.app; import steward.cli; print('ok')" +/tmp/rt-verify/bin/steward --help ``` Expected: `ok` and CLI help output. No ImportError. @@ -501,20 +501,20 @@ rm -rf /tmp/rt-verify - [ ] **Step 3: No commit needed (verification only).** -**PR 2 done.** Open PR with title `refactor: rename python package fabledscryer → roundtable`. +**PR 2 done.** Open PR with title `refactor: rename python package fabledscryer → steward`. --- ## PR 3 — Config & Environment Variables -### Task 15: Add env-var fallback shim in `roundtable/config.py` +### Task 15: Add env-var fallback shim in `steward/config.py` **Files:** -- Modify: `roundtable/config.py` +- Modify: `steward/config.py` - [ ] **Step 1: Replace the env var lookups with a fallback helper** -Open `roundtable/config.py` and replace the existing `load_bootstrap` body so it reads `ROUNDTABLE_*` first and falls back to `FABLEDSCRYER_*` with a deprecation warning. +Open `steward/config.py` and replace the existing `load_bootstrap` body so it reads `STEWARD_*` first and falls back to `FABLEDSCRYER_*` with a deprecation warning. ```python def _env_with_fallback(new_name: str, old_name: str) -> str | None: @@ -536,19 +536,19 @@ Then inside `load_bootstrap`, replace the existing env lookups with: ```python database_url = ( - _env_with_fallback("ROUNDTABLE_DATABASE_URL", "FABLEDSCRYER_DATABASE_URL") - or _env_with_fallback("ROUNDTABLE_DATABASE__URL", "FABLEDSCRYER_DATABASE__URL") + _env_with_fallback("STEWARD_DATABASE_URL", "FABLEDSCRYER_DATABASE_URL") + or _env_with_fallback("STEWARD_DATABASE__URL", "FABLEDSCRYER_DATABASE__URL") or raw.get("database", {}).get("url") ) if not database_url: raise ValueError( - "Database URL is required. Set ROUNDTABLE_DATABASE_URL env var " + "Database URL is required. Set STEWARD_DATABASE_URL env var " "or add 'database.url' to config.yaml." ) # ... plugin_dir = ( - _env_with_fallback("ROUNDTABLE_PLUGIN_DIR", "FABLEDSCRYER_PLUGIN_DIR") + _env_with_fallback("STEWARD_PLUGIN_DIR", "FABLEDSCRYER_PLUGIN_DIR") or raw.get("plugin_dir", "plugins") ) ``` @@ -557,15 +557,15 @@ And in `_resolve_secret_key`: ```python from_env = ( - _env_with_fallback("ROUNDTABLE_SECRET_KEY", "FABLEDSCRYER_SECRET_KEY") + _env_with_fallback("STEWARD_SECRET_KEY", "FABLEDSCRYER_SECRET_KEY") or raw.get("secret_key") ) ``` -- [ ] **Step 2: Grep for any other `FABLEDSCRYER_` or `FABLEDNETMON_` env var reads across `roundtable/`** +- [ ] **Step 2: Grep for any other `FABLEDSCRYER_` or `FABLEDNETMON_` env var reads across `steward/`** ```bash -grep -rn "FABLEDSCRYER_\|FABLEDNETMON_" roundtable/ +grep -rn "FABLEDSCRYER_\|FABLEDNETMON_" steward/ ``` For each hit, apply the same pattern (new name first, old as fallback with warning). If `FABLEDNETMON_` is pure residue with no current consumer, delete the reference. @@ -573,8 +573,8 @@ For each hit, apply the same pattern (new name first, old as fallback with warni - [ ] **Step 3: Commit** ```bash -git add roundtable/config.py -git commit -m "feat: ROUNDTABLE_* env vars with FABLEDSCRYER_* fallback" +git add steward/config.py +git commit -m "feat: STEWARD_* env vars with FABLEDSCRYER_* fallback" ``` ### Task 16: Update `.env.example` and `config.example.yaml` @@ -588,11 +588,11 @@ git commit -m "feat: ROUNDTABLE_* env vars with FABLEDSCRYER_* fallback" grep -n "FABLEDSCRYER\|FABLEDNETMON" .env.example ``` -Replace every occurrence with `ROUNDTABLE_*`. Add a short comment at top: +Replace every occurrence with `STEWARD_*`. Add a short comment at top: ``` -# Roundtable environment configuration. -# Only ROUNDTABLE_DATABASE_URL is required. Other settings live in the DB. +# Steward environment configuration. +# Only STEWARD_DATABASE_URL is required. Other settings live in the DB. ``` - [ ] **Step 2: Rewrite `config.example.yaml`** @@ -600,25 +600,25 @@ Replace every occurrence with `ROUNDTABLE_*`. Add a short comment at top: Replace the header and example strings: ```yaml -# Roundtable — Bootstrap Configuration Example +# Steward — Bootstrap Configuration Example # # The only REQUIRED setting is the database URL. # Set it via env var (recommended for Docker): # -# ROUNDTABLE_DATABASE_URL=postgresql+asyncpg://user:pass@host/db +# STEWARD_DATABASE_URL=postgresql+asyncpg://user:pass@host/db # # All other settings are stored in the database and managed via # the Settings UI at /settings/. database: - url: "postgresql+asyncpg://roundtable:password@localhost/roundtable" + url: "postgresql+asyncpg://steward:password@localhost/steward" ``` - [ ] **Step 3: Commit** ```bash git add .env.example config.example.yaml -git commit -m "docs: ROUNDTABLE_* env var examples" +git commit -m "docs: STEWARD_* env var examples" ``` ### Task 17: Manual config verification @@ -626,8 +626,8 @@ git commit -m "docs: ROUNDTABLE_* env var examples" - [ ] **Step 1: Run the app with only the new env var** ```bash -ROUNDTABLE_DATABASE_URL=postgresql+asyncpg://fabledscryer:fabledscryer@localhost/fabledscryer \ - python -m roundtable.cli --host 127.0.0.1 --port 5001 +STEWARD_DATABASE_URL=postgresql+asyncpg://fabledscryer:fabledscryer@localhost/fabledscryer \ + python -m steward.cli --host 127.0.0.1 --port 5001 ``` (Use whatever local DB you have; the old db name is fine — data isn't being renamed.) @@ -638,14 +638,14 @@ Expected: app starts, no deprecation warning. ```bash FABLEDSCRYER_DATABASE_URL=postgresql+asyncpg://fabledscryer:fabledscryer@localhost/fabledscryer \ - python -m roundtable.cli --host 127.0.0.1 --port 5001 + python -m steward.cli --host 127.0.0.1 --port 5001 ``` Expected: app starts, deprecation warning logged once. - [ ] **Step 3: Kill processes.** No commit. -**PR 3 done.** Open PR with title `feat: ROUNDTABLE_* env vars (FABLEDSCRYER_* fallback)`. +**PR 3 done.** Open PR with title `feat: STEWARD_* env vars (FABLEDSCRYER_* fallback)`. --- @@ -664,12 +664,12 @@ COPY fabledscryer/ fabledscryer/ ``` to ```dockerfile -COPY roundtable/ roundtable/ +COPY steward/ steward/ ``` Change the CMD: ```dockerfile -CMD ["roundtable", "--host", "0.0.0.0", "--port", "5000"] +CMD ["steward", "--host", "0.0.0.0", "--port", "5000"] ``` Leave everything else (apt packages, gosu, app user, EXPOSE 5000) untouched. @@ -678,7 +678,7 @@ Leave everything else (apt packages, gosu, app user, EXPOSE 5000) untouched. ```bash git add Dockerfile -git commit -m "build: update Dockerfile for roundtable package" +git commit -m "build: update Dockerfile for steward package" ``` ### Task 19: Update `entrypoint.sh` @@ -688,15 +688,15 @@ git commit -m "build: update Dockerfile for roundtable package" - [ ] **Step 1: Replace the `nut_setup.py` invocation path** -Change every `/app/fabledscryer/nut_setup.py` to `/app/roundtable/nut_setup.py`. +Change every `/app/fabledscryer/nut_setup.py` to `/app/steward/nut_setup.py`. -Update any comment that says "FabledScryer container entrypoint." to "Roundtable container entrypoint." +Update any comment that says "FabledScryer container entrypoint." to "Steward container entrypoint." - [ ] **Step 2: Commit** ```bash git add entrypoint.sh -git commit -m "build: entrypoint.sh uses roundtable package path" +git commit -m "build: entrypoint.sh uses steward package path" ``` ### Task 20: Update `docker-compose.yml` @@ -708,10 +708,10 @@ git commit -m "build: entrypoint.sh uses roundtable package path" ```yaml services: - roundtable: + steward: build: . - container_name: roundtable - image: roundtable:latest + container_name: steward + image: steward:latest ports: - "5000:5000" volumes: @@ -720,7 +720,7 @@ services: - /mnt/Data/traefik/log:/var/log/traefik:ro - /var/run/docker.sock:/var/run/docker.sock:ro environment: - - ROUNDTABLE_DATABASE_URL=postgresql+asyncpg://roundtable:roundtable@db/roundtable + - STEWARD_DATABASE_URL=postgresql+asyncpg://steward:steward@db/steward depends_on: db: condition: service_healthy @@ -729,13 +729,13 @@ services: db: image: postgres:16-alpine environment: - POSTGRES_USER: roundtable - POSTGRES_PASSWORD: roundtable - POSTGRES_DB: roundtable + POSTGRES_USER: steward + POSTGRES_PASSWORD: steward + POSTGRES_DB: steward volumes: - pgdata:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U roundtable"] + test: ["CMD-SHELL", "pg_isready -U steward"] interval: 5s timeout: 5s retries: 5 @@ -752,37 +752,37 @@ volumes: ```bash git add docker-compose.yml -git commit -m "build: docker-compose service → roundtable" +git commit -m "build: docker-compose service → steward" ``` ### Task 21: Flip user-visible wordmark and title **Files:** -- Modify: `roundtable/templates/base.html` +- Modify: `steward/templates/base.html` - [ ] **Step 1: Update the `` block (around line 6)** ```html -<title>{% block title %}Roundtable{% endblock %} +{% block title %}Steward{% endblock %} ``` - [ ] **Step 2: Update the nav wordmark (around line 214)** -Change `Fabled Scryer` inside `nav .brand` to `Roundtable`. +Change `Fabled Scryer` inside `nav .brand` to `Steward`. - [ ] **Step 3: Grep for any other user-visible "Fabled Scryer" strings in templates** ```bash -grep -rn "Fabled Scryer\|FabledScryer" roundtable/templates/ +grep -rn "Fabled Scryer\|FabledScryer" steward/templates/ ``` -Replace each with "Roundtable". +Replace each with "Steward". - [ ] **Step 4: Commit** ```bash -git add roundtable/templates/ -git commit -m "copy: flip visible wordmark → Roundtable" +git add steward/templates/ +git commit -m "copy: flip visible wordmark → Steward" ``` ### Task 22: Manual container verification @@ -795,8 +795,8 @@ docker compose up --build ``` - [ ] **Step 2: Verify** - - Container named `roundtable` runs - - Browser shows "Roundtable" in tab title and nav + - Container named `steward` runs + - Browser shows "Steward" in tab title and nav - Login, dashboard, and error pages all render with the new palette and wordmark - Logs show no import errors and no unhandled deprecation warnings @@ -806,7 +806,7 @@ docker compose up --build docker compose down ``` -**PR 4 done.** Open PR with title `feat: roundtable container & wordmark flip`. +**PR 4 done.** Open PR with title `feat: steward container & wordmark flip`. --- @@ -828,9 +828,9 @@ grep -rn "fabledscryer\|FabledScryer\|Fabled Scryer" README.md docs/ ```bash grep -rl "fabledscryer\|FabledScryer\|Fabled Scryer" README.md docs/ \ | xargs sed -i \ - -e 's/FabledScryer/Roundtable/g' \ - -e 's/fabledscryer/roundtable/g' \ - -e 's/Fabled Scryer/Roundtable/g' + -e 's/FabledScryer/Steward/g' \ + -e 's/fabledscryer/steward/g' \ + -e 's/Fabled Scryer/Steward/g' ``` Then read each changed file top-to-bottom and fix any mangled sentences (e.g. capitalization at the start of sentences, code blocks that now reference a directory that still needs a `./` prefix, etc.). Pay extra attention to: @@ -843,12 +843,12 @@ Then read each changed file top-to-bottom and fix any mangled sentences (e.g. ca ```bash git add README.md docs/ -git commit -m "docs: roundtable rename sweep" +git commit -m "docs: steward rename sweep" ``` ### Task 24: Rename the Forgejo repo -- [ ] **Step 1: Via Forgejo UI or API, rename the repo from `FabledScryer` to `Roundtable`.** The old URL will redirect for one grace period. +- [ ] **Step 1: Via Forgejo UI or API, rename the repo from `FabledScryer` to `Steward`.** The old URL will redirect for one grace period. - [ ] **Step 2: Update your local remote URL** @@ -862,13 +862,13 @@ git remote -v ```bash # from the parent dir cd .. -mv FabledScryer Roundtable -cd Roundtable +mv FabledScryer Steward +cd Steward ``` - [ ] **Step 4: No commit.** -**PR 5 done.** Open PR with title `docs: roundtable rename sweep`. +**PR 5 done.** Open PR with title `docs: steward rename sweep`. --- @@ -877,11 +877,11 @@ cd Roundtable ### Task 25: Remove env var fallback shim **Files:** -- Modify: `roundtable/config.py` +- Modify: `steward/config.py` -- [ ] **Step 1: Delete `_env_with_fallback` and inline direct `os.environ.get("ROUNDTABLE_*")` lookups** +- [ ] **Step 1: Delete `_env_with_fallback` and inline direct `os.environ.get("STEWARD_*")` lookups** -Replace each `_env_with_fallback("ROUNDTABLE_X", "FABLEDSCRYER_X")` call with `os.environ.get("ROUNDTABLE_X")`. Delete the helper. +Replace each `_env_with_fallback("STEWARD_X", "FABLEDSCRYER_X")` call with `os.environ.get("STEWARD_X")`. Delete the helper. - [ ] **Step 2: Grep to confirm no `FABLEDSCRYER_` references remain** @@ -894,7 +894,7 @@ Expected: zero hits (or only historical commit messages, which don't show up in - [ ] **Step 3: Commit** ```bash -git add roundtable/config.py +git add steward/config.py git commit -m "refactor: remove FABLEDSCRYER_* env var fallback shim" ``` @@ -923,5 +923,5 @@ git commit -m "refactor: final rename sweep" - Spec coverage: all 5 spec sections mapped to PRs 1–5; PR 6 handles the cleanup implied by the shim in PR 3. - No placeholders — every template replacement includes concrete code; every grep includes the actual pattern. -- Type consistency: config helper is `_env_with_fallback` in both Task 15 and Task 25; env var names are `ROUNDTABLE_DATABASE_URL`, `ROUNDTABLE_PLUGIN_DIR`, `ROUNDTABLE_SECRET_KEY` consistently. +- Type consistency: config helper is `_env_with_fallback` in both Task 15 and Task 25; env var names are `STEWARD_DATABASE_URL`, `STEWARD_PLUGIN_DIR`, `STEWARD_SECRET_KEY` consistently. - Tests intentionally skipped per project feedback; verification is manual (browser + container). diff --git a/docs/superpowers/specs/2026-04-13-rebrand-design.md b/docs/superpowers/specs/2026-04-13-rebrand-design.md index 72e3d6a..24a980b 100644 --- a/docs/superpowers/specs/2026-04-13-rebrand-design.md +++ b/docs/superpowers/specs/2026-04-13-rebrand-design.md @@ -1,21 +1,21 @@ -# Roundtable Rebrand — Design +# Steward Rebrand — Design **Date:** 2026-04-13 **Status:** Approved for planning ## 1. Identity -**Name:** Roundtable (dropping the "Fabled" prefix). +**Name:** Steward (dropping the "Fabled" prefix). -**Why the change:** The old name `FabledScryer` framed the app as a single seer peering into one crystal ball. The actual product is a gathering point — hosts, metrics, alerts, plugins, and dashboards from across the homelab converge here. "Roundtable" captures that: a place where the realm's tools and information meet. +**Why the change:** The old name `FabledScryer` framed the app as a single seer peering into one crystal ball. The actual product is a gathering point — hosts, metrics, alerts, plugins, and dashboards from across the homelab converge here. "Steward" captures that: a place where the realm's tools and information meet. **Name conflict check:** -- *Roundtable Software* — a Progress ABL tooling vendor. Different demographic, no overlap. +- *Steward Software* — a Progress ABL tooling vendor. Different demographic, no overlap. - *Fabled.gg* — a virtual tabletop product. Closest concern, and the reason the "Fabled" prefix is dropped entirely to avoid confusion inside the tabletop/fantasy space. -**Umbrella:** `fabledsword.com` remains the family umbrella domain; `git.fabledsword.com` remains the plugin catalog host. Roundtable becomes one product under that umbrella. +**Umbrella:** `fabledsword.com` remains the family umbrella domain; `git.fabledsword.com` remains the plugin catalog host. Steward becomes one product under that umbrella. -**Tone:** Heraldic / Arthurian register. The roundtable is the gathering place of those who keep watch over the realm. +**Tone:** Heraldic / Arthurian register. The steward is the gathering place of those who keep watch over the realm. ## 2. Visual System @@ -78,7 +78,7 @@ Candlelit glow — a soft, warm radial wash behind the main content area. Replac ### Wordmark -"Roundtable" in EB Garamond, gold (`#c8a840`), paired with the seal mark to the left in the nav header. +"Steward" in EB Garamond, gold (`#c8a840`), paired with the seal mark to the left in the nav header. ## 3. Voice & Copy @@ -100,21 +100,21 @@ Candlelit glow — a soft, warm radial wash behind the main content area. Replac ## 4. Rename Mechanics **Code & packaging** -- `fabledscryer/` package directory → `roundtable/` -- All `from fabledscryer...` / `import fabledscryer...` → `roundtable` -- `pyproject.toml`: project name, CLI entry point (`fabledscryer = "fabledscryer.cli:main"` → `roundtable = "roundtable.cli:main"`), tool sections +- `fabledscryer/` package directory → `steward/` +- All `from fabledscryer...` / `import fabledscryer...` → `steward` +- `pyproject.toml`: project name, CLI entry point (`fabledscryer = "fabledscryer.cli:main"` → `steward = "steward.cli:main"`), tool sections - `__init__.py` package metadata **Runtime config** -- Env vars: `FABLEDSCRYER_*` → `ROUNDTABLE_*` +- Env vars: `FABLEDSCRYER_*` → `STEWARD_*` - Residual `FABLEDNETMON_*` env vars removed -- Config dir: `~/.config/fabledscryer` → `~/.config/roundtable` (one-shot copy on first boot) +- Config dir: `~/.config/fabledscryer` → `~/.config/steward` (one-shot copy on first boot) - Log file names, systemd unit names if any **Container & deploy** - `Dockerfile` labels, workdir - `docker-compose.yml` service name, image tag, volume names, `container_name` -- Published image: `fabledscryer:latest` → `roundtable:latest` +- Published image: `fabledscryer:latest` → `steward:latest` **Database** - Alembic `script_location` and any customized version table @@ -144,10 +144,10 @@ Staged so `main` stays runnable between PRs. New palette tokens, locked logo SVG, EB Garamond font swap, candlelit glow background, themed edge copy (login, empty states, 404/500, dashboard hero "Under Watch, Under Care"). Still named FabledScryer internally. Ships independently, low risk. **PR 2 — Python package rename** -`fabledscryer/` → `roundtable/`, all imports, `pyproject.toml` name + entry point, `__init__.py` metadata. Docker/config untouched. Package installable as `roundtable` locally. +`fabledscryer/` → `steward/`, all imports, `pyproject.toml` name + entry point, `__init__.py` metadata. Docker/config untouched. Package installable as `steward` locally. **PR 3 — Config & env vars** -`FABLEDSCRYER_*` → `ROUNDTABLE_*` with a short-lived fallback reader (accept both, warn on old) so self-hosters don't break mid-upgrade. Config dir migration on first boot. Drop `FABLEDNETMON_*` residue. +`FABLEDSCRYER_*` → `STEWARD_*` with a short-lived fallback reader (accept both, warn on old) so self-hosters don't break mid-upgrade. Config dir migration on first boot. Drop `FABLEDNETMON_*` residue. **PR 4 — Container & deploy** `Dockerfile`, `docker-compose.yml` service/image/volume/container names. Published image tag switch. Template wordmark + `` + meta (the last user-visible string flip). diff --git a/entrypoint.sh b/entrypoint.sh index ea128e0..bf2e2a2 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Roundtable container entrypoint. +# Steward container entrypoint. # # Drops from root to the 'app' user via gosu, then runs the command. # The container starts as root only so /data permissions can be fixed up diff --git a/pyproject.toml b/pyproject.toml index 613b322..465001e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "roundtable" +name = "steward" version = "0.1.0" requires-python = ">=3.11" dependencies = [ @@ -32,11 +32,11 @@ dev = [ ] [project.scripts] -roundtable = "roundtable.cli:main" +steward = "steward.cli:main" [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] [tool.hatch.build.targets.wheel] -packages = ["roundtable"] +packages = ["steward"] diff --git a/roundtable/__init__.py b/roundtable/__init__.py deleted file mode 100644 index a125e63..0000000 --- a/roundtable/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -__version__ = "0.1.0" - -# Compatibility shim: allow `import fabledscryer[.x.y]` to resolve to -# `roundtable[.x.y]`. Lets unmodified plugin repos keep working during the -# rebrand transition. Remove once all plugins migrate to `roundtable`. -import sys as _sys -import importlib as _importlib -import importlib.abc as _abc -import importlib.util as _util - -_ALIAS = "fabledscryer" - - -class _RoundtableAliasLoader(_abc.Loader): - def __init__(self, real_name): - self._real_name = real_name - - def create_module(self, spec): - return _importlib.import_module(self._real_name) - - def exec_module(self, module): - pass - - -class _RoundtableAliasFinder(_abc.MetaPathFinder): - def find_spec(self, fullname, path=None, target=None): - if fullname != _ALIAS and not fullname.startswith(_ALIAS + "."): - return None - real_name = "roundtable" + fullname[len(_ALIAS):] - if _util.find_spec(real_name) is None: - return None - return _util.spec_from_loader( - fullname, _RoundtableAliasLoader(real_name) - ) - - -if not any(isinstance(f, _RoundtableAliasFinder) for f in _sys.meta_path): - _sys.meta_path.insert(0, _RoundtableAliasFinder()) -_sys.modules.setdefault(_ALIAS, _sys.modules[__name__]) diff --git a/roundtable/settings/__init__.py b/roundtable/settings/__init__.py deleted file mode 100644 index 0acad26..0000000 --- a/roundtable/settings/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# roundtable/settings/__init__.py diff --git a/steward/__init__.py b/steward/__init__.py new file mode 100644 index 0000000..3dc1f76 --- /dev/null +++ b/steward/__init__.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/roundtable/alerts/__init__.py b/steward/alerts/__init__.py similarity index 100% rename from roundtable/alerts/__init__.py rename to steward/alerts/__init__.py diff --git a/roundtable/alerts/routes.py b/steward/alerts/routes.py similarity index 97% rename from roundtable/alerts/routes.py rename to steward/alerts/routes.py index bbcb9ef..9c71119 100644 --- a/roundtable/alerts/routes.py +++ b/steward/alerts/routes.py @@ -1,13 +1,13 @@ from __future__ import annotations from datetime import datetime, timezone from quart import Blueprint, render_template, request, redirect, url_for, current_app, session -from roundtable.core.audit import log_audit +from steward.core.audit import log_audit from sqlalchemy import select -from roundtable.auth.middleware import require_role -from roundtable.models.alerts import AlertRule, AlertState, AlertStateEnum, AlertOperator, MaintenanceWindow -from roundtable.models.hosts import Host -from roundtable.models.metrics import PluginMetric -from roundtable.models.users import UserRole +from steward.auth.middleware import require_role +from steward.models.alerts import AlertRule, AlertState, AlertStateEnum, AlertOperator, MaintenanceWindow +from steward.models.hosts import Host +from steward.models.metrics import PluginMetric +from steward.models.users import UserRole alerts_bp = Blueprint("alerts", __name__, url_prefix="/alerts") diff --git a/roundtable/ansible/__init__.py b/steward/ansible/__init__.py similarity index 100% rename from roundtable/ansible/__init__.py rename to steward/ansible/__init__.py diff --git a/roundtable/ansible/executor.py b/steward/ansible/executor.py similarity index 97% rename from roundtable/ansible/executor.py rename to steward/ansible/executor.py index a7f1eaf..22e62c9 100644 --- a/roundtable/ansible/executor.py +++ b/steward/ansible/executor.py @@ -1,4 +1,4 @@ -# roundtable/ansible/executor.py +# steward/ansible/executor.py from __future__ import annotations import asyncio import logging @@ -77,7 +77,7 @@ async def start_run( async def _flush(final_status: str | None = None) -> None: nonlocal lines_since_flush, last_flush_at from sqlalchemy import update - from roundtable.models.ansible import AnsibleRun, AnsibleRunStatus + from steward.models.ansible import AnsibleRun, AnsibleRunStatus values: dict = {"output": db_output} if final_status is not None: diff --git a/roundtable/ansible/routes.py b/steward/ansible/routes.py similarity index 95% rename from roundtable/ansible/routes.py rename to steward/ansible/routes.py index 273f0b2..d01f860 100644 --- a/roundtable/ansible/routes.py +++ b/steward/ansible/routes.py @@ -1,4 +1,4 @@ -# roundtable/ansible/routes.py +# steward/ansible/routes.py from __future__ import annotations import asyncio import uuid @@ -11,10 +11,10 @@ from quart import ( ) from sqlalchemy import select, update -from roundtable.ansible import executor, sources as src_module -from roundtable.auth.middleware import require_role -from roundtable.models.ansible import AnsibleRun, AnsibleRunStatus -from roundtable.models.users import UserRole +from steward.ansible import executor, sources as src_module +from steward.auth.middleware import require_role +from steward.models.ansible import AnsibleRun, AnsibleRunStatus +from steward.models.users import UserRole ansible_bp = Blueprint("ansible", __name__, url_prefix="/ansible") diff --git a/roundtable/ansible/sources.py b/steward/ansible/sources.py similarity index 96% rename from roundtable/ansible/sources.py rename to steward/ansible/sources.py index 8d1a040..f6e6135 100644 --- a/roundtable/ansible/sources.py +++ b/steward/ansible/sources.py @@ -17,7 +17,7 @@ def get_sources(ansible_cfg: dict) -> list[dict]: Config structure in config.yaml:: ansible: - cache_dir: /var/cache/roundtable/ansible + cache_dir: /var/cache/steward/ansible sources: - name: my-playbooks type: local @@ -29,7 +29,7 @@ def get_sources(ansible_cfg: dict) -> list[dict]: pull_interval_seconds: 3600 """ sources = ansible_cfg.get("sources", []) - cache_dir = ansible_cfg.get("cache_dir", "/var/cache/roundtable/ansible") + cache_dir = ansible_cfg.get("cache_dir", "/var/cache/steward/ansible") result = [] for src in sources: src_type = src.get("type", "local") diff --git a/roundtable/app.py b/steward/app.py similarity index 99% rename from roundtable/app.py rename to steward/app.py index 2e75829..676593a 100644 --- a/roundtable/app.py +++ b/steward/app.py @@ -1,4 +1,4 @@ -# roundtable/app.py +# steward/app.py from __future__ import annotations import asyncio from pathlib import Path diff --git a/roundtable/audit/__init__.py b/steward/audit/__init__.py similarity index 100% rename from roundtable/audit/__init__.py rename to steward/audit/__init__.py diff --git a/roundtable/audit/routes.py b/steward/audit/routes.py similarity index 89% rename from roundtable/audit/routes.py rename to steward/audit/routes.py index fe7e755..5ce5b29 100644 --- a/roundtable/audit/routes.py +++ b/steward/audit/routes.py @@ -2,9 +2,9 @@ from __future__ import annotations import json from quart import Blueprint, render_template, request, current_app from sqlalchemy import select -from roundtable.auth.middleware import require_role -from roundtable.models.audit import AuditEvent -from roundtable.models.users import UserRole +from steward.auth.middleware import require_role +from steward.models.audit import AuditEvent +from steward.models.users import UserRole audit_bp = Blueprint("audit", __name__, url_prefix="/audit") diff --git a/roundtable/auth/__init__.py b/steward/auth/__init__.py similarity index 100% rename from roundtable/auth/__init__.py rename to steward/auth/__init__.py diff --git a/roundtable/auth/ldap_auth.py b/steward/auth/ldap_auth.py similarity index 99% rename from roundtable/auth/ldap_auth.py rename to steward/auth/ldap_auth.py index e64b93e..a79eb25 100644 --- a/roundtable/auth/ldap_auth.py +++ b/steward/auth/ldap_auth.py @@ -1,4 +1,4 @@ -"""roundtable/auth/ldap_auth.py +"""steward/auth/ldap_auth.py LDAP authentication helper. Requires the optional `ldap3` package. Falls back gracefully if ldap3 is not installed. diff --git a/roundtable/auth/middleware.py b/steward/auth/middleware.py similarity index 96% rename from roundtable/auth/middleware.py rename to steward/auth/middleware.py index 17e9579..d57bd34 100644 --- a/roundtable/auth/middleware.py +++ b/steward/auth/middleware.py @@ -1,7 +1,7 @@ from __future__ import annotations import functools from quart import session, redirect, url_for, abort, g -from roundtable.models.users import UserRole +from steward.models.users import UserRole _ROLE_ORDER = [UserRole.viewer, UserRole.operator, UserRole.admin] diff --git a/roundtable/auth/oidc.py b/steward/auth/oidc.py similarity index 99% rename from roundtable/auth/oidc.py rename to steward/auth/oidc.py index 0d1aac8..934e3a8 100644 --- a/roundtable/auth/oidc.py +++ b/steward/auth/oidc.py @@ -1,4 +1,4 @@ -"""roundtable/auth/oidc.py +"""steward/auth/oidc.py OIDC Authorization Code flow helpers using httpx. No extra dependencies — works with any OIDC provider (Authentik, Keycloak, etc.). diff --git a/roundtable/auth/routes.py b/steward/auth/routes.py similarity index 93% rename from roundtable/auth/routes.py rename to steward/auth/routes.py index 49de43a..e646c6b 100644 --- a/roundtable/auth/routes.py +++ b/steward/auth/routes.py @@ -2,8 +2,8 @@ from __future__ import annotations import bcrypt from quart import Blueprint, render_template, request, redirect, url_for, session from sqlalchemy import select, func -from roundtable.auth.middleware import login_user, logout_user -from roundtable.models.users import User, UserRole +from steward.auth.middleware import login_user, logout_user +from steward.models.users import User, UserRole auth_bp = Blueprint("auth", __name__) @@ -15,7 +15,7 @@ async def _provision_external_user(app, username: str, email: str, role_str: str Their role is updated on every login to reflect current IdP group membership. """ import secrets - from roundtable.models.users import UserRole + from steward.models.users import UserRole async with app.db_sessionmaker() as db: result = await db.execute(select(User).where(User.username == username)) user = result.scalar_one_or_none() @@ -60,7 +60,7 @@ async def login(): @auth_bp.post("/login") async def login_post(): from quart import current_app - from roundtable.core.audit import log_audit + from steward.core.audit import log_audit form = await request.form username = form.get("username", "").strip() password_str = form.get("password", "") @@ -77,7 +77,7 @@ async def login_post(): # Try LDAP fallback if enabled ldap_cfg = current_app.config.get("LDAP", {}) if ldap_cfg.get("enabled"): - from roundtable.auth.ldap_auth import ldap_authenticate + from steward.auth.ldap_auth import ldap_authenticate ldap_info = await ldap_authenticate(ldap_cfg, username, password_str) if ldap_info: user = await _provision_external_user( @@ -99,7 +99,7 @@ async def login_post(): @auth_bp.get("/login/oidc") async def login_oidc(): from quart import current_app - from roundtable.auth.oidc import get_discovery, build_authorize_url, generate_state + from steward.auth.oidc import get_discovery, build_authorize_url, generate_state oidc_cfg = current_app.config.get("OIDC", {}) if not oidc_cfg.get("enabled") or not oidc_cfg.get("discovery_url"): return redirect(url_for("auth.login")) @@ -123,8 +123,8 @@ async def login_oidc(): @auth_bp.get("/login/oidc/callback") async def login_oidc_callback(): from quart import current_app - from roundtable.auth.oidc import get_discovery, exchange_code, get_userinfo, map_role - from roundtable.core.audit import log_audit + from steward.auth.oidc import get_discovery, exchange_code, get_userinfo, map_role + from steward.core.audit import log_audit oidc_cfg = current_app.config.get("OIDC", {}) error = request.args.get("error") @@ -190,7 +190,7 @@ async def setup(): @auth_bp.post("/setup") async def setup_post(): from quart import current_app - from roundtable.core.audit import log_audit + from steward.core.audit import log_audit if await get_user_count(current_app) > 0: return redirect(url_for("auth.login")) form = await request.form diff --git a/roundtable/cli.py b/steward/cli.py similarity index 92% rename from roundtable/cli.py rename to steward/cli.py index ddeba2e..aafdd8f 100644 --- a/roundtable/cli.py +++ b/steward/cli.py @@ -9,6 +9,6 @@ from .app import create_app @click.option("--port", default=5000, type=int) @click.option("--debug", is_flag=True, default=False) def main(config: str, host: str, port: int, debug: bool) -> None: - """Start the Roundtable server.""" + """Start the Steward server.""" app = create_app(config_path=Path(config)) app.run(host=host, port=port, debug=debug) diff --git a/roundtable/config.py b/steward/config.py similarity index 83% rename from roundtable/config.py rename to steward/config.py index 4d9832d..b119a25 100644 --- a/roundtable/config.py +++ b/steward/config.py @@ -1,4 +1,4 @@ -# roundtable/config.py +# steward/config.py from __future__ import annotations import logging import os @@ -12,14 +12,7 @@ _SECRET_KEY_FILE = Path("/data/secret.key") def _env(suffix: str) -> str | None: - """Read ROUNDTABLE_<suffix>, falling back to FABLEDSCRYER_<suffix>. - - Allows existing deployments to keep their old env vars working through - the rebrand transition. Remove the fallback once users have migrated. - """ - return os.environ.get(f"ROUNDTABLE_{suffix}") or os.environ.get( - f"FABLEDSCRYER_{suffix}" - ) + return os.environ.get(f"STEWARD_{suffix}") def load_bootstrap(config_path: Path | str | None = None) -> dict[str, str]: @@ -50,7 +43,7 @@ def load_bootstrap(config_path: Path | str | None = None) -> dict[str, str]: ) if not database_url: raise ValueError( - "Database URL is required. Set ROUNDTABLE_DATABASE_URL env var " + "Database URL is required. Set STEWARD_DATABASE_URL env var " "or add 'database.url' to config.yaml." ) diff --git a/roundtable/core/__init__.py b/steward/core/__init__.py similarity index 100% rename from roundtable/core/__init__.py rename to steward/core/__init__.py diff --git a/roundtable/core/alerts.py b/steward/core/alerts.py similarity index 98% rename from roundtable/core/alerts.py rename to steward/core/alerts.py index d77516f..eebd55c 100644 --- a/roundtable/core/alerts.py +++ b/steward/core/alerts.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING from sqlalchemy import and_, or_, select from sqlalchemy.ext.asyncio import AsyncSession -from roundtable.models.alerts import ( +from steward.models.alerts import ( AlertEvent, AlertOperator, AlertRule, @@ -16,7 +16,7 @@ from roundtable.models.alerts import ( AlertStateEnum, MaintenanceWindow, ) -from roundtable.models.metrics import PluginMetric +from steward.models.metrics import PluginMetric if TYPE_CHECKING: from quart import Quart @@ -226,7 +226,7 @@ async def _dispatch_notification( event_id: str, ) -> None: """Run after the transaction commits. Sends notifications and updates the event row.""" - from roundtable.core.notifications import dispatch_notifications + from steward.core.notifications import dispatch_notifications alert_data = { "rule_name": rule.name, diff --git a/roundtable/core/audit.py b/steward/core/audit.py similarity index 94% rename from roundtable/core/audit.py rename to steward/core/audit.py index 9342666..ed48620 100644 --- a/roundtable/core/audit.py +++ b/steward/core/audit.py @@ -1,4 +1,4 @@ -"""roundtable/core/audit.py +"""steward/core/audit.py Helpers for writing audit log entries. Each call opens its own DB session so audit events are committed independently of the calling @@ -23,7 +23,7 @@ async def log_audit( detail: dict | None = None, ) -> None: """Write one audit event. Never raises — failures are logged and swallowed.""" - from roundtable.models.audit import AuditEvent + from steward.models.audit import AuditEvent try: async with app.db_sessionmaker() as db: async with db.begin(): diff --git a/roundtable/core/cleanup.py b/steward/core/cleanup.py similarity index 87% rename from roundtable/core/cleanup.py rename to steward/core/cleanup.py index 55329e9..94ec4d5 100644 --- a/roundtable/core/cleanup.py +++ b/steward/core/cleanup.py @@ -5,9 +5,9 @@ from typing import TYPE_CHECKING from sqlalchemy import delete -from roundtable.models.monitors import DnsResult, PingResult -from roundtable.models.metrics import PluginMetric -from roundtable.models.ansible import AnsibleRun +from steward.models.monitors import DnsResult, PingResult +from steward.models.metrics import PluginMetric +from steward.models.ansible import AnsibleRun if TYPE_CHECKING: from quart import Quart diff --git a/roundtable/core/migration_runner.py b/steward/core/migration_runner.py similarity index 98% rename from roundtable/core/migration_runner.py rename to steward/core/migration_runner.py index 2a030b7..d8e513c 100644 --- a/roundtable/core/migration_runner.py +++ b/steward/core/migration_runner.py @@ -1,4 +1,4 @@ -# roundtable/core/migration_runner.py +# steward/core/migration_runner.py from __future__ import annotations import logging from pathlib import Path diff --git a/roundtable/core/migrations.py b/steward/core/migrations.py similarity index 100% rename from roundtable/core/migrations.py rename to steward/core/migrations.py diff --git a/roundtable/core/notifications.py b/steward/core/notifications.py similarity index 95% rename from roundtable/core/notifications.py rename to steward/core/notifications.py index 4a4080a..c98b020 100644 --- a/roundtable/core/notifications.py +++ b/steward/core/notifications.py @@ -33,8 +33,8 @@ async def dispatch_notifications( async def _send_email(cfg: dict, alert: dict) -> dict: try: msg = EmailMessage() - msg["Subject"] = f"[Roundtable] {alert['state']} — {alert['rule_name']}" - msg["From"] = cfg.get("username", "roundtable@localhost") + msg["Subject"] = f"[Steward] {alert['state']} — {alert['rule_name']}" + msg["From"] = cfg.get("username", "steward@localhost") msg["To"] = ", ".join(cfg["recipients"]) msg.set_content( f"Alert: {alert['state']}\n" diff --git a/roundtable/core/plugin_index.py b/steward/core/plugin_index.py similarity index 98% rename from roundtable/core/plugin_index.py rename to steward/core/plugin_index.py index 82e52ee..ac2f02b 100644 --- a/roundtable/core/plugin_index.py +++ b/steward/core/plugin_index.py @@ -1,4 +1,4 @@ -# roundtable/core/plugin_index.py +# steward/core/plugin_index.py """Remote plugin catalog: fetch, parse, and cache the plugin index.yaml.""" from __future__ import annotations import logging diff --git a/roundtable/core/plugin_manager.py b/steward/core/plugin_manager.py similarity index 95% rename from roundtable/core/plugin_manager.py rename to steward/core/plugin_manager.py index 4f87168..2bc0e2c 100644 --- a/roundtable/core/plugin_manager.py +++ b/steward/core/plugin_manager.py @@ -1,4 +1,4 @@ -# roundtable/core/plugin_manager.py +# steward/core/plugin_manager.py from __future__ import annotations import hashlib import importlib @@ -41,7 +41,7 @@ def _import_plugin(name: str, plugin_path: Path): """ import importlib.util - module_key = f"_roundtable_plugin_{name}" + module_key = f"_steward_plugin_{name}" if module_key in sys.modules: return sys.modules[module_key] @@ -78,7 +78,7 @@ def load_plugins(app: "Quart") -> None: 7. Register blueprint at /plugins/<name>/ if get_blueprint() exists 8. Append get_scheduled_tasks() results to app._task_registry """ - import roundtable + import steward plugin_dir = Path(app.config["PLUGIN_DIR"]) plugins_cfg: dict = app.config["PLUGINS"] @@ -126,13 +126,13 @@ def load_plugins(app: "Quart") -> None: min_ver = meta.get("min_app_version") if min_ver: try: - if Version(roundtable.__version__) < Version(min_ver): + if Version(steward.__version__) < Version(min_ver): _FAILED_PLUGINS[name] = ( - f"Requires roundtable>={min_ver}, running {roundtable.__version__}" + f"Requires steward>={min_ver}, running {steward.__version__}" ) logger.error( - "Plugin %r: requires roundtable>=%s but running %s, skipping", - name, min_ver, roundtable.__version__, + "Plugin %r: requires steward>=%s but running %s, skipping", + name, min_ver, steward.__version__, ) continue except Exception as exc: @@ -267,7 +267,7 @@ async def download_and_install_plugin( # Detect a single top-level directory to strip. Handles: # name/ (clean plugin zip) # name-v1.0.0/ (GitHub release tag archive) - # roundtable-plugins-main/name/ (whole-repo archive) + # steward-plugins-main/name/ (whole-repo archive) # Strategy: if there is exactly one top-level item and it is a # directory whose name starts with the plugin name (case-insensitive), # strip it. Otherwise extract flat into dest. @@ -302,7 +302,7 @@ async def download_and_install_plugin( try: mdir = plugin_dir / name / "migrations" if mdir.exists(): - from roundtable.core.migration_runner import ( + from steward.core.migration_runner import ( run_plugin_migrations, discover_all_plugin_migration_dirs, ) @@ -325,7 +325,7 @@ def hot_reload_plugin(app: "Quart", name: str) -> tuple[bool, str]: Returns (success, message). """ - import roundtable + import steward if name in _LOADED_PLUGINS: return False, "Plugin already loaded — restart required to apply updates" @@ -352,10 +352,10 @@ def hot_reload_plugin(app: "Quart", name: str) -> tuple[bool, str]: min_ver = meta.get("min_app_version") if min_ver: try: - if Version(roundtable.__version__) < Version(min_ver): + if Version(steward.__version__) < Version(min_ver): return False, ( - f"Plugin requires roundtable>={min_ver} " - f"but running {roundtable.__version__}" + f"Plugin requires steward>={min_ver} " + f"but running {steward.__version__}" ) except Exception: return False, f"Invalid min_app_version: {min_ver!r}" @@ -381,7 +381,7 @@ def hot_reload_plugin(app: "Quart", name: str) -> tuple[bool, str]: mdir = plugin_path / "migrations" if mdir.exists(): try: - from roundtable.core.migration_runner import ( + from steward.core.migration_runner import ( run_plugin_migrations, discover_all_plugin_migration_dirs, ) diff --git a/roundtable/core/reports.py b/steward/core/reports.py similarity index 94% rename from roundtable/core/reports.py rename to steward/core/reports.py index 992aedc..39f0d7a 100644 --- a/roundtable/core/reports.py +++ b/steward/core/reports.py @@ -1,4 +1,4 @@ -"""roundtable/core/reports.py +"""steward/core/reports.py Weekly digest report: uptime, active alerts, alert events, top metrics. Called by the scheduler (hourly check) or triggered manually from Settings. @@ -13,10 +13,10 @@ from email.message import EmailMessage from sqlalchemy import and_, case, func, select -from roundtable.models.alerts import AlertRule, AlertState, AlertStateEnum, AlertEvent -from roundtable.models.hosts import Host -from roundtable.models.metrics import PluginMetric -from roundtable.models.monitors import PingResult, PingStatus +from steward.models.alerts import AlertRule, AlertState, AlertStateEnum, AlertEvent +from steward.models.hosts import Host +from steward.models.metrics import PluginMetric +from steward.models.monitors import PingResult, PingStatus logger = logging.getLogger(__name__) @@ -152,7 +152,7 @@ def _render_report_text(data: dict) -> str: now = data["generated_at"] lines: list[str] = [] - lines.append("Roundtable — Weekly Report") + lines.append("Steward — Weekly Report") lines.append(f"Generated: {now.strftime('%Y-%m-%d %H:%M UTC')}") lines.append("") @@ -204,7 +204,7 @@ def _render_report_text(data: dict) -> str: lines.append("") lines.append("─" * 40) - lines.append("Roundtable — https://git.fabledsword.com/bvandeusen/Roundtable") + lines.append("Steward — https://git.fabledsword.com/bvandeusen/Steward") return "\n".join(lines) @@ -222,17 +222,17 @@ async def send_report(app) -> tuple[bool, str]: body = _render_report_text(data) date_str = data["generated_at"].strftime("%Y-%m-%d") - subject = f"[Roundtable] Weekly Report — {date_str}" + subject = f"[Steward] Weekly Report — {date_str}" msg = EmailMessage() msg["Subject"] = subject - msg["From"] = smtp_cfg.get("username", "roundtable@localhost") + msg["From"] = smtp_cfg.get("username", "steward@localhost") msg["To"] = ", ".join(smtp_cfg["recipients"]) msg.set_content(body) try: loop = asyncio.get_running_loop() - from roundtable.core.notifications import _smtp_send_sync + from steward.core.notifications import _smtp_send_sync await loop.run_in_executor(None, _smtp_send_sync, smtp_cfg, msg) logger.info("Weekly report sent to %s", smtp_cfg["recipients"]) return True, f"Report sent to {', '.join(smtp_cfg['recipients'])}." @@ -243,7 +243,7 @@ async def send_report(app) -> tuple[bool, str]: async def check_and_send(app) -> None: """Hourly scheduler hook: send if day/hour match and not sent recently.""" - from roundtable.core.settings import get_setting, set_setting + from steward.core.settings import get_setting, set_setting async with app.db_sessionmaker() as db: enabled = await get_setting(db, "reports.enabled") diff --git a/roundtable/core/scheduler.py b/steward/core/scheduler.py similarity index 100% rename from roundtable/core/scheduler.py rename to steward/core/scheduler.py diff --git a/roundtable/core/settings.py b/steward/core/settings.py similarity index 96% rename from roundtable/core/settings.py rename to steward/core/settings.py index c6ae158..5acc0d1 100644 --- a/roundtable/core/settings.py +++ b/steward/core/settings.py @@ -1,15 +1,15 @@ -# roundtable/core/settings.py +# steward/core/settings.py """DB-backed application settings. Keys use dotted notation (e.g. "smtp.host"). Values are JSON-encoded in the DB. Usage in create_app() (before event loop): - from roundtable.core.settings import load_settings_sync + from steward.core.settings import load_settings_sync settings = load_settings_sync(db_url) Usage at runtime (inside async handlers): - from roundtable.core.settings import get_setting, set_setting + from steward.core.settings import get_setting, set_setting async with app.db_sessionmaker() as session: value = await get_setting(session, "smtp.host") await set_setting(session, "smtp.host", "mail.example.com") @@ -24,7 +24,7 @@ from typing import Any from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from roundtable.models.settings import AppSetting +from steward.models.settings import AppSetting logger = logging.getLogger(__name__) @@ -53,7 +53,7 @@ DEFAULTS: dict[str, Any] = { "ansible.sources": [], "ping.threshold.good_ms": 50, "ping.threshold.warn_ms": 200, - "plugins.index_url": "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/raw/branch/main/index.yaml", + "plugins.index_url": "https://git.fabledsword.com/bvandeusen/Steward-plugins/raw/branch/main/index.yaml", # OIDC single-sign-on "oidc.enabled": False, "oidc.discovery_url": "", @@ -209,7 +209,7 @@ def load_settings_sync(db_url: str) -> dict[str, Any]: # ───────────────────────────────────────────────────────────────────────────── def public_base_url(request) -> str: - """Return the externally-reachable base URL for this Roundtable instance. + """Return the externally-reachable base URL for this Steward instance. Prefers the admin-configured 'general.public_base_url' setting (cached in current_app.config['PUBLIC_BASE_URL']) when set. Falls back to diff --git a/roundtable/core/time_range.py b/steward/core/time_range.py similarity index 97% rename from roundtable/core/time_range.py rename to steward/core/time_range.py index ad984f3..ac7423c 100644 --- a/roundtable/core/time_range.py +++ b/steward/core/time_range.py @@ -1,4 +1,4 @@ -# roundtable/core/time_range.py +# steward/core/time_range.py """Shared time-range utilities for scoping queries and sparkline data.""" from __future__ import annotations from datetime import datetime, timedelta, timezone diff --git a/roundtable/core/widgets.py b/steward/core/widgets.py similarity index 99% rename from roundtable/core/widgets.py rename to steward/core/widgets.py index 15f8b56..e956a4b 100644 --- a/roundtable/core/widgets.py +++ b/steward/core/widgets.py @@ -1,4 +1,4 @@ -# roundtable/core/widgets.py +# steward/core/widgets.py # # Central widget registry. Each entry describes one widget type that can be # placed on a dashboard. Plugin widgets declare which plugin must be enabled. diff --git a/roundtable/dashboard/__init__.py b/steward/dashboard/__init__.py similarity index 100% rename from roundtable/dashboard/__init__.py rename to steward/dashboard/__init__.py diff --git a/roundtable/dashboard/routes.py b/steward/dashboard/routes.py similarity index 98% rename from roundtable/dashboard/routes.py rename to steward/dashboard/routes.py index 36afebe..b9a3da1 100644 --- a/roundtable/dashboard/routes.py +++ b/steward/dashboard/routes.py @@ -6,13 +6,13 @@ from datetime import datetime, timezone, timedelta from urllib.parse import urlencode from quart import Blueprint, render_template, current_app, abort, redirect, request, session from sqlalchemy import select, func, update -from roundtable.auth.middleware import require_role, current_user_id -from roundtable.models.users import UserRole -from roundtable.models.hosts import Host -from roundtable.models.monitors import PingResult, DnsResult -from roundtable.models.dashboard import Dashboard, DashboardWidget, DashboardShareToken -from roundtable.core.settings import public_base_url -from roundtable.core.widgets import get_available_widgets, WIDGET_REGISTRY +from steward.auth.middleware import require_role, current_user_id +from steward.models.users import UserRole +from steward.models.hosts import Host +from steward.models.monitors import PingResult, DnsResult +from steward.models.dashboard import Dashboard, DashboardWidget, DashboardShareToken +from steward.core.settings import public_base_url +from steward.core.widgets import get_available_widgets, WIDGET_REGISTRY dashboard_bp = Blueprint("dashboard", __name__) diff --git a/roundtable/database.py b/steward/database.py similarity index 100% rename from roundtable/database.py rename to steward/database.py diff --git a/roundtable/dns/__init__.py b/steward/dns/__init__.py similarity index 100% rename from roundtable/dns/__init__.py rename to steward/dns/__init__.py diff --git a/roundtable/dns/routes.py b/steward/dns/routes.py similarity index 89% rename from roundtable/dns/routes.py rename to steward/dns/routes.py index 04b9ca7..91f30be 100644 --- a/roundtable/dns/routes.py +++ b/steward/dns/routes.py @@ -1,10 +1,10 @@ from __future__ import annotations from quart import Blueprint, current_app, render_template from sqlalchemy import select, func -from roundtable.auth.middleware import require_role -from roundtable.models.users import UserRole -from roundtable.models.hosts import Host -from roundtable.models.monitors import DnsResult +from steward.auth.middleware import require_role +from steward.models.users import UserRole +from steward.models.hosts import Host +from steward.models.monitors import DnsResult dns_bp = Blueprint("dns", __name__, url_prefix="/dns") diff --git a/roundtable/hosts/__init__.py b/steward/hosts/__init__.py similarity index 100% rename from roundtable/hosts/__init__.py rename to steward/hosts/__init__.py diff --git a/roundtable/hosts/routes.py b/steward/hosts/routes.py similarity index 96% rename from roundtable/hosts/routes.py rename to steward/hosts/routes.py index aa1af6c..33afa1a 100644 --- a/roundtable/hosts/routes.py +++ b/steward/hosts/routes.py @@ -2,11 +2,11 @@ from __future__ import annotations from datetime import datetime, timedelta, timezone from quart import Blueprint, render_template, request, redirect, url_for, current_app, session from sqlalchemy import and_, case, select, func -from roundtable.auth.middleware import require_role -from roundtable.core.audit import log_audit -from roundtable.models.hosts import Host, ProbeType -from roundtable.models.monitors import PingResult, DnsResult, PingStatus -from roundtable.models.users import UserRole +from steward.auth.middleware import require_role +from steward.core.audit import log_audit +from steward.models.hosts import Host, ProbeType +from steward.models.monitors import PingResult, DnsResult, PingStatus +from steward.models.users import UserRole hosts_bp = Blueprint("hosts", __name__, url_prefix="/hosts") diff --git a/roundtable/migrations/__init__.py b/steward/migrations/__init__.py similarity index 100% rename from roundtable/migrations/__init__.py rename to steward/migrations/__init__.py diff --git a/roundtable/migrations/env.py b/steward/migrations/env.py similarity index 89% rename from roundtable/migrations/env.py rename to steward/migrations/env.py index 8785208..3da677f 100644 --- a/roundtable/migrations/env.py +++ b/steward/migrations/env.py @@ -4,8 +4,8 @@ from sqlalchemy import pool from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import async_engine_from_config from alembic import context -from roundtable.models.base import Base -import roundtable.models # noqa: F401 — registers all models in metadata +from steward.models.base import Base +import steward.models # noqa: F401 — registers all models in metadata config = context.config if config.config_file_name is not None: @@ -18,9 +18,7 @@ def get_url() -> str: import os, yaml def _env(suffix: str) -> str | None: - return os.environ.get(f"ROUNDTABLE_{suffix}") or os.environ.get( - f"FABLEDSCRYER_{suffix}" - ) + return os.environ.get(f"STEWARD_{suffix}") cfg_path = _env("CONFIG") or "config.yaml" try: diff --git a/roundtable/migrations/script.py.mako b/steward/migrations/script.py.mako similarity index 100% rename from roundtable/migrations/script.py.mako rename to steward/migrations/script.py.mako diff --git a/roundtable/migrations/versions/0001_core_initial.py b/steward/migrations/versions/0001_core_initial.py similarity index 100% rename from roundtable/migrations/versions/0001_core_initial.py rename to steward/migrations/versions/0001_core_initial.py diff --git a/roundtable/migrations/versions/0002_core_monitors.py b/steward/migrations/versions/0002_core_monitors.py similarity index 100% rename from roundtable/migrations/versions/0002_core_monitors.py rename to steward/migrations/versions/0002_core_monitors.py diff --git a/roundtable/migrations/versions/0003_ansible_runs.py b/steward/migrations/versions/0003_ansible_runs.py similarity index 100% rename from roundtable/migrations/versions/0003_ansible_runs.py rename to steward/migrations/versions/0003_ansible_runs.py diff --git a/roundtable/migrations/versions/0004_app_settings.py b/steward/migrations/versions/0004_app_settings.py similarity index 100% rename from roundtable/migrations/versions/0004_app_settings.py rename to steward/migrations/versions/0004_app_settings.py diff --git a/roundtable/migrations/versions/0005_dashboards.py b/steward/migrations/versions/0005_dashboards.py similarity index 100% rename from roundtable/migrations/versions/0005_dashboards.py rename to steward/migrations/versions/0005_dashboards.py diff --git a/roundtable/migrations/versions/0006_dashboard_is_default.py b/steward/migrations/versions/0006_dashboard_is_default.py similarity index 100% rename from roundtable/migrations/versions/0006_dashboard_is_default.py rename to steward/migrations/versions/0006_dashboard_is_default.py diff --git a/roundtable/migrations/versions/0007_dashboard_ownership.py b/steward/migrations/versions/0007_dashboard_ownership.py similarity index 100% rename from roundtable/migrations/versions/0007_dashboard_ownership.py rename to steward/migrations/versions/0007_dashboard_ownership.py diff --git a/roundtable/migrations/versions/0008_share_tokens.py b/steward/migrations/versions/0008_share_tokens.py similarity index 100% rename from roundtable/migrations/versions/0008_share_tokens.py rename to steward/migrations/versions/0008_share_tokens.py diff --git a/roundtable/migrations/versions/0009_widget_variants.py b/steward/migrations/versions/0009_widget_variants.py similarity index 100% rename from roundtable/migrations/versions/0009_widget_variants.py rename to steward/migrations/versions/0009_widget_variants.py diff --git a/roundtable/migrations/versions/0010_maintenance_windows.py b/steward/migrations/versions/0010_maintenance_windows.py similarity index 100% rename from roundtable/migrations/versions/0010_maintenance_windows.py rename to steward/migrations/versions/0010_maintenance_windows.py diff --git a/roundtable/migrations/versions/0011_audit_log.py b/steward/migrations/versions/0011_audit_log.py similarity index 100% rename from roundtable/migrations/versions/0011_audit_log.py rename to steward/migrations/versions/0011_audit_log.py diff --git a/roundtable/migrations/versions/__init__.py b/steward/migrations/versions/__init__.py similarity index 100% rename from roundtable/migrations/versions/__init__.py rename to steward/migrations/versions/__init__.py diff --git a/roundtable/models/__init__.py b/steward/models/__init__.py similarity index 100% rename from roundtable/models/__init__.py rename to steward/models/__init__.py diff --git a/roundtable/models/alerts.py b/steward/models/alerts.py similarity index 100% rename from roundtable/models/alerts.py rename to steward/models/alerts.py diff --git a/roundtable/models/ansible.py b/steward/models/ansible.py similarity index 100% rename from roundtable/models/ansible.py rename to steward/models/ansible.py diff --git a/roundtable/models/audit.py b/steward/models/audit.py similarity index 100% rename from roundtable/models/audit.py rename to steward/models/audit.py diff --git a/roundtable/models/base.py b/steward/models/base.py similarity index 100% rename from roundtable/models/base.py rename to steward/models/base.py diff --git a/roundtable/models/dashboard.py b/steward/models/dashboard.py similarity index 100% rename from roundtable/models/dashboard.py rename to steward/models/dashboard.py diff --git a/roundtable/models/hosts.py b/steward/models/hosts.py similarity index 100% rename from roundtable/models/hosts.py rename to steward/models/hosts.py diff --git a/roundtable/models/metrics.py b/steward/models/metrics.py similarity index 100% rename from roundtable/models/metrics.py rename to steward/models/metrics.py diff --git a/roundtable/models/monitors.py b/steward/models/monitors.py similarity index 100% rename from roundtable/models/monitors.py rename to steward/models/monitors.py diff --git a/roundtable/models/settings.py b/steward/models/settings.py similarity index 93% rename from roundtable/models/settings.py rename to steward/models/settings.py index 02c014d..4ed454a 100644 --- a/roundtable/models/settings.py +++ b/steward/models/settings.py @@ -2,7 +2,7 @@ from __future__ import annotations from datetime import datetime, timezone from sqlalchemy import DateTime, String, Text from sqlalchemy.orm import Mapped, mapped_column -from roundtable.models.base import Base +from steward.models.base import Base class AppSetting(Base): diff --git a/roundtable/models/users.py b/steward/models/users.py similarity index 100% rename from roundtable/models/users.py rename to steward/models/users.py diff --git a/roundtable/monitors/__init__.py b/steward/monitors/__init__.py similarity index 100% rename from roundtable/monitors/__init__.py rename to steward/monitors/__init__.py diff --git a/roundtable/monitors/dns.py b/steward/monitors/dns.py similarity index 94% rename from roundtable/monitors/dns.py rename to steward/monitors/dns.py index 7f123db..2c32aae 100644 --- a/roundtable/monitors/dns.py +++ b/steward/monitors/dns.py @@ -6,9 +6,9 @@ import socket from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from roundtable.core.alerts import record_metric -from roundtable.models.hosts import Host -from roundtable.models.monitors import DnsResult, DnsStatus +from steward.core.alerts import record_metric +from steward.models.hosts import Host +from steward.models.monitors import DnsResult, DnsStatus logger = logging.getLogger(__name__) diff --git a/roundtable/monitors/ping.py b/steward/monitors/ping.py similarity index 93% rename from roundtable/monitors/ping.py rename to steward/monitors/ping.py index 13e4007..6faf845 100644 --- a/roundtable/monitors/ping.py +++ b/steward/monitors/ping.py @@ -5,9 +5,9 @@ import time from sqlalchemy.ext.asyncio import AsyncSession -from roundtable.core.alerts import record_metric -from roundtable.models.hosts import Host, ProbeType -from roundtable.models.monitors import PingResult, PingStatus +from steward.core.alerts import record_metric +from steward.models.hosts import Host, ProbeType +from steward.models.monitors import PingResult, PingStatus logger = logging.getLogger(__name__) diff --git a/roundtable/ping/__init__.py b/steward/ping/__init__.py similarity index 100% rename from roundtable/ping/__init__.py rename to steward/ping/__init__.py diff --git a/roundtable/ping/routes.py b/steward/ping/routes.py similarity index 92% rename from roundtable/ping/routes.py rename to steward/ping/routes.py index 6e6021e..de1846e 100644 --- a/roundtable/ping/routes.py +++ b/steward/ping/routes.py @@ -2,12 +2,12 @@ from __future__ import annotations import logging from quart import Blueprint, current_app, render_template, request, redirect, url_for from sqlalchemy import select, func, case -from roundtable.auth.middleware import require_role -from roundtable.models.users import UserRole -from roundtable.models.hosts import Host -from roundtable.models.monitors import PingResult -from roundtable.core.settings import get_all_settings, set_setting, DEFAULTS -from roundtable.core.time_range import parse_range, DEFAULT_RANGE +from steward.auth.middleware import require_role +from steward.models.users import UserRole +from steward.models.hosts import Host +from steward.models.monitors import PingResult +from steward.core.settings import get_all_settings, set_setting, DEFAULTS +from steward.core.time_range import parse_range, DEFAULT_RANGE ping_bp = Blueprint("ping", __name__, url_prefix="/ping") logger = logging.getLogger(__name__) diff --git a/steward/settings/__init__.py b/steward/settings/__init__.py new file mode 100644 index 0000000..21dbe26 --- /dev/null +++ b/steward/settings/__init__.py @@ -0,0 +1 @@ +# steward/settings/__init__.py diff --git a/roundtable/settings/routes.py b/steward/settings/routes.py similarity index 95% rename from roundtable/settings/routes.py rename to steward/settings/routes.py index 6b45894..c3e5b58 100644 --- a/roundtable/settings/routes.py +++ b/steward/settings/routes.py @@ -1,14 +1,14 @@ -# roundtable/settings/routes.py +# steward/settings/routes.py from __future__ import annotations import json import logging from pathlib import Path from quart import Blueprint, current_app, render_template, request, redirect, url_for, session -from roundtable.auth.middleware import require_role -from roundtable.core.audit import log_audit -from roundtable.models.users import UserRole -from roundtable.core.settings import ( +from steward.auth.middleware import require_role +from steward.core.audit import log_audit +from steward.models.users import UserRole +from steward.core.settings import ( DEFAULTS, get_all_settings, set_setting, to_smtp_cfg, to_webhook_cfg, to_ansible_cfg, to_plugins_cfg, to_oidc_cfg, to_ldap_cfg, @@ -235,12 +235,12 @@ async def ansible_sync_source(idx: int): source = sources[idx] if source.get("type") != "git": return '<span style="color:var(--text-muted);font-size:0.8rem;">Not a git source</span>' - from roundtable.ansible.sources import git_pull - from roundtable.core.settings import to_ansible_cfg + from steward.ansible.sources import git_pull + from steward.core.settings import to_ansible_cfg async with current_app.db_sessionmaker() as db: settings = await get_all_settings(db) ansible_cfg = to_ansible_cfg(settings) - from roundtable.ansible.sources import get_sources + from steward.ansible.sources import get_sources resolved = next((s for s in get_sources(ansible_cfg) if s["name"] == source["name"]), None) if resolved is None: return '<span style="color:var(--red);font-size:0.8rem;">Could not resolve source path</span>' @@ -298,7 +298,7 @@ async def save_auth_settings(): await log_audit(current_app, session.get("user_id"), session.get("username", ""), "settings.saved", detail={"section": "auth"}) # Clear OIDC discovery cache so new settings take effect - from roundtable.auth.oidc import _discovery_cache + from steward.auth.oidc import _discovery_cache _discovery_cache.clear() return redirect(url_for("settings.auth_settings")) @@ -330,7 +330,7 @@ async def save_reports(): @settings_bp.post("/reports/send-now/") @require_role(UserRole.admin) async def send_report_now(): - from roundtable.core.reports import send_report + from steward.core.reports import send_report async with current_app.db_sessionmaker() as db: settings = await get_all_settings(db) ok, message = await send_report(current_app._get_current_object()) @@ -440,7 +440,7 @@ async def plugin_detail(name: str): if plugin is None: return redirect(url_for("settings.plugins")) _merge_plugin_config([plugin], to_plugins_cfg(settings)) - from roundtable.core.plugin_manager import _LOADED_PLUGINS, get_plugin_failures + from steward.core.plugin_manager import _LOADED_PLUGINS, get_plugin_failures return await render_template( "settings/plugin_detail.html", plugin=plugin, @@ -469,7 +469,7 @@ async def save_plugin_detail(name: str): await set_setting(db, f"plugin.{name}", plugin_cfg) await _reload_app_config() - from roundtable.core.plugin_index import clear_catalog_cache + from steward.core.plugin_index import clear_catalog_cache clear_catalog_cache() new_enabled = plugin_cfg.get("enabled", False) @@ -478,7 +478,7 @@ async def save_plugin_detail(name: str): await log_audit(current_app, session.get("user_id"), session.get("username", ""), action, entity_type="plugin", entity_id=name) if new_enabled and not old_enabled: - from roundtable.core.plugin_manager import hot_reload_plugin + from steward.core.plugin_manager import hot_reload_plugin hot_reload_plugin(current_app._get_current_object(), name) return redirect(url_for("settings.plugin_detail", name=name)) @@ -500,7 +500,7 @@ async def plugin_repos_add(): async with current_app.db_sessionmaker() as db: async with db.begin(): await _save_plugin_repos(db, repos) - from roundtable.core.plugin_index import clear_catalog_cache + from steward.core.plugin_index import clear_catalog_cache clear_catalog_cache() return await _plugin_repos_partial({"plugins.repositories": repos}) @@ -516,7 +516,7 @@ async def plugin_repos_remove(idx: int): async with current_app.db_sessionmaker() as db: async with db.begin(): await _save_plugin_repos(db, repos) - from roundtable.core.plugin_index import clear_catalog_cache + from steward.core.plugin_index import clear_catalog_cache clear_catalog_cache() return await _plugin_repos_partial({"plugins.repositories": repos}) @@ -536,7 +536,7 @@ async def plugin_repos_save(idx: int): async with current_app.db_sessionmaker() as db: async with db.begin(): await _save_plugin_repos(db, repos) - from roundtable.core.plugin_index import clear_catalog_cache + from steward.core.plugin_index import clear_catalog_cache clear_catalog_cache() return await _plugin_repos_partial({"plugins.repositories": repos}) @@ -561,8 +561,8 @@ async def plugins_catalog(): error="No plugin repositories configured.", ) - from roundtable.core.plugin_index import fetch_catalog - from roundtable.core.plugin_manager import _LOADED_PLUGINS + from steward.core.plugin_index import fetch_catalog + from steward.core.plugin_manager import _LOADED_PLUGINS force = request.args.get("refresh") == "1" try: @@ -603,7 +603,7 @@ async def install_plugin(name: str): message="No download URL provided.", ) - from roundtable.core.plugin_manager import download_and_install_plugin, hot_reload_plugin + from steward.core.plugin_manager import download_and_install_plugin, hot_reload_plugin ok, msg = await download_and_install_plugin( current_app._get_current_object(), name, download_url, checksum @@ -637,7 +637,7 @@ async def install_plugin(name: str): @require_role(UserRole.admin) async def reload_plugin(name: str): """Hot-reload a plugin that was installed but not yet active.""" - from roundtable.core.plugin_manager import hot_reload_plugin + from steward.core.plugin_manager import hot_reload_plugin ok, msg = hot_reload_plugin(current_app._get_current_object(), name) return await render_template( "settings/_install_result.html", @@ -653,7 +653,7 @@ async def reload_plugin(name: str): async def restart_app_route(): """Trigger an in-app restart (replaces the process via os.execv).""" import asyncio - from roundtable.core.plugin_manager import restart_app + from steward.core.plugin_manager import restart_app # Schedule the restart slightly after we return the response async def _delayed_restart(): diff --git a/roundtable/templates/_time_range.html b/steward/templates/_time_range.html similarity index 100% rename from roundtable/templates/_time_range.html rename to steward/templates/_time_range.html diff --git a/roundtable/templates/alerts/_rule_fields.html b/steward/templates/alerts/_rule_fields.html similarity index 100% rename from roundtable/templates/alerts/_rule_fields.html rename to steward/templates/alerts/_rule_fields.html diff --git a/roundtable/templates/alerts/list.html b/steward/templates/alerts/list.html similarity index 98% rename from roundtable/templates/alerts/list.html rename to steward/templates/alerts/list.html index df2e643..966f8f2 100644 --- a/roundtable/templates/alerts/list.html +++ b/steward/templates/alerts/list.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Alerts — Roundtable{% endblock %} +{% block title %}Alerts — Steward{% endblock %} {% block content %} <h1 class="page-title">Alerts</h1> diff --git a/roundtable/templates/alerts/maintenance.html b/steward/templates/alerts/maintenance.html similarity index 97% rename from roundtable/templates/alerts/maintenance.html rename to steward/templates/alerts/maintenance.html index 5fd62bc..284c413 100644 --- a/roundtable/templates/alerts/maintenance.html +++ b/steward/templates/alerts/maintenance.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Maintenance Windows — Roundtable{% endblock %} +{% block title %}Maintenance Windows — Steward{% endblock %} {% block content %} <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;"> <h1 class="page-title" style="margin-bottom:0;">Maintenance Windows</h1> diff --git a/roundtable/templates/alerts/maintenance_form.html b/steward/templates/alerts/maintenance_form.html similarity index 98% rename from roundtable/templates/alerts/maintenance_form.html rename to steward/templates/alerts/maintenance_form.html index a7224f4..2336c2a 100644 --- a/roundtable/templates/alerts/maintenance_form.html +++ b/steward/templates/alerts/maintenance_form.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}New Maintenance Window — Roundtable{% endblock %} +{% block title %}New Maintenance Window — Steward{% endblock %} {% block content %} <div style="max-width:560px;margin:2rem auto;"> <h1 class="page-title">New Maintenance Window</h1> diff --git a/roundtable/templates/alerts/rules_form.html b/steward/templates/alerts/rules_form.html similarity index 99% rename from roundtable/templates/alerts/rules_form.html rename to steward/templates/alerts/rules_form.html index f1a148f..99ded85 100644 --- a/roundtable/templates/alerts/rules_form.html +++ b/steward/templates/alerts/rules_form.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}{% if rule %}Edit Rule{% else %}New Alert Rule{% endif %} — Roundtable{% endblock %} +{% block title %}{% if rule %}Edit Rule{% else %}New Alert Rule{% endif %} — Steward{% endblock %} {% block content %} <div style="max-width:600px;margin:2rem auto;"> <h1 class="page-title">{% if rule %}Edit Rule{% else %}New Alert Rule{% endif %}</h1> diff --git a/roundtable/templates/ansible/browse.html b/steward/templates/ansible/browse.html similarity index 98% rename from roundtable/templates/ansible/browse.html rename to steward/templates/ansible/browse.html index bf9ed05..93dae22 100644 --- a/roundtable/templates/ansible/browse.html +++ b/steward/templates/ansible/browse.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Browse Playbooks — Roundtable{% endblock %} +{% block title %}Browse Playbooks — Steward{% endblock %} {% block content %} <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;"> <h1 class="page-title" style="margin-bottom:0;">Browse Playbooks</h1> diff --git a/roundtable/templates/ansible/index.html b/steward/templates/ansible/index.html similarity index 96% rename from roundtable/templates/ansible/index.html rename to steward/templates/ansible/index.html index a924da6..9807f46 100644 --- a/roundtable/templates/ansible/index.html +++ b/steward/templates/ansible/index.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Ansible Runs — Roundtable{% endblock %} +{% block title %}Ansible Runs — Steward{% endblock %} {% block content %} <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;"> <h1 class="page-title" style="margin-bottom:0;">Ansible Runs</h1> diff --git a/roundtable/templates/ansible/run_detail.html b/steward/templates/ansible/run_detail.html similarity index 97% rename from roundtable/templates/ansible/run_detail.html rename to steward/templates/ansible/run_detail.html index 57e1175..236ad81 100644 --- a/roundtable/templates/ansible/run_detail.html +++ b/steward/templates/ansible/run_detail.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Run {{ run.id[:8] }} — Roundtable{% endblock %} +{% block title %}Run {{ run.id[:8] }} — Steward{% endblock %} {% block content %} <div style="display:flex;align-items:center;gap:1rem;margin-bottom:1.5rem;"> <a href="/ansible/" class="btn btn-ghost btn-sm">← Runs</a> diff --git a/roundtable/templates/ansible/run_started.html b/steward/templates/ansible/run_started.html similarity index 100% rename from roundtable/templates/ansible/run_started.html rename to steward/templates/ansible/run_started.html diff --git a/roundtable/templates/audit/list.html b/steward/templates/audit/list.html similarity index 98% rename from roundtable/templates/audit/list.html rename to steward/templates/audit/list.html index 0051f76..6aa94e3 100644 --- a/roundtable/templates/audit/list.html +++ b/steward/templates/audit/list.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Audit Log — Roundtable{% endblock %} +{% block title %}Audit Log — Steward{% endblock %} {% block content %} <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;"> <h1 class="page-title" style="margin-bottom:0;">Audit Log</h1> diff --git a/roundtable/templates/auth/login.html b/steward/templates/auth/login.html similarity index 96% rename from roundtable/templates/auth/login.html rename to steward/templates/auth/login.html index e481b76..19c2745 100644 --- a/roundtable/templates/auth/login.html +++ b/steward/templates/auth/login.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Login — Roundtable{% endblock %} +{% block title %}Login — Steward{% endblock %} {% block content %} <div style="max-width:400px;margin:4rem auto;"> <div class="card"> diff --git a/roundtable/templates/auth/setup.html b/steward/templates/auth/setup.html similarity index 93% rename from roundtable/templates/auth/setup.html rename to steward/templates/auth/setup.html index 6172efe..48a076a 100644 --- a/roundtable/templates/auth/setup.html +++ b/steward/templates/auth/setup.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}First-Run Setup — Roundtable{% endblock %} +{% block title %}First-Run Setup — Steward{% endblock %} {% block content %} <div style="max-width:400px;margin:4rem auto;"> <div class="card"> diff --git a/roundtable/templates/base.html b/steward/templates/base.html similarity index 99% rename from roundtable/templates/base.html rename to steward/templates/base.html index ec8d295..3f6837f 100644 --- a/roundtable/templates/base.html +++ b/steward/templates/base.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>{% block title %}Roundtable{% endblock %} + {% block title %}Steward{% endblock %} @@ -195,7 +195,7 @@ textarea { resize: vertical; } - Roundtable + Steward Dashboard Hosts diff --git a/roundtable/templates/dashboard/_edit_panels.html b/steward/templates/dashboard/_edit_panels.html similarity index 100% rename from roundtable/templates/dashboard/_edit_panels.html rename to steward/templates/dashboard/_edit_panels.html diff --git a/roundtable/templates/dashboard/edit.html b/steward/templates/dashboard/edit.html similarity index 95% rename from roundtable/templates/dashboard/edit.html rename to steward/templates/dashboard/edit.html index 27a44bc..6faee34 100644 --- a/roundtable/templates/dashboard/edit.html +++ b/steward/templates/dashboard/edit.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Edit: {{ dashboard.name }} — Roundtable{% endblock %} +{% block title %}Edit: {{ dashboard.name }} — Steward{% endblock %} {% block content %}

Edit: {{ dashboard.name }}

diff --git a/roundtable/templates/dashboard/index.html b/steward/templates/dashboard/index.html similarity index 98% rename from roundtable/templates/dashboard/index.html rename to steward/templates/dashboard/index.html index 1abd060..f03a073 100644 --- a/roundtable/templates/dashboard/index.html +++ b/steward/templates/dashboard/index.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}{{ dashboard.name }} — Roundtable{% endblock %} +{% block title %}{{ dashboard.name }} — Steward{% endblock %} {% block content %} {# ── Header ────────────────────────────────────────────────────────────────── #} diff --git a/roundtable/templates/dashboard/list.html b/steward/templates/dashboard/list.html similarity index 99% rename from roundtable/templates/dashboard/list.html rename to steward/templates/dashboard/list.html index 8fe4ea2..a9372e4 100644 --- a/roundtable/templates/dashboard/list.html +++ b/steward/templates/dashboard/list.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Dashboards — Roundtable{% endblock %} +{% block title %}Dashboards — Steward{% endblock %} {% block content %}

Dashboards

diff --git a/roundtable/templates/dashboard/share.html b/steward/templates/dashboard/share.html similarity index 98% rename from roundtable/templates/dashboard/share.html rename to steward/templates/dashboard/share.html index 234936a..f072a58 100644 --- a/roundtable/templates/dashboard/share.html +++ b/steward/templates/dashboard/share.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Share: {{ dashboard.name }} — Roundtable{% endblock %} +{% block title %}Share: {{ dashboard.name }} — Steward{% endblock %} {% block content %}

Share: {{ dashboard.name }}

diff --git a/roundtable/templates/dashboard/share_created.html b/steward/templates/dashboard/share_created.html similarity index 95% rename from roundtable/templates/dashboard/share_created.html rename to steward/templates/dashboard/share_created.html index e5ffeb9..a9dc001 100644 --- a/roundtable/templates/dashboard/share_created.html +++ b/steward/templates/dashboard/share_created.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Share Link Created — Roundtable{% endblock %} +{% block title %}Share Link Created — Steward{% endblock %} {% block content %}

Share Link Created

diff --git a/roundtable/templates/dashboard/share_view.html b/steward/templates/dashboard/share_view.html similarity index 97% rename from roundtable/templates/dashboard/share_view.html rename to steward/templates/dashboard/share_view.html index b8ee771..4f1d37e 100644 --- a/roundtable/templates/dashboard/share_view.html +++ b/steward/templates/dashboard/share_view.html @@ -3,7 +3,7 @@ - {{ dashboard.name }} — Roundtable + {{ dashboard.name }} — Steward @@ -67,7 +67,7 @@ {{ dashboard.name }} - Roundtable · read-only view + Steward · read-only view
diff --git a/roundtable/templates/dns/index.html b/steward/templates/dns/index.html similarity index 90% rename from roundtable/templates/dns/index.html rename to steward/templates/dns/index.html index 423204d..460127a 100644 --- a/roundtable/templates/dns/index.html +++ b/steward/templates/dns/index.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}DNS Monitor — Roundtable{% endblock %} +{% block title %}DNS Monitor — Steward{% endblock %} {% block content %}

DNS Monitor

diff --git a/roundtable/templates/dns/rows.html b/steward/templates/dns/rows.html similarity index 100% rename from roundtable/templates/dns/rows.html rename to steward/templates/dns/rows.html diff --git a/roundtable/templates/errors/404.html b/steward/templates/errors/404.html similarity index 89% rename from roundtable/templates/errors/404.html rename to steward/templates/errors/404.html index cc15b0f..6787b18 100644 --- a/roundtable/templates/errors/404.html +++ b/steward/templates/errors/404.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Not Found — Roundtable{% endblock %} +{% block title %}Not Found — Steward{% endblock %} {% block content %}

404

diff --git a/roundtable/templates/errors/500.html b/steward/templates/errors/500.html similarity index 90% rename from roundtable/templates/errors/500.html rename to steward/templates/errors/500.html index 81f4868..0aa7caf 100644 --- a/roundtable/templates/errors/500.html +++ b/steward/templates/errors/500.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Error — Roundtable{% endblock %} +{% block title %}Error — Steward{% endblock %} {% block content %}

500

diff --git a/roundtable/templates/hosts/form.html b/steward/templates/hosts/form.html similarity index 98% rename from roundtable/templates/hosts/form.html rename to steward/templates/hosts/form.html index c667bf4..ef2cd82 100644 --- a/roundtable/templates/hosts/form.html +++ b/steward/templates/hosts/form.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}{% if host %}Edit Host{% else %}New Host{% endif %} — Roundtable{% endblock %} +{% block title %}{% if host %}Edit Host{% else %}New Host{% endif %} — Steward{% endblock %} {% block content %}

{% if host %}Edit Host{% else %}Add Host{% endif %}

diff --git a/roundtable/templates/hosts/list.html b/steward/templates/hosts/list.html similarity index 99% rename from roundtable/templates/hosts/list.html rename to steward/templates/hosts/list.html index b619ebd..1cc0815 100644 --- a/roundtable/templates/hosts/list.html +++ b/steward/templates/hosts/list.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Hosts — Roundtable{% endblock %} +{% block title %}Hosts — Steward{% endblock %} {% block content %}

Hosts

diff --git a/roundtable/templates/hosts/uptime.html b/steward/templates/hosts/uptime.html similarity index 98% rename from roundtable/templates/hosts/uptime.html rename to steward/templates/hosts/uptime.html index b3ff1b7..3a408d1 100644 --- a/roundtable/templates/hosts/uptime.html +++ b/steward/templates/hosts/uptime.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Uptime / SLA — Roundtable{% endblock %} +{% block title %}Uptime / SLA — Steward{% endblock %} {% block content %}

Uptime / SLA

diff --git a/roundtable/templates/hosts/uptime_widget.html b/steward/templates/hosts/uptime_widget.html similarity index 100% rename from roundtable/templates/hosts/uptime_widget.html rename to steward/templates/hosts/uptime_widget.html diff --git a/roundtable/templates/ping/index.html b/steward/templates/ping/index.html similarity index 97% rename from roundtable/templates/ping/index.html rename to steward/templates/ping/index.html index 728f115..61776a3 100644 --- a/roundtable/templates/ping/index.html +++ b/steward/templates/ping/index.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% block title %}Ping Monitor — Roundtable{% endblock %} +{% block title %}Ping Monitor — Steward{% endblock %} {% block content %}
diff --git a/roundtable/templates/ping/rows.html b/steward/templates/ping/rows.html similarity index 100% rename from roundtable/templates/ping/rows.html rename to steward/templates/ping/rows.html diff --git a/roundtable/templates/settings/_ansible_sources.html b/steward/templates/settings/_ansible_sources.html similarity index 100% rename from roundtable/templates/settings/_ansible_sources.html rename to steward/templates/settings/_ansible_sources.html diff --git a/roundtable/templates/settings/_catalog_partial.html b/steward/templates/settings/_catalog_partial.html similarity index 100% rename from roundtable/templates/settings/_catalog_partial.html rename to steward/templates/settings/_catalog_partial.html diff --git a/roundtable/templates/settings/_install_result.html b/steward/templates/settings/_install_result.html similarity index 100% rename from roundtable/templates/settings/_install_result.html rename to steward/templates/settings/_install_result.html diff --git a/roundtable/templates/settings/_plugin_repos.html b/steward/templates/settings/_plugin_repos.html similarity index 100% rename from roundtable/templates/settings/_plugin_repos.html rename to steward/templates/settings/_plugin_repos.html diff --git a/roundtable/templates/settings/_restart_pending.html b/steward/templates/settings/_restart_pending.html similarity index 100% rename from roundtable/templates/settings/_restart_pending.html rename to steward/templates/settings/_restart_pending.html diff --git a/roundtable/templates/settings/_tabs.html b/steward/templates/settings/_tabs.html similarity index 100% rename from roundtable/templates/settings/_tabs.html rename to steward/templates/settings/_tabs.html diff --git a/roundtable/templates/settings/ansible.html b/steward/templates/settings/ansible.html similarity index 86% rename from roundtable/templates/settings/ansible.html rename to steward/templates/settings/ansible.html index f7b0532..f0b1d4a 100644 --- a/roundtable/templates/settings/ansible.html +++ b/steward/templates/settings/ansible.html @@ -1,6 +1,6 @@ -{# roundtable/templates/settings/ansible.html #} +{# steward/templates/settings/ansible.html #} {% extends "base.html" %} -{% block title %}Settings — Ansible — Roundtable{% endblock %} +{% block title %}Settings — Ansible — Steward{% endblock %} {% block content %} {% set active_tab = "ansible" %} {% include "settings/_tabs.html" %} diff --git a/roundtable/templates/settings/auth.html b/steward/templates/settings/auth.html similarity index 96% rename from roundtable/templates/settings/auth.html rename to steward/templates/settings/auth.html index aecece1..655fd08 100644 --- a/roundtable/templates/settings/auth.html +++ b/steward/templates/settings/auth.html @@ -1,6 +1,6 @@ -{# roundtable/templates/settings/auth.html #} +{# steward/templates/settings/auth.html #} {% extends "base.html" %} -{% block title %}Settings — Auth — Roundtable{% endblock %} +{% block title %}Settings — Auth — Steward{% endblock %} {% block content %} {% set active_tab = "auth" %} {% include "settings/_tabs.html" %} @@ -25,7 +25,7 @@
+ placeholder="https://auth.example.com/application/o/steward/.well-known/openid-configuration">

Authentik: https://<host>/application/o/<slug>/.well-known/openid-configuration

@@ -72,13 +72,13 @@
+ placeholder="steward-admins">

Members → admin role

+ placeholder="steward-operators">

Members → operator role; others → viewer

@@ -119,7 +119,7 @@
+ placeholder="cn=svc-steward,ou=service,dc=example,dc=com">
diff --git a/roundtable/templates/settings/general.html b/steward/templates/settings/general.html similarity index 91% rename from roundtable/templates/settings/general.html rename to steward/templates/settings/general.html index 7c85287..5d5be93 100644 --- a/roundtable/templates/settings/general.html +++ b/steward/templates/settings/general.html @@ -1,6 +1,6 @@ -{# roundtable/templates/settings/general.html #} +{# steward/templates/settings/general.html #} {% extends "base.html" %} -{% block title %}Settings — General — Roundtable{% endblock %} +{% block title %}Settings — General — Steward{% endblock %} {% block content %} {% set active_tab = "general" %} {% include "settings/_tabs.html" %} @@ -15,7 +15,7 @@ value="{{ settings['general.public_base_url'] }}" placeholder="https://monitor.example.com">
- External URL where this Roundtable instance is reachable from the outside. + External URL where this Steward instance is reachable from the outside. Leave blank to use whatever hostname you're currently browsing via. Used by install scripts, share links, and alert notifications.
diff --git a/roundtable/templates/settings/index.html b/steward/templates/settings/index.html similarity index 98% rename from roundtable/templates/settings/index.html rename to steward/templates/settings/index.html index 7454c62..a092914 100644 --- a/roundtable/templates/settings/index.html +++ b/steward/templates/settings/index.html @@ -1,6 +1,6 @@ -{# roundtable/templates/settings/index.html #} +{# steward/templates/settings/index.html #} {% extends "base.html" %} -{% block title %}Settings — Roundtable{% endblock %} +{% block title %}Settings — Steward{% endblock %} {% block content %}

Settings

diff --git a/roundtable/templates/settings/notifications.html b/steward/templates/settings/notifications.html similarity index 96% rename from roundtable/templates/settings/notifications.html rename to steward/templates/settings/notifications.html index f9e18da..ed2d4e9 100644 --- a/roundtable/templates/settings/notifications.html +++ b/steward/templates/settings/notifications.html @@ -1,6 +1,6 @@ -{# roundtable/templates/settings/notifications.html #} +{# steward/templates/settings/notifications.html #} {% extends "base.html" %} -{% block title %}Settings — Notifications — Roundtable{% endblock %} +{% block title %}Settings — Notifications — Steward{% endblock %} {% block content %} {% set active_tab = "notifications" %} {% include "settings/_tabs.html" %} diff --git a/roundtable/templates/settings/plugin_detail.html b/steward/templates/settings/plugin_detail.html similarity index 98% rename from roundtable/templates/settings/plugin_detail.html rename to steward/templates/settings/plugin_detail.html index 94c4ad5..aac1c00 100644 --- a/roundtable/templates/settings/plugin_detail.html +++ b/steward/templates/settings/plugin_detail.html @@ -1,6 +1,6 @@ -{# roundtable/templates/settings/plugin_detail.html #} +{# steward/templates/settings/plugin_detail.html #} {% extends "base.html" %} -{% block title %}{{ plugin.get('name', plugin._dir) }} Settings — Roundtable{% endblock %} +{% block title %}{{ plugin.get('name', plugin._dir) }} Settings — Steward{% endblock %} {% block content %} {% set active_tab = "plugins" %} {% include "settings/_tabs.html" %} diff --git a/roundtable/templates/settings/plugins.html b/steward/templates/settings/plugins.html similarity index 97% rename from roundtable/templates/settings/plugins.html rename to steward/templates/settings/plugins.html index 4959ec0..31d1a7c 100644 --- a/roundtable/templates/settings/plugins.html +++ b/steward/templates/settings/plugins.html @@ -1,6 +1,6 @@ -{# roundtable/templates/settings/plugins.html #} +{# steward/templates/settings/plugins.html #} {% extends "base.html" %} -{% block title %}Settings — Plugins — Roundtable{% endblock %} +{% block title %}Settings — Plugins — Steward{% endblock %} {% block content %} {% set active_tab = "plugins" %} {% include "settings/_tabs.html" %} diff --git a/roundtable/templates/settings/reports.html b/steward/templates/settings/reports.html similarity index 97% rename from roundtable/templates/settings/reports.html rename to steward/templates/settings/reports.html index 02e03ec..8df780b 100644 --- a/roundtable/templates/settings/reports.html +++ b/steward/templates/settings/reports.html @@ -1,6 +1,6 @@ -{# roundtable/templates/settings/reports.html #} +{# steward/templates/settings/reports.html #} {% extends "base.html" %} -{% block title %}Settings — Reports — Roundtable{% endblock %} +{% block title %}Settings — Reports — Steward{% endblock %} {% block content %} {% set active_tab = "reports" %} {% include "settings/_tabs.html" %} diff --git a/tests/conftest.py b/tests/conftest.py index 1e68dd0..1031522 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ import pytest import pytest_asyncio from pathlib import Path import textwrap -from roundtable.app import create_app +from steward.app import create_app @pytest.fixture diff --git a/tests/core/test_settings_public_base_url.py b/tests/core/test_settings_public_base_url.py index a7b597b..e0a1b5e 100644 --- a/tests/core/test_settings_public_base_url.py +++ b/tests/core/test_settings_public_base_url.py @@ -7,7 +7,7 @@ quart.current_app so the helper runs in isolation. from types import SimpleNamespace import pytest -from roundtable.core import settings as settings_module +from steward.core import settings as settings_module def _fake_request(host_url: str) -> SimpleNamespace: diff --git a/tests/plugins/host_agent/test_agent_config.py b/tests/plugins/host_agent/test_agent_config.py index 0364d86..8dfc44c 100644 --- a/tests/plugins/host_agent/test_agent_config.py +++ b/tests/plugins/host_agent/test_agent_config.py @@ -7,12 +7,12 @@ from plugins.host_agent.agent import read_config, ConfigError def test_parses_flat_key_value(tmp_path): p = tmp_path / "agent.conf" p.write_text( - "url = https://roundtable.example\n" + "url = https://steward.example\n" "token = abc123\n" "interval_seconds = 45\n" ) cfg = read_config(str(p)) - assert cfg["url"] == "https://roundtable.example" + assert cfg["url"] == "https://steward.example" assert cfg["token"] == "abc123" assert cfg["interval_seconds"] == 45 diff --git a/tests/plugins/host_agent/test_install_template.py b/tests/plugins/host_agent/test_install_template.py index 07714df..dc3f928 100644 --- a/tests/plugins/host_agent/test_install_template.py +++ b/tests/plugins/host_agent/test_install_template.py @@ -46,12 +46,12 @@ def test_render_contains_hardening_directives(): def test_render_has_uninstall_branch(): out = _render() assert "--uninstall" in out - assert "systemctl disable --now roundtable-agent.service" in out + assert "systemctl disable --now steward-agent.service" in out def test_render_has_systemctl_enable(): out = _render() - assert "systemctl enable --now roundtable-agent.service" in out + assert "systemctl enable --now steward-agent.service" in out def test_render_uses_agent_py_url(): diff --git a/tests/test_app.py b/tests/test_app.py index 006f674..5b59d7c 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,5 +1,5 @@ import pytest -from roundtable.app import create_app +from steward.app import create_app @pytest.mark.asyncio diff --git a/tests/test_auth.py b/tests/test_auth.py index f6933d4..9c54b4b 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -1,7 +1,7 @@ import pytest import bcrypt from unittest.mock import AsyncMock, patch -from roundtable.models.users import User, UserRole +from steward.models.users import User, UserRole def make_user(role=UserRole.admin): @@ -23,7 +23,7 @@ async def test_login_page_returns_200(client): @pytest.mark.asyncio async def test_login_redirects_to_setup_when_no_users(client, app): - with patch("roundtable.auth.routes.get_user_count", new=AsyncMock(return_value=0)): + with patch("steward.auth.routes.get_user_count", new=AsyncMock(return_value=0)): response = await client.get("/login") assert response.status_code == 302 assert "/setup" in response.headers["Location"] @@ -32,8 +32,8 @@ async def test_login_redirects_to_setup_when_no_users(client, app): @pytest.mark.asyncio async def test_login_success_redirects_to_dashboard(client, app): user = make_user() - with patch("roundtable.auth.routes.get_user_count", new=AsyncMock(return_value=1)), \ - patch("roundtable.auth.routes.get_user_by_username", new=AsyncMock(return_value=user)): + with patch("steward.auth.routes.get_user_count", new=AsyncMock(return_value=1)), \ + patch("steward.auth.routes.get_user_by_username", new=AsyncMock(return_value=user)): response = await client.post("/login", form={"username": "admin", "password": "password"}) assert response.status_code == 302 assert "/" in response.headers["Location"] @@ -42,8 +42,8 @@ async def test_login_success_redirects_to_dashboard(client, app): @pytest.mark.asyncio async def test_login_failure_returns_400(client, app): user = make_user() - with patch("roundtable.auth.routes.get_user_count", new=AsyncMock(return_value=1)), \ - patch("roundtable.auth.routes.get_user_by_username", new=AsyncMock(return_value=user)): + with patch("steward.auth.routes.get_user_count", new=AsyncMock(return_value=1)), \ + patch("steward.auth.routes.get_user_by_username", new=AsyncMock(return_value=user)): response = await client.post("/login", form={"username": "admin", "password": "wrong"}) assert response.status_code == 400 diff --git a/tests/test_config.py b/tests/test_config.py index ba5a019..d4151bd 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,25 +1,25 @@ import os import textwrap import pytest -from roundtable.config import load_bootstrap +from steward.config import load_bootstrap def test_load_bootstrap_from_yaml(tmp_path): cfg_file = tmp_path / "config.yaml" cfg_file.write_text(textwrap.dedent("""\ database: - url: postgresql+asyncpg://user:pass@localhost/roundtable + url: postgresql+asyncpg://user:pass@localhost/steward secret_key: test-secret """)) cfg = load_bootstrap(cfg_file) - assert cfg["database_url"] == "postgresql+asyncpg://user:pass@localhost/roundtable" + assert cfg["database_url"] == "postgresql+asyncpg://user:pass@localhost/steward" assert cfg["secret_key"] == "test-secret" def test_env_var_database_url_overrides_yaml(tmp_path, monkeypatch): cfg_file = tmp_path / "config.yaml" cfg_file.write_text("database:\n url: original\nsecret_key: s\n") - monkeypatch.setenv("FABLEDSCRYER_DATABASE_URL", "overridden") + monkeypatch.setenv("STEWARD_DATABASE_URL", "overridden") cfg = load_bootstrap(cfg_file) assert cfg["database_url"] == "overridden" @@ -27,7 +27,7 @@ def test_env_var_database_url_overrides_yaml(tmp_path, monkeypatch): def test_env_var_secret_key_overrides_yaml(tmp_path, monkeypatch): cfg_file = tmp_path / "config.yaml" cfg_file.write_text("database:\n url: x\nsecret_key: from-yaml\n") - monkeypatch.setenv("FABLEDSCRYER_SECRET_KEY", "from-env") + monkeypatch.setenv("STEWARD_SECRET_KEY", "from-env") cfg = load_bootstrap(cfg_file) assert cfg["secret_key"] == "from-env" diff --git a/tests/test_models.py b/tests/test_models.py index fbed0c7..2bce1c7 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,5 +1,5 @@ import pytest -from roundtable.models.users import User, UserRole +from steward.models.users import User, UserRole def test_user_model_fields():