# Apply Progress: Runtime Service Registry **Change:** `service-registry` **Apply run:** PR 1 / Slice 1 — Backend service foundation **Date:** 2026-06-19 ## Completed tasks (Slice 1) - [x] 1.1 Add encryption helper (`services/secrets.py`) - [x] 1.2 Add integrations base classes (`integrations/base.py`) - [x] 1.3 Add five service definitions + registry - [x] 1.4 Add `services` + `service_task_runs` tables + store CRUD with cascade delete - [x] 1.5 Add service Pydantic models + `/api/services*` router - [x] 1.6 Validate `MANAGE_ENCRYPTION_KEY` on startup - [x] 1.7 Add backend tests (`tests/test_services.py`) - [x] 1.8 Verify (ruff + pytest green) ## Files changed (Slice 1) ### New files - `backend/src/media_library_viewer_api/integrations/__init__.py` — package marker. - `backend/src/media_library_viewer_api/integrations/base.py` — `ServiceConfigBase`, `WidgetConfigBase`, `SecretField`, `WidgetKind`, `ServiceDefinition`, `widget_kind()`, `validate_config()`. - `backend/src/media_library_viewer_api/integrations/{grafana,prometheus,jellyfin,nextcloud,ssh_tasks}.py` — one Pydantic-config + widget-config definition per service. - `backend/src/media_library_viewer_api/integrations/registry.py` — closed `SERVICE_DEFINITIONS` + helpers. - `backend/src/media_library_viewer_api/services/secrets.py` — Fernet encrypt/decrypt - key validation. - `backend/src/media_library_viewer_api/models/services.py` — request/response models. - `backend/src/media_library_viewer_api/routers/services.py` — `/api/services/types` - `/api/services/instances` CRUD. - `backend/tests/test_services.py` — 25 tests. ### Modified files - `backend/src/media_library_viewer_api/services/settings_store.py` — `services` and `service_task_runs` tables; service CRUD; cascade delete (defensive against the not-yet-present `dashboard_widgets.service_id` column); task-run history helpers. - `backend/src/media_library_viewer_api/main.py` — register `services_router`; validate encryption key on startup. - `backend/pyproject.toml` — declare `cryptography>=42.0` direct dependency. - `docker-compose.yml`, `docker-compose.dev.yml`, `.env.example`, `README.md` — require and document `MANAGE_ENCRYPTION_KEY`. ## Verification (Slice 1) ```bash cd backend .venv/bin/ruff check . # All checks passed PYTHONPATH=src .venv/bin/python -m pytest # 225 passed cd ../frontend npm run lint # 0 errors npm run build # success ``` Smoke: encryption round-trip OK; missing `MANAGE_ENCRYPTION_KEY` raises on startup. ## Deviations from design - Service-config and widget-config schemas are derived from **Pydantic models** (`model_json_schema()`), matching the user's "proper pydantic config definitions" request. The design's hand-written JSON schemas were replaced by model-derived ones. - Service-table CRUD lives on `SettingsStore` (not a separate `service_store.py`) to match how widgets/saved_tasks/ssh_keys are already handled there. This keeps a single store owner for all tables. - The cascade delete defensively checks for `dashboard_widgets.service_id` (added in Slice 2) so Slice 1 stays green without the column. ## Remaining work - Slice 2: Backend widget rebind to services (add `service_id`/`widget_kind`, refactor adapters to take a `ServiceRecord`, retire old widget registry, SSH run logging). - Slice 3: Frontend services runtime (types, API, hooks, frontend registry, service pages, route swap). - Slice 4: Dashboard picker, settings rework, remove `grafana_url`/`prometheus_url` env vars, stop default seeding, docs + changelog.