Vanguard media stack: ingest, copyparty, Syncthing, normalize sweep, Authentik, Jellyfin + Vanguard Metadata plugin, MariaDB, Gitea, bot runtime

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 15:43:30 -05:00
commit c602a6ff52
66 changed files with 4229 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
# Vanguard Metadata — Jellyfin plugin
Metadata provider for gameplay recordings. Each streamer/submitter folder is a show; season and
episode numbers come from the recording timestamp under a rolling policy; names, avatars and episode
titles come from Discord via Authentik's identity link; new recordings and clips are announced in a
Discord channel.
## Layout
- `Jellyfin.Plugin.Vanguard.Core/` — pure logic: `SeasonPolicy` (rolling buckets, naming, overrides),
`ItemTimestamp` (sidecar → filename patterns → mtime). No Jellyfin dependency. Unit-tested.
- `Jellyfin.Plugin.Vanguard/` — the plugin: providers (Series, Series images, Season, Episode),
services (Discord REST, Authentik REST, event store, identity resolver, feed), scheduled tasks
(Discord snapshot every 5 min; season roll nightly after the rollover hour), config page.
- `Jellyfin.Plugin.Vanguard.Tests/` — xunit.
## Season scheme
| age of item | season # | season name |
|---|---|---|
| current ISO week | `YYYYMMDD` | "Thursday, Oct 29" |
| current month | `1YYYYWW` | "Week 44" |
| current year | `YYYYMM` | "October 2026" |
| older | `YYYY` | "2026" |
Episode # = `MMDDHHMMSS` of the local timestamp — never changes when the season rolls.
Broadcast day rolls at the configured hour (default 04:00). Overrides: `seasonNumber=Name`, one per line.
## Timestamp sources, in order
1. `<name>.json` / `<name>.clip.json` sidecar with `"timestamp"` (UTC ISO-8601) — written by the clip bot
2. filename: MediaMTX `2026-10-29_21-03-11`, Game Bar `2026-10-29 21-03-11`,
ShadowPlay `2026.10.29 - 21.03.11`, Steam `20261029_210311`
3. file modification time (Syncthing preserves the gamer's mtime)
## Build / package
./package.sh # tests + Release build → dist/VanguardMetadata_<ver>.zip + manifest.json
Requires .NET 10 SDK. Bump `version` in `build.yaml` for a release.
## Install on the server (manual, v0.1)
scp dist/VanguardMetadata_0.1.0.0.zip debian@HOST:/tmp/
ssh debian@HOST 'sudo -u "#1000" mkdir -p "/srv/appdata/jellyfin/config/plugins/Vanguard Metadata_0.1.0.0" && sudo -u "#1000" python3 -m zipfile -e /tmp/VanguardMetadata_0.1.0.0.zip "/srv/appdata/jellyfin/config/plugins/Vanguard Metadata_0.1.0.0/" && cd ~/vanguard-stream && docker compose restart jellyfin'
Later: serve `dist/` at `https://HOST/plugins/` (Caddy) and add it as a repository in Jellyfin so updates
come through the Catalog.
## Runs with an empty config
Seasons, episode numbers, timestamps and the nightly roll need nothing external. Without a Discord bot
token the show is named after its folder, has no poster, and episode titles are the dock title; without
Authentik no folder→Discord mapping is attempted; without a feed channel nothing is posted. Add the
credentials later and re-run "roll seasons" / refresh the libraries to pick up names and avatars.
## Configure (Dashboard → Plugins → Vanguard Metadata)
- Discord: bot token (add a **Bot** to the existing Authentik Discord application; enable the Server
Members intent; invite it to the server), guild ID, feed channel ID, public Jellyfin URL.
- Authentik: URL + API token of a read-only service account (used to map folder → Discord user).
- Folder → identity overrides if a streamer's folder name isn't their Authentik username.
- Libraries (Dashboard → Libraries → each of streams/clips): content type Shows; metadata downloaders:
**only** "Vanguard Metadata" for Series, Season and Episode; image fetchers: Vanguard Metadata (Series)
and **Screen Grabber** (Episode) — the grab is what the Discord embed attaches.
## Tasks (Dashboard → Scheduled Tasks → Vanguard)
- "snapshot Discord events": every 5 min + startup. Run it once after configuring.
- "roll seasons": daily at rollover+10 min. Run it once after the first library scan.