Phase 2: Docker and OIDC auth

This commit is contained in:
2026-05-04 13:50:53 +02:00
parent 47baee854b
commit 4226628d5a
71 changed files with 9722 additions and 1347 deletions
+49 -200
View File
@@ -34,12 +34,42 @@ Phase 1: Jellyfin media index, SSH-based remote filesystem inspection, server mo
- File browser interaction should stay explicit and simple: read-only listing plus explicit Open/Select actions rather than another row-selection grid.
- Use valid Jellyfin `Fields` query values only, because invalid field names can cause `400 Bad Request` responses.
### Users & Communication
- Provide a Users tab that lists all available users the system knows about.
- Use Jellyfin as the base source of truth for the user list.
- Optionally enrich Jellyfin users with Jellyseerr data when Jellyseerr is configured and reachable.
- Be tolerant of Jellyseerr response-shape differences across versions; for example, some endpoints may return a wrapped `{ users: [...] }` payload instead of a raw list.
- Surface whatever contact/identity fields are available from the configured source(s), such as email, avatar/thumb, role/permissions, and notification/contact eligibility.
- Email should only render actual email addresses; usernames or other non-email identifiers should be suppressed instead of shown as email.
- Keep communication actions separate from listing/identity data so the UI can support future email/notification workflows without redesigning the user list.
- SMTP-backed user messages should be queued asynchronously and return immediately; delivery must not block the rest of the API request path.
- The Users tab should expose a live queue status indicator so users can see when the outbound email queue is idle, busy, stopped, or failing.
- The queue status indicator should clearly show the current queue item count.
- The Users tab should include a one-click SMTP test action that validates connectivity/authentication without sending a real message.
- The SMTP test action should visibly show when it is running.
- The SMTP test should surface the chosen protocol/port and, for Fastmail, try both 465/SSL and 587/STARTTLS so configuration mismatches are easier to diagnose.
- Rework the Users data into an internal merged state so user identity can be combined with related now-playing/session data.
- Clicking a now-playing row should navigate to the Users tab and open the matching user detail drawer, keeping the selection deep-linkable.
- Provide an explicit "Open in Users" action in now-playing rows in addition to row-click navigation.
- Provide a compact per-field source summary for the Users detail drawer so it is obvious which backend source supplied name, email, avatar, and access data.
- Jellyseerr user list pagination must use `take`/`skip`, not `page`/`pageSize`.
- The Users tab table should stay compact and readable: center the avatar and email cells, keep backend source diagnostics out of the table itself, and prefer a simpler hand-built row layout when a dense grid makes text positioning awkward.
- The Users table activity column should stay compact and show only a brief status badge for playing/paused/idle/no-session state instead of a multi-line activity summary.
- The Dashboard activity panel should reuse the same compact session-table styling as the Users activity details so the two views feel consistent.
- In the shared session activity table, the user column should come before state, title/type, and device because the user is the most relevant identifier.
- The shared session table should keep a compact overall status summary line above the rows that reports total sessions plus playing, paused, and idle counts.
- The shared session table should keep the session identifier under the user name in a caption instead of giving it a full column, to keep the table tighter.
- The Users tab may open a read-only detail drawer for a selected user, but any communication actions in that drawer should remain clearly disabled/placeholders until the workflow is implemented.
- Backend startup should log a secret-safe configuration summary and request/activity diagnostics so configuration issues can be debugged without exposing API keys.
### Remote Filesystem over SSH
- Connect to a remote media server via SSH.
- Use strict SSH host key behavior; users should connect manually once to populate `known_hosts`.
- Browse remote directories and files rooted at a configurable default media path.
- File browser handoff should map Jellyfin paths to `REMOTE_MEDIA_ROOT` when possible (for example `/media/...` -> `/srv/media/...` when root is `/srv/media`).
- Media index paths should be stored in the SSH-visible form by default, using the same Jellyfin-to-SSH mapping so the Media tab and file browser agree on paths.
- Support a configurable Jellyfin-to-SSH fallback path prefix for cases where `REMOTE_MEDIA_ROOT` mapping alone is not sufficient.
- Support manual path entry and refresh.
- Remote file listing must be compact, structured, and navigable.
@@ -94,22 +124,31 @@ Phase 1: Jellyfin media index, SSH-based remote filesystem inspection, server mo
### Dashboard / Server Monitoring
- Provide a dashboard tab with a compact Jellyfin media library overview and server resource overview.
- Support OIDC login in the frontend using an OIDC client library, with backend JWT validation for protected API requests.
- Provide Docker Compose deployment files at the repository root for production and local development.
- Show Jellyfin media counts for movies, series, and series episodes on the dashboard.
- Show currently playing Jellyfin sessions on the dashboard, including user, media title, playback state, and whether transcoding is active.
- Show dashboard session activity from Jellyfin, including both currently playing sessions and logged-in idle sessions.
- Activity rows should include user, media title (or `(idle)`), playback state (`playing`/`paused`/`idle`), and whether transcoding is active.
- Provide a dashboard tab with a compact server resource overview over SSH.
- Provide a separate Monitoring tab for detailed resource charts, collector controls, diagnostics, and raw samples.
- The Monitoring tab should request all retained collector samples by default, while the dashboard overview can continue to use a shorter recent window.
- Show CPU and RAM usage for the last hour.
- Show IO wait percentage for the last hour.
- On the dashboard overview, summarize monitoring metrics as 10-minute averages with high/low values for quick inspection.
- Show average and spike/peak values for network throughput and disk I/O.
- Show used, available, and total disk space for the configured media root, falling back to `/`.
- Render Monitoring charts directly with D3 so the UI can support brush-based range selection, hover tooltips with a moving vertical cursor and snapped point markers, summary chips, and moving averages without a separate wrapper library.
- Use a lightweight remote collector that reads Linux `/proc`, `/sys/block`, and `df` data into a JSONL file under `/tmp`.
- The collector should rotate/prune its JSONL metrics file so it does not grow unbounded; default retention is 7 days with a 70,000-line safety cap.
- The collector should be startable/stoppable/restartable from the dashboard and should not require installing a full monitoring stack.
- Last-hour charts require the collector to have been running long enough to collect samples.
- Network throughput should be shown split into down/download and up/upload.
- Because the collector keeps only a bounded history, the Monitoring tab can safely load all retained samples up to the retention/max-lines cap.
- Network throughput should be shown as a combined traffic chart with download and upload lines.
- Network throughput should use bytes-per-second display units such as KB/s, MB/s, and GB/s to avoid bit/byte ambiguity.
- Disk throughput should be shown split into read and write.
- Disk throughput should be shown as a combined I/O chart with read and write lines.
- Network and disk throughput charts should scale values into readable units such as KB/s, MB/s, and GB/s.
- Each Monitoring chart should show compact summary chips such as min/avg/max for quick inspection.
- The Monitoring toolbar should offer quick time-range buttons such as 1h, 8h, 1 day, and 7 days in addition to free brush selection.
### Remote Jobs
@@ -120,202 +159,12 @@ Phase 1: Jellyfin media index, SSH-based remote filesystem inspection, server mo
- Command template values must be shell-quoted before execution.
- Future destructive jobs should require explicit confirmation.
## Current Architecture
- `app.py` - Thin root Streamlit entrypoint for `streamlit run app.py`.
- `pyproject.toml` - Authoritative package metadata, dependencies, and tool configuration.
- `requirements.txt` - Convenience install file that installs the local package editable.
- `src/media_library_viewer/app.py` - Thin Streamlit orchestration layer.
- `src/media_library_viewer/ui/` - Streamlit UI modules split by feature area (dashboard, media, file browser, library, preview/tools).
- `src/media_library_viewer/clients/jellyfin.py` - Jellyfin API wrapper.
- `src/media_library_viewer/clients/ssh.py` - SSH command execution, directory listing, `stat`, and `ffprobe` helpers.
- `src/media_library_viewer/domain/` - UI-independent normalization/domain helpers.
- `src/media_library_viewer/services/` - UI-independent application services such as the SQLite media index.
- `src/media_library_viewer/jobs.py` - Remote job template definitions and runner.
- `src/media_library_viewer/utils.py` - Formatting and media metadata summarization helpers.
- `src/media_library_viewer/config.py` - Environment variable and `.env` configuration loading.
- `docs/REQUIREMENTS.md` - Living requirements and decision log.
- `tests/` - Reserved for future test coverage.
## Key Implementation Decisions
- Prefer the Jellyfin API for library and server metadata.
- Prefer SSH plus `ffprobe` for disk-authoritative stream/container metadata.
- Use API-key auth for Jellyfin, but select a user explicitly for user-scoped endpoints.
- Use `streamlit-aggrid` as a required dependency for Media table row selection. Avoid optional frontend fallbacks that create multiple interaction models.
- Keep remote jobs template-based to reduce accidental destructive actions.
- Keep the Phase 1 UI compact and structured rather than using large per-row buttons.
- Use a `src/` package layout so the project can grow without accumulating many root-level modules.
- Keep root `app.py` as a compatibility/convenience wrapper for Streamlit.
- Keep clients, domain normalization, and application services independent from Streamlit so the frontend can later be replaced by React/FastAPI or another UI.
- Keep Streamlit rendering split into small UI modules so interaction bugs can be debugged in feature-local code instead of one monolithic app file.
## Security and Safety Requirements
- Do not hardcode secrets.
- Use `.env`, environment variables, or Streamlit secrets for credentials.
- Keep `.env` and Streamlit secrets out of version control.
- Reject unknown SSH host keys by default.
- Treat SSH jobs as potentially dangerous and keep them explicit/template-based.
- Add confirmation steps before implementing cleanup, delete, transcode-replace, or other destructive workflows.
## Configuration Requirements
Supported environment variables:
```bash
JELLYFIN_URL=
JELLYFIN_API_KEY=
JELLYFIN_USER_ID=
SSH_HOST=
SSH_USERNAME=
SSH_PORT=22
SSH_KEY_FILENAME=
SSH_PASSWORD=
REMOTE_MEDIA_ROOT=
REMOTE_PATH_PREFIX=
```
## Known External Requirements
Remote server should have:
- Linux `/proc` and `/sys/block` for resource metrics
- `/bin/sh` for POSIX command execution, even when the user's login shell is fish or another non-POSIX shell
- POSIX shell utilities including `awk`, `date`, `tail`, `df`, `kill`, and `nohup`
- `python3`
- GNU/coreutils-compatible `find` and `stat`
- `ffprobe` for media metadata inspection
Local app dependencies are declared in `pyproject.toml`; `requirements.txt` installs the package editable for convenience. Runtime dependencies include:
- `streamlit`
- `streamlit-aggrid`
- `requests`
- `paramiko`
- `python-dotenv`
- `pandas`
## Backlog / Future Extensions
- Add transcode job templates.
- Add cleanup job templates with dry-run and explicit confirmation.
- Add subtitle/audio-track diagnostics.
- Add sidecar file inspection for `.nfo`, `.srt`, images, and metadata files.
- Compare Jellyfin metadata against disk metadata and sidecars.
- Add long-running job tracking/log streaming.
- Add saved presets for common media roots and job templates.
- Add file previews for text sidecars.
- Add richer HDR/Dolby Vision/bit-depth summaries from `ffprobe`.
- Add optional integration with existing monitoring stacks such as Prometheus/node_exporter, Netdata, or sysstat/sar.
## Decision Log
### 2026-04-30 - Initial app plan
- Planned a Streamlit app that uses the Jellyfin API as the primary metadata source.
- Decided SSH should be used for disk inspection and future maintenance jobs.
### 2026-04-30 - Phase 1 implementation
- Created the initial app structure with Jellyfin, SSH, jobs, config, and utility modules.
- Added safe/read-only remote job templates.
- Added `ffprobe` and `stat` inspection.
### 2026-04-30 - Jellyfin API fixes
- Replaced `/Users/Me` usage with `GET /Users` plus user selection.
- Added `JELLYFIN_USER_ID` override.
- Cleaned Jellyfin `Fields` values to avoid 400 responses.
- Added defensive stripping of trailing `/web` from Jellyfin URLs.
### 2026-04-30 - Remote file browser evolution
- Added interactive remote listing.
- Removed emoji and hard-to-render characters.
- Added search, filtering, sorting, pagination, and compact listing summary.
- Reworked listing from large button rows into a compact table.
- Switched to `streamlit-aggrid` for file-browser-like row click behavior.
- Removed visible checkbox/selection column behavior.
- Added `[UP] ..` top row for parent directory navigation.
### 2026-04-30 - Selected-file metadata preview
- Added a requirement for automatic `ffprobe` preview when known video files are selected.
- Initially explored asynchronous/non-blocking preview, then changed to a blocking call with a spinner because it is more streamlined for this app.
- Decided to cache preview results briefly and provide a manual reload action.
- Decided `ffprobe` output should be separated into container, video, audio, and subtitle sections to avoid sparse mixed-stream tables.
### 2026-04-30 - Process requirement
- Added this living requirements and decision log document.
- Added a global agent skill to encourage maintaining such a document for future projects.
### 2026-04-30 - Repository restructuring
- Restructured the project into a larger-project-ready `src/media_library_viewer/` package layout.
- Kept a thin root `app.py` entrypoint so `streamlit run app.py` remains the primary launch command.
- Moved service clients into `src/media_library_viewer/clients/`.
- Added `pyproject.toml` with runtime dependencies, development extras, Ruff configuration, and pytest configuration.
- Simplified `requirements.txt` to install the local project editable.
- Expanded `.gitignore` for Python caches, build artifacts, virtual environments, local secrets, editor files, and logs.
### 2026-04-30 - Resource dashboard
- Added a dashboard requirement for CPU, RAM, network, disk I/O, and disk space overview.
- Decided that true last-hour metrics require collection over time; implemented a lightweight SSH-started remote collector instead of requiring Prometheus, Netdata, or sysstat.
- The collector stores JSONL samples in `/tmp` and can be started/stopped from the dashboard.
- Charts show the last hour of collected samples; the dashboard becomes more useful once the collector has been running for a while.
- Last-hour filtering uses epoch seconds rather than local naive datetimes to avoid timezone-offset issues between the app host and remote sample timestamps.
- Fixed SSH command execution to explicitly use `/bin/sh -c` so POSIX resource commands work even when the remote user's login shell is fish.
- Added explicit Streamlit keys to dashboard/file/tool buttons to avoid duplicate auto-generated element IDs as the UI grows.
- Changed the resource collector script from bash-specific syntax to POSIX `/bin/sh` syntax and added dashboard diagnostics/restart controls for collector troubleshooting.
- Added 7-day metrics file pruning plus a 70,000-line safety cap to prevent the JSONL file from growing without bound.
- Split network charts and metrics into download and upload, and disk charts and metrics into read and write.
- Changed network display units from bits per second to bytes per second to avoid Kbps/KB/s ambiguity; the collector still stores bit-rate compatibility fields for old/debug consumers.
- Scaled network and disk throughput charts into readable units such as KB/s, MB/s, and GB/s instead of plotting raw base units.
- Updated collector startup to remove old temporary metrics/log files when a new collector process is started after a schema/display change.
- Moved detailed resource charts, raw samples, diagnostics, and collector controls into a dedicated Monitoring tab; the Dashboard now keeps a compact overview.
- Removed the CPU/RAM chart from the Dashboard and kept detailed charts in the Monitoring tab.
- Renamed the remote files tab to File browser.
- Added Jellyfin media counts for movies, series, and episodes to the Dashboard using lightweight count queries.
- Added a Dashboard now-playing section sourced from Jellyfin sessions, showing who is currently playing what and whether each session is transcoding.
### 2026-04-30 - Media inventory tab
- Added a paginated Media tab for file-oriented Jellyfin metadata.
- Decided not to fetch all media at once because large libraries can make API responses and Streamlit rendering slow.
- Decided to derive length, size, bitrate, HDR flag, date added, codec, and resolution from Jellyfin metadata for now.
- Added series name, season, and episode number for episode rows.
- Added server-side sort/order controls and read-only AG Grid column sorting/filtering for the loaded page.
- Reworked the Media tab to use a local SQLite media index for full-library sorting/filtering, including numeric sorting for size and bitrate.
- Added last index build duration metadata to the Media tab status line.
- Replaced single-library selection with multi-library selection so users can include/exclude multiple libraries in the indexed table.
- Changed HDR display from blank/no-value to explicit yes/no.
- Added row selection plus an Open folder action in the Media tab that sets the File browser to the containing folder.
- Restored row-based Media table selection while keeping File browser state changes limited to the explicit Open folder button.
- Updated Media tab behavior so selecting a row automatically syncs the File browser folder to that item's containing directory; removed the extra Open folder button step.
- Restored File browser table row selection with AG Grid (single-select), using a table interaction style consistent with the Media tab.
- Reintroduced open-on-select behavior in File browser: selecting a directory row (including `[UP] ..`) opens it immediately, while file rows update selected target path.
- Refined Media table column presentation with explicit user-friendly headers and null-safe display formatting to keep the grid readable and consistent.
- Renamed Resources tab to Monitoring; added IO wait (iowait) percentage to the collector script, metrics, dashboard summary, and detailed charts.
- Removed the Jellyfin library poster-grid tab and its associated cached API calls and UI module; the Media index tab now covers library browsing needs.
- Simplified File browser navigation: removed Up/Go/Select folder buttons; pressing Enter in the path text input navigates directly.
- Added broad inline/module documentation across clients, domain, services, and Streamlit adapter modules to make debugging and future frontend extraction easier.
- Simplified the File browser by removing its interactive AG Grid and using a read-only listing with explicit Open/Select controls, reducing cross-tab state interactions with the Media grid.
- Removed optional/compatibility code paths around the Media table grid and old file-browser state aliases to keep the interaction model easier to reason about during debugging.
- Split the Streamlit frontend into dedicated UI modules (dashboard, media, file browser, library, preview/tools) and reduced `app.py` to orchestration glue.
- Reviewed remote path handling and kept shell interactions routed through quoted paths (`shlex.quote`) while UI/path-parent operations use POSIX path handling, preserving paths with spaces.
- Fixed file browser handoff/navigation to reset stale search/filter/page state when changing folders, preventing old filters from hiding all entries in the newly opened folder.
- Reworked file browser state to separate current directory from selected path. Selecting a file no longer changes the directory being listed, while opening a folder updates the current directory and keeps path input synchronized.
- Made remote directory listing fail explicitly when the current path is not a directory and recover by listing the parent, preventing file paths from appearing as empty directories.
- Made File browser Refresh/Select folder apply a manually typed path if it differs from the current folder, reducing confusion when manually navigating.
- Moved media normalization into `domain/media.py` and index/query logic into `services/media_index.py` to make the project less Streamlit-specific and easier to expose through a future API/React frontend.
- Deferred full ffprobe enrichment for every item to a future cached/background scan.
- Fixed network byte parsing to split `/proc/net/dev` lines at the colon first, so interface indentation differences do not shift fields and accidentally report packet counts instead of byte counts.
- Fixed a follow-up `/proc/net/dev` parsing issue where leading whitespace after the colon could produce an empty first split field in some `awk` implementations, resulting in zero network rates. Added `/proc/net/dev` snapshots to collector diagnostics.
- Simplified File browser directory error behavior: stopped automatic parent-directory fallback and now show the direct listing error for the current path.
- Added configurable `REMOTE_PATH_PREFIX` support so Jellyfin paths can be mapped to SSH-visible paths when opening folders from Media/Library tabs (for example `/media/...` -> `/srv/media/...`).
- Updated path handoff logic to prefer mapping through `REMOTE_MEDIA_ROOT` (anchor replacement using the root basename, e.g. `media`) and use `REMOTE_PATH_PREFIX` as fallback.
- Added a public-repo readiness note in README describing what local/sensitive files must stay out of version control.
- Added `LICENSE` (MIT) and `CONTRIBUTING.md` for public-repo baseline documentation.
- 2026-05-03: Reaffirmed that the Monitoring tab charts should be rendered directly with D3 and expose brush-based time-range selection plus moving averages.
- 2026-05-03: Added hover tooltips, summary chips, a moving vertical cursor, snapped point markers, and a selected-range label to the D3 Monitoring charts for faster visual inspection.
- 2026-05-03: Combined network download/upload into one traffic chart and disk read/write into one I/O chart for clearer Monitoring layout.
- 2026-05-03: Confirmed the shared session activity table should keep the session identifier as a caption under the user name instead of a full column.
- 2026-05-03: Confirmed the shared session table should keep the compact overall status summary line above the rows.
- 2026-05-03: Updated the dashboard monitoring cards to show 10-minute averages with high/low subtext instead of only the latest sample.
- 2026-05-03: Added OIDC/JWT auth support plus root-level Docker Compose deployment files for production and dev workflows.