The service detail page showed non-secret connection config (base_url, user_id, username, timeout_seconds) as read-only. Render schema-driven editable inputs (reusing the create-dialog pattern) with a draftConfig state hydrated from the instance, and unify the save button to persist both config and secrets. Number fields render as type=number; the base_url schema description surfaces as helper text.
Manage Backend API
FastAPI backend serving the REST API for Jellyfin media browsing, SSH file inspection, server monitoring, and JWT-protected access for Manage.
Project structure
backend/
├── pyproject.toml
├── README.md
├── src/
│ └── media_library_viewer_api/
│ ├── __init__.py
│ ├── main.py # FastAPI app entrypoint
│ ├── config.py # pydantic-settings config
│ ├── auth.py # OIDC/JWT + API key auth
│ ├── dependencies.py # Dependency injection
│ ├── observability.py # Prometheus metrics + request IDs
│ ├── logging_utils.py # Structured JSON/text logging
│ ├── path_utils.py # Jellyfin→SSH path resolution
│ ├── jobs.py # Job templates
│ ├── utils.py # Formatting helpers
│ ├── routers/
│ │ ├── backups.py
│ │ ├── dashboard.py
│ │ ├── files.py
│ │ ├── jobs.py
│ │ ├── media.py
│ │ ├── monitoring.py
│ │ ├── services.py
│ │ ├── settings.py
│ │ ├── tasks.py
│ │ ├── users.py (+ users_impl.py)
│ │ └── widgets.py
│ ├── clients/
│ │ ├── jellyfin.py
│ │ ├── jellyseerr.py
│ │ ├── local.py
│ │ └── ssh.py
│ ├── integrations/ # Service-registry definitions
│ ├── domain/
│ │ └── media.py
│ ├── models/ # Pydantic request/response models
│ ├── services/
│ │ ├── media_index.py (+ _impl.py)
│ │ ├── settings_store.py
│ │ ├── secrets.py # Fernet encryption at rest
│ │ ├── targets.py # Node Exporter target discovery
│ │ ├── task_runner.py
│ │ ├── mail_queue.py (+ mailer.py/_impl.py)
│ │ ├── backup_alert_engine.py (+ backup_poller.py)
│ │ ├── known_hosts.py
│ │ └── db_maintenance.py
│ ├── widgets/ # Widget sources (dashboard data adapters)
│ └── workers/ # Background workers (media index)
└── tests/
Setup
cd backend
python -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
Configuration
Set environment variables directly in your shell or a wrapper script before running the app or Compose. The Docker Compose files use interpolation and do not require an env_file entry.
For local .env development, you can still create one if you prefer, but it is optional.
# Optional backend logging level
LOG_LEVEL=INFO
# Optional SMTP settings for the Users -> message popup
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=your-smtp-username
SMTP_PASSWORD=your-smtp-password
SMTP_FROM_ADDRESS=no-reply@example.com
SMTP_FROM_NAME=Manage
SMTP_USE_TLS=true
SMTP_USE_SSL=false
SMTP_TIMEOUT=30
# Jellyfin, Jellyseerr, and SSH targets are now configured per machine in the app's Settings tab.
# The backend seeds a local machine automatically, so no global Jellyfin or SSH env vars are required.
# Versioning
# The backend tries to auto-detect its version from installed package metadata.
# If needed, you can override the displayed version/build markers with APP_VERSION and APP_BUILD_INFO.
APP_VERSION=0.1.0
APP_BUILD_INFO=dev
# Authentik / OIDC
AUTH_ENABLED=true
OIDC_ISSUER_URL=https://authentik.example/application/o/media-library-viewer/
OIDC_AUDIENCE=media-library-viewer
OIDC_JWKS_URL=
OIDC_CLOCK_SKEW_SECONDS=30
Running
cd backend
uvicorn media_library_viewer_api.main:app --reload --port 8000
Or with PYTHONPATH if not installed:
PYTHONPATH=src uvicorn media_library_viewer_api.main:app --reload --port 8000
API docs available at: http://localhost:8000/docs
Docker
The repository root includes a production docker-compose.yml and a development docker-compose.dev.yml.
The backend media index is stored in the backend_cache Docker volume so it survives container restarts and image rebuilds.
The production compose file expects required environment variables to be supplied via interpolation (shell exports or inline VAR=value docker compose ...).
Configuration workflow examples
- Export your runtime variables before launching Compose:
export BACKEND_APP_HOST=manage.example.com
export FRONTEND_APP_HOST=manage.example.com
export CERT_RESOLVER=letsencrypt
export VITE_OIDC_ISSUER=https://authentik.example/application/o/manage/
export VITE_OIDC_CLIENT_ID=manage
export VITE_OIDC_REDIRECT_URI=https://manage.example.com/
export VITE_OIDC_POST_LOGOUT_REDIRECT_URI=https://manage.example.com/
docker compose up --build
-
After the API is running, open the app, go to Settings, and add machine entries:
- Local: monitors the API host itself without SSH.
- SSH: monitors another machine using a host, username, and a private key pasted directly into the machine settings, with an optional passphrase.
- The machine editor groups Connection, Monitoring / Files, Jellyfin, Jellyseerr, and Notes under separate headings so each service area is easier to scan.
- Saving a monitoring machine validates the banner/auth flow and records the first trusted host key into the backend-managed
known_hostsfile. - If the host cannot be reached or authenticated, the save flow surfaces the SSH error directly in the dialog.
- Use Validate SSH + trust host in the machine editor before saving if you want to test the banner/auth flow explicitly.
- The first successful SSH connection uses trust-on-first-use: the backend records that machine's host key into its managed
known_hostsfile automatically, then continues verifying it strictly on later connects.
-
Open Observability to see Alertmanager alerts, Prometheus scrape targets, and Grafana deep-links for configured machines. Alertmanager, Grafana, and Prometheus are configured as service instances on the Services page; system metrics (disk, CPU, memory) are owned by the external observability stack (Prometheus + node_exporter + Grafana), not by the Manage backend.
For local development, docker compose -f docker-compose.dev.yml up --build does not require an SSH key unless you configure remote SSH machines in the Settings tab.
API Endpoints
GET /api/dashboard/counts— Movie/series/episode totalsGET /api/dashboard/libraries— Per-library breakdownGET /api/dashboard/now-playing— Active playback sessionsGET /api/monitoring/machines— Persistent monitoring machine definitionsGET /api/monitoring/prometheus-targets— Prometheus scrape targets for remote Node Exporters (consumed by external Prometheus viahttp_sd_configs)GET /api/monitoring/alerts— Active Alertmanager alerts summary (resolves the configured alertmanager service)GET /api/monitoring/alertmanager-status— Alertmanager cluster/statusGET /api/monitoring/grafana-status— Grafana service healthGET /api/monitoring/prometheus-status— Prometheus service healthPOST /api/monitoring/alertmanager-webhook— Receive Alertmanager webhooks (log-only)GET /api/settings/machines— Manage machine definitionsGET /api/media/status— Index statusPOST /api/media/build— Rebuild indexGET /api/media/query— Query with filters/sort/paginationGET /api/files/list?path=— Directory listingGET /api/files/ffprobe?path=— ffprobe JSONGET /api/files/stat?path=— stat outputGET /api/files/resolve-path?path=— Path resolutionGET /api/jobs/templates— Available jobsPOST /api/jobs/run— Execute a jobGET /api/users— Jellyfin users with optional Jellyseerr enrichmentGET /api/version— Backend version/build metadata for the UI