Files
ai-context/streamhoard.md
T
Myron Blair 4fad312f87 Full rewrite for current infra state — StreamHoard consolidation, VoIP split out, dead references purged
- Replace all Jellyfin/MediaStack/WireGuard-CT110 references (destroyed 2026-07-23) with
  StreamHoard/CT104 throughout; add streamhoard.md covering the full consolidated stack,
  MSP360 backup setup, and migration incidents
- Split FusionPBX/SignalWire/landline content into its own voip.md (was scattered across
  jarvis.md and sites.md, and referenced the old destroyed DO droplet IP)
- Fix stale facts: JARVIS webhook port (:1972 -> 80/https), rotated registration key/GitHub
  PAT, FortiGate admin port (9443 not 443), current WAN-IP-drift/VIP gotchas
- Add reference.md for repo inventory notes, assistant hard limits, ZeroTraceGPT IQ, payments
  migration status
- Update gotchas.md with unprivileged-LXC mount limitations and MSP360 dpkg-repack gotcha
2026-07-25 22:47:31 -05:00

8.5 KiB

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-repacking an already-initialized install captures a package that can never bootstrap its own runtime state (/opt/local/Online Backup/<machine-guid>/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 <email> -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-CT110already 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).