Compare commits
3 Commits
f1d19ba32d
...
7e935c31d1
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e935c31d1 | |||
| b661ee795b | |||
| 6db786e00e |
@@ -246,7 +246,7 @@ Checklist:
|
||||
publishes the plugin zip is the obvious first pipeline
|
||||
- risk: the runner's Docker socket is root on the host for anyone who can push a workflow
|
||||
|
||||
### Phase 10 — Gitea login via Authentik (OIDC) (later)
|
||||
### Phase 10 — Gitea login via Authentik (OIDC) (compose done; Authentik + Gitea UI steps pending)
|
||||
Same pattern as Jellyfin: Authentik is the identity, Gitea trusts it.
|
||||
- [ ] Authentik → Applications → Create with provider → OAuth2/OpenID: name `gitea`, redirect
|
||||
`https://git.HOST/user/oauth2/authentik/callback`, authorization flow implicit-consent, scopes
|
||||
@@ -255,12 +255,10 @@ Same pattern as Jellyfin: Authentik is the identity, Gitea trusts it.
|
||||
provider OpenID Connect, name `authentik` (must match the callback path above),
|
||||
client ID/secret, discovery URL `https://auth.HOST/application/o/<slug>/.well-known/openid-configuration`,
|
||||
"Skip local 2FA", claim mapping: group claim `groups` → Gitea admin group `admins`
|
||||
- [ ] Gitea app.ini / env: `GITEA__oauth2_client__ENABLE_AUTO_REGISTRATION=true`,
|
||||
`GITEA__oauth2_client__ACCOUNT_LINKING=auto`, `GITEA__oauth2_client__USERNAME=nickname` so first
|
||||
Discord login creates the Gitea user with no form (registration stays closed for local accounts)
|
||||
- [ ] optional: `GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION=true`, hide the password form on the
|
||||
login page (`GITEA__oauth2_client__OPENID_CONNECT_SCOPES`, custom template) so Discord is the only way in
|
||||
- [ ] policy on the Authentik application: who may reach Gitea at all (e.g. `admins` + a `devs` group)
|
||||
- [x] Gitea env: `DISABLE_REGISTRATION=false` + `ALLOW_ONLY_EXTERNAL_REGISTRATION=true` (local self-signup stays
|
||||
off), `ENABLE_AUTO_REGISTRATION`, `ACCOUNT_LINKING=auto`, `USERNAME=nickname`, `UPDATE_AVATAR`
|
||||
- [ ] optional: hide the password form on the login page (custom template) so Discord is the only visible way in
|
||||
- [ ] Authentik group `git-users` (from a Discord role) bound on the `Gitea` application (+ `admins`); only they can log in
|
||||
- [ ] test with a non-admin Discord user; confirm admin mapping via `admins` group
|
||||
|
||||
### Phase 11 — MediaMTX auth through Authentik (no stream keys anywhere) ✅ live
|
||||
@@ -287,7 +285,7 @@ Discord roles); the publish path must start with the user's own name.
|
||||
- [ ] dock + README; remove streamers from configure.py; delete `streamers.json`
|
||||
- [ ] test: OBS with an app password; Go Live with no key; a non-streamer gets denied
|
||||
|
||||
### Phase 12 — frame-accurate timecode for multicam via an overscan band (built; deploy pending)
|
||||
### Phase 12 — frame-accurate timecode for multicam via an overscan band ✅ live, verified (OBS: strip, −3 ms drift, 0 gaps)
|
||||
Target: clips from several streamers align to the frame (16.7 ms @60) on a multicam timeline; the game
|
||||
image is untouched and finals carry nothing. Reference clock = the server; streamer PC clocks are never trusted.
|
||||
|
||||
|
||||
25
README.md
25
README.md
@@ -204,6 +204,24 @@ already existed run `./createGiteaDb.sh` once.
|
||||
Runner caveat: it holds the Docker socket, so anyone who can push a workflow can run as root on the box.
|
||||
Keep repo write access to trusted people.
|
||||
|
||||
**Login via Authentik (Discord)** — same pattern as Jellyfin, done once:
|
||||
1. Authentik → Applications → Create with provider → name `Gitea`, slug `gitea`; **OAuth2/OpenID Provider**,
|
||||
authorization flow implicit-consent, client type Confidential, redirect URI (strict)
|
||||
`https://git.HOST/user/oauth2/authentik/callback`, signing key = the default certificate, scopes
|
||||
`openid email profile` (the default profile mapping carries `preferred_username` and `groups`). Copy client ID + secret.
|
||||
2. Gitea → Site Administration → Identity & Access → Authentication Sources → Add: type **OAuth2**, name
|
||||
**`authentik`** (must match the callback path), provider **OpenID Connect**, client ID/secret, OpenID Connect
|
||||
Auto Discovery URL `https://auth.HOST/application/o/gitea/.well-known/openid-configuration`, tick "Skip local
|
||||
2FA", claim name for groups `groups`, administrator group claim value `admins`. Save.
|
||||
3. compose (already set): registration only via external sources, auto-registration + account linking on,
|
||||
username from `preferred_username`. A first Discord login creates the Gitea user with no form.
|
||||
4. Access = Authentik group **`git-users`**: Directory → Groups → create it (add a Discord role to it in the
|
||||
roles→groups table); Applications → `Gitea` → Policy / Group / User Bindings → bind `git-users` (and `admins`).
|
||||
Non-members are refused by Authentik before Gitea sees them.
|
||||
5. Test in a private window: https://git.HOST/user/login → "Sign in with authentik" → Discord → logged in.
|
||||
Members of `admins` become Gitea site admins on login. The local admin account stays as a fallback.
|
||||
Optional later: hide the password form (custom template) so Discord is the only visible way in.
|
||||
|
||||
## 5f. Live portal (`live.HOST`)
|
||||
|
||||
React + dockview page (`live/`) behind Authentik forward-auth: every active stream is a dockable window,
|
||||
@@ -289,9 +307,10 @@ MediaMTX asks dumpd (`POST /mtx-auth` on 127.0.0.1:8081, reachable only from Doc
|
||||
connection. Reads/playback are allowed (viewers are gated by Caddy + Authentik). Publishing needs a token
|
||||
minted by `GET /me/stream-token` behind Authentik for a member of `streamers`/`admins`/`media-admins`; dumpd
|
||||
checks the HMAC signature (`/etc/vanguard/stream-token.secret`, generated into `.env` as `STREAM_TOKEN_SECRET`),
|
||||
the expiry, and that the path starts with that user's name. MediaMTX hot-reloads paths/recording settings
|
||||
but **not** the auth section: after changing auth, `docker compose restart mediamtx` between streams.
|
||||
`configure.py` reloads Caddy in place and restarts only copyparty.
|
||||
the expiry, and that the path starts with that user's name. MediaMTX does not pick up `mediamtx.yml` changes
|
||||
live on this setup: after any change to it, `docker compose restart mediamtx` between streams. Day-to-day
|
||||
operations (streamers, tokens, titles) never touch that file. `configure.py` reloads Caddy in place and
|
||||
restarts only copyparty.
|
||||
|
||||
## Timecode for multicam editing (frame accurate)
|
||||
|
||||
|
||||
15
STATUS.md
15
STATUS.md
@@ -25,7 +25,7 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea
|
||||
|
||||
| Who | Activity | Where | Login |
|
||||
|---|---|---|---|
|
||||
| Streamer | One-time: dock → Timecode band → "Add band + clock source" (OBS) or leave the Go Live checkbox on — gives editors frame-accurate timecode | OBS dock / Go Live | — **pending deploy** |
|
||||
| Streamer | One-time: dock → Timecode band → "Add band + clock source" (OBS) or leave the Go Live checkbox on — gives editors frame-accurate timecode | OBS dock / Go Live | live |
|
||||
| Streamer | Type a title (live preview + naming rules), Go Live, Stop. "Dump tonight" if it was junk (3-day undo). Manual-setup accordion with endpoints and a hidden token | OBS browser dock `https://ovh1…/dock/` | Discord login in the dock; dock mints a 12 h token per Go Live — **no passwords, verified live** |
|
||||
| Gamer | Nothing; clips save where they always did | their clipper + Syncthing, paired once | — |
|
||||
| Editor | Multicam: drop clips on a timeline, sync by the embedded timecode track (frame-accurate when the band was on), cross-check with voice audio; export at the game resolution to drop the band | Resolve / Premiere | — |
|
||||
@@ -47,8 +47,8 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea
|
||||
|---|---|---|
|
||||
| MediaMTX | RTMP/SRT ingest, records fMP4; publish auth delegated to dumpd (tokens via `/mtx-auth`) | live, token auth verified |
|
||||
| copyparty | browse/scrub/download/finals upload | behind Authentik, group permissions (`editors`, `media-admins`), Discord-only, no WebDAV | live |
|
||||
| dumpd + dock | dump, stream tokens, MediaMTX auth hook, `/time`; OBS dock with timecode-band setup | identity from Authentik headers | live; band/timecode **pending deploy** |
|
||||
| Timecode | `bin/vgstrip.py` decoder, `tmcd` + sidecar in the sweep, `/dock/clock/` strip page, Go Live band composite, portal crop | server clock is the reference | **built, pending deploy** |
|
||||
| dumpd + dock | dump, stream tokens, MediaMTX auth hook, `/time`; OBS dock with timecode-band setup | identity from Authentik headers | live |
|
||||
| Timecode | `bin/vgstrip.py` decoder, `tmcd` + sidecar in the sweep, `/dock/clock/` strip page, Go Live band composite, portal crop | server clock is the reference | **live, verified** (OBS: frame-accurate, 0 gaps) |
|
||||
| Syncthing | clip intake, UI at `/sync/` | UI behind Authentik + `media-admins` | live |
|
||||
| normalize sweep | editing standard, one copy per clip | live |
|
||||
| nftables | host firewall (22, 80/443, 1935, 2222, 3306, 8890/udp, 22000) | live |
|
||||
@@ -57,7 +57,7 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea
|
||||
| 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.3 built** (flat `<Title>__<ts>` names, policy task, feed wording) — install pending; server has 0.1.0 or 0.1.2 |
|
||||
| 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 |
|
||||
| Gitea | git hosting on MariaDB, Actions enabled; holds this repo | live, local admin; Discord login via Authentik OIDC **compose ready, UI steps pending** |
|
||||
| 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** |
|
||||
@@ -65,11 +65,11 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea
|
||||
|
||||
## Unfinished, in rough priority
|
||||
|
||||
0. **Deploy phase 12** (timecode): vgstrip + sweep + dumpd `/time` + configure (Caddy `/time` routes, MediaMTX `%f`) + dock/clock + portal. Then a streamer adds the band and we check a recording's `tmcd` and sidecar.
|
||||
0. `docker compose restart mediamtx` at a quiet moment so recording filenames gain microseconds (the arrival-time fallback); everything else in phase 12 is live.
|
||||
1. Authentik: `discord-roles-to-groups` policy so `streamers`/`editors`/`media-admins`/`bot-users` follow Discord roles (`bot-users` group needed for the bot host). Set the provider's Invalidation flow to `default-provider-invalidation-flow` so every logout forces a full re-login.
|
||||
2. Confirm plugin 0.1.3 under Jellyfin → Plugins and the Gitea runner registered.
|
||||
3. Portal viewer during a real stream: corner chip `webrtc`; Windows Go Live with WarDogs audio (first Windows streamer).
|
||||
4. Gitea login via Authentik (plan phase 10).
|
||||
4. Gitea login via Authentik: deploy the compose change, then the Authentik provider + Gitea auth source (README 5e).
|
||||
5. Housekeeping: pin `AUTHENTIK_TAG`, rotate the Discord client secret seen in a screenshot, nightly Postgres dump, Caddyfile formatting warning.
|
||||
6. Medal filename pattern in the plugin (need a sample filename).
|
||||
7. Home archive puller on the home box.
|
||||
@@ -86,7 +86,8 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea
|
||||
- Streams are not re-encoded for VFR/gaps by default (dropped frames are legitimate; a 3 h re-encode is hours of CPU); players hold the last frame across a gap. The timecode sidecar lists gaps. **Gap filling is available** (`FILL_GAPS_MS` in `normalize-sweep.service`, README "Timecode") — turn on when the box has a hardware encoder. Clips always get CFR.
|
||||
- The sweep's VFR check used a gawk-only function; Debian's mawk made it pass everything silently. Portable now.
|
||||
- A deploy block that is only half-run leaves old binaries in `/usr/local/bin`; `grep -c` the installed script before blaming logic.
|
||||
- MediaMTX hot-reloads `paths`/recording settings but NOT `authMethod`/auth settings: those need a container restart (between streams).
|
||||
- MediaMTX does not reload `mediamtx.yml` live here (bind-mounted file): any change to it needs `docker compose restart mediamtx` between streams. Nothing day-to-day touches that file.
|
||||
- **Timecode verified live (2026-10-31)**: OBS + NVENC with the band → `source: strip`, drift −3 ms over 80 s, 0 gaps, 1920×1092 (band present); arrival time was 2.3 s late vs the first frame, which the strip corrected. Go Live from Linux Chromium (software encode) → strip decoded fine but 239 gaps and −85 ms: frame dropping, as expected on that path.
|
||||
- MediaMTX stamps recording filenames in its container clock: without `TZ` it is UTC and the plugin reads names as Eastern (4 h off). `TZ=America/New_York` is set on the container.
|
||||
- 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.
|
||||
|
||||
@@ -13,6 +13,7 @@ ROLE_TO_GROUP = { # <- Discord role id : A
|
||||
# "222222222222222222": "streamers",
|
||||
# "333333333333333333": "editors",
|
||||
# "444444444444444444": "admins",
|
||||
"555555555555555555": "git-users",
|
||||
}
|
||||
|
||||
from authentik.core.models import Group
|
||||
|
||||
@@ -364,7 +364,8 @@ srtAddress: :8890
|
||||
# ---- auth -------------------------------------------------------------
|
||||
# Publishing is authorised by dumpd (/mtx-auth) with short-lived tokens minted for Discord-authenticated
|
||||
# members of the streamers group. Reads are allowed; viewers are gated by Caddy + Authentik. No passwords.
|
||||
# NOTE: MediaMTX hot-reloads paths/recording settings but NOT the auth section — restart after auth changes.
|
||||
# NOTE: on this setup (bind-mounted file) MediaMTX does not reload this file live: restart after any change here.
|
||||
# Day-to-day operations (streamers, tokens, titles) never touch this file.
|
||||
authMethod: http
|
||||
authHTTPAddress: http://{auth_gw}:8081/mtx-auth
|
||||
authHTTPExclude:
|
||||
@@ -475,8 +476,9 @@ def apply(domain, admin_pw, streamers, editors, auth_host="", watch_host="", dis
|
||||
run(dc + ["up", "-d", "--build"]) # recreates only services whose compose definition changed
|
||||
run(dc + ["restart", "copyparty"]) # copyparty re-reads its config on start
|
||||
run(dc + ["exec", "caddy", "caddy", "reload", "--config", "/etc/caddy/Caddyfile"]) # zero-downtime
|
||||
print(" mediamtx: hot-reloads most of mediamtx.yml, but NOT auth changes. If you changed auth settings, run")
|
||||
print(" docker compose restart mediamtx between streams (cuts a live recording).")
|
||||
print(" mediamtx: config changes are NOT picked up live on this setup (bind-mounted file). If mediamtx.yml changed,")
|
||||
print(" run docker compose restart mediamtx between streams (cuts a live recording). Adding/removing")
|
||||
print(" streamers or tokens never touches mediamtx.yml, so day-to-day nothing restarts.")
|
||||
|
||||
def show(domain, admin_pw, streamers, editors, auth_host="", watch_host="", git_host="", bot_host="", live_host=""):
|
||||
print(f"\n== domain : {domain or '(not set)'}")
|
||||
|
||||
@@ -219,7 +219,16 @@ services:
|
||||
GITEA__server__SSH_DOMAIN: ${GIT_HOST:-git.example.com}
|
||||
GITEA__server__SSH_PORT: "2222" # what users put in their remotes
|
||||
GITEA__server__SSH_LISTEN_PORT: "22" # inside the container
|
||||
GITEA__service__DISABLE_REGISTRATION: "true" # accounts are created by an admin (or via Authentik OIDC later)
|
||||
# accounts come from Authentik (Discord) via OIDC: external auto-registration on, local self-registration off
|
||||
GITEA__service__DISABLE_REGISTRATION: "false"
|
||||
GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION: "true"
|
||||
GITEA__service__SHOW_REGISTRATION_BUTTON: "false"
|
||||
GITEA__service__REQUIRE_SIGNIN_VIEW: "false"
|
||||
GITEA__oauth2_client__ENABLE_AUTO_REGISTRATION: "true"
|
||||
GITEA__oauth2_client__ACCOUNT_LINKING: auto
|
||||
GITEA__oauth2_client__USERNAME: nickname # Authentik preferred_username
|
||||
GITEA__oauth2_client__UPDATE_AVATAR: "true"
|
||||
GITEA__oauth2_client__OPENID_CONNECT_SCOPES: openid email profile
|
||||
GITEA__actions__ENABLED: "true"
|
||||
GITEA__actions__DEFAULT_ACTIONS_URL: github
|
||||
GITEA__webhook__ALLOWED_HOST_LIST: private,loopback,external
|
||||
|
||||
Reference in New Issue
Block a user