Auch verfügbar in: EN English

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.

  1. init-dirs — Legt die Verzeichnisse unter /data an.
  2. postgres-init — Legt beim ersten Start Cluster, Benutzer und Datenbank an, danach startet postgres auf 127.0.0.1:5432.
  3. migrate — Wartet auf Postgres und führt prisma migrate deploy aus.
  4. redis (:6379) und minio (:9000, Konsole :9001, nur loopback) — Starten nach init-dirs.
  5. api (:4000) und worker — Starten erst, wenn migrate, minio und redis bereit sind. Die Buckets legen API und Worker beim Start an, nicht der MinIO-Service.
  6. web (:3001) und nginx (:3000) — Hängen nur an init-dirs und starten deshalb früh. Nginx beantwortet /health sofort.

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)

  1. Nutzer gibt eine URL im Studio ein.
  2. API legt einen Video-Datensatz mit status=PENDING, source=EXTERNAL und Sichtbarkeit PRIVATE an und stellt einen Job in die Queue import.
  3. Worker lädt die Quelle via yt-dlp herunter und transcodiert mit FFmpeg.
  4. 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