Commit Graph

113 Commits

Author SHA1 Message Date
bvandeusen d925709c77 ci: add Forgejo CI (lint + unit + Postgres integration) + ci-requirements.md
CI / lint (push) Failing after 2s
CI / unit (push) Successful in 7s
CI / integration (push) Successful in 2m8s
Single-repo CI now that plugins are bundled in-tree. Three lanes on push to
dev/main, modeled on FabledCurator's canonical ci.yml (minus frontend/Redis):
- lint: ruff check steward/ plugins/ tests/ (no dep install)
- unit: pytest -m 'not integration' — whole current suite (testing=True mocks DB)
- integration: postgres:16-alpine service via socket-discovered bridge IP; a
  boot-and-migrate test creates the real app, running core + all bundled-plugin
  migrations, then round-trips the DB — guards the folded-in migration graph.

Registers the 'integration' pytest marker; adds tests/integration/test_boot.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 08:37:32 -04:00
bvandeusen a7a281cb11 feat(plugins): fold first-party plugins in-tree; bundled + external roots
First-party plugins (host_agent, http, snmp, traefik, unifi, docker) are now
tracked under plugins/ and baked into the image, so they version atomically
with core — ending the cross-repo import drift the roundtable->steward rename
exposed. History for these files is preserved in the archived Roundtable-plugins
repo.

Plugin discovery becomes multi-root: PLUGIN_DIR (single) -> PLUGIN_DIRS
(bundled first, then external) + PLUGIN_INSTALL_DIR. Bundled ships in the image;
third-party plugins still mount at runtime into the external root
(STEWARD_PLUGIN_DIR, default /data/plugins) and downloads/installs land there.
Bundled shadows external on a name collision.

- config.py: load_bootstrap returns plugin_dirs + plugin_install_dir
- app.py: iterate PLUGIN_DIRS at the migration + load sites
- migration_runner.py: discover_all_in() unions every plugin root
- plugin_manager.py: resolve_plugin_path() (pure, first-root-wins); load /
  install / hot-reload span all roots; installs target the external root
- settings/routes.py: _discover_plugins scans all roots, dedup bundled-first
- Dockerfile: COPY plugins/ ; docker-compose: drop host bind, document external
- tests/test_plugin_dirs.py: resolution, multi-root discovery, bootstrap split

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 08:37:24 -04:00
bvandeusen 88ab5b917e chore: rename project Roundtable → Steward
Renames the Python package directory, CLI command, env var prefix,
docker-compose service/container/image, Postgres role/db, and all
visible branding. Marketing form is "Fabled Steward".

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

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

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 16:20:14 -04:00
bvandeusen e118543b2e feat(dashboard): use public_base_url helper for share link generation 2026-04-16 00:18:45 -04:00
bvandeusen b7293588be feat(settings): public_base_url field in general settings UI 2026-04-15 20:30:12 -04:00
bvandeusen 5bc8ef2566 feat(settings): add public_base_url helper with request.host_url fallback 2026-04-15 20:29:49 -04:00
bvandeusen 3a3d094a2a test(settings): failing tests for public_base_url helper 2026-04-15 20:29:28 -04:00
bvandeusen 2c68ba5094 feat(settings): cache PUBLIC_BASE_URL into app.config on reload 2026-04-15 20:29:13 -04:00
bvandeusen fdbcc58385 feat(settings): add general.public_base_url default 2026-04-15 20:29:02 -04:00
bvandeusen dbe50794d3 Merge pull request 'feat: host_agent plugin — push-model host resource monitoring' (#1) from feat/host-agent into main 2026-04-15 13:43:57 +00:00
bvandeusen 44cc2f2fce docs(plugins): catalog entry for host_agent
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 00:31:27 -04:00
bvandeusen 3382bd956b test(host_agent): stale-agent filter (pure function)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 00:30:42 -04:00
bvandeusen f7e21c0a21 feat(host_agent): register widgets and alert metric catalog entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 22:31:31 -04:00
bvandeusen ded768c089 test(host_agent): install.sh template render + agent.py source parse
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 22:29:15 -04:00
bvandeusen 5e0bfcc2b0 test(host_agent): pure-function tests for metric expansion
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 22:27:08 -04:00
bvandeusen 673ba72510 test(host_agent): agent POST, backoff, and main loop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 22:22:45 -04:00
bvandeusen 8bed58073b test(host_agent): ring buffer and payload builder unit tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 22:09:12 -04:00
bvandeusen b6b1c8adca test(host_agent): agent resource collectors unit tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 22:07:39 -04:00
bvandeusen 6056bad01e test(host_agent): agent config parser unit tests
Add read_config/ConfigError unit tests covering flat key-value parsing,
blank lines/comments, list fields, defaults, missing required keys, and
malformed lines. Also add tests/__init__.py to fix pytest namespace
collision with tests/plugins/ shadowing the root plugins/ package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 21:44:46 -04:00
bvandeusen b1cfc377da docs(plugins): host_agent plan — path 1 note (no DB tests)
Records the mid-execution pivot: test harness uses testing=True which
mocks the DB, so plugin tests stick to pure-Python unit tests (agent +
metric expander). Server routes are verified manually.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 18:13:41 -04:00
bvandeusen 98003f3458 docs(plugins): host_agent implementation plan
15-task TDD plan covering plugin scaffolding, agent collectors, ingest
route, install script, settings UI, widgets, detail page, scheduler,
and integration test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 23:46:37 -04:00
bvandeusen e243e0e0e1 docs(plugins): host-agent plugin design spec
Design for a push-based Python agent that collects CPU/memory/storage/
load/uptime from remote Linux hosts and POSTs to a new host_agent
plugin. Uses per-host bearer tokens, systemd install via curl|sh,
in-memory ring buffer with exponential backoff, and writes to the
existing PluginMetric bus plus a plugin-private registrations table.

No core schema changes — plugin stays strictly isolated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 23:35:12 -04:00
bvandeusen d6e094bbb5 refactor: remove broken NUT/UPS plugin and managed-NUT startup
The UPS plugin's default driver was snmp-ups, so NUT was just translating
network SNMP back into its own protocol — no value over polling the UPS
directly through the existing snmp plugin. The managed-NUT entrypoint path
also broke container startup when config was incomplete.

Deletes nut_setup.py, the NUT block from entrypoint.sh, NUT packages from
the Dockerfile, the ups entry from the plugin catalog example, and the
ups wiring in alerts METRIC_CATALOG, widgets, settings routes, and the
rules form. The plugins/ups source tree (untracked) is also removed from
the working copy. Existing ups_* DB tables are orphaned but harmless in
dev; a drop migration isn't needed.

Follow-up: rebuild "on battery → Ansible shutdown" automation on top of
the alerts system as a new action type that runs a playbook.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 22:41:08 -04:00
bvandeusen 0596f1d9c1 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.
2026-04-13 20:16:25 -04:00
bvandeusen 55a4fac3e9 copy: flip visible wordmark → Roundtable
Updates nav brand, page <title> blocks across all templates, and the
read-only share view footer. Removes duplicate Roundtable heading from
login card now that the nav wordmark reads correctly.
2026-04-13 18:40:30 -04:00
bvandeusen 62d28b1032 build: docker-compose service → roundtable
Service, container_name, image, and env var name flipped to roundtable.
DB credentials kept as fabledscryer to preserve the existing pgdata
volume — a separate migration can rename the role/db later if desired.
2026-04-13 18:40:26 -04:00
bvandeusen ee0b22bafd chore(compose): mount docker socket and traefik log path
Enables the docker plugin to scrape container state and the traefik
access_log plugin to read host log files.
2026-04-13 18:06:05 -04:00
bvandeusen a64f2195de feat: ROUNDTABLE_* env vars with FABLEDSCRYER_* fallback
Reads ROUNDTABLE_<NAME> first, falls back to FABLEDSCRYER_<NAME> so
existing deployments keep working through the rebrand transition.
Covers DATABASE_URL, DATABASE__URL, SECRET_KEY, PLUGIN_DIR, CONFIG.
2026-04-13 17:40:16 -04:00
bvandeusen fcd26346a2 build: container paths point at roundtable package
Keeps the image buildable after the package rename. Image name, container
name, compose service, and env vars still flip in PR 4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 17:29:47 -04:00
bvandeusen fad1c12eac feat: fabledscryer→roundtable import alias shim
Meta path finder redirects fabledscryer[.*] imports to roundtable[.*],
keeping unmodified plugin repos working during the rebrand transition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 17:26:30 -04:00
bvandeusen 1ecc831673 build: point alembic at roundtable/migrations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 17:14:14 -04:00
bvandeusen 2df0d421c2 build: rename package to roundtable in pyproject
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 17:14:07 -04:00
bvandeusen 94a35da86e refactor: update imports fabledscryer → roundtable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 17:13:55 -04:00
bvandeusen 8aad2ab43d refactor: rename package directory fabledscryer → roundtable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 17:10:31 -04:00
bvandeusen fd9c6d941d style(login): placeholder labels instead of field headers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 16:39:06 -04:00
bvandeusen ce43a3900b style(login): larger wordmark + tagline, drop Sign In heading
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 16:37:35 -04:00
bvandeusen c6401d8416 feat(login): Roundtable wordmark above tagline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 16:13:36 -04:00
bvandeusen 6c883e4121 copy: relocate "Under Watch, Under Care" to login tagline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 15:45:05 -04:00
bvandeusen 4d154653ba copy: dashboard hero "Under Watch, Under Care"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:58:38 -04:00
bvandeusen b0d9e9b6ad feat: themed 404 and 500 pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:57:45 -04:00
bvandeusen b6954bd9d2 copy: broader themed empty states + restore DNS CTA
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:56:41 -04:00
bvandeusen 5a701e0558 copy: themed empty states + login tagline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:55:10 -04:00
bvandeusen ba2c698801 style: candlelit glow background replaces star field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:52:27 -04:00
bvandeusen e83c69c6c2 feat: heraldic seal logo (crown & ellipse table)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:51:47 -04:00
bvandeusen 33df0c2f9a style: swap Libertinus Serif → EB Garamond
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:51:19 -04:00
bvandeusen e8691bc7e4 style: pewter & gold palette tokens
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:51:12 -04:00
bvandeusen 1f6404f0fd docs: rebrand implementation plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:40:26 -04:00
bvandeusen 39074252d1 docs: rebrand design spec (FabledScryer → Roundtable)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:30:40 -04:00
bvandeusen 00dc4cdc9c feat: per-plugin settings pages and multi-repo plugin catalog
Plugin settings UI:
- Settings → Plugins now shows a clean list (status dot, name, enabled badge)
  with a Settings button per plugin linking to its own detail page
- Per-plugin detail page at /settings/plugins/<name>/ shows enable toggle,
  all config fields, load status, and failure notice if the plugin errored
- Config saving now scoped per-plugin via POST /settings/plugins/<name>/;
  removes the monolithic save-all-plugins form

Plugin repositories:
- Replace single Plugin Index URL field with a managed list of repositories
  (HTMX add/edit/remove per entry, same pattern as Ansible sources)
- plugin_index.py: fetch_catalog() now accepts a list of URLs, caches per-URL,
  and merges results deduplicating by plugin name (first repo wins)
- Legacy plugins.index_url is auto-migrated to the new list format on first
  access so existing installs don't lose their configured URL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 18:41:31 -04:00
bvandeusen 8558d15eeb feat: plugin fault isolation notices + NUT managed mode fixes
Plugin failures:
- Track load-time failures in _FAILED_PLUGINS dict with human-readable reasons
- Inject plugin_failures into all templates via context processor
- Show admin-only warning banner in base.html when any plugin fails to load
- Show inline failure notice per plugin card in Settings → Plugins

NUT managed mode:
- entrypoint.sh: make nut_setup.py failure non-fatal so the container starts
  even if nut_ups_host isn't set yet (allows fixing config via UI)
- save_plugins: when nut_managed is enabled, auto-set nut_host=localhost so
  the plugin connects to the managed NUT instance without extra manual config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 18:04:53 -04:00