Architektur
Inhalt
Überblick
ITSWEBER Play wird als einzelner Docker-Container ausgeliefert, der genau einen Port (3000) exponiert. Eine Nginx-Instanz innerhalb des Containers multiplext eingehende Anfragen und leitet sie an den jeweils zuständigen internen Service weiter. Docker-Compose-Orchestrierung oder ein Container-Netzwerk werden nicht benötigt — alles läuft in einem Prozessbaum, der von s6-overlay verwaltet wird.
Container-Topologie
┌─────────────────────────────────────────┐
│ itsweber-play:3000 │
│ ┌──────────────────────────────────┐ │
│ │ Nginx :3000 (intern) │ │
│ └──┬──────────────┬────────────────┘ │
│ │ │ │
│ ┌──▼──┐ ┌───▼────┐ │
│ │ Web │ │ API │ │
│ │:3001│ │ :4000 │ │
│ └─────┘ └───┬────┘ │
│ │ │
│ ┌─────┐ ┌──────┐ │ ┌──────┐ │
│ │Redis│ │MinIO │ │ │Pgsql │ │
│ │:6379│ │:9000 │ │ │:5432 │ │
│ └─────┘ └──────┘ │ └──────┘ │
│ ┌───▼────┐ │
│ │ Worker │ │
│ └────────┘ │
└─────────────────────────────────────────┘
Die gesamte Inter-Service-Kommunikation läuft über 127.0.0.1. Das Web-Frontend ist eine Next.js-15-App-Router-Anwendung. Das API ist ein Fastify-Server, der tRPC-Prozeduren und Better-Auth-Endpunkte bereitstellt. Der Worker ist ein BullMQ-Consumer, der FFmpeg- und yt-dlp-Jobs ausführt.
s6-overlay Startreihenfolge
s6-overlay startet die Services anhand ihrer Abhängigkeiten und startet abgestürzte Services automatisch neu. Eine strikt lineare Reihenfolge gibt es nicht.
init-dirs— Legt die Verzeichnisse unter/dataan.postgres-init— Legt beim ersten Start Cluster, Benutzer und Datenbank an, danach startetpostgresauf127.0.0.1:5432.migrate— Wartet auf Postgres und führtprisma migrate deployaus.redis(:6379) undminio(:9000, Konsole:9001, nur loopback) — Starten nachinit-dirs.api(:4000) undworker— Starten erst, wennmigrate,minioundredisbereit sind. Die Buckets legen API und Worker beim Start an, nicht der MinIO-Service.web(:3001) undnginx(:3000) — Hängen nur aninit-dirsund starten deshalb früh. Nginx beantwortet/healthsofort.
Services
| Service | Interner Port | Zweck |
|---|---|---|
| Nginx | 3000 | Request-Multiplexer; leitet /api/* (inkl. /api/auth/* und /api/trpc/*) ans API, /s3/* an MinIO, alles andere ans Web |
| Web (Next.js) | 3001 | SSR-Frontend, Studio, Admin-Panel |
| API (Fastify) | 4000 | tRPC, Better Auth, Upload-Endpunkt, Theme-Events per SSE |
| Worker (BullMQ) | — | FFmpeg-Transcodierung, yt-dlp-Import, Thumbnail-Extraktion |
| Postgres 16 | 5432 | Primäre Datenbank (Prisma ORM) |
| Redis 7 | 6379 | BullMQ-Job-Queues, Rate-Limit-Zähler, Pub/Sub für Theme-Live-Updates |
| MinIO | 9000 | S3-kompatibler Objektspeicher für Videos, Thumbnails, Assets |
Datenflüsse
Upload
Browser → POST /api/upload (ein Request, Body als Stream) → API → MinIO (play-raw)
→ BullMQ-Job "transcode"
Worker → MinIO (raw holen) → ffprobe + FFmpeg → HLS → MinIO (play-videos/)
Studio → fragt den Status per tRPC alle 2 Sekunden ab, solange PENDING/PROCESSING
Externer Import (yt-dlp)
- Nutzer gibt eine URL im Studio ein.
- API legt einen Video-Datensatz mit
status=PENDING,source=EXTERNALund SichtbarkeitPRIVATEan und stellt einen Job in die Queueimport. - Worker lädt die Quelle via
yt-dlpherunter und transcodiert mit FFmpeg. - Das Video wird sichtbar. Die Standard-Sichtbarkeit nach dem Import ist
private— der Nutzer muss es explizit veröffentlichen.
Auth
Browser → POST /api/auth/sign-in/email → Better Auth → Session-Cookie (HTTP-only, SameSite=Lax)
Browser → GET /api/trpc/* → API liest Cookie → validiert Session → gibt Daten zurück
Theme-Update
Admin speichert Token-Änderung → API schreibt in DB → SSE-Ereignis wird gesendet
Browser empfängt SSE → ersetzt <style id="theme-vars"> → Live-Update, kein Reload
Speicher-Layout
MinIO organisiert die Daten in folgenden Buckets:
| Bucket | Inhalt |
|---|---|
play-raw |
Original-Videodateien nach Upload oder Import (bleiben nach der Transcodierung erhalten, Trim nutzt sie erneut) |
play-videos |
HLS-Playlisten und Segmente (.m3u8, init.mp4, .m4s) |
play-thumbs |
Automatisch generierte Thumbnail-Kandidaten und ausgewählte Thumbnails |
play-assets |
Logo, Kanal-Avatare, Banner, Untertitel |
Nur play-raw ist privat. play-videos, play-thumbs und play-assets bekommen beim Start eine anonyme Lese-Policy, der Browser lädt HLS, Thumbnails und Logos direkt über /s3/. Signierte URLs gibt es im Code nicht.
Monorepo-Pakete
| Paket | Zweck |
|---|---|
apps/web |
Next.js-15-App-Router-Frontend |
apps/api |
Fastify + tRPC + Better Auth Backend |
apps/worker |
BullMQ + FFmpeg + yt-dlp Video-Pipeline |
apps/remotion |
Demo-Video-Rendering (Remotion) |
packages/db |
Prisma-Schema und generierter Client |
packages/theme |
Tokens, Presets und CSS-Erzeugung für das Theming (der Live-Editor liegt in apps/web) |
packages/shared |
Gemeinsam genutzte TypeScript-Typen und Enums |
packages/storage |
MinIO-Client-Wrapper |
docker/all-in-one |
Dockerfile, s6-overlay-Service-Definitionen, Nginx-Konfiguration |
docker/unraid |
Unraid Community Apps XML-Template |