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>
This commit is contained in:
2026-10-30 23:04:33 -05:00
parent 463d9b2385
commit 82df0b1583
11 changed files with 258 additions and 10 deletions

View File

@@ -19,6 +19,8 @@ SRC_DIRS = [ROOT / "streams", ROOT / "clips"]
TRASH = ROOT / "trash"
MARK = ROOT / ".markers"
USERS_FILE = Path(os.environ.get("DUMP_USERS", "/etc/vanguard/dump-users.txt"))
STREAMERS_FILE = Path(os.environ.get("STREAMERS_FILE", "/etc/vanguard/streamers.json")) # {"techy": "pass", ...} written by configure.py
AUTH_USER_HEADER = os.environ.get("AUTH_USER_HEADER", "X-Authentik-Username")
LISTEN = ("127.0.0.1", 8080)
TZ = ZoneInfo(os.environ.get("DUMP_TZ", "America/New_York"))
NIGHT_START = time(17, 0) # 5 pm
@@ -92,6 +94,12 @@ def do_move(files, night):
d.rmdir()
return moved, size
def load_streamers():
try:
return json.loads(STREAMERS_FILE.read_text())
except (OSError, json.JSONDecodeError):
return {}
class H(BaseHTTPRequestHandler):
server_version = "dumpd/1"
def _json(self, code, obj):
@@ -129,6 +137,17 @@ class H(BaseHTTPRequestHandler):
return req_user or "all"
return auth_user
def do_GET(self):
if self.path.startswith("/me/stream-key"):
# Only reachable through Caddy behind Authentik forward-auth, which injects the username header.
# dumpd binds to loopback, so a request carrying this header is one Caddy already authenticated.
who = (self.headers.get(AUTH_USER_HEADER) or "").strip().lower()
if not who:
return self._json(401, {"error": "not authenticated"})
streamers = load_streamers()
pw = streamers.get(who)
if not pw:
return self._json(403, {"error": f"no stream key for '{who}' — ask the admin to add you as a streamer with that exact name"})
return self._json(200, {"user": who, "pass": pw})
if not self.path.startswith("/dump"):
return self._json(404, {"error": "not found"})
u = check_auth(self.headers.get("Authorization"))