Files
manage/backend
Developer 787f46700f Fix: media index build never starts (blocking Jellyfin dependency)
The build endpoint had Depends(get_jellyfin_client) and Depends(get_user_id)
which executed BEFORE the function body. If Jellyfin was unreachable, these
raised HTTPException(503), the function never ran, and the worker was never
started. The frontend mutation had no onError handler, so the failure was
completely silent — the button briefly showed 'Building...' then reverted
to 'Build index' with zero feedback.

Backend fix: removed the Jellyfin dependencies from post_build_index.
The worker subprocess resolves its own Jellyfin connection via
_resolve_jellyfin(service_id) — the endpoint just needs to start the
worker process. The libraries count starts at 0 and gets updated by
the worker once it connects.

Frontend fix: added onError to useBuildIndex that invalidates the status
query (so the UI reflects the non-building state). MediaTab now displays
the build error inline: 'Build failed: <message>' next to the button.

283 backend tests pass (updated build test for new no-dependency flow);
128 frontend tests pass; ruff/eslint clean.
2026-07-06 15:25:48 +00:00
..
2026-05-07 15:17:31 +02:00

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

  1. 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
  1. 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_hosts file.
    • 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_hosts file automatically, then continues verifying it strictly on later connects.
  2. 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 totals
  • GET /api/dashboard/libraries — Per-library breakdown
  • GET /api/dashboard/now-playing — Active playback sessions
  • GET /api/monitoring/machines — Persistent monitoring machine definitions
  • GET /api/monitoring/prometheus-targets — Prometheus scrape targets for remote Node Exporters (consumed by external Prometheus via http_sd_configs)
  • GET /api/monitoring/alerts — Active Alertmanager alerts summary (resolves the configured alertmanager service)
  • GET /api/monitoring/alertmanager-status — Alertmanager cluster/status
  • GET /api/monitoring/grafana-status — Grafana service health
  • GET /api/monitoring/prometheus-status — Prometheus service health
  • POST /api/monitoring/alertmanager-webhook — Receive Alertmanager webhooks (log-only)
  • GET /api/settings/machines — Manage machine definitions
  • GET /api/media/status — Index status
  • POST /api/media/build — Rebuild index
  • GET /api/media/query — Query with filters/sort/pagination
  • GET /api/files/list?path= — Directory listing
  • GET /api/files/ffprobe?path= — ffprobe JSON
  • GET /api/files/stat?path= — stat output
  • GET /api/files/resolve-path?path= — Path resolution
  • GET /api/jobs/templates — Available jobs
  • POST /api/jobs/run — Execute a job
  • GET /api/users — Jellyfin users with optional Jellyseerr enrichment
  • GET /api/version — Backend version/build metadata for the UI