Uebergabe auf den aktuellen Stand

Stand 2026-09-17. Der Dienst laeuft seitdem zentral, der Go-Live-Ping geht
raus, und das Overlay steht.

- Neuer Abschnitt "Neu: der Dienst laeuft zentral" mit der Tabelle, was wo
  laeuft, und dem Hinweis, dass ADR-0005 damit seine Hauptbegruendung
  verliert.
- "Der Go-Live-Ping geht raus": Ablauf, der Fallstrick mit `allowed_mentions`,
  und die drei Sperren gegen den doppelten Ping. Dazu ehrlich, dass er noch
  nie von allein ausgeloest hat.
- "Das Overlay steht": fuenf Seiten, SSE statt WebSocket, und dass Follows
  fehlen.
- Der Discord-Knopf haengt nicht mehr am Daemon - dafuer ein neuer offener
  Punkt: der Bot darf nur an einer Stelle laufen, sonst antworten zwei
  Prozesse auf denselben Klick.
- Redirect-URI der Twitch-App auf die oeffentliche Adresse umgestellt, mit
  dem Hinweis, dass Twitch sie erst nach dem Login prueft und ein Test von
  aussen darueber nichts sagt.
- 162 -> 264 Tests.

config.example.yaml dokumentiert broadcaster, rewards, overlay und publicUrl.
apps/hub bekommt ein README mit dem Schnitt zwischen Hub und Ingest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZeasyAlexandClaude Opus 5 committed 2026-09-18 00:14:08 +02:00
1 parent 24f0528ff6
commit 0a3d462854
3 files changed
+219 -27

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
# @abdulive/hub
Der zentrale Dienst. Alles, was nur Internet braucht: Twitch-Chat,
Zuschauerzahlen, der Discord-Rollen-Knopf, die Go-Live-Ankuendigung und die
Overlay-Seiten fuer OBS.
Laeuft im LXC, nicht auf Abdus Rechner - damit der Ping auch dann rausgeht,
wenn der Stream-PC gerade nicht laeuft. Der Schnitt zwischen Hub und Ingest
steht in [ADR-0008](../../docs/decisions/ADR-0008-geteilter-betrieb.md).
```
abdulive hub
```
Bedienung ueber die Weboberflaeche des Overlay-Servers: Uebersicht auf `/`,
Einrichtung auf `/setup`. Beides hinter einem Passwort
(`abdulive web:passwort`).
Nicht hier drin und auch nicht hierher zu holen: OBS, der Wiedergabepuffer,
ffmpeg und whisper. Die bleiben in `abdulive ingest`.
+61 -3
View File
@@ -70,16 +70,30 @@ clipper:
# Der Hotkey wird in OBS auf "Wiedergabepuffer speichern" gelegt. Es gibt
# bewusst keinen eigenen Hotkey-Dienst - siehe ADR-0004, Punkt 4.
# Go-Live-Ankuendigung auf Discord.
#
# mode: live -> Post in #ankuendigungen, mit Ping an die Rolle Live-Ping
# draft -> derselbe Post in #technik-log, ohne Ping. Zum Zusehen.
#
# Zum Ausprobieren, ohne auf einen Stream zu warten:
# abdulive golive:test (interner Kanal, ohne Ping)
# abdulive golive:test --echt (echter Kanal, mit Ping)
broadcaster:
enabled: true
mode: draft # draft | live
mode: live
# Twitch setzt Titel und Kategorie erst kurz nach dem Start. Wer sofort
# postet, kuendigt regelmaessig den Titel des letzten Streams an.
delaySecondsAfterOnline: 60
# Innerhalb dieser Zeit wird kein zweites Mal angekuendigt. Faengt den Fall
# ab, dass Twitch nach einem Aussetzer eine neue Stream-ID vergibt.
cooldownHours: 6
pingRole: Live-Ping # leer = Post ohne Ping
obsScreenshot: true # Vorschaubild aus OBS statt von Twitch
platforms:
discord: true
bluesky: true
bluesky: false # noch nicht gebaut
mastodon: false
instagramStoryImage: true
instagramStoryImage: false
community:
greetings:
@@ -109,8 +123,52 @@ recap:
loyalty:
enabled: false # spaetere Ausbaustufe
# Kanalpunkt-Belohnungen und was sie ausloesen.
#
# Zugeordnet wird ueber den Titel der Belohnung auf Twitch (Gross-/Klein-
# schreibung egal) oder ueber die stabile ID; liegt beides vor, gewinnt die ID.
# Belohnungen, die hier nicht stehen, werden protokolliert und sonst in Ruhe
# gelassen - nicht jede Belohnung geht das Toolkit etwas an.
#
# Aktionen: konfetti | emote-regen | einblendung | tts | nichts
rewards:
enabled: false # aus, bis hier bewusst Belohnungen stehen
belohnungen: []
# Beispiel:
# belohnungen:
# - titel: Konfetti
# aktion: konfetti
# - titel: Nachricht vorlesen
# aktion: tts
# maxZeichen: 150
tts:
# Vorgabe ist die Freigabe von Hand, und das aus gutem Grund: Kanalpunkte
# sammeln sich beim Zugucken von allein an und sind damit kaum eine
# Huerde. Auf `false` gestellt hat man eine Vorlesefunktion fuer Fremde
# im eigenen Stream. Siehe docs/projects/chat-control.md.
freigabe: true
maxZeichen: 200
# Zu langer Text wird abgelehnt, nicht gekuerzt - ein halb vorgelesener
# Satz ist schlimmer als gar keiner.
blockliste: []
proPersonProStunde: 3
# Overlay-Server. Zugleich die einzige Tuer nach aussen, wenn der Dienst
# zentral laeuft: Uebersicht, Einrichtung und OAuth-Rueckruf haengen daran.
overlay:
port: 4791
enabled: true
# Oeffentliche Adresse, ohne Schraegstrich am Ende.
#
# Leer = der Dienst laeuft lokal und bindet an 127.0.0.1. Ist etwas gesetzt,
# bindet er an 0.0.0.0 (der Reverse-Proxy sitzt in einem anderen Container)
# und der OAuth-Rueckruf von Twitch geht an <publicUrl>/callback - genau so
# muss er auch in der Twitch-App hinterlegt sein.
#
# Die Umgebungsvariable PUBLIC_URL gewinnt gegen diesen Wert.
publicUrl: ""
dashboard:
port: 4792
ingestServer:
+138 -24
View File
@@ -1,6 +1,6 @@
# Übergabe
Stand **2026-09-15**, letztes gebautes Paket 0.5.4. Wer hier weitermacht,
Stand **2026-09-17**, letztes gebautes Paket 0.5.4. Wer hier weitermacht,
liest zuerst diese Seite, dann [`07-mvp-plan.md`](07-mvp-plan.md). Für Abdu
selbst gibt es das [Handbuch](handbuch/handbuch.pdf).
@@ -20,9 +20,85 @@ Auslöser bis zum fertigen Short steht im Code vollständig. Geprüft ist sie
| S5 Onboarding + Sichtung | offen | |
| S6 Erstchatter-Begrüßung | offen | |
162 Tests, alle grün. Was noch fehlt, steht vollständig in
193 Tests, alle grün. Was noch fehlt, steht vollständig in
[`offene-punkte.md`](offene-punkte.md).
## Neu: der Dienst läuft zentral
Seit 2026-09-17 läuft ein Teil des Toolkits nicht mehr auf Abdus Rechner,
sondern in einem LXC auf dem Proxmox-Host. Der Schnitt und seine Begründung
stehen in [ADR-0008](decisions/ADR-0008-geteilter-betrieb.md); die Kurzfassung
ist: alles, was nur Internet braucht, gehört dorthin, wo durchgehend Strom
ist.
| | Hub (LXC 141, `192.168.2.15`) | Ingest (Abdus PC) |
|---|---|---|
| Befehl | `abdulive hub` | `abdulive ingest` |
| Chat, Zuschauerzahlen | ja | ja |
| Discord-Knopf, Go-Live-Ping | **ja** | nein |
| Overlay für OBS | **ja** | nein |
| OBS, `!clip`, Rendern | nein | **ja** |
Erreichbar unter `https://abdu.dimension47.de`, hinter dem vorhandenen Nginx
Proxy Manager (Proxy-Eintrag 42, eigenes Let's-Encrypt-Zertifikat, Pufferung
abgeschaltet - ohne das sammelt nginx die Overlay-Ereignisse und liefert sie
gebündelt aus). Ausgerollt wird mit `scripts/deploy-hub.sh`, gestartet von
systemd (`abdulive-hub`). Der Container hat keinen Zugang zum Repo - das
Skript schiebt den Arbeitsstand als Archiv hinüber.
Der Discord-Teil der Zugangsdaten ist übertragen, der Bot hängt am Hub und
meldet beim Start "Rollen-Knöpfe sind scharf". Die Twitch-Broadcaster-App ist
hinterlegt und geprüft (Client-ID registriert, Secret passt - Twitch stellt
darauf ein App-Token aus). **Was fehlt, ist der Klick von Abdu**, siehe unten.
**Das kostet ADR-0005 seine Hauptbegründung.** "Bindet nur auf 127.0.0.1" gilt
für den Hub nicht mehr. An seine Stelle treten ein Passwort für alles, was ein
Mensch bedient, und ein Schlüssel in der Adresse für die Browser-Sources.
## Der Go-Live-Ping geht raus
War in der letzten Übergabe noch Vorarbeit, ist jetzt gebaut
(`apps/broadcaster`). Ablauf: neuer Stream erkannt, eine Minute warten, Titel
und Kategorie frisch holen, Webhook posten, Rolle `Live-Ping` anpingen.
Der Ping funktioniert nur mit `allowed_mentions` im Nachrichtenkörper. `<@&ID>`
allein färbt die Erwähnung ein und benachrichtigt niemanden - das ist der
Fehler, den man erst nach drei leeren Streams bemerkt, und dagegen steht ein
eigener Test.
Gegen den doppelten Ping stehen drei Dinge, denn wer zweimal in zehn Minuten
angepingt wird, nimmt sich die Rolle wieder ab: die Tabelle `posts` mit einem
eindeutigen Index je Stream, eine Abklingzeit von sechs Stunden, und der
Umstand, dass der Fortsetzungszweig im Polling gar nicht erst auslöst.
Der Hub steht auf `mode: draft` - der Post geht vollständig, aber in
`#technik-log` und ohne Ping. Ein Probelauf ist dort gelandet, der Weg zum
Webhook ist damit nachgewiesen. Zum Scharfschalten: `mode: live` in
`/var/lib/abdulive/config/config.yaml`.
**Ausgelöst hat er noch nie**, und das liegt nicht am Broadcaster, sondern an
Twitch: die Erkennung des Stream-Starts hängt am Helix-Polling, und das
braucht ein Broadcaster-Token. Solange das fehlt, wartet der Ping auf ein
Ereignis, das nie kommt.
## Das Overlay steht
Fünf Browser-Sources unter `apps/overlay-web/public`, ausgeliefert vom
Overlay-Server: Einblendungen, Chat, Statusleiste, Effekte, Pausenbild. Kein
Build, kein Framework - OBS lädt die Dateien direkt.
Die Ereignisse kommen über Server-Sent Events statt WebSocket. Warum, und was
gegen die tote Browser-Source nach einem Neustart getan ist, steht in
[`apps/overlay-server/README.md`](../apps/overlay-server/README.md).
Abos, Raids und Bits kommen über IRC-USERNOTICE herein, das wird jetzt
ausgewertet. **Follows fehlen** - die gibt es nur über EventSub.
Zum Ansehen ohne Stream: auf der Übersichtsseite gibt es unter
**Ausprobieren** für jede Einblendung, jede Chat-Variante und jeden Effekt
einen Knopf. Die Ereignisse gehen nur an die Browser-Sources. Dasselbe im
Dauerbetrieb: `abdulive overlay --demo` oder der Knopf „Vorführung starten".
## Der Discord-Server steht
Dazugekommen seit der letzten Übergabe, und kein Teil des MVP: der
@@ -44,10 +120,14 @@ die Kurzfassung ist, dass bei 5 bis 30 Zuschauern fünf sichtbare Kanäle besser
sind als zwanzig leere.
Ein Teil davon läuft dauerhaft: der Knopf unter dem Willkommenstext, mit dem
man sich die Rolle `Live-Ping` selbst gibt. Er hängt als Gateway-Verbindung im
Ingest-Daemon mit drin und meldet sich als `discordConnected` auf der
Statusseite. **Läuft der Daemon nicht, tut der Knopf nichts** - der Klick
läuft dann in Discords Zeitüberschreitung.
man sich die Rolle `Live-Ping` selbst gibt. Er hängt als Gateway-Verbindung mit
im Prozess und meldet sich als `discordConnected` auf der Statusseite. Seit dem
Umzug in den Hub läuft er durch, auch wenn Abdus Rechner aus ist.
Dazugekommen ist ein vierter Webhook: `technikWebhook` in `#technik-log`. Dort
landet der Go-Live-Post im Entwurfsmodus - dieselbe Nachricht wie im Betrieb,
nur ohne Ping und nur für Mods sichtbar. Wer den Server vor dieser Fassung
eingerichtet hat, muss `abdulive discord:setup` einmal erneut laufen lassen.
## Was jemand wissen muss, bevor er etwas anfasst
@@ -58,9 +138,10 @@ keine Parameter-Properties im Konstruktor, keine `namespace`.
`erasableSyntaxOnly` in der `tsconfig.json` erzwingt das und hat schon einen
echten Fehler gefangen.
**Alles läuft in einem Prozess.** Schwere Arbeit (ffmpeg, whisper) läuft als
Kindprozess und darf abstürzen. Der Ingest darf nie sterben. Was er nicht
mitschreibt, ist für immer weg.
**Alles läuft in einem Prozess** - aber es gibt jetzt zwei davon, Hub und
Ingest (siehe oben). Schwere Arbeit (ffmpeg, whisper) läuft als Kindprozess und
darf abstürzen. Der Chat-Prozess darf nie sterben. Was er nicht mitschreibt,
ist für immer weg.
**Zugangsdaten kommen aus der Weboberfläche**, nicht aus einer Datei. Sie
liegen verschlüsselt neben der Datenbank, aber bewusst nicht *in* ihr. Die
@@ -97,9 +178,16 @@ Zum Entwickeln reicht ein Checkout und Node 24 oder neuer:
```powershell
node apps/cli/main.ts check-env
node --test "packages/**/*.test.ts" "apps/**/*.test.ts"
node apps/cli/main.ts overlay --demo # Overlay ohne Stream ansehen
node scripts/build-package.ts --version 0.5.5 # baut dist/
```
Den Hub ausrollen (braucht SSH auf den Proxmox-Host):
```bash
scripts/deploy-hub.sh
```
Typprüfung, das Einzige wofür npm gebraucht wird:
```powershell
@@ -214,12 +302,25 @@ oben.
## Die drei Dinge, die Abdu tun muss
1. **Broadcaster-Konto verbinden.** Die beiden Twitch-Apps
(dev.twitch.tv/console/apps, Redirect-URI `http://localhost:4792/callback`)
kann auch jemand anders anlegen, sie hängen nicht am Kanal. Was nur Abdu
kann: auf der Einrichtungsseite eingeloggt als er selbst auf „Mit Twitch
verbinden" klicken. Dafür muss die Anwendung auf seinem Rechner laufen,
die Seite hört nur auf `127.0.0.1`.
1. **Broadcaster-Konto verbinden.** Das ist jetzt der Flaschenhals: ohne
Broadcaster-Token gibt es keine Zuschauerzahlen, keine Stream-Sitzungen und
damit auch keinen Go-Live-Ping.
Die beiden Twitch-Apps (dev.twitch.tv/console/apps) kann auch jemand anders
anlegen, sie hängen nicht am Kanal. Seit dem Umzug in den Hub brauchen sie
als Redirect-URI **`https://abdu.dimension47.de/callback`**; die alte
`http://localhost:4792/callback` bleibt daneben stehen, wenn auf dem
Stream-PC weiter eingerichtet werden soll. Twitch vergleicht zeichengenau.
Die App ist inzwischen hinterlegt. Was nur Abdu kann: auf
`https://abdu.dimension47.de/setup` eingeloggt als er selbst auf „Mit
Twitch verbinden" klicken. Der Vorteil gegenüber vorher: das geht von jedem
Gerät, der Stream-PC muss nicht laufen.
Ob die Redirect-URI in der App richtig eingetragen ist, zeigt sich erst bei
diesem Klick. Twitch prüft sie erst nach dem Login und gibt vorher auch bei
einer falschen URI ein freundliches 302 zurück - von außen ist das nicht
nachweisbar.
2. **OBS einrichten**: obs-websocket an (Port 4455), Wiedergabepuffer 90 s,
Hotkey auf „Wiedergabepuffer speichern", getrennte Audiospuren.
3. **Layout umstellen** nach [`obs-layout.md`](obs-layout.md), danach
@@ -248,10 +349,14 @@ in der ganzen Kette, und alles Weitere hängt daran. Seit es `abdulive sources`
gibt, ist auch die Frage beantwortbar, welche OBS-Quelle fremden Text ins Bild
malt. Das war vorher nicht zu erraten.
**Discord braucht jetzt nichts mehr.** Der Server steht, die Webhooks liegen
bereit. Wer als Nächstes `apps/broadcaster` oder `apps/recap` baut, findet
alles vor und muss nur noch posten. Das wäre auch ein guter Nebenweg, wenn der
Stream-PC gerade nicht verfügbar ist, denn anders als S2 hängt daran nichts.
**Den Go-Live-Ping scharf schalten.** Er steht auf `draft`. Einmal
`abdulive golive:test --echt` laufen lassen, prüfen ob das Handy klingelt, dann
`mode: live`. Das ist der kleinste verbleibende Handgriff mit der größten
Wirkung.
**`apps/recap` bauen.** Der Webhook liegt bereit, der Hub hat die Daten, und
anders als S2 hängt daran nichts. Guter Nebenweg, wenn der Stream-PC gerade
nicht verfügbar ist.
**Danach S5.** Nicht wegen des Codes, sondern weil das Werkzeug ohne
Sichtungsseite für Abdu nicht bedienbar ist. Die Kette funktioniert, aber sie
@@ -277,11 +382,20 @@ konkrete Verbesserungen stehen in [`offene-punkte.md`](offene-punkte.md).
Solange nur Abdu installiert, reicht ein Klick. Sobald jemand Drittes es
bekommt, nicht mehr.
**Der Discord-Knopf hängt am Daemon.** Läuft der Ingest nicht, passiert beim
Klick nichts, und die klickende Person sieht nur „Interaktion fehlgeschlagen".
Das ist vertretbar, solange der Daemon ohnehin durchläuft, aber es ist eine
stille Abhängigkeit zwischen zwei Dingen, die sonst nichts miteinander zu tun
haben.
**Der Discord-Knopf hängt am Daemon** - das war der Punkt aus der letzten
Übergabe und ist mit dem Hub erledigt. Dafür gibt es jetzt einen neuen: der
Bot darf nur an **einer** Stelle laufen. Steht auf dem Stream-PC ein
Discord-Bot-Token im Speicher und läuft dort `ingest`, antworten zwei Prozesse
auf denselben Klick. Auf dem Stream-PC bleibt das Discord-Token leer.
**Der Overlay-Schlüssel steht in der Adresse.** Damit steht er in jedem
OBS-Profil und auf jedem Screenshot der Einstellungen. Neu würfeln geht auf
der Übersichtsseite, aber danach müssen alle fünf Adressen in OBS neu
eingetragen werden.
**Das Overlay hat nie unter Last gelaufen.** Geprüft ist es gegen den
Vorführmodus und im Browser. Wie es sich bei 200 Nachrichten pro Minute
verhält, und was der Emote-Regen beim Encoder kostet, ist offen.
**Der Discord-Bot ist nie unter Last gelaufen.** Getestet sind die Bausteine
und ein Klickweg gegen einen nachgebauten Server; am echten Server ist die