# Apply Progress: Runtime Service Registry **Change:** `service-registry` **Apply run:** PRs #7–#10 (Slices 1–4a) **Date:** 2026-06-19 ## Slices 1–3 (MERGED) - Slice 1 (#7): backend service foundation — encryption, integrations registry, services + service_task_runs tables, `/api/services*` CRUD. - Slice 2 (#8): backend widget rebind — service_id + widget_kind, ServiceRecord adapters, built-ins, SSH run logging, retired old widget registry. - Slice 3 (#9): frontend services runtime — types/API/hooks, frontend registry, ServicePage, route swap, reconciled widget components + config dialog. ## Slice 4a — Cleanup + services admin UI + docs (this PR) ### Completed tasks - [x] Removed addon pages (`/addons/:addonId`, `AddonPage.tsx`, `addons/*`) — superseded by service pages. - [x] Removed `grafana_url` / `prometheus_url` from `config.py`, both compose files, `.env.example`, and README. (Frontend `VITE_GRAFANA_URL` / `VITE_PROMETHEUS_URL` deep-link vars retained.) - [x] Added a **Services page** (`/services`) with create/list/delete and a nav entry, so service pages are reachable and services are configurable in the tool itself. - [x] Registered `/services` route in both route trees + sidebar nav. - [x] Updated `docs/REQUIREMENTS.md` (service registry section) and added `CHANGELOG.md` with the breaking-upgrade note. ### Decision resolved mid-slice "Full machine migration" was scoped into **4a (cleanup) + 4b (Jellyfin/Jellyseerr migration)** because removing machine-level Jellyfin/Jellyseerr fields is deeply coupled to the Media/Users/Files pages (load-bearing) and there is no `jellyseerr` service definition yet. 4a ships the safe cleanup + the services admin UI; 4b does the machine-app-field migration as its own reviewable change. ### Files changed (Slice 4a) - Backend: `config.py` (removed grafana_url/prometheus_url). - Compose/env/docs: `docker-compose.yml`, `docker-compose.dev.yml`, `.env.example`, `README.md`, `docs/REQUIREMENTS.md`, `CHANGELOG.md` (new). - Frontend: new `pages/ServicesPage.tsx`; `App.tsx` (routes + nav); removed `pages/AddonPage.tsx`, `addons/*`. ### Verification (Slice 4a) ```bash cd backend .venv/bin/ruff check . # clean PYTHONPATH=src .venv/bin/python -m pytest # 222 passed cd ../frontend npm run lint # 0 errors npm run build # success npm run test # 70 passed ``` ## Slice 4b — Jellyfin/Jellyseerr → services migration (in progress) ### Completed (backend, this PR) - [x] Added `jellyseerr` service definition (`integrations/jellyseerr.py`) and registered it (6 service types total). - [x] Added `user_id` to the Jellyfin service config. - [x] `dependencies.py`: new `_request_jellyfin_service_id` + `_service_record` (decrypt-on-read). Rewrote `get_jellyfin_client`, `get_jellyseerr_client`, and `get_user_id` to resolve against the service registry via the `jellyfin_service_id` query param (first enabled instance as fallback). - [x] SSH/Files transport (`get_ssh_client`) unchanged — still uses `machine_id`. - [x] Updated service-registry tests for 6 types. ### Selection model (decided) Split query params: `?jellyfin_service_id=` selects the Jellyfin/Jellyseerr instance; `?machine_id=` selects SSH/Files transport. Pages that need both pass both. ### Remaining (frontend, next PR) - Thread `jellyfinServiceId` through Media / Applications / Dashboard / Users: list `jellyfin` service instances instead of `useMonitoringSettings()` Jellyfin machines; pass `jellyfin_service_id` to Jellyfin API calls. - Files page keeps `machine_id`. - Settings UI: remove machine-level Jellyfin/Jellyseerr fields. - Remove machine app fields from `settings_store.py` + `routers/settings.py` once the UI no longer writes them. ### Verification (backend half) ```bash cd backend .venv/bin/ruff check . # clean PYTHONPATH=src .venv/bin/python -m pytest # 222 passed cd ../frontend npm run lint && npm run build && npm run test # green (unchanged) ```