Discord-only login: drop WebDAV/app-password path from docs and generated config comments

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 15:00:48 -05:00
parent 50c037e514
commit 15402eeb82
4 changed files with 11 additions and 14 deletions

View File

@@ -37,8 +37,8 @@ Bundle: `vanguard-stream/` in this repo; live copy at `~/vanguard-stream` on the
- **Raw clipper files live in hidden `.src/` folders** so Jellyfin and copyparty show one item per clip. - **Raw clipper files live in hidden `.src/` folders** so Jellyfin and copyparty show one item per clip.
- **Hostnames:** `auth.` and `watch.` are CNAMEs to the main host. Caddy routes by SNI, Let's Encrypt - **Hostnames:** `auth.` and `watch.` are CNAMEs to the main host. Caddy routes by SNI, Let's Encrypt
follows CNAMEs. One A record to change if the IP ever moves. follows CNAMEs. One A record to change if the IP ever moves.
- **WebDAV through Authentik** uses Authentik app passwords as HTTP basic auth (a mounted drive - **Discord login only, no passwords anywhere** → no WebDAV (a mounted drive can't do a browser login).
can't do a browser login). Editors download from copyparty's web UI (zip/tar for folders).
## Target layout ## Target layout
@@ -190,7 +190,7 @@ Checklist:
replaces today's workaround of logging in as the local `admin` account to modify streams/clips) replaces today's workaround of logging in as the local `admin` account to modify streams/clips)
- [ ] keep the local accounts in `copyparty.conf` until an `editors` Discord user has browsed and - [ ] keep the local accounts in `copyparty.conf` until an `editors` Discord user has browsed and
downloaded through the new path; then remove them (`configure.py` drops the editors section) downloaded through the new path; then remove them (`configure.py` drops the editors section)
- [ ] WebDAV: editor creates an app password in Authentik → mounts with Discord username + app password - [x] decided: no WebDAV (Discord login only); browser downloads
- [ ] policy on the Application: only `editors` + `admins` + `streamers` may pass - [ ] policy on the Application: only `editors` + `admins` + `streamers` may pass
### Phase 6 — Syncthing UI + dump behind Authentik (built together with phase 5; apply pending) ### Phase 6 — Syncthing UI + dump behind Authentik (built together with phase 5; apply pending)

View File

@@ -228,8 +228,8 @@ Design: **Authentik authenticates once for the whole domain; Caddy authorizes pe
3. Per-host group requirement = `.env`: `BOT_REQUIRE_GROUP=bot-users` (default), `LIVE_REQUIRE_GROUP=` 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). (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 On the main host copyparty itself authorizes by group (`editors`, `media-admins`); the dump endpoint lets
`admins`/`media-admins` dump anyone's night. Enable **"Intercept header authentication"** on the proxy `admins`/`media-admins` dump anyone's night. Leave "Intercept header authentication" **off**: login is
provider so WebDAV clients can log in with Authentik app passwords. `./configure.py` apply regenerates the Caddy blocks; a user outside the 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. group gets a 403 from Caddy before the app sees the request.
4. Discord roles → groups: Directory → Groups: create `bot-users` (and keep `streamers`, `editors`, `admins`). 4. Discord roles → groups: Directory → Groups: create `bot-users` (and keep `streamers`, `editors`, `admins`).
Discord source → Scopes: `guilds guilds.members.read`. Customization → Policies → Expression Policy Discord source → Scopes: `guilds guilds.members.read`. Customization → Policies → Expression Policy
@@ -248,9 +248,8 @@ Log in with Discord at https://media.example.com/ (copyparty behind Caddy + Auth
group: **editors** read streams and clips and write `/finals`; **media-admins** have full rights everywhere 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. including `/trash`. Scrub any `.mp4` in the browser, download what you want.
WebDAV mount: same URL. Username = your Authentik username, password = an **Authentik app password** No WebDAV: login is Discord only (no passwords of any kind), and a mounted drive cannot do a browser login.
(auth.HOST → your user settings → Tokens and App passwords → create). The proxy provider must have Download from the browser: single files, or select files/folders and use copyparty's zip/tar download.
"Intercept header authentication" enabled for this to work.
Every `.mp4` an editor sees is the same kind of file (see "Editing standard"). Raw clipper output that Every `.mp4` an editor sees is the same kind of file (see "Editing standard"). Raw clipper output that
needed converting is kept in a hidden `.src/` folder next to it (media-admins can show dotfiles in copyparty). needed converting is kept in a hidden `.src/` folder next to it (media-admins can show dotfiles in copyparty).

View File

@@ -28,7 +28,6 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea
| Streamer | Type a title, Go Live, Stop. "Dump tonight" if it was junk (3-day undo) | OBS browser dock `https://ovh1…/dock/` | Discord login in the dock (once); stream password in dock settings for the OBS key | | Streamer | Type a title, Go Live, Stop. "Dump tonight" if it was junk (3-day undo) | OBS browser dock `https://ovh1…/dock/` | Discord login in the dock (once); stream password in dock settings for the OBS key |
| Gamer | Nothing; clips save where they always did | their clipper + Syncthing, paired once | — | | 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…/` | Discord (Authentik); rights by group `editors` / `media-admins` — **pending apply** | | Editor | Browse by streamer/title, scrub in browser, download originals, upload finals | copyparty `https://ovh1…/` | Discord (Authentik); rights by group `editors` / `media-admins` — **pending apply** |
| Editor | Mount the library as a drive | WebDAV, same URL | Authentik username + app password — **pending apply + "Intercept header authentication"** |
| Anyone in the Discord server | Watch recordings and clips by streamer and season | Jellyfin `https://watch.…/` | Discord (Authentik) | | 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; viewer side not yet exercised during a stream | | 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; viewer side not yet exercised during a stream |
| Streamer (Windows) | **Go Live** from the browser: pick the WarDogs window (+audio on Windows Chrome 141+; Linux/Mac video only), streams as `<user>/HelmetCam-<date>`; no OBS needed | Live portal → Go Live | Discord; key fetched for the logged-in user — **verified end to end** (publish → `.live` → sweep → title folder) | | Streamer (Windows) | **Go Live** from the browser: pick the WarDogs window (+audio on Windows Chrome 141+; Linux/Mac video only), streams as `<user>/HelmetCam-<date>`; no OBS needed | Live portal → Go Live | Discord; key fetched for the logged-in user — **verified end to end** (publish → `.live` → sweep → title folder) |
@@ -69,7 +68,7 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea
1d. Watch a real stream from a second browser: corner chip should say `webrtc`; confirm WarDogs audio came through. 1d. Watch a real stream from a second browser: corner chip should say `webrtc`; confirm WarDogs audio came through.
2. **Register the runner** (`./registerGiteaRunner.sh`). 2. **Register the runner** (`./registerGiteaRunner.sh`).
3. **`bot` CNAME + Authentik proxy provider** — first use of forward-auth; proves the pattern for copyparty. 3. **`bot` CNAME + Authentik proxy provider** — first use of forward-auth; proves the pattern for copyparty.
4. **Apply phase 5+6** (copyparty, dock, dump, /sync behind Authentik; group permissions). Authentik side: group `media-admins` (techy in it), "Intercept header authentication" on the provider for WebDAV app passwords. 4. **Apply phase 5+6** (copyparty, dock, dump, /sync behind Authentik; group permissions). Authentik side: group `media-admins` with techy in it. Decision: Discord login only → no WebDAV; browser downloads (zip/tar) instead.
5. Dock fetches the caller's own stream key (phase 7) — or skip straight to phase 11 (no keys). 5. Dock fetches the caller's own stream key (phase 7) — or skip straight to phase 11 (no keys).
6. **Gitea login via Authentik** (OIDC source, auto-register, `admins` → Gitea admin). 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. 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.

View File

@@ -372,8 +372,7 @@ def write_copyparty():
Path("copyparty.conf").write_text("""# copyparty — behind Caddy + Authentik forward-auth. (generated by configure.py) Path("copyparty.conf").write_text("""# copyparty — behind Caddy + Authentik forward-auth. (generated by configure.py)
# Identity comes from the proxy: X-Authentik-Username / X-Authentik-Groups (groups separated by |). # Identity comes from the proxy: X-Authentik-Username / X-Authentik-Groups (groups separated by |).
# No local accounts. Groups: editors (read streams/clips, write finals), media-admins (everything incl. trash). # No local accounts. Groups: editors (read streams/clips, write finals), media-admins (everything incl. trash).
# WebDAV: same URL, HTTP Basic with the Authentik username + an Authentik *app password* # No WebDAV: login is Discord only (browser session); download via the web UI (zip/tar for folders).
# (the proxy provider must have "Intercept header authentication" enabled).
# Docs: https://github.com/9001/copyparty#identity-providers # Docs: https://github.com/9001/copyparty#identity-providers
[global] [global]
@@ -471,7 +470,7 @@ def show(domain, admin_pw, streamers, editors, auth_host="", watch_host="", git_
print(f" bot : db {env.get('BOT_DB_NAME','vanguardbot')} user {env.get('BOT_DB_USER','bot')} pass {env.get('BOT_DB_PASSWORD','(run apply)')}") print(f" bot : db {env.get('BOT_DB_NAME','vanguardbot')} user {env.get('BOT_DB_USER','bot')} pass {env.get('BOT_DB_PASSWORD','(run apply)')}")
print(f" gitea : db gitea user gitea pass {env.get('GITEA_DB_PASSWORD','(run apply)')} (created on MariaDB first init, else ./createGiteaDb.sh)") print(f" gitea : db gitea user gitea pass {env.get('GITEA_DB_PASSWORD','(run apply)')} (created on MariaDB first init, else ./createGiteaDb.sh)")
print("== access : by Authentik group — editors (read streams/clips, write finals), media-admins (everything), admins (dump anyone)") print("== access : by Authentik group — editors (read streams/clips, write finals), media-admins (everything), admins (dump anyone)")
print(" WebDAV: Authentik username + an Authentik app password") print(" Discord login only — no WebDAV; downloads via the web UI")
print("== streamers:") print("== streamers:")
for u, p in streamers.items(): for u, p in streamers.items():
print(f"\n {u} password {p}") print(f"\n {u} password {p}")
@@ -480,7 +479,7 @@ def show(domain, admin_pw, streamers, editors, auth_host="", watch_host="", git_
print(f" SRT : srt://{domain or 'HOST'}:8890?streamid=publish:{u}/TITLE:{u}:{p}") print(f" SRT : srt://{domain or 'HOST'}:8890?streamid=publish:{u}/TITLE:{u}:{p}")
print(f" Dock URL : https://{domain or 'HOST'}/dock/ (settings: host={domain or 'HOST'}, user={u}, pass={p})") print(f" Dock URL : https://{domain or 'HOST'}/dock/ (settings: host={domain or 'HOST'}, user={u}, pass={p})")
if not streamers: print(" (none)") if not streamers: print(" (none)")
print(f"\n editors log in with Discord at https://{domain or 'HOST'}/ — WebDAV at the same URL (app password)") print(f"\n editors log in with Discord at https://{domain or 'HOST'}/")
print(f" verify: curl -sI https://{domain or 'HOST'}/dock/ | head -1 (expect 200)") print(f" verify: curl -sI https://{domain or 'HOST'}/dock/ | head -1 (expect 200)")
print(f" curl -s -o /dev/null -w '%{{http_code}}\\n' https://{domain or 'HOST'}/dump (expect 401)\n") print(f" curl -s -o /dev/null -w '%{{http_code}}\\n' https://{domain or 'HOST'}/dump (expect 401)\n")