From 0596f1d9c1c66e983c14b31602a52a3dd522f924 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 13 Apr 2026 20:16:25 -0400 Subject: [PATCH] docs: roundtable rename sweep Sweeps README, docs/, example configs, code docstrings, report and notification subject lines, plugin catalog URL default, and stray comments in Dockerfile/entrypoint.sh/.gitignore. Adds a legacy note to docs/core/configuration.md explaining the FABLEDSCRYER_* fallback. docker-compose DB credentials intentionally left as fabledscryer to preserve the existing pgdata volume. --- .env.example | 6 +- .gitignore | 2 +- Dockerfile | 2 +- README.md | 10 +-- config.example.yaml | 6 +- 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/index.yaml.example | 50 ++++++------- docs/plugins/overview.md | 10 +-- docs/plugins/writing-a-plugin.md | 32 ++++----- docs/reference/code-map.md | 118 +++++++++++++++---------------- entrypoint.sh | 2 +- roundtable/cli.py | 2 +- roundtable/core/notifications.py | 2 +- roundtable/core/reports.py | 6 +- roundtable/core/settings.py | 2 +- roundtable/nut_setup.py | 2 +- 20 files changed, 166 insertions(+), 164 deletions(-) diff --git a/.env.example b/.env.example index 6932cf4..16af250 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 -FABLEDSCRYER_SECRET_KEY=change-me -FABLEDSCRYER_DATABASE__URL=postgresql+asyncpg://fabledscryer:password@localhost/fabledscryer -FABLEDSCRYER_SMTP__PASSWORD= +ROUNDTABLE_SECRET_KEY=change-me +ROUNDTABLE_DATABASE__URL=postgresql+asyncpg://roundtable:password@localhost/roundtable +ROUNDTABLE_SMTP__PASSWORD= diff --git a/.gitignore b/.gitignore index 5d7aecb..a0290f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Planning files docs/superpowers/ -# Plugin directory — managed as a separate repo (bvandeusen/fabledscryer-plugins) +# Plugin directory — managed as a separate repo (bvandeusen/roundtable-plugins) # PLUGIN_DIR in config.yaml points here at runtime /plugins/ diff --git a/Dockerfile b/Dockerfile index 11224a0..26c8aec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ RUN chmod +x /entrypoint.sh # Runtime directories — owned by app user. # The container runs as root so the entrypoint can start NUT daemons (USB # access requires root); it drops to 'app' (uid 1000) via gosu before -# launching fabledscryer. +# launching roundtable. RUN useradd -m -u 1000 app \ && mkdir -p /data/playbook_cache /data/plugins \ && chown -R app:app /data /app diff --git a/README.md b/README.md index aecae15..9e14986 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Fabled Scryer +# Roundtable -A self-hosted network monitoring and infrastructure management hub for home servers. Fabled Scryer 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. Roundtable 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 FABLEDSCRYER_DATABASE_URL in .env +# Set ROUNDTABLE_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 -fabledscryer --host 0.0.0.0 --port 5000 +roundtable --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 | `FABLEDSCRYER_DATABASE_URL` env var or `database.url` in `config.yaml` | +| Database URL | `ROUNDTABLE_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/config.example.yaml b/config.example.yaml index 30db988..6bace6a 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -1,9 +1,9 @@ -# Fabled Scryer — Bootstrap Configuration Example +# Roundtable — Bootstrap Configuration Example # # The only REQUIRED setting is the database URL. # Set it via env var (recommended for Docker): # -# FABLEDSCRYER_DATABASE_URL=postgresql+asyncpg://user:pass@host/db +# ROUNDTABLE_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://fabledscryer:password@localhost/fabledscryer" + url: "postgresql+asyncpg://roundtable:password@localhost/roundtable" # 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/docs/architecture.md b/docs/architecture.md index e2aebbf..3da5bb2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,12 +1,12 @@ # Architecture -Fabled Scryer 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. +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. --- ## Startup Sequence -`create_app()` in `fabledscryer/app.py` runs these steps synchronously before the event loop starts: +`create_app()` in `roundtable/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` | `fabledscryer/auth/routes.py` | -| `/` | `dashboard_bp` | `fabledscryer/dashboard/routes.py` | -| `/hosts/` | `hosts_bp` | `fabledscryer/hosts/routes.py` | -| `/ping/` | `ping_bp` | `fabledscryer/ping/routes.py` | -| `/dns/` | `dns_bp` | `fabledscryer/dns/routes.py` | -| `/alerts/` | `alerts_bp` | `fabledscryer/alerts/routes.py` | -| `/ansible/` | `ansible_bp` | `fabledscryer/ansible/routes.py` | -| `/settings/` | `settings_bp` | `fabledscryer/settings/routes.py` | +| `/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` | | `/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 -`fabledscryer/core/scheduler.py` exports two things: +`roundtable/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 (`fabledscryer/templates/`) +- **Jinja2 templates** rendered server-side (`roundtable/templates/`) - **HTMX** for live-updating fragments (dashboard widgets, ping pills, DNS status) -- A single CSS design system in `fabledscryer/templates/base.html` using CSS custom properties +- A single CSS design system in `roundtable/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 9e20fb5..6d9eef8 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:** `fabledscryer/core/alerts.py` → `record_metric(session, source_module, resource_name, metric_name, value)` +**Key function:** `roundtable/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 `fabledscryer/models/alerts.py`: +Defined in `roundtable/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 012ce8d..a0667df 100644 --- a/docs/core/ansible.md +++ b/docs/core/ansible.md @@ -1,6 +1,6 @@ # Ansible Integration -Fabled Scryer 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. +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. --- @@ -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 `fabledscryer/models/ansible.py`): +`ansible_runs` table (defined in `roundtable/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 | |---|---| -| `fabledscryer/ansible/sources.py` | Source discovery, git pull logic | -| `fabledscryer/ansible/executor.py` | Subprocess execution and output streaming | -| `fabledscryer/ansible/routes.py` | HTTP routes (browse, trigger, stream, history) | -| `fabledscryer/models/ansible.py` | `AnsibleRun` model | +| `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 | diff --git a/docs/core/configuration.md b/docs/core/configuration.md index acbebb3..3ab5a17 100644 --- a/docs/core/configuration.md +++ b/docs/core/configuration.md @@ -1,6 +1,6 @@ # Configuration -Fabled Scryer 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. +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. --- @@ -10,29 +10,31 @@ These three values are read at startup from `config.yaml` and/or environment var | Key | Env var | Default | Description | |---|---|---|---| -| `database.url` | `FABLEDSCRYER_DATABASE_URL` | — | PostgreSQL async URL. **Required.** | -| `secret_key` | `FABLEDSCRYER_SECRET_KEY` | auto-generated | Flask/Quart session signing key. Auto-generated and saved to `/data/secret.key` if not set. | -| `plugin_dir` | `FABLEDSCRYER_PLUGIN_DIR` | `plugins` | Path to the plugins directory. | +| `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. | -**Resolution order for `database_url`:** env var `FABLEDSCRYER_DATABASE_URL` → env var `FABLEDSCRYER_DATABASE__URL` (legacy double-underscore) → `database.url` in `config.yaml`. +**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 `secret_key`:** env var `FABLEDSCRYER_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 `ROUNDTABLE_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/fabledscryer" + url: "postgresql+asyncpg://user:password@localhost/roundtable" ``` ### Minimal env-only setup (Docker) ```bash -FABLEDSCRYER_DATABASE_URL=postgresql+asyncpg://user:password@db/fabledscryer +ROUNDTABLE_DATABASE_URL=postgresql+asyncpg://user:password@db/roundtable ``` 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) @@ -66,7 +68,7 @@ Default webhook template: ### Reading and Writing Settings in Code ```python -from fabledscryer.core.settings import get_setting, set_setting +from roundtable.core.settings import get_setting, set_setting # Read async with current_app.db_sessionmaker() as session: @@ -82,7 +84,7 @@ async with current_app.db_sessionmaker() as session: ### The DEFAULTS Dict -`fabledscryer/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. +`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. --- diff --git a/docs/core/monitors.md b/docs/core/monitors.md index f69ed4e..0accd47 100644 --- a/docs/core/monitors.md +++ b/docs/core/monitors.md @@ -1,13 +1,13 @@ # Core Monitors -Fabled Scryer 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. +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. --- ## Ping Monitor -**Source:** `fabledscryer/monitors/ping.py` -**Scheduler task:** `ping_monitor` in `fabledscryer/app.py` +**Source:** `roundtable/monitors/ping.py` +**Scheduler task:** `ping_monitor` in `roundtable/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 `fabledscryer/models/monitors.py`): +`ping_results` table (defined in `roundtable/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:** `fabledscryer/monitors/dns.py` -**Scheduler task:** `dns_monitor` in `fabledscryer/app.py` +**Source:** `roundtable/monitors/dns.py` +**Scheduler task:** `dns_monitor` in `roundtable/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 `fabledscryer/models/monitors.py`): +`dns_results` table (defined in `roundtable/models/monitors.py`): | Column | Type | Description | |---|---|---| diff --git a/docs/plugins/index.yaml.example b/docs/plugins/index.yaml.example index 62e6783..23770e2 100644 --- a/docs/plugins/index.yaml.example +++ b/docs/plugins/index.yaml.example @@ -1,10 +1,10 @@ -# fabledscryer-plugins / index.yaml +# roundtable-plugins / index.yaml # -# This file is the catalog index for the fabledscryer plugin repository. +# This file is the catalog index for the roundtable plugin repository. # It is fetched by the app's Settings → Plugins → Plugin Catalog UI. # -# Fabled Scryer reads this file from: -# https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/raw/branch/main/index.yaml +# Roundtable reads this file from: +# https://git.fabledsword.com/bvandeusen/Roundtable-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/FabledScryer-plugins/releases/download/traefik-v1.0.0/traefik.zip +# https://git.fabledsword.com/bvandeusen/Roundtable-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: "FabledScryer" + author: "Roundtable" license: "MIT" min_app_version: "0.1.0" - repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" - homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/http" - download_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/releases/download/http-v1.0.0/http.zip" + 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" 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: "FabledScryer" + author: "Roundtable" license: "MIT" min_app_version: "0.1.0" - repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" - homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/docker" - download_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/releases/download/docker-v1.0.0/docker.zip" + 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" 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: "FabledScryer" + author: "Roundtable" license: "MIT" min_app_version: "0.1.0" - repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" - homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/traefik" - download_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/releases/download/traefik-v1.0.0/traefik.zip" + 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" 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: "FabledScryer" + author: "Roundtable" license: "MIT" min_app_version: "0.1.0" - repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" - homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/unifi" - download_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/releases/download/unifi-v1.0.0/unifi.zip" + 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" checksum_sha256: "" tags: - network @@ -98,12 +98,12 @@ plugins: - name: ups version: "1.0.0" description: "UPS monitoring via NUT (Network UPS Tools) with Ansible shutdown automation" - author: "FabledScryer" + author: "Roundtable" license: "MIT" min_app_version: "0.1.0" - repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" - homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/ups" - download_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/releases/download/ups-v1.0.0/ups.zip" + repository_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins" + homepage: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/src/branch/main/ups" + download_url: "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/releases/download/ups-v1.0.0/ups.zip" checksum_sha256: "" tags: - ups diff --git a/docs/plugins/overview.md b/docs/plugins/overview.md index 75b6164..fc27136 100644 --- a/docs/plugins/overview.md +++ b/docs/plugins/overview.md @@ -1,12 +1,12 @@ # Plugin System Overview -Plugins extend Fabled Scryer 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 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. --- ## 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 `fabledscryer/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 `roundtable/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 Fabled Scryer version required +min_app_version: "0.1.0" # Minimum Roundtable 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 fabledscryer.core.scheduler import ScheduledTask +from roundtable.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 (`fabledscryer/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 (`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. --- diff --git a/docs/plugins/writing-a-plugin.md b/docs/plugins/writing-a-plugin.md index 225a047..87005c6 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 `fabledscryer.models.base`. +If your plugin stores data, define SQLAlchemy models using the shared `Base` from `roundtable.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 fabledscryer.models.base import Base +from roundtable.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=fabledscryer@head \ + --head=roundtable@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 fabledscryer.core.scheduler import ScheduledTask -from fabledscryer.core.alerts import record_metric +from roundtable.core.scheduler import ScheduledTask +from roundtable.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 fabledscryer.auth.middleware import require_role -from fabledscryer.models.users import UserRole +from roundtable.auth.middleware import require_role +from roundtable.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 — Fabled Scryer{% endblock %} +{% block title %}My Plugin — Roundtable{% 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 fabledscryer.core.alerts import record_metric +from roundtable.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 `fabledscryer.auth.middleware`: +Use the `@require_role` decorator from `roundtable.auth.middleware`: ```python -from fabledscryer.auth.middleware import require_role -from fabledscryer.models.users import UserRole +from roundtable.auth.middleware import require_role +from roundtable.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/FabledScryer-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/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. ### Repo layout ``` -fabledscryer-plugins/ -├── index.yaml ← catalog index — the only file Fabled Scryer fetches +roundtable-plugins/ +├── index.yaml ← catalog index — the only file Roundtable fetches ├── myplugin/ │ ├── plugin.yaml │ ├── __init__.py @@ -381,7 +381,7 @@ myplugin.zip Generate the zip and its checksum: ```bash -cd fabledscryer-plugins +cd roundtable-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 13a3625..b273351 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 | `fabledscryer/app.py` | `create_app()` | -| CLI entry point | `fabledscryer/cli.py` | `main()` | -| Bootstrap config loading | `fabledscryer/config.py` | `load_bootstrap()` | -| Secret key resolution | `fabledscryer/config.py` | `_resolve_secret_key()` | -| DB engine init | `fabledscryer/database.py` | `init_db()` | -| Core migrations | `fabledscryer/core/migration_runner.py` | `run_core_migrations()` | -| Plugin migrations | `fabledscryer/core/migration_runner.py` | `run_plugin_migrations()` | -| Core task registration | `fabledscryer/app.py` | `_register_core_tasks()` | -| Scheduler loop | `fabledscryer/core/scheduler.py` | `start_scheduler()` | +| 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()` | --- @@ -24,15 +24,15 @@ Quick reference for where key functions, models, and entry points live in the co | What | File | Function | |---|---|---| -| All defaults | `fabledscryer/core/settings.py` | `DEFAULTS` dict | -| Read a setting | `fabledscryer/core/settings.py` | `get_setting(session, key)` | -| Write a setting | `fabledscryer/core/settings.py` | `set_setting(session, key, value)` | -| Read all settings | `fabledscryer/core/settings.py` | `get_all_settings(session)` | -| Sync load at startup | `fabledscryer/core/settings.py` | `load_settings_sync(db_url)` | -| Extract SMTP dict | `fabledscryer/core/settings.py` | `to_smtp_cfg(settings)` | -| Extract webhook dict | `fabledscryer/core/settings.py` | `to_webhook_cfg(settings)` | -| Extract Ansible dict | `fabledscryer/core/settings.py` | `to_ansible_cfg(settings)` | -| Extract plugins dict | `fabledscryer/core/settings.py` | `to_plugins_cfg(settings)` | +| 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)` | --- @@ -40,9 +40,9 @@ Quick reference for where key functions, models, and entry points live in the co | What | File | Function | |---|---|---| -| Plugin loading | `fabledscryer/core/plugin_manager.py` | `load_plugins(app)` | -| ScheduledTask dataclass | `fabledscryer/core/scheduler.py` | `ScheduledTask` | -| Task runner | `fabledscryer/core/scheduler.py` | `start_scheduler(tasks)` | +| 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)` | --- @@ -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 | `fabledscryer/core/alerts.py` | `record_metric(session, source_module, resource_name, metric_name, value)` | -| Init alert pipeline | `fabledscryer/core/alerts.py` | `init_alerts(app)` | -| Rule evaluation | `fabledscryer/core/alerts.py` | `_evaluate_rule()` (internal) | -| Notification dispatch | `fabledscryer/core/alerts.py` | `_dispatch_notification()` (internal) | -| Email + webhook send | `fabledscryer/core/notifications.py` | `dispatch_notifications()` | +| 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()` | --- @@ -62,9 +62,9 @@ Quick reference for where key functions, models, and entry points live in the co | What | File | Function | |---|---|---| -| Ping a host | `fabledscryer/monitors/ping.py` | `ping_check(host, session)` | -| DNS check a host | `fabledscryer/monitors/dns.py` | `dns_check(host, session)` | -| Data cleanup | `fabledscryer/core/cleanup.py` | `run_cleanup(app)` | +| 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)` | --- @@ -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 | `fabledscryer/auth/middleware.py` | `@require_role(UserRole.X)` | -| Login / session handling | `fabledscryer/auth/middleware.py` | `login_user()`, `logout_user()` | -| User count (for first-run) | `fabledscryer/auth/middleware.py` | `get_user_count(app)` | +| 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)` | --- @@ -82,18 +82,18 @@ Quick reference for where key functions, models, and entry points live in the co | Model | File | Table | |---|---|---| -| `Host` | `fabledscryer/models/hosts.py` | `hosts` | -| `PingResult` | `fabledscryer/models/monitors.py` | `ping_results` | -| `DnsResult` | `fabledscryer/models/monitors.py` | `dns_results` | -| `AlertRule` | `fabledscryer/models/alerts.py` | `alert_rules` | -| `AlertState` | `fabledscryer/models/alerts.py` | `alert_states` | -| `AlertEvent` | `fabledscryer/models/alerts.py` | `alert_events` | -| `PluginMetric` | `fabledscryer/models/metrics.py` | `plugin_metrics` | -| `AnsibleRun` | `fabledscryer/models/ansible.py` | `ansible_runs` | -| `User` | `fabledscryer/models/users.py` | `users` | -| `AppSetting` | `fabledscryer/models/settings.py` | `app_settings` | +| `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` | | `TraefikMetric` | `plugins/traefik/models.py` | `traefik_metrics` | -| SQLAlchemy `Base` | `fabledscryer/models/base.py` | (shared declarative base) | +| SQLAlchemy `Base` | `roundtable/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` | `fabledscryer/dashboard/routes.py` | -| `/auth/login`, `/auth/logout` | `auth_bp` | `fabledscryer/auth/routes.py` | -| `/hosts/` | `hosts_bp` | `fabledscryer/hosts/routes.py` | -| `/ping/`, `/ping/rows`, `/ping/settings` | `ping_bp` | `fabledscryer/ping/routes.py` | -| `/dns/`, `/dns/rows` | `dns_bp` | `fabledscryer/dns/routes.py` | -| `/alerts/` | `alerts_bp` | `fabledscryer/alerts/routes.py` | -| `/ansible/` | `ansible_bp` | `fabledscryer/ansible/routes.py` | -| `/settings/` | `settings_bp` | `fabledscryer/settings/routes.py` | +| `/` (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` | | `/plugins/traefik/`, `/plugins/traefik/widget` | `traefik_bp` | `plugins/traefik/routes.py` | -| `/health` | (inline) | `fabledscryer/app.py` | +| `/health` | (inline) | `roundtable/app.py` | --- @@ -118,12 +118,12 @@ Quick reference for where key functions, models, and entry points live in the co | Template | Purpose | |---|---| -| `fabledscryer/templates/base.html` | Layout, navigation, full CSS design system | -| `fabledscryer/templates/dashboard/index.html` | Dashboard with stat strip and widget grid | -| `fabledscryer/templates/ping/rows.html` | HTMX fragment: ping pill rows (shared by dashboard and /ping/) | -| `fabledscryer/templates/ping/index.html` | Full /ping/ page | -| `fabledscryer/templates/dns/rows.html` | HTMX fragment: DNS status rows | -| `fabledscryer/templates/dns/index.html` | Full /dns/ page | +| `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 | | `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 | |---|---| -| `fabledscryer/migrations/versions/` | Core schema (hosts, users, monitors, alerts, app_settings) | +| `roundtable/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/entrypoint.sh b/entrypoint.sh index 4a34089..97f9a4e 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,5 +1,5 @@ #!/bin/sh -# FabledScryer container entrypoint. +# Roundtable container entrypoint. # # When NUT_MANAGED=1: # - Generates /etc/nut/*.conf from environment variables diff --git a/roundtable/cli.py b/roundtable/cli.py index 4387264..ddeba2e 100644 --- a/roundtable/cli.py +++ b/roundtable/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 Fabled Scryer server.""" + """Start the Roundtable server.""" app = create_app(config_path=Path(config)) app.run(host=host, port=port, debug=debug) diff --git a/roundtable/core/notifications.py b/roundtable/core/notifications.py index 932ba14..4a4080a 100644 --- a/roundtable/core/notifications.py +++ b/roundtable/core/notifications.py @@ -33,7 +33,7 @@ async def dispatch_notifications( async def _send_email(cfg: dict, alert: dict) -> dict: try: msg = EmailMessage() - msg["Subject"] = f"[Fabled Scryer] {alert['state']} — {alert['rule_name']}" + msg["Subject"] = f"[Roundtable] {alert['state']} — {alert['rule_name']}" msg["From"] = cfg.get("username", "roundtable@localhost") msg["To"] = ", ".join(cfg["recipients"]) msg.set_content( diff --git a/roundtable/core/reports.py b/roundtable/core/reports.py index 26dc6fd..992aedc 100644 --- a/roundtable/core/reports.py +++ b/roundtable/core/reports.py @@ -152,7 +152,7 @@ def _render_report_text(data: dict) -> str: now = data["generated_at"] lines: list[str] = [] - lines.append("Fabled Scryer — Weekly Report") + lines.append("Roundtable — 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("Fabled Scryer — https://git.fabledsword.com/bvandeusen/FabledScryer") + lines.append("Roundtable — https://git.fabledsword.com/bvandeusen/Roundtable") return "\n".join(lines) @@ -222,7 +222,7 @@ async def send_report(app) -> tuple[bool, str]: body = _render_report_text(data) date_str = data["generated_at"].strftime("%Y-%m-%d") - subject = f"[Fabled Scryer] Weekly Report — {date_str}" + subject = f"[Roundtable] Weekly Report — {date_str}" msg = EmailMessage() msg["Subject"] = subject diff --git a/roundtable/core/settings.py b/roundtable/core/settings.py index ea7944d..e6568f8 100644 --- a/roundtable/core/settings.py +++ b/roundtable/core/settings.py @@ -50,7 +50,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/FabledScryer-plugins/raw/branch/main/index.yaml", + "plugins.index_url": "https://git.fabledsword.com/bvandeusen/Roundtable-plugins/raw/branch/main/index.yaml", # OIDC single-sign-on "oidc.enabled": False, "oidc.discovery_url": "", diff --git a/roundtable/nut_setup.py b/roundtable/nut_setup.py index 6a48a3a..828ea3f 100644 --- a/roundtable/nut_setup.py +++ b/roundtable/nut_setup.py @@ -2,7 +2,7 @@ Called by entrypoint.sh before the NUT daemons start. Reads config either from environment variables (legacy NUT_MANAGED=1 path) or from a JSON file written -by the FabledScryer settings page (/data/nut_managed.json). +by the Roundtable settings page (/data/nut_managed.json). Environment variables (used when no --from-file is given): NUT_DRIVER NUT driver (default: snmp-ups)