Commit Graph

45 Commits

Author SHA1 Message Date
223458550a Bot hosting: releases are self-contained CI bundles, and the bot serves its own web UI
The image no longer installs the bot's dependencies: its CI builds one tar.gz holding the app plus a
vendor/ directory of prebuilt wheels, and PYTHONPATH=/app/vendor makes the code import them. A new
dependency therefore needs no image rebuild and the server contacts no package index.

- deployBotBundle.sh: install / --latest / --status / --rollback, verifies every file against BUILD.json
  and keeps the previous release beside the live one.
- compose: mount ${BOT_APP_DIR:-/srv/bot/app} at /app, set DB_BACKEND, DATA_DIR, WEB_ALLOWED_GROUPS,
  WEB_SERVE_UI and PYTHONPATH. bot/app placeholder untracked; install.sh creates /srv/bot/app.
- The placeholder requirements would have crashed the bot on import (no sqlglot), which is why the
  image now ships none of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-02 21:18:15 -06:00
855651c5a4 Metrics taps: node_exporter + cAdvisor containers, MediaMTX and Caddy metrics on loopback (Prometheus format, store-agnostic)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 19:55:42 -06:00
704bc8c4d3 Logging framework: logfmt events in our scripts, container labels + rotated json-file logs, Caddy JSON access logs; LOGGING.md
Store-agnostic: journald + Docker logs are the only taps any collector (Alloy/Loki, VictoriaLogs, Netdata) needs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 19:53:05 -06:00
f32730ba94 Clips folders: create an empty .stlocal with the folder (Syncthing refuses to scan while the #include target is missing)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 19:08:58 -06:00
645f447015 Live portal: custom layouts are left alone — new streams chime and wait in a draggable tray; Arrange re-grids and returns to auto
layout store (auto/custom + pending), marked custom on manual move (onDidMovePanel outside arrange), sash drag,
stream/group rename. Tray chips: HTML5 drag accepted via onUnhandledDragOver/onDidDrop (position -> direction),
click = new column. WebAudio two-note chime. Gateway page instructions updated. Verified headless (custom vs auto).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 14:36:01 -06:00
d5b2ba502b Live portal: rename streams (tab double-click, persisted per browser) and name tab groups (header slot, session)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 14:12:21 -06:00
9bdf9f1947 WebRTC audio mirror: AAC cannot cross WebRTC, so transcode audio to Opus into <path>.webrtc
bin/mtx-on-ready.sh (runOnReady inside the mediamtx container) posts the is-live notice and, for sources whose
audio is not Opus, runs ffmpeg: video copy, audio -> Opus, published over MediaMTX's loopback RTSP to
<user>/<Title>.webrtc (record: no). dumpd accepts .webrtc publishes only from 127.0.0.1. The portal hides
mirror paths, plays the mirror over WHEP (HLS stays on the source), holds an AAC source for 15 s while its
mirror comes up, and corrects the viewer count. Verified headless with a mocked path list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 13:58:15 -06:00
3432be691f Live portal: auto-arrange panels as an even grid on join/leave, Arrange button
Moves existing panels (video keeps playing) into cols=ceil(sqrt(n)) row-major and equalizes group sizes.
Verified headless for 3-6 streams.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 13:25:05 -06:00
68bb0e66d5 Live portal: clip the timecode band with a measured game-aspect frame
CSS aspect-ratio on a flex:1 child was ignored, so object-fit:cover cropped arbitrarily. Player now
measures the panel (ResizeObserver: fires on every dockview resize/move), fits a frame of the game aspect,
lays the video across its width and hides the overflow at the bottom.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 13:14:25 -06:00
0d537ccb65 STATUS: hostname restructure deployed, band removal live
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 13:09:36 -06:00
2cd936beaf Sweep: crop the timecode band off published streams (re-encode after decode); vgdeband.sh catch-up
The published recording is the game picture + audio + tmcd track, nothing else. Streams without the
band are still remuxed losslessly. vgstrip plan exports TC_BAND and marks the sidecar band_removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 13:05:35 -06:00
bb09565388 Fix README section cross-reference (5e)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 01:29:33 -06:00
9d67a240d9 Runbook F5b + STATUS: nested-groups scope mapping, verified
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 01:29:14 -06:00
18f4e07de8 Split hosts: media (gateway at /), files, clips, sync; live gains /dock, /dock/clock, /dump, /me, /time
configure.py: three new host roles (files/sync/clips) in --full, --hosts, .env, Caddy generator;
main host serves about/ at the root (/about* redirects); /time only where it is fetched same-origin.
about/index.html links across hosts; addCommunityMediaServer.sh asks for the new names; Caddyfile.example
regenerated; README/FRESH-INSTALL/HOSTNAME-CHANGE (procedure C)/STATUS updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 01:38:35 -05:00
66f05cf9a7 Push-to-deploy (OFF by default): vanguard-deploy applies only changed parts with a live-stream guard for MediaMTX, rollback, pending timer; dumpd /deploy webhook gated by DEPLOY_AUTO; configure --apply; switchToClone.sh; draft CI workflow; committed plugin zip
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 01:08:43 -05:00
07ffae0746 Soft per-user clip quotas: dumpd pauses/resumes Syncthing folders by folder size (CLIPS_QUOTA_GB, per-user overrides file); clips page shows usage
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 18:30:22 -05:00
63ba628fad STATUS: /about and /clips live
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 18:02:48 -05:00
48409b5dbf Self-service clip intake (/clips + dumpd /me/clips via Syncthing REST) and role-aware gateway page (/about + dumpd /me)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 18:00:01 -05:00
68c2866a28 Syncthing: GUI auth disabled behind the Authentik gate; docs
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 17:49:17 -05:00
446fd32c81 STATUS: MediaMTX restarted (microsecond names live); roles policy to be proven by natural logins
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 17:45:31 -05:00
97d1ba97a9 STATUS/plan: phase 10 Gitea via Authentik verified
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 17:37:28 -05:00
b661ee795b Phase 10: Gitea OIDC login prerequisites in compose (external-only registration, auto-register, account linking); README steps
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 17:09:33 -05:00
6db786e00e Phase 12 verified live; MediaMTX config changes documented as restart-only on this setup
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 16:54:08 -05:00
f1d19ba32d Plan: off-site gap filling option (home NVENC / Spot GPU) and SYS-3 availability note
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 16:26:44 -05:00
9dc81ae5aa README/STATUS: gap filling availability and the on/off commands
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 16:25:08 -05:00
f36487d4b5 Timecode: nominal fps + proper drop-frame detection, shell-safe plan output; streams never VFR-re-encoded; gap list in the sidecar; docs
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 16:17:26 -05:00
3fcb8fbe99 Phase 12: frame-accurate timecode via an overscan band — vgstrip encoder/decoder, sweep writes tmcd + creation_time + sidecar, dumpd /time, Caddy /time routes, MediaMTX microsecond names, /dock/clock/ page, dock band setup via obs-websocket, Go Live band composite + toggle, portal crops the band
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 16:07:56 -05:00
a7ad3dc224 STATUS/plan: phases 5-7 and 11 live and verified; refreshed unfinished list
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 15:48:33 -05:00
5085f8ab55 Docs/apply note: MediaMTX does not hot-reload auth settings; restart between streams after auth changes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 15:46:25 -05:00
dcc347e1ea Phase 7+11: token-based publishing — MediaMTX authMethod http -> dumpd /mtx-auth (bridge-only port 8081); dumpd mints 12 h HMAC tokens for Discord-authenticated streamers; dock rewritten (no passwords, naming rules with live preview, prefix/suffix, manual-setup accordion with hidden token); portal uses tokens; configure.py drops streamers; MediaMTX hot-reload, Caddy reload — no stream interruption
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 15:22:15 -05:00
15402eeb82 Discord-only login: drop WebDAV/app-password path from docs and generated config comments
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 15:00:48 -05:00
50c037e514 Phase 5+6: main host behind Authentik — copyparty trusts X-Authentik-* headers with group permissions (editors, media-admins), dumpd authorizes from headers (admins/media-admins), /sync gated to media-admins, dock drops its dump password; configure.py loses editors/admin
WebDAV continues via Authentik app passwords (provider: Intercept header authentication).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 14:55:48 -05:00
76a7d06df4 Go Live: state the Windows-only quality/audio reality in the form; STATUS note
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 13:37:21 -05:00
e02accb855 Streams published flat as <user>/<Title>__<ts>.mp4 so each recording is its own Jellyfin episode; plugin 0.1.3 parses the title from the name
Jellyfin merges several videos in one non-season folder into a single episode with versions,
which collapsed a night of helmet cams into one entry. Recording still lands in <Title>/.live/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 03:42:53 -05:00
f737fc6289 Go Live: state the Windows-only window-audio limitation in the UI and docs
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 01:47:45 -05:00
e3fb5db424 STATUS: Go Live verified end to end; remaining items
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 01:22:14 -05:00
f17e57db63 MediaMTX container TZ=America/New_York so recording filenames match the server clock
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 01:18:54 -05:00
f06ebc1ec1 Plan: phase 11 — MediaMTX auth via Authentik (JWT for the browser, app passwords for OBS, no stream keys)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 00:58:50 -05:00
dff31b0b37 Docs audit: phase-1 script prints domain-level forward-auth + roles sync; install.sh creates /srv/live; STATUS current
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 00:55:33 -05:00
384a7ed552 Per-host group gating in Caddy from X-Authentik-Groups; Discord roles -> Authentik groups policy; domain-level forward auth documented
BOT_REQUIRE_GROUP / LIVE_REQUIRE_GROUP in .env drive a header_regexp matcher after forward_auth
(403 before the app). authentik/discord-roles-to-groups.py syncs Discord roles into groups at
every login so access follows Discord roles without manual edits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-30 23:22:20 -05:00
82df0b1583 Live portal: Go Live — browser window capture published over WHIP as <user>/HelmetCam-<date>; dumpd /me/stream-key
Go Live closes viewer windows, fetches the Authentik-identified user's MediaMTX key from dumpd
(/etc/vanguard/streamers.json written by configure.py), opens Chrome's window-only picker with
audio, prefers H.264 so MediaMTX can record, caps at 12 Mbps / 1440p60, shows live stats, Stop.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-30 23:04:33 -05:00
463d9b2385 Live portal: React + dockview multi-stream viewer behind Authentik; MediaMTX HLS/WebRTC/API re-enabled; is-live Discord hook
live/: every active stream opens as a dockable window (WHEP, HLS fallback), audio follows the
focused window, per-window mute/volume, pinning. Caddy live.HOST block with forward-auth proxies
/api /webrtc /hls to MediaMTX on loopback and serves /srv/live. WebRTC media on 8189 (firewall).
configure.py asks the live hostname and an optional Discord webhook; MediaMTX -ffmpeg image runs
the runOnReady hook. deployLive.sh builds and ships dist/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-30 23:01:11 -05:00
3d42271ae2 Record streams into hidden .live/ and publish finished files via the sweep; plugin 0.1.2 feed wording
Jellyfin indexed in-progress fragmented recordings (no duration -> seek jumped to next episode).
MediaMTX now writes under <Title>/.live/; the sweep remuxes finished files into the title folder
and removes the source. Feed says 'New recording' since it fires after publish.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-30 22:45:30 -05:00
6fb0f6da30 STATUS: repo now hosted on the stack's Gitea
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-30 15:56:44 -05:00
c602a6ff52 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>
2026-10-30 15:43:30 -05:00