Hub: der Dienst laeuft zentral statt auf dem Stream-PC

Zwei Dinge, die Reichweite bringen sollen, hingen bisher daran, dass jemand
vor seinem Rechner sitzt: der Discord-Rollen-Knopf und die Go-Live-
Ankuendigung. Beide brauchen nur Internet.

Der Schnitt verlaeuft entlang einer Frage: braucht die Aufgabe eine Datei
oder ein Programm, das nur auf dem Stream-PC existiert?

  Hub (LXC)                    Ingest (Abdus PC)
  ---------                    -----------------
  Chat, Zuschauerzahlen        OBS-Wiedergabepuffer
  Discord: Knopf und Go-Live   !clip, Rendern, Untertitel
  Overlay-Seiten fuer OBS

Beide schreiben in ihre eigene Datenbank. Eine gemeinsame waere eine
Netzwerkfreigabe fuer SQLite - der zuverlaessigste bekannte Weg zu einer
beschaedigten Datei.

Das kostet ADR-0005 seine Hauptbegruendung: "bindet nur auf 127.0.0.1" gilt
fuer den Hub nicht mehr. An die Stelle treten ein Passwort mit signiertem
Sitzungs-Cookie fuer alles, was ein Mensch bedient, und ein zufaelliger
Schluessel in der Adresse fuer die Browser-Sources. Der Schluessel ist kein
schoenes Verfahren - er steht in jedem OBS-Profil -, aber eine Browser-Source
kann kein Formular ausfuellen und keinen Login durchhalten.

`publicUrl` haengt der OAuth-Rueckruf daran. Twitch vergleicht die
Redirect-URI zeichengenau, und ein fehlender Schraegstrich sieht im Fehlertext
aus wie ein falsches Client-Secret.

Die Routen der Einrichtungsseite sind aus ingest/server.ts herausgeloest,
weil sie jetzt an zwei Servern haengen.

Begruendung und Konsequenzen in ADR-0008.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZeasyAlexandClaude Opus 5 committed 2026-09-18 00:12:58 +02:00
1 parent e18336c57f
commit 3b606e36fb
8 files changed
+985 -10

No files matched your search

+31 -5
View File
@@ -59,18 +59,45 @@ async function handle(
req: http.IncomingMessage, res: http.ServerResponse, deps: ServerDeps
): Promise<void> {
const url = new URL(req.url ?? '/', `http://localhost:${deps.port}`);
// Auf dem Stream-PC ist die Einrichtung das Einzige, was dieser Server
// kann - also faengt die Wurzel dorthin. Im Hub steht auf / die
// Overlay-Uebersicht, deshalb liegt die Weiche hier und nicht in
// `setupRoutes`.
if (url.pathname === '/') {
res.writeHead(303, { location: '/setup' });
res.end();
return;
}
if (await setupRoutes(req, res, deps, url)) return;
res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
res.end('Nicht gefunden');
}
/**
* Die Routen der Einrichtung, ohne 404 am Ende.
*
* Ausgelagert, weil sie seit dem zentralen Betrieb an zwei Servern haengen:
* hier auf dem Stream-PC und im Overlay-Server des Hubs. Der Rueckgabewert
* sagt, ob die Anfrage beantwortet wurde - wer sie einbindet, macht sonst
* selbst weiter.
*/
export async function setupRoutes(
req: http.IncomingMessage, res: http.ServerResponse, deps: ServerDeps, url: URL
): Promise<boolean> {
const html = (body: string, code = 200) => {
res.writeHead(code, { 'content-type': 'text/html; charset=utf-8',
'cache-control': 'no-store', 'referrer-policy': 'no-referrer' });
res.end(body);
return true;
};
const back = (kind: 'ok' | 'err', text: string) => {
res.writeHead(303, { location: `/setup?${kind}=${encodeURIComponent(text)}` });
res.end();
return true;
};
// ---- Einrichtungsseite
if (url.pathname === '/' || url.pathname === '/setup') {
if (url.pathname === '/setup') {
const ok = url.searchParams.get('ok');
const err = url.searchParams.get('err');
return html(setupPage(secretStatus(), {
@@ -119,7 +146,7 @@ async function handle(
try {
res.writeHead(303, { location: authorizeUrl(role, deps.port) });
res.end();
return;
return true;
} catch (err) {
return back('err', (err as Error).message);
}
@@ -144,9 +171,8 @@ async function handle(
if (url.pathname === '/health') {
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(deps.health(), null, 2));
return;
return true;
}
res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
res.end('Nicht gefunden');
return false;
}