Files
ai-context/streamhoard.md
T

7.2 KiB

StreamHoard Reference Card (CT104, 10.48.200.104)

What it is

Self-hosted Netflix-style media app — repo myronblair/kino-app (private), branded "StreamHoard" in the UI (internal names/container names still say kino, cosmetic only). FastAPI + React + MongoDB, Docker Compose in /opt/kino on CT104 (Debian 12 LXC on PVE1, 8 cores/12GB RAM/500GB disk).

Login: admin@streamhoard.local — password set via the app's own self-service "Change Password" (Settings page), not stored in this doc. App version shown in the navbar (GET /api/version, source of truth is backend/VERSION in the repo).

Container stack

Container Role Port (host)
streamhoard-frontend React UI, reverse-proxies /api to backend 8080
streamhoard-backend FastAPI — auth, catalog, streaming, transcode queue, library scan/cleanup internal 8001
streamhoard-mongo MongoDB pinned to 4.4 (PVE1's CPU lacks AVX, mongo:7+ SIGILL-crashes without this pin) internal
streamhoard-gluetun VPN gateway (NordVPN/WireGuard), real kill switch — iptables default policy DROP, only tun0/LAN/VPN-handshake traffic allowed out proxies 7878/8989/9696/8082
streamhoard-radarr / -sonarr / -prowlarr / -qbittorrent network_mode: service:gluetun — share gluetun's netns, so kill switch covers all their traffic, not just torrent via gluetun
streamhoard-portainer Docker management GUI (https://10.48.200.104:9443) 9443

Media/music mounts

  • NAS (10.48.200.249) mounts happen on PVE1 itself, then pass into CT104 as LXC mountpoints (/etc/pve/lxc/104.conf) — unprivileged LXCs can't do NFS/CIFS client mounts directly.
  • mp0: NFS /volume1/video/mnt/nas-video (PVE1) → /mnt/nas/video (CT104). Radarr/Sonarr root folders /mnt/nas/video/movies, /mnt/nas/video/tv.
  • mp1: CIFS Storage share → /mnt/nas-backups — MSP360 backup destination.
  • mp2: CIFS music share (no NFS export exists for it) → /mnt/nas-music (PVE1, read-only) → /mnt/nas/music (CT104, read-only). Real content is under Music/<Artist>/<Album>/<Track>; sibling folders (Apple Music, Automatically Add to Apple Music, Downloads-Music) are Music.app-internal and deliberately not scanned — may contain DRM'd Apple Music subscription tracks, which no third-party player can decrypt/play.
  • Backend container bind-mounts /mnt/nas/video and /mnt/nas/music read-only at their identical absolute paths, so Radarr/Sonarr/Library-Scan-reported file paths resolve with zero translation.

Library Scan (admin, separate from Radarr/Sonarr)

Walks the NAS mounts directly for movies/TV/music not already in the library — storage_type="scan" on the resulting Movie/Episode/Track record, fully integrated (streamable, transcodable, listed in Admin) same as Radarr/Sonarr imports. Select All / Deselect All per tab. Movie/TV scans are fast (filesystem stat only); music scan defers tag reads to import time (reading ID3/MP4 tags during the scan itself was the original bottleneck).

Music library

Track model (title/artist/album/track_number/duration/album_art_url), streamed via ranged /api/stream/track/{id}. Persistent bottom player (queue, seek, output-device picker via the Audio Output Devices API for already-paired Bluetooth speakers — Chrome/Edge only, browsers can't pair new BT devices). Album art: MusicBrainz + Cover Art Archive lookup (free, no key, 1 req/sec rate limit respected), triggered from Library Scan's Music tab; falls back to a branded placeholder (not a bare icon) until art is set. NAS mount is read-only, so Apple Music/iTunes can keep managing the same files independently.

Library Cleanup (admin)

Background job (POST .../scan/start, GET .../status, POST .../stop — has a real Stop button, not just a spinner) checking for: missing files, orphaned episodes/HLS output, stuck transcode jobs (running 2h+ with no completion), duplicate imports, orphaned subtitles, dangling watchlist/progress rows. Nothing deletes until you click Fix. File-existence checks run in a small thread pool off the event loop — an earlier version did this synchronously and froze the entire server for as long as the scan took (a plain GET /api/movies would hang); confirmed fixed, don't reintroduce blocking filesystem calls in an async endpoint here.

Transcode queue

One job at a time, nice -n 10 (was 19), threads divided across however many ABR variants a job has so the total stays near a fixed budget instead of each variant claiming the full count independently. Auto-transcode setting (off/quick/abr, in Settings) auto-queues new imports.

Three quality modes:

  • quick — stream-copy both streams (fast, no quality loss, but doesn't fix incompatible audio).
  • abr — full re-encode to multi-bitrate H.264/AAC HLS (slow — 4 variants, only way to fix HEVC/x265 video).
  • audio — video stream-copied untouched, only audio re-encoded to AAC (fast — no video re-encoding). Fixes the single most common playback problem: BDRip/AVI sources very often carry AC3/DTS/EAC3 audio, which no browser can decode natively — browsers respond by hiding the volume control entirely, not by erroring. POST /transcode/queue/fix-audio-all cancels the pending backlog and replaces it with the fast audio-only fix library-wide; run a real ABR pass later for actual multi-bitrate quality once there's no urgency.

Retry (/transcode/queue/{id}/retry) and bulk retry-failed never delete the movie/episode — only the job history and hls_status flag change.

Requests → Radarr/Sonarr

Pending user requests can be Approved (Movie or TV) by an admin: looks up the title via Radarr/Sonarr's own lookup API, adds the best match with search-on-add enabled. Atomically claims the request (pending → approving → approved) so a double-click or two admins can't both add the same content; releases the claim back to pending on failure so it can be retried. Live status line (searching/queued/downloading/downloaded) reads from Radarr/Sonarr's own queue, polled every 15s — real data relayed through Radarr's own refresh cycle, not a direct qBittorrent connection.

MSP360 backup

Weekly full / daily incremental to the NAS (/mnt/nas-backups/MSPBackups, plan StreamHoard-Backup). Installed via dpkg-repack from the NovaCPX install (no public Linux .deb download exists). Excludes the media/music NAS mounts, the backup destination itself, Docker image layers, and the regenerable HLS transcode cache.

JARVIS agent

Registered as streamhoard104, watch_services: ["docker"] — watches the Docker daemon only, not individual containers. A /usr/local/bin/streamhoard-watchdog.sh cron (every 5 min) separately restarts any StreamHoard container that isn't running, since Docker's own restart: unless-stopped policy doesn't always trigger reliably.

Repo has a NOTICE file ("Property of TomTom Enterprises."). Commits to this repo omit AI-authorship trailers.

Open items

  • No reverse proxy/TLS in front of StreamHoard, no LAN-only firewall rule.
  • Large transcode backlog from initial library import — being worked through via the fast audio-fix pass; a full ABR pass for real adaptive-bitrate quality is still pending for most of the library.