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>
This commit is contained in:
2026-11-01 01:38:35 -05:00
parent c1a46d66dc
commit 18f4e07de8
9 changed files with 418 additions and 181 deletions

View File

@@ -2,6 +2,21 @@
> **FRESH-INSTALL.md** = the ordered walkthrough from an empty box to a working stack and pipeline, manual steps included.
> **HOSTNAME-CHANGE.md** = every place a hostname lives and how to rename a subdomain or move to a new base domain safely.
**Hostnames** (all CNAMEs to the main host; one Discord login covers all of them):
| Host | What | Who |
|---|---|---|
| `media.BASE` (main) | gateway page at `/` (role-aware links + how-tos), `/whoami`, `/logout` | everyone |
| `files.BASE` | copyparty: browse, scrub, download, upload finals | editors / media-admins |
| `clips.BASE` | self-service clip intake (pair your Syncthing) | any member |
| `sync.BASE` | Syncthing admin UI | media-admins |
| `live.BASE` | live portal, Go Live, OBS dock `/dock/`, clock source `/dock/clock/`, `/dump`, `/me`, `/time` | everyone (`LIVE_REQUIRE_GROUP`) |
| `watch.BASE` | Jellyfin | Discord via OIDC |
| `git.BASE`, `bot.BASE`, `auth.BASE` | Gitea, bot web UI (`bot-users`), Authentik | — |
`/time` (the server clock the timecode strip syncs to) lives only on `live` because browsers fetch it
same-origin: both pages that use it, the clock source and Go Live, are on that host.
> **STATUS.md** = what runs, who does what, what's unfinished (kept current). **CommunityDiscordMedia.md** = design + phases.
Ingest from OBS, browse/scrub/download for editors, automatic normalization to one editing
@@ -82,7 +97,7 @@ Everything else is dropped and a trickle is logged (`journalctl -k | grep vg-dro
Jellyfin (8096), copyparty, the Syncthing UI and dumpd bind to 127.0.0.1 only and are reached via Caddy
or an ssh tunnel, so they are deliberately not opened. MariaDB runs on the host network (not a Docker
published port) precisely so the firewall can govern it.
Syncthing's admin UI is proxied at `https://HOST/sync/` behind the Authentik gate + group `media-admins`; its
Syncthing's admin UI is proxied at `https://sync.BASE/` behind the Authentik gate + group `media-admins`; its
own GUI login is disabled (no user/password, `insecureAdminAccess=true`) so Discord is the only login:
docker exec syncthing syncthing cli --home /var/syncthing/config config gui insecure-admin-access set true
@@ -103,7 +118,7 @@ There are no stream passwords or keys to hand out: the dock mints a **12-hour pu
logged-in Discord user each time Go Live is pressed and sets it as the OBS stream key itself.
OBS → Tools → WebSocket Server Settings → enable, set a password.
OBS → Docks → Custom Browser Docks → `https://media.example.com/dock/` → log in with Discord once.
OBS → Docks → Custom Browser Docks → `https://live.BASE/dock/` → log in with Discord once.
In the dock's Settings: ingest host (prefilled), OBS WebSocket password/port, optional title prefix/suffix
(`{date}` → YYYY-MM-DD, e.g. prefix `HelmetCam-` or suffix `-{date}`).
Per stream: type a title, click **Go Live**. The dock shows the final stream name (`<you>/<Title>`) as you type
@@ -120,7 +135,7 @@ Browser streaming without OBS: the live portal's **Go Live** (Windows Chrome 141
## 5a. Onboarding a gamer's clips (Syncthing) — self-service
Gamers do it themselves at **https://HOST/clips/** (Discord login): the page shows our Syncthing Device ID and
Gamers do it themselves at **https://clips.BASE/** (Discord login): the page shows our Syncthing Device ID and
asks for theirs; on submit, dumpd creates `clips/<username>` with the video-only `.stignore`, adds their device
to our Syncthing (REST API, key read from `/srv/media/.syncthing/config.xml`) and creates the receive-only
folder `clips-<username>` shared with them (min free 300 GB). They accept the device + folder on their PC,
@@ -136,16 +151,16 @@ Manual fallback (admin, same result for the server folder):
./addClipUser.sh <username> # creates /srv/media/clips/<username> with the video-only .stignore
Their side (Windows: Syncthing installer or SyncTrayzor; Mac/Linux: Syncthing): open the UI, **Actions → Show ID**,
send you the Device ID. In their UI: **Add Remote Device** with the server's ID (ours: `/sync/` → Actions → Show ID),
send you the Device ID. In their UI: **Add Remote Device** with the server's ID (ours: `sync.BASE` → Actions → Show ID),
then **Add Folder** on their clip directory (ShadowPlay/Medal/Steam saves), Sharing tab → tick the server,
Folder Type **Send Only**, Save.
Our side at https://HOST/sync/: **Add Remote Device** → paste their ID, name it after them, Save. When their folder
Our side at https://sync.BASE/: **Add Remote Device** → paste their ID, name it after them, Save. When their folder
offer pops up: **Add** → Folder Path `/var/syncthing/clips/<username>`, Folder Type **Receive Only**, Advanced →
Minimum Free Disk Space **300 GB**, Save. Clips start arriving; the sweep normalizes them; Jellyfin shows the
folder as that person's show. Their own folder structure is mirrored; only video files are pulled.
## 5a². Gateway page — https://HOST/about/
## 5a². Gateway page — https://media.BASE/ (the main host root; `/about/` redirects here)
Role-aware landing page (Discord login): everyone sees Watch / Live / Send clips / Sign out with short how-tos;
`streamers` get the dock + OBS setup steps; `editors` the browser + multicam/timecode notes; `media-admins`
@@ -162,7 +177,7 @@ already `.mp4`), the raw's name is appended to `.stlocal` (an `#include`d ignore
drops it without flagging or re-pulling), and the raw is deleted locally — it still exists on the
gamer's PC. `configure.py` creates the folder + ignore file for each streamer.
In the UI (https://HOST/sync/):
In the UI (https://sync.BASE/):
- each clips folder → path `/var/syncthing/clips/<user>`, **Folder Type: Receive Only**,
Edit → Advanced → **Minimum Free Disk Space: 300 GB** (so clips can never fill the mirror and stall
the recordings).
@@ -171,8 +186,8 @@ In the UI (https://HOST/sync/):
## 5c. Authentik (Discord login) and Jellyfin
DNS: `auth` and `watch` as CNAMEs to the main host. Then run `./addCommunityMediaServer.sh` — it asks
for both hostnames, checks DNS, generates `.env` (Authentik secret + DB password; never commit it),
DNS: `auth`, `watch` (and `files`, `clips`, `sync`, `live`, `git`, `bot`) as CNAMEs to the main host. Then run `./addCommunityMediaServer.sh` — it asks
for every hostname, checks DNS, generates `.env` (Authentik secret + DB password; never commit it),
adds the Caddy site blocks, starts the containers, waits for them and prints the steps below with your
hostnames filled in. (`./configure.py --full` asks for the same hostnames if you prefer.) Then:
@@ -285,7 +300,7 @@ from `/me/stream-token` for the logged-in Discord user (member of `streamers`).
Bandwidth: no transcoding, every viewer gets the source bitrate (~15 Mbps) → ~30 concurrent viewers on
the 500 Mbps line. For a crowd, restream to Twitch from OBS.
## 5g. Authentik forward-auth for the Caddy-gated hosts (main host, `live.`, `bot.`)
## 5g. Authentik forward-auth for the Caddy-gated hosts (`media.`, `files.`, `clips.`, `sync.`, `live.`, `bot.`)
Design: **Authentik authenticates once for the whole domain; Caddy authorizes per host** from the
`X-Authentik-Groups` header the outpost returns; **Discord roles fill the Authentik groups** at login.
@@ -296,8 +311,8 @@ Design: **Authentik authenticates once for the whole domain; Caddy authorizes pe
cookie domain = the parent domain (e.g. `dumpstervigilantes.rip`). Edit the Embedded Outpost →
Applications → add it → Update. One login now covers every gated host.
3. Per-host group requirement = `.env`: `BOT_REQUIRE_GROUP=bot-users` (default), `LIVE_REQUIRE_GROUP=`
(empty = any logged-in member), `SYNC_REQUIRE_GROUP=media-admins` (the `/sync` Syncthing UI on the main host).
On the main host copyparty itself authorizes by group (`editors`, `media-admins`); the dump endpoint lets
(empty = any logged-in member), `SYNC_REQUIRE_GROUP=media-admins` (the Syncthing UI on `sync.`).
On `files.` copyparty itself authorizes by group (`editors`, `media-admins`); the dump endpoint lets
`admins`/`media-admins` dump anyone's night. Leave "Intercept header authentication" **off**: login is
Discord only, no app passwords, no WebDAV. `./configure.py` apply regenerates the Caddy blocks; a user outside the
group gets a 403 from Caddy before the app sees the request.
@@ -325,7 +340,7 @@ Jellyfin and Gitea use OIDC providers instead, not this.
## 6. Editors
Log in with Discord at https://media.example.com/ (copyparty behind Caddy + Authentik). Access is by Authentik
Log in with Discord at https://files.BASE/ (copyparty behind Caddy + Authentik). Access is by Authentik
group: **editors** read streams and clips and write `/finals`; **media-admins** have full rights everywhere
including `/trash`. Scrub any `.mp4` in the browser, download what you want.
@@ -371,7 +386,7 @@ Where the time comes from, best first:
(`bin/vgstrip.py`) at the start, middle and end of each recording → the timecode is frame-accurate at the
start and the sidecar records drift over the file. Game image untouched. OBS: dock → *Timecode band* →
"Add band + clock source" (canvas grows by W/160 rows, a locked browser source `Vanguard Clock` →
`/dock/clock/` sits in the band; only while not streaming). Go Live: the "Timecode band" checkbox (default on).
`https://live.BASE/dock/clock/` sits in the band; only while not streaming). Go Live: the "Timecode band" checkbox (default on).
2. **Server arrival time** (no band) — microseconds in the recording filename. Within ~0.1–0.3 s per streamer
(encoder + network delay); coarse but always available.
@@ -535,4 +550,4 @@ evening; after 17:00 it is tonight. The 05:00 trash purge is 05:00 Eastern.
|------|------|------|
| normalize-sweep.timer | every 5 min | remux/re-encode finished files to the editing standard |
| trash-purge.timer | daily 05:00 Eastern | deletes trash older than 3 days |
| dumpd.service | always | the dump endpoint on 127.0.0.1:8080, behind Caddy at /dump |
| dumpd.service | always | the dump endpoint on 127.0.0.1:8080, behind Caddy on `live.` at /dump, /me, /time and on `clips.` at /me/clips |