The 2026-06-16/17 observability update externalised metrics to
Prometheus + node_exporter + Grafana, but the legacy Manage-side
SSH-scraping monitor was never removed. It duplicated the new stack,
ran SSH df on every machine every 300s, and fed nothing (its UI was
deleted in e2ad731). This slice decommissions the duplication.
Removed (backend):
- services/monitoring_poller.py (MonitoringPoller) — entire file
- services/monitoring_actions.py (disk_space, run_machine_operation,
poll_machine_snapshot, build_machine_client) — entire file;
run_machine_operation had only 2 callers (the poller + /disk), both gone
- tests/test_monitoring_actions.py
- endpoints: POST /api/monitoring/poller, GET /machines/{id}/actions,
GET /disk (and the now-dead _resolve_machine helper)
- lifespan wiring (main.py), dependency wrapper (dependencies.py),
poller.start()/kick() from machine save (routers/settings.py)
- SettingsStore: monitoring_machine_actions table CREATE + 2 indexes +
record/list/prune_machine_actions methods; DROP TABLE IF EXISTS on
startup cleans existing DBs (user-approved)
- config knobs: monitoring_poll_interval_seconds,
monitoring_poll_initial_delay_seconds, monitoring_action_retention_days
- test_api.py: TestMonitoring._ensure_machine + test_disk
Kept (fits the new model): /machines, /prometheus-targets, /alerts,
/alertmanager-status, /alertmanager-webhook; the disk_usage JOB template
(manual on-demand, not monitoring); node_exporter_* machine fields
(they point Prometheus at the right host).
Gate: backend pytest 173 passed; ruff clean.
Manage
Manage is a media and server operations tool with Jellyfin integration, SSH file inspection, server monitoring, and safe remote job templates.
See docs/REQUIREMENTS.md for the living requirements, decisions, and planning history.
See docs/MIGRATION_PLAN.md for the FastAPI + React architecture plan.
Project policy/docs:
- License:
LICENSE(MIT) - Contributing guide:
CONTRIBUTING.md
Architecture
The project consists of two subprojects:
backend/— FastAPI Python API (seebackend/README.md)frontend/— React + TypeScript SPA (seefrontend/README.md)archive/— Original Streamlit prototype (preserved for reference)
Features
- Dashboard with now-playing sessions, server monitoring overview, and per-library media counts
- Server monitoring with CPU, IO wait, RAM, network, and disk I/O charts plus a sortable dashboard table covering all configured machines
- Per-machine monitoring settings with local and remote targets managed in the UI, plus backend-collected recent action history per machine
- SQLite-indexed media table with full-library sort/filter
- Read-only Users tab with Jellyfin as the base source and optional Jellyseerr enrichment
- Remote file browser with ffprobe preview and job execution
- Jellyfin API integration for library metadata and user identity data
- SSH-based file inspection and remote job templates
Quick Start
Docker Compose (recommended)
Production-style deployment with the frontend serving the SPA and proxying /api to the backend. The compose files rely on environment-variable interpolation, so export the required values in your shell before running them (no env_file is needed):
docker compose up --build
Open the app at http://localhost:8080.
Local development with hot reload:
docker compose -f docker-compose.dev.yml up --build
Frontend runs on http://localhost:5173 and the backend on http://localhost:8000.
The backend media index is persisted in a Docker volume (backend_cache) so rebuilds and container restarts do not force a full re-index.
Monitoring machine definitions and recent machine activity are stored in the backend so the UI can show one section per configured machine and preserve history across restarts.
Manual backend/frontend development
cd backend
python -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
uvicorn media_library_viewer_api.main:app --reload --port 8000
cd frontend
npm install
npm run dev
Configuration
The Compose files use environment-variable interpolation. Export the required variables in your shell or pass them inline; a .env file is optional, not required.
Compose examples
Production-style example with shell exports:
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
Inline one-liner example:
BACKEND_APP_HOST=manage.example.com FRONTEND_APP_HOST=manage.example.com CERT_RESOLVER=letsencrypt VITE_OIDC_ISSUER=https://authentik.example/application/o/manage/ VITE_OIDC_CLIENT_ID=manage VITE_OIDC_REDIRECT_URI=https://manage.example.com/ VITE_OIDC_POST_LOGOUT_REDIRECT_URI=https://manage.example.com/ docker compose up --build
For local development, no SSH key is required unless you want to connect to remote SSH machines later:
docker compose -f docker-compose.dev.yml up --build
Example environment variables:
# 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.
#
# Remote SSH machines can store their private key and optional passphrase directly in Settings,
# so no SSH key mount is required for normal use.
# 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
# Frontend OIDC settings
VITE_OIDC_ENABLED=true
VITE_OIDC_ISSUER=https://authentik.example/application/o/media-library-viewer/
VITE_OIDC_CLIENT_ID=media-library-viewer
VITE_OIDC_SCOPE=openid profile email
VITE_OIDC_REDIRECT_URI=http://localhost:8080/
VITE_OIDC_POST_LOGOUT_REDIRECT_URI=http://localhost:8080/
Remote server requirements
The remote server needs:
- Linux
/procand/sys/blockfor monitoring /bin/sh(POSIX shell)python3,ffprobe,find,stat,df,awk
The SSH client rejects unknown host keys. Connect manually once first:
ssh user@host
Development
# Backend
cd backend && PYTHONPATH=src python -m py_compile src/media_library_viewer_api/main.py
# Frontend
cd frontend && npx tsc --noEmit && npm run build
Notes
- Jellyfin server root URL required (not
/web). The client strips trailing/webdefensively. - SSH commands run through
/bin/sh -cregardless of remote login shell. - Job templates are shell-quoted. Add new templates in
backend/src/media_library_viewer_api/jobs.py. - Monitoring collector uses JSONL in
/tmp, pruned to 7 days / 70k lines. - Root-level Docker Compose files are provided for production (
docker-compose.yml) and local development (docker-compose.dev.yml), and both rely on Compose interpolation rather thanenv_fileentries.