refactor(plugins): no plugin catalog; plugins ship in the image or are mounted (#5374)
CI / lint (push) Successful in 3s
CI / integration (push) Failing after 1m12s
CI / unit (push) Successful in 1m25s
CI / publish (push) Skipped

First-party plugins have shipped inside the image since a7a281c, and the
catalog's only repository (Steward-plugins) answers 404, so Steward polled a
dead URL every minute for nothing. The operator chose to remove the catalog
entirely (#5214 "Review: readable and fast on the live instance").

Removed:
- core/plugin_index.py and the plugin_catalog_refresh startup task;
- the plugins.index_url default, and migration 0035 deletes the stored
  plugins.index_url / plugins.repositories rows;
- the Settings -> Plugins repository editor, catalog, refresh, install and
  reload routes and their templates;
- the download/checksum/extract install path in plugin_manager, and the
  PLUGIN_INSTALL_DIR config it wrote to;
- plugins/index.yaml, docs/plugins/index.yaml.example and the guide's
  "Publishing to the Catalog" section (now "Shipping a Plugin").

Kept: enabling and configuring plugins, hot-reload on enable, restart, orphan
cleanup, and the external root (/data/plugins) for operator-mounted plugins.
tests/test_no_plugin_catalog.py guards the routes, default and task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 13:15:55 -04:00
co-authored by Claude Opus 5.5
parent 501a345aed
commit f67678998c
17 changed files with 70 additions and 1258 deletions
-22
View File
@@ -28,14 +28,12 @@ def create_app(
"secret_key": "test-secret-key",
"db_connect_timeout": DB_CONNECT_TIMEOUT_SECONDS,
"plugin_dirs": ["plugins"],
"plugin_install_dir": "plugins",
}
app.config.update(
SECRET_KEY=bootstrap["secret_key"],
DATABASE_URL=bootstrap["database_url"],
PLUGIN_DIRS=bootstrap["plugin_dirs"],
PLUGIN_INSTALL_DIR=bootstrap["plugin_install_dir"],
TESTING=testing,
# Lax keeps the session cookie off cross-site POSTs and iframes; the
# CSRF token below is the check that does not depend on the browser.
@@ -477,26 +475,6 @@ def _register_core_tasks(app: Quart) -> None:
)
)
# Fetch the plugin catalog from the configured repositories as each copy
# falls due (plugin_index.refresh_due), so Settings → Plugins only reads
# what is stored (Background Jobs ruling, 2026-10-08). The repositories are
# read on every tick, so an edit in Settings is fetched within a minute.
async def run_catalog_refresh():
from .core.plugin_index import refresh_due, repo_urls
from .core.settings import get_all_settings
async with app.db_sessionmaker() as db:
settings = await get_all_settings(db)
await refresh_due(repo_urls(settings))
app._task_registry.append(
ScheduledTask(
name="plugin_catalog_refresh",
coro_factory=run_catalog_refresh,
interval_seconds=60,
run_on_startup=True,
)
)
# Fire due Ansible schedules (recurring playbook runs). Checks every minute;
# each schedule's own interval gates whether it actually runs.
async def run_ansible_schedules():