# StreamHoard Reference Card (CT104, 10.48.200.104) **Replaces the old Jellyfin (VM112), MediaStack (VM103), and WireGuard exit container (CT110) — all three destroyed 2026-07-23.** Everything media-related now lives on one Debian 12 LXC (CT104, 8 cores/12GB RAM/60GB disk) on PVE1. ## What it is A self-hosted Netflix-style media app (repo `myronblair/kino-app`, private) branded **"StreamHoard"** in the actual UI — internal names (container names, some file paths) still literally say `kino`/`Kino`, that's cosmetic leftover from the original scaffold, not a second app. FastAPI + React + MongoDB. Deployed via Docker Compose in `/opt/kino` on CT104. Login: `admin@streamhoard.local` — password was rotated by the user via the app's own self-service "Change Password" feature (Settings page, requires current password) — not stored in this doc. ## Container stack | Container | Role | Port (host) | |---|---|---| | `streamhoard-frontend` | React UI, reverse-proxies `/api` to backend | 8080 | | `streamhoard-backend` | FastAPI — auth, catalog, streaming, transcode queue | internal 8001 | | `streamhoard-mongo` | MongoDB **pinned to 4.4** | internal | | `streamhoard-gluetun` | VPN gateway (NordVPN/WireGuard) for the whole *arr stack + qBittorrent | proxies 7878/8989/9696/8082 | | `streamhoard-radarr` / `-sonarr` / `-prowlarr` / `-qbittorrent` | Migrated **with existing config/library/history** from the old MediaStack VM — `network_mode: service:gluetun` so ALL their traffic is VPN'd, not just qBittorrent's | via gluetun | | `streamhoard-portainer` | Docker management GUI | 9443 (https) | **Why mongo:4.4:** PVE1's physical CPU lacks AVX, and mongo:7+ hard-requires it (SIGILL crash-loop without this pin). ## Media path - NAS (`10.48.200.249:/volume1/video`) is mounted **on PVE1 itself** (`/mnt/nas-video`, `/etc/fstab`, `_netdev` — persists across PVE1 reboots), then passed into CT104 as an **LXC mountpoint** (`mp0`, config lives in `/etc/pve/lxc/104.conf` — persists across CT104 reboots too). - **Why not mount NFS/CIFS directly inside CT104:** unprivileged LXCs can't reliably do NFS *or* CIFS client mounts even with the `mount=nfs;cifs` feature flag enabled — confirmed both fail with "Operation not permitted." This is a hard Proxmox/kernel-capability limitation, don't waste time retrying it — always go through the PVE1-host-mount + LXC-mountpoint pattern instead. - Radarr/Sonarr root folders: `/mnt/nas/video/movies`, `/mnt/nas/video/tv` (unchanged from the old MediaStack setup). StreamHoard's backend container bind-mounts the same NAS path **read-only at the identical absolute path** (`/mnt/nas/video:/mnt/nas/video:ro`) so Radarr/Sonarr-reported `file_path` values resolve with zero translation. - StreamHoard's own Radarr/Sonarr integration settings point at `http://gluetun:7878` / `http://gluetun:8989` (internal Docker network — gluetun's container shares its netns with radarr/sonarr, so other containers reach them via gluetun's hostname). - NAS export ACL (`/etc/exports` on the NAS) now only allows **PVE1 (10.48.200.90)** — cleaned up the old MediaStack-only entry after that VM was destroyed. ## Transcoding Almost the entire imported library needed re-encoding — most source files are HEVC/H.265 video and/or DTS/EAC3 audio, neither browser-native. This is what causes playback "jitter"/silent-audio, **not** a network/NAS bottleneck (NAS read tested at 122 MB/s). Fix: - Queued ABR transcode (re-encode to H.264/AAC multi-bitrate HLS) for the whole existing library via the transcode-queue API. - Fixed a real code gap: `sonarr_import` was missing the auto-transcode-on-import hook that `radarr_import` already had (both now call `_enqueue_transcode` when the global `auto_transcode` setting is on). That setting is now globally `"abr"`, so future imports auto-queue transcoding with no manual step. - If a newly-imported title plays choppy or silent, check its codec first (`ffprobe` inside `streamhoard-backend`) before assuming a server problem. ## MSP360 backup (weekly full / daily incremental to the NAS) - Installed via `dpkg-repack` from the working NovaCPX install (no public download URL for the Linux .deb worked — MSP360/CloudBerry doesn't publish a stable direct link). - **Real gap hit and fixed:** the package's config templates (`cloudBackup.conf`, `wt_config.xml`) are consumed/deleted from disk after a machine's first run, so `dpkg-repack`ing an already-initialized install captures a package that can never bootstrap its own runtime state (`/opt/local/Online Backup//config/...`) — the daemon just retries forever waiting for a file that will never appear. Fix: copy the *generated* versions of those two files from the source machine's live runtime directory (not the package) into the new install's equivalent path before first start. - Storage account: File System type, path `/mnt/nas-backups/MSPBackups` (same PVE1-mount + LXC-mountpoint pattern as the media NAS mount, separate mountpoint `mp1`). - Free license, self-registered via CLI: `cbbV2 license activate -f -e -c`. - Plan **StreamHoard-Backup**: daily incremental 2:00 AM, weekly full every Monday 2:00 AM, 4-week retention. Excludes: the huge media NAS mount, the backup destination itself, ephemeral Docker image layers (`overlay2`/`containers`/`image`/`buildkit`), and the regenerable HLS transcode cache (`/opt/kino/media/hls`, several GB, not "configuration"). - **A leftover worker process from a killed/deleted plan will silently block a new plan's worker** (near-zero CPU usage despite the job showing "Running" for many minutes is the tell) — `ps aux | grep cbbWorker` and kill any process referencing an old/deleted plan ID before assuming a new plan is broken. - CLI binary: `/opt/local/MSP360\ Backup/bin/cbb` (legacy, `plan -l`/`-r`/`-s -f`) and `cbbV2` (full plan create/edit/account management) — the space in the path needs quoting in every invocation. ## Portainer - `https://10.48.200.104:9443` — first-run setup token appears in `docker logs streamhoard-portainer`, and expires after ~5 minutes; restart the container to get a fresh one if it lapses. - Endpoint renamed "StreamHoard" for clarity; local Docker socket auto-detected, no extra config needed. ## JARVIS agent Installed and registered (hostname `streamhoard104`, `watch_services: ["docker"]`) — see jarvis.md Agent System section. Watch out for the container's actual OS hostname needing to match what you want reported (a typo here caused a duplicate stale agent registration once). ## Incidents hit during the 2026-07-23 migration (useful context if déjà vu happens elsewhere) - **Live duplicate-IP conflict**: an unidentified device (SSH banner `OpenSSH_8.4p1`, NFS shares `TimeMachineBackup`/`Public`/`iTunes` — looks like an old Buffalo-brand NAS) was actively answering ARP for `10.48.200.35` alongside the real MediaStack VM's MAC. Not stale cache — a live second device on the wire, never identified/removed. Worked around by moving MediaStack to a different IP mid-migration (moot now, VM destroyed) — `.35` is still squatted, avoid it. - That IP-reassignment **silently broke MediaStack's NordVPN kill-switch** (the `fwmark → routing table` `ip rule` entry vanished after the netplan change) — traffic briefly went out unprotected. Lesson: any NIC/netplan change on a host running NordVPN's Linux CLI app should be followed by `ip rule show`, not just `nordvpn status` — status can say "Connected" while the actual policy routing is gone. Fix: `nordvpn disconnect && nordvpn connect` reasserts the rule. - The NAS's own NFS export root (`/volume1/video` itself) has mode `000` due to Synology's `hide` export option — real root (PVE1) bypasses this via DAC-override, an unprivileged LXC's "root" cannot, causing `Permission denied` even though subfolders were readable. Fixed with `chmod 755` on the mount root + `chmod 777` on `movies`/`tv`/`downloads` specifically (also sidesteps a UID mismatch between the NAS's real file ownership and whatever PUID/PGID the new containers use). - gluetun needs `/dev/net/tun` explicitly passed into the LXC (`pct set 104 -dev0 /dev/net/tun`) or it fails immediately trying to create the WireGuard interface. ## Not yet done / open items - FortiGate VIP `tunnel-51820 / Wireguard-CT110` — **already cleaned up** (removed 2026-07-24 along with its dead firewall policy, confirmed Ref:0 before deletion). - The rogue device on `10.48.200.35` was never identified or removed from the network. - No reverse proxy/TLS in front of StreamHoard yet, and no LAN-only firewall rule (the app itself has no built-in rate limiting).