289 lines
15 KiB
Markdown
289 lines
15 KiB
Markdown
# Sendewerk
|
|
|
|
Sendewerk ist die redaktionelle Webanwendung für den Weg vom vorbereiteten
|
|
Inhalt bis zur moderierten und archivierten Sendung. Der aktuelle Stand umfasst
|
|
Etappe 1 bis 7: technisches Grundgerüst, Anmeldung und Rollen, Fundus,
|
|
Sendungsbau, Live-Steuerung, Präsentationsausgabe, Abschluss, Archiv,
|
|
Freigaben, RSS sowie technische REST- und MCP-Zugänge.
|
|
|
|
## Voraussetzungen
|
|
|
|
- Linux
|
|
- Node.js 22.17 oder neuer (Node 24 LTS ist für das Cloudron-Image vorgesehen)
|
|
- npm 10 oder neuer
|
|
|
|
Cloudron, Docker, eine Domain und externe Dienste sind für den lokalen Betrieb
|
|
nicht erforderlich.
|
|
|
|
## Frischer lokaler Start
|
|
|
|
```sh
|
|
npm ci
|
|
cp .env.example .env
|
|
SETUP_TOKEN="$(openssl rand -hex 32)"
|
|
sed -i "s/^SENDEWERK_SETUP_TOKEN=.*/SENDEWERK_SETUP_TOKEN=$SETUP_TOKEN/" .env
|
|
printf 'Betreiber-Setup-Token: %s\n' "$SETUP_TOKEN"
|
|
npm run setup
|
|
npm run dev
|
|
```
|
|
|
|
Danach ist die Anwendung unter <http://localhost:5173> erreichbar. `setup`
|
|
erstellt die konfigurierten Verzeichnisse, führt ausstehende Migrationen aus
|
|
und lädt den reproduzierbaren Beispieldatensatz. Der Seed darf beliebig oft
|
|
ausgeführt werden und ersetzt nur die von ihm verwalteten Beispieldaten.
|
|
|
|
Beim ersten Aufruf verlangt `/setup` den ausgegebenen Wert aus
|
|
`SENDEWERK_SETUP_TOKEN` und legt genau einen Barkeeper an. Der Befehl schreibt
|
|
den Wert in die nicht versionierte `.env`, damit auch spätere lokale Starts
|
|
dieselbe Konfiguration verwenden. Nach erfolgreicher Einrichtung ist `/setup`
|
|
dauerhaft geschlossen und der Token kann aus `.env` entfernt werden. Der Seed
|
|
enthält bewusst kein Konto und kein Standardpasswort.
|
|
|
|
## Konfiguration
|
|
|
|
| Variable | Vorgabe | Bedeutung |
|
|
| --------------------------- | ------------------------ | ---------------------------------------------------- |
|
|
| `SENDEWERK_DB_PATH` | `.data/sendewerk.sqlite` | Pfad zur persistenten SQLite-Datei |
|
|
| `SENDEWERK_UPLOAD_DIR` | `.data/uploads` | Persistentes Upload-Verzeichnis |
|
|
| `SENDEWERK_MAIL_OUTBOX_DIR` | `.data/mail-outbox` | Lokale, nicht öffentlich ausgelieferte Testmail |
|
|
| `BODY_SIZE_LIMIT` | `6M` | Requestlimit für private Bild-Uploads bis 5 MB |
|
|
| `SENDEWERK_SETUP_TOKEN` | keine | einmaliges Betreibergeheimnis, mindestens 32 Zeichen |
|
|
| `SENDEWERK_APP_ORIGIN` | `http://localhost:5173` | feste Origin für Links und Cookie-Sicherheit |
|
|
| `SENDEWERK_MAIL_TRANSPORT` | `local` | `local` oder ausdrücklich `smtp` |
|
|
| `SENDEWERK_MAIL_FROM` | lokaler Absender | Absender; bei SMTP verpflichtend |
|
|
| `SENDEWERK_SMTP_HOST` | keine | SMTP-Server, nur im SMTP-Modus |
|
|
| `SENDEWERK_SMTP_PORT` | `587` | SMTP-Port |
|
|
| `SENDEWERK_SMTP_SECURE` | `false` | `true` für implizites TLS, üblicherweise Port 465 |
|
|
| `SENDEWERK_SMTP_USER` | keine | optionaler SMTP-Benutzer |
|
|
| `SENDEWERK_SMTP_PASSWORD` | keine | optionales SMTP-Passwort |
|
|
| `HOST` | adapterabhängig | Bind-Adresse des Produktionsservers |
|
|
| `PORT` | `3000` | Port des Produktionsservers |
|
|
|
|
Relative Sendewerk-Pfade werden vom Projektverzeichnis aus aufgelöst. In
|
|
Cloudron liegen Datenbank und Uploads unter `/app/data`.
|
|
|
|
Eine öffentliche Produktions-Origin muss HTTPS verwenden. Nur für lokale
|
|
Loopback-Adressen bleibt HTTP zulässig.
|
|
|
|
Der Vorgabemodus `local` baut keine Netzwerkverbindung auf. Einladungen und
|
|
Passwort-Resets landen als JSON-Dateien in `.data/mail-outbox`; die enthaltenen
|
|
Links sind Geheimnisse und dürfen nicht veröffentlicht werden. Echter Versand
|
|
findet nur statt, wenn `SENDEWERK_MAIL_TRANSPORT=smtp` und die SMTP-Werte
|
|
explizit gesetzt sind.
|
|
|
|
## Inhalte und private Bilder
|
|
|
|
Neue Gedanken oder Links lassen sich im Eingang schnell erfassen. Im Fundus
|
|
stehen leere Pieces sowie Vorlagen für Gespräch, Reaction/Fundstück und
|
|
Interaktion bereit. Der Editor speichert Änderungen nach kurzer Pause
|
|
automatisch; ein sichtbarer Status meldet Erfolg, Fehler oder eine parallele
|
|
Änderung. In diesem Fall muss die Seite neu geladen werden, bevor erneut
|
|
gespeichert wird.
|
|
|
|
Bereits gesendete Pieces und Pieces mit einer Einplanung in einer noch nicht
|
|
abgeschlossenen Sendung sind standardmäßig ausgeblendet. Als gesendet gilt nur
|
|
eine erledigte Verwendung in einer abgeschlossenen Sendung; übersprungene oder
|
|
reservierte Inhalte gelten nicht als gesendet. Über **Auch verwendete Inhalte
|
|
zeigen** und **Auch eingeplante Inhalte zeigen** lassen sich beide Gruppen
|
|
unabhängig einblenden. Dann erscheinen auch Datum, Sendung und die aufklappbare
|
|
Verwendungshistorie.
|
|
|
|
Markdown ist in Einleitung, Recherche, Moderation, Überleitung und Präsentation
|
|
möglich. Die Ausgabe wird serverseitig auf eine begrenzte Auswahl sicherer
|
|
Elemente und Links reduziert. Bilder dürfen höchstens 5 MB groß sein und müssen
|
|
als JPEG, PNG, WebP oder GIF erkannt werden. Sie liegen im konfigurierten
|
|
Uploadverzeichnis und sind nur über eine angemeldete Route erreichbar.
|
|
|
|
Für die Präsentation kann ein Piece alternativ genau eine seiner vorhandenen
|
|
Quellen als Linkkarte oder Video verwenden. Linktitel, Anreißer und Bildadresse
|
|
lassen sich manuell korrigieren. **Vorschau laden** ruft Metadaten ausdrücklich
|
|
neu ab; manuell geänderte Felder werden dabei nicht überschrieben. Die
|
|
**Präsentation testen**-Ansicht öffnet den aktuellen Piece-Inhalt isoliert und
|
|
verändert weder eine Sendung noch deren laufende Ausgabe.
|
|
|
|
## Sendungen planen
|
|
|
|
Unter **Sendungen** lässt sich ein neuer Stream leer, mit dem Standardablauf
|
|
oder als Spieleabend anlegen. Abschnitte sind frei benennbar und können eine
|
|
explizite Dauer erhalten; andernfalls ergibt sich ihre Dauer aus den
|
|
eingeplanten Pieces. Die Reservebank zählt nie zur Planzeit.
|
|
|
|
Der sendefertige Fundus steht neben dem Ablauf. Pieces können mehrfach
|
|
eingeplant und je Verwendung mit eigenem Titel, eigener Dauer und eigenen
|
|
Redaktionstexten bearbeitet werden, ohne das Original zu verändern. Das
|
|
Stift-Symbol öffnet dafür einen auch mobil scrollbaren Editor-Dialog. **Aus
|
|
Fundus aktualisieren** übernimmt auf Wunsch Titel, Dauer und Redaktionstexte des
|
|
aktuellen Originals; eigene Notizen, Live-Status und gemessene Zeiten bleiben
|
|
erhalten. Abschnitte und Verwendungen lassen sich per Drag-and-drop sowie
|
|
gleichwertig mit sichtbaren Schaltflächen und Auswahlfeldern verschieben.
|
|
Twitch-Titel und Ankündigung sind kopierbar. Gleichzeitige Änderungen werden
|
|
über die Revision der gesamten Sendung erkannt und nicht still überschrieben.
|
|
|
|
Die Quellspalte blendet gesendete und bereits in derselben Sendung eingeplante
|
|
Originale zunächst aus; beide Gruppen lassen sich unabhängig wieder anzeigen.
|
|
Eine konkrete Einplanung kann direkt hinter sich dupliziert werden. Dabei
|
|
bleiben ihre angepassten Texte und ihre Dauer erhalten, während Live-Status und
|
|
gemessene Zeiten neu beginnen. **Piece duplizieren** im Fundus erzeugt dagegen
|
|
weiterhin einen eigenständigen Fundusinhalt.
|
|
|
|
## Live moderieren und präsentieren
|
|
|
|
Die Live-Steuerung öffnet sich aus einer geplanten Sendung. Sie zeigt
|
|
Einleitung, Moderationsstichpunkte und Überleitung in einstellbarer
|
|
Prompter-Schrift; die private Recherche bleibt standardmäßig geschlossen.
|
|
Zeiten und Ablaufleiste können ausgeblendet werden. Schriftgröße, Sichtbarkeit
|
|
und Leseposition bleiben im Browser erhalten.
|
|
|
|
Start, Erledigen, Überspringen und Zurück werden persistent mit tatsächlichen
|
|
Zeitintervallen gespeichert. Wiederholte Befehle erzeugen keine doppelte
|
|
Zeitbuchung. Offene Pieces und die Reserve lassen sich während der Sendung
|
|
sortieren beziehungsweise als nächster Beitrag einfügen.
|
|
|
|
Das separate Präsentationsfenster unter `/heinzlive/...` verwendet einen langen,
|
|
widerrufbaren Zufallstoken. Es ändert sich ausschließlich durch die Aktion
|
|
**Präsentieren** und erhält nur den bei der Einplanung gespeicherten Text-, Link-
|
|
oder Video-Snapshot. YouTube- und TikTok-Adressen werden auf validierte IDs
|
|
reduziert und ohne Autoplay in offiziellen Player-Iframes dargestellt. Andere
|
|
oder unvollständige Videoquellen erscheinen als Linkkarte. Recherche,
|
|
Moderationsnotizen, eigene Notizen, Kontodaten und interne Dateipfade werden
|
|
weder im HTML noch im JSON-Zustand ausgeliefert. Der Token ist ein
|
|
Zugangsgeheimnis und darf nicht weitergegeben oder protokolliert werden.
|
|
|
|
Metadaten und externe Vorschaubilder werden ausschließlich serverseitig mit
|
|
DNS-, Redirect-, Zeit- und Größenlimits abgerufen. Private und lokale
|
|
Netzwerkziele sind gesperrt. Eingebettetes HTML oder Skripte einer Quellseite
|
|
werden nicht übernommen.
|
|
|
|
## MCP und ChatGPT
|
|
|
|
Der lesende Streamable-HTTP-Endpunkt liegt unter `/mcp`. Seine konkrete
|
|
Adresse ist die konfigurierte Anwendungs-Origin mit angehängtem Pfad:
|
|
|
|
```text
|
|
https://<Cloudron-App-Domain>/mcp
|
|
```
|
|
|
|
Beispiel: Bei `SENDEWERK_APP_ORIGIN=https://sendewerk.example.org` lautet die
|
|
MCP-URL `https://sendewerk.example.org/mcp`. Niemals einen technischen
|
|
REST-Token an die URL hängen oder in ChatGPT eintragen. MCP verwendet einen
|
|
eigenen OAuth-2.1-Flow mit Authorization Code, PKCE und kurzlebigen Tokens.
|
|
|
|
Die Verbindung stellt ausschließlich lesende Werkzeuge bereit:
|
|
|
|
- `get_profile` identifiziert das verbundene Sendewerk-Konto ohne E-Mail-Adresse.
|
|
- `list_pieces` und `get_piece` lesen Fundus-Pieces.
|
|
- `list_shows` und `get_show_rundown` lesen Sendungen und deren Abläufe.
|
|
|
|
Die Detailwerkzeuge können private Recherche, Moderationsnotizen, Quellen,
|
|
eigene Notizen und Reserveinhalte liefern. Der angemeldete Nutzer muss diesem
|
|
Umfang ausdrücklich zustimmen. Sperrung oder Passwortwechsel widerrufen
|
|
bestehende OAuth-Tokens; jede MCP-Anfrage prüft Konto, Scope, Ablauf und
|
|
Zielressource erneut. MCP kann keine Inhalte schreiben.
|
|
|
|
### In ChatGPT einrichten
|
|
|
|
1. Zuerst Version `0.8.1` unter der endgültigen öffentlichen HTTPS-Origin
|
|
bereitstellen. Lokalhost ohne öffentliches HTTPS ist für eine direkte
|
|
ChatGPT-Verbindung nicht erreichbar.
|
|
2. In [ChatGPT Plugins](https://chatgpt.com/plugins) das Plus wählen und
|
|
**Add custom MCP server** öffnen.
|
|
3. Einen Namen und eine Beschreibung vergeben und unter **Connection** die
|
|
vollständige URL `https://<Cloudron-App-Domain>/mcp` eintragen.
|
|
4. OAuth als Authentifizierung verwenden, den Risikohinweis prüfen und
|
|
**Create as a plugin** wählen. ChatGPT entdeckt die OAuth-Endpunkte und
|
|
registriert einen öffentlichen PKCE-Client dynamisch.
|
|
5. Im geöffneten Sendewerk-Fenster mit dem eigenen Konto anmelden, Clientname
|
|
und Rücksprung-Domain prüfen und den lesenden Zugriff ausdrücklich erlauben.
|
|
6. Die gefundenen fünf Werkzeuge kontrollieren, das Plugin installieren und in
|
|
einer neuen Unterhaltung über `@` auswählen.
|
|
|
|
Eine erfolgreich lokale OAuth- und MCP-Prüfung beweist noch keine reale
|
|
ChatGPT-Verbindung. Diese muss nach dem Cloudron-Update separat in der
|
|
jeweiligen ChatGPT-Workspace-Konfiguration durchgeführt werden.
|
|
|
|
## Befehle
|
|
|
|
| Befehl | Zweck |
|
|
| -------------------- | ---------------------------------------------------------- |
|
|
| `npm run setup` | Verzeichnisse, Migrationen und Seed für lokale Entwicklung |
|
|
| `npm run db:migrate` | Ausstehende Migrationen atomar ausführen |
|
|
| `npm run db:seed` | Reproduzierbare Beispieldaten laden |
|
|
| `npm run dev` | Migrationen ausführen und Entwicklungsserver starten |
|
|
| `npm run check` | Svelte- und TypeScript-Prüfung |
|
|
| `npm run lint` | Formatierung und ESLint prüfen |
|
|
| `npm test` | Verhaltenstests mit temporärer, getrennter Datenbank |
|
|
| `npm run test:e2e` | Browserprüfungen gegen eine getrennte E2E-Datenbank |
|
|
| `npm run build` | Produktionsartefakt in `build/` erstellen |
|
|
| `npm start` | gebautes Artefakt mit `node build` starten |
|
|
|
|
## Lokaler Produktionslauf
|
|
|
|
```sh
|
|
npm ci
|
|
npm run setup
|
|
export SENDEWERK_APP_ORIGIN=http://localhost:3000
|
|
npm run build
|
|
HOST=127.0.0.1 PORT=3000 npm start
|
|
```
|
|
|
|
Der Healthcheck liegt unter <http://localhost:3000/healthz> und prüft auch den
|
|
Datenbankzugriff.
|
|
|
|
Für lokales HTTP wird das Sitzungscookie gezielt ohne `Secure` gesetzt, bleibt
|
|
aber `HttpOnly` und `SameSite=Lax`. Bei einer `https:`-Origin ist `Secure`
|
|
automatisch aktiv. Die Build-Origin muss beim lokalen Produktionsbuild mit der
|
|
später verwendeten Browser-Origin übereinstimmen; SvelteKits Origin-Prüfung
|
|
bleibt aktiv.
|
|
|
|
Vor der ersten Browserprüfung wird der Testbrowser einmalig installiert:
|
|
|
|
```sh
|
|
npx playwright install chromium
|
|
npm run test:e2e
|
|
```
|
|
|
|
## Container und Cloudron
|
|
|
|
Das frühe Cloudron-Paket besteht aus `Dockerfile`, `CloudronManifest.json` und
|
|
`start.sh`. Ein lokaler Container-Smoke-Test kann nach dem Build so erfolgen:
|
|
|
|
```sh
|
|
docker build -t sendewerk:local .
|
|
docker run --rm -p 3000:3000 \
|
|
-e SENDEWERK_APP_ORIGIN=http://localhost:3000 \
|
|
-e SENDEWERK_SETUP_TOKEN="$(openssl rand -hex 32)" \
|
|
-v sendewerk-data:/app/data sendewerk:local
|
|
```
|
|
|
|
Auf Cloudron muss das Betreiber-Setup-Token vor dem ersten Browseraufruf als
|
|
Umgebungsvariable gesetzt werden. `CLOUDRON_APP_ORIGIN` liefert dort die feste
|
|
HTTPS-Origin. SMTP-Zugangsdaten werden ebenfalls ausschließlich als
|
|
Laufzeitvariablen gesetzt, nicht in Manifest oder Image geschrieben.
|
|
|
|
Für MCP sind keine zusätzlichen Geheimnisse erforderlich. Seit Version `0.7.3`
|
|
führt der bestehende Startprozess Migration `0020_oauth_mcp.sql`
|
|
aus. Anschließend muss die von Cloudron gelieferte HTTPS-Origin mit der in
|
|
ChatGPT eingetragenen MCP-Domain übereinstimmen. Reverse Proxy und öffentliche
|
|
Erreichbarkeit wurden ohne Cloudron-Zugang nicht geprüft.
|
|
|
|
Das ersetzt keinen Test auf einem echten Cloudron. Insbesondere Addon-Backup,
|
|
Restore, Proxy-Origin und das read-only Laufzeitdateisystem können erst dort
|
|
abschließend geprüft werden.
|
|
|
|
## Dokumentation
|
|
|
|
- `docs/ARCHITECTURE.md`: Systemgrenzen und geplanter Gesamtdatenbestand
|
|
- `docs/DECISIONS.md`: technische Entscheidungen und offizielle Quellen
|
|
- `docs/ACCEPTANCE.md`: Abnahmekriterien und konkrete Nachweise
|
|
- `docs/STATUS.md`: aktueller Projektstand und bekannte Grenzen
|
|
|
|
Der Anwendungscode steht unter der MIT-Lizenz. Rechte an später eingestellten
|
|
Medien und redaktionellen Inhalten werden dadurch nicht verändert.
|
|
|
|
Die direkten Laufzeit- und Werkzeugabhängigkeiten verwenden MIT- oder
|
|
Apache-2.0-Lizenzen: Svelte/SvelteKit, Vite, Vitest, ESLint und
|
|
`better-sqlite3`, `@node-rs/argon2`, Tiptap, Marked, sanitize-html, file-type und
|
|
Metascraper sind MIT-lizenziert; Nodemailer verwendet MIT-0, TypeScript und
|
|
Playwright stehen unter Apache-2.0. Die Lockdatei fixiert den tatsächlich
|
|
installierten Baum.
|