Files
Media-Server-Setup/STATUS.md

8.5 KiB

Vanguard stack — what runs, what people do, what's unfinished

Living document. Updated whenever something is deployed or changes behaviour. Last update: 2026-10-31. Host: ovh1.dumpstervigilantes.rip (OVH SYS-1, Debian 13). Bundle: ~/vanguard-stream on the box. Source of truth: this repo, techy/Media-Server-Setup on the stack's own Gitea (ssh://git@git.dumpstervigilantes.rip:2222/techy/Media-Server-Setup.git).

Runs on its own

Cadence What happens Done by
Continuous OBS streams record into a hidden streams/<user>/<Title>/.live/ folder, invisible to Jellyfin and copyparty until finished MediaMTX
Continuous Gamers' clip folders mirror onto the server (video files only, their structure untouched, receive-only) Syncthing
Continuous New recordings and clips are announced in a Discord channel with the streamer's name and a frame grab Jellyfin plugin (needs bot token + channel in its config)
Every 5 min Finished files are checked. Streams idle ≥3 min: lossless remux out of .live/ into the title folder as a flat, seekable MP4 (this is when they appear in Jellyfin/copyparty and the Discord notice fires). Non-compliant clips: converted to the editing standard, raw removed, one copy per clip normalize sweep (systemd timer)
Every 5 min Discord scheduled events snapshotted into a history so recordings can be matched to their session Jellyfin plugin
Every 30 min + startup Every non-admin Jellyfin user set to direct-play-only (no hardware encoder on this box) Jellyfin plugin
Daily 04:10 ET Recordings roll between weekday → week → month → year seasons as the calendar moves; no files change Jellyfin plugin
Daily 05:00 ET Dumped nights older than 3 days deleted for good trash purge (systemd timer)
On boot Everything restarts itself after the media mount is up; interrupted recordings stay playable systemd + Docker
Continuous Active streams are watchable live (WebRTC, HLS fallback) by Discord-server members; optional "is live" Discord post when a stream starts MediaMTX + live portal (page deployed; MediaMTX port fix pending apply)
Not yet running Pull of files older than 15 days to the home HDD home/rclone-archive.sh, needs the home box
Not yet running Nightly backup of the Authentik database (Postgres) planned

What people do

Who Activity Where Login
Streamer Type a title, Go Live, Stop. "Dump tonight" if it was junk (3-day undo) OBS browser dock https://ovh1…/dock/ stream password in dock settings
Gamer Nothing; clips save where they always did their clipper + Syncthing, paired once —
Editor Browse by streamer/title, scrub in browser, download originals, upload finals copyparty https://ovh1…/ local account (to move to Discord)
Editor Mount the library as a drive WebDAV, same URL same
Anyone in the Discord server Watch recordings and clips by streamer and season Jellyfin https://watch.…/ Discord (Authentik)
Anyone in the Discord server Watch live: all active streams in dockable windows, audio follows focus, per-window mute/volume Live portal https://live.…/ Discord (Authentik, domain-level) — live, first real stream untested
Streamer Go Live from the browser: pick the WarDogs window (+audio), streams as <user>/HelmetCam-<date>; no OBS needed Live portal → Go Live Discord; key fetched for the logged-in user — awaiting MediaMTX port fix
Devs Host code, review, CI Gitea https://git.…/, ssh 2222 local account (Discord later)
Bot dev Push code, restart container, read logs; web UI at https://bot.…/ Gitea + compose; bot/AGENT.md web UI behind Discord (Authentik), once its provider exists
Admin Add/remove streamers & editors, hostnames, bot token ./configure.py ssh
Admin Pair a gamer's device, approve their folder Syncthing UI https://ovh1…/sync/ Syncthing GUI password
Admin Groups, who may log in, app access Authentik https://auth.…/ Discord or akadmin
Admin Install/upgrade the Jellyfin plugin ./installJellyfinPlugin.sh ssh
Admin Register the CI runner ./registerGiteaRunner.sh ssh

Services and their state

Service Role State
MediaMTX RTMP/SRT ingest, records fMP4 live
copyparty browse/scrub/download/WebDAV/finals upload live, local accounts
dumpd + dock dump-tonight endpoint, OBS dock page live, own password file
Syncthing clip intake, UI at /sync/ live, Syncthing GUI login
normalize sweep editing standard, one copy per clip live
nftables host firewall (22, 80/443, 1935, 2222, 3306, 8890/udp, 22000) live
Caddy TLS + routing for all hostnames live
Authentik (+Postgres, Redis) Discord login, groups, guild-membership policy live
Jellyfin watch page, SSO via Authentik, direct play only live
Vanguard Metadata plugin seasons by tag, Discord names/avatars/events, feed, playback policy 0.1.0 live, 0.1.2 built (policy task, "new recording" feed wording) — install pending
MariaDB databases for the bot and Gitea; public :3306, root local-only live
Gitea git hosting on MariaDB, Actions enabled; holds this repo live, local admin
act_runner CI jobs in Docker not registered
discord-bot Python runtime, idles until bot/app/main.py exists live, no code yet
bot. host bot web UI behind Authentik forward-auth; Caddy requires group bot-users (from a Discord role) Caddy block live; bot CNAME still points at the wrong IP; roles→groups policy pending
Live portal (live/, live. host) React + dockview multi-stream viewer + browser Go Live (WHIP); MediaMTX HLS/WebRTC/API on loopback; dumpd /me/stream-key page live behind Authentik (domain-level); MediaMTX recreate with new ports pending

Unfinished, in rough priority

  1. Recreate MediaMTX with the corrected compose (loopback HLS/WebRTC/API ports) → portal list + Go Live work. 1b. bot CNAME → ovh1; Authentik discord-roles-to-groups policy + bot-users group. 1c. Confirm plugin 0.1.2 installed and runner registered (were in the same deploy block).
  2. Register the runner (./registerGiteaRunner.sh).
  3. bot CNAME + Authentik proxy provider — first use of forward-auth; proves the pattern for copyparty.
  4. copyparty behind Authentik (group permissions, WebDAV via app passwords, then drop local accounts). The one change that can lock editors out; keep a local admin until a Discord editor has used the new path.
  5. Syncthing UI + dump behind Authentik; dump reads the identity header; dock fetches the caller's own stream key.
  6. Gitea login via Authentik (OIDC source, auto-register, admins → Gitea admin).
  7. Housekeeping: pin AUTHENTIK_TAG in .env, rotate the Discord client secret that was in a screenshot, nightly Postgres dump, Jellyfin login branding (Discord button first), check a non-admin Discord user's Jellyfin view.
  8. Medal filename pattern in the plugin's timestamp parser — need one sample filename.
  9. Home archive puller on the home box.
  10. Plugin repository served by Caddy so Jellyfin updates the plugin from its catalog.
  11. Clip submissions (Medal/Twitch/YouTube links, Discord uploads) — belongs to the bot.
  12. SYS-3 when transcoding or sweep speed hurts: Quick Sync, flip the plugin's policy toggle off.

Verified behaviours worth remembering

  • Never scp generated configs (mediamtx.yml, copyparty.conf, Caddyfile, dump-users.txt, .env): a template copy wiped all streamers once. They are gitignored now; *.example.* are reference only.
  • Caddy runs in a container: anything it serves from disk must be a mount (/srv/live was missing → 404).
  • MediaMTX 1.21 refuses to start without webrtcAdditionalHosts or webrtcIPsFromInterfaces; configure.py writes the public host in.
  • Jellyfin honours provider-supplied season numbers on a Shows library (day seasons appeared). Next check: Monday 04:10 ET should fold them into a week season.
  • A stream indexed by Jellyfin while still recording has no duration and skips to the next episode on seek — fixed by recording into .live/ so nothing sees a file until it is finished and flat.
  • Sweep leaves compliant Syncthing files untouched and keeps exactly one copy of converted ones (tested).
  • Firewall: Docker-published ports bypass the input chain; MariaDB uses host networking precisely so the rules apply.
  • Never systemctl stop|restart nftables (flushes Docker's NAT). Reload only.