Files
infrastructure/docs/platform/llm-server/encoding/GNENC-002-PLAN_Pipeline.md
T

12 KiB
Raw Blame History

Encoding-Pipeline

Dokumenten-ID: GNENC-002-PLAN Titel: Encoding-Pipeline: Komponenten, Konten, Verzeichnislayout und Datenfluss Stand: 2026-10-19 Status: Finaler Plan nach Betreiberentscheidungen vom 19.10.2026 Vorgänger: GNENC-001-PLAN_Architecture.md Monitoring: GNMON-001-PLAN_Architecture.md Samba/NFS (CT-101): GNAI-017-PLAN_Samba-NFS-Export.md

1. Architekturprinzip

Jede Komponente führt einen Schritt aus und lebt in einem Podman-Container (rootless, eigener Store je Konto). Der Handover erfolgt ausschließlich über das Filesystem auf dem NFS-Mount /var/lib/ghostnet-encoding: Jede Komponente liest einen Input-Ordner, der der Output-Ordner des vorangegangenen Schritts ist. In-Progress-Daten liegen in Work-Ordnern; Übergaben erfolgen per atomarem mv (rename innerhalb desselben Mounts). Dadurch erkennt der nächste Schritt niemals halbfertige Dateien.

2. Komponenten

Komponente Konto Image Aufgabe Betrieb
VPN-Sidecar gn-download (983) qmcgaw/gluetun (gepinnter Digest) ProtonVPN-WireGuard-Tunnel mit Kill Switch; jDownloader2 teilt seinen Netzwerk-Namespace, gesamter Download-Verkehr läuft durch den Tunnel dauerhaft
Download gn-download (983) jlesage/jdownloader-2 (gepinnter Digest) liest .dlc aus input/<Profil>/, lädt über den VPN-Tunnel herunter, entpackt nach jd/, Event-Scripter verschiebt den Release-Ordner atomar nach queue/<Profil>/<Release>/ und räumt die .dlc auf dauerhaft
Encoding-Worker gn-encoding (982) Worker-Image (Eigenbau, gepinnt) scannt queue/, filtert, encodet alle gültigen Videodateien, validiert, räumt auf, schreibt Status, Housekeeping dauerhaft; Fenster-Check 00:00–06:00 Europe/Berlin

Dashboard und Metrics-Collector werden im Monitoring-Bereich betrieben (GNMON-001); das Dashboard liest state/ read-only.

3. Konten und Berechtigungen

Konto UID/GID Sub-ID-Bereich Home Config Gruppen
gn-encoding 982 493216–558751 /var/lib/ghostnet-encoding /etc/ghostnet-encoding gn-media
gn-download 983 558752–624287 /var/lib/ghostnet-download /etc/ghostnet-download gn-media
gn-dashboard 984 624288–689823 /var/lib/ghostnet-dashboard /etc/ghostnet-dashboard gn-media (nur lesend)

Gruppe gn-media: gemeinsame Gruppe der drei Konten für die Übergabe- Verzeichnisse und den lesenden Zugriff auf state/; GID 970, auf ghostnet-ki und CT-101 identisch angelegt (GNAI-017). Alle Konten sind gesperrt (Passwort !, Shell /usr/sbin/nologin, Linger aktiv).

4. Verzeichnislayout

/var/lib/ghostnet-encoding/          (NFS-Mount: 10.0.0.9:/media/datenhdd3/Encoding, CT-101)
├── input/<Profil>/                  gn-media 2770    Drop-Ziel, nur .dlc
├── jd/                              gn-download       JD2-Arbeitsverzeichnis
│   ├── <Release>/                   Download + Entpacken
│   ├── dlc_done/                    erfolgreich abgearbeitete .dlc
│   └── failed/                      .dlc mit Download-Fehler
├── queue/<Profil>/<Release>/        gn-media 2770    Roh-Queue, atomar befüllt
├── work/<Release>/                  gn-encoding 0750 Encode-Ziele in Arbeit
├── output/
│   ├── <Profil>/<Release>/          gn-encoding       Erfolge bis Abholung
│   └── _failed/<Release>/           gn-encoding       Quarantäne
└── state/
    ├── jobs/<job_id>.json           gn-encoding       Status pro Videodatei
    ├── tmp/                         gn-encoding       atomare Schreib-Zwischenlage
    └── system.json                  Collector         Systemmetriken (Monitoring)

state/ ist gn-encoding:gn-media mit Mode 2750, damit gn-dashboard lesen kann; state/jobs/ und state/tmp/ bleiben 0750 (nur Worker). state/system.json wird vom Metrics-Collector des Monitoring-Bereichs mit 0644 geschrieben.

Handover-Regeln:

  • Übergabe immer per atomarem mv innerhalb des Mounts; Work-Dateien liegen nie in Übergabe-Ordnern.
  • input/, jd/, queue/ mit Mode 2770 (setgid), damit neue Dateien die Gruppe gn-media erben.
  • jDownloader2 läuft mit UMASK=022, damit der Worker die Dateien lesen kann.
  • Der Worker löscht Quelldateien und geleerte Release-Ordner aus queue/; Löschrechte folgen aus dem Schreibrecht der Gruppe am Verzeichnis (kein Sticky-Bit).
  • work/ wird pro Lauf geleert; state/tmp/ dient nur atomaren Schreibzugriffen.

Abholung: Die fertigen Outputs werden über CT-101 per SMB abgeholt: Share [Encode] (Pfad /media/datenhdd3/Encoding/output, RW für ghost, lesend für daidalaw/debugofen). Die Entnahme per mv in die Bibliotheks-Shares (Filme/Serien/Anime) räumt output/ auf. Ein optionaler Clean-Job (Worker-Housekeeping, Abschnitt 6) löscht Dateien aus output/, die älter als 30 Tage sind. Auf ghostnet-ki läuft kein smbd. Der .dlc-Drop erfolgt über Share [Encode-In] (Pfad /media/datenhdd3/Encoding/input, RW für ghost) — siehe GNAI-017.

5. Ablauf: Download (jDownloader2 über VPN)

  1. .dlc-Datei in input/<Profil>/ ablegen (Profil = Preset-Name) — per SMB über die Share Encode-In oder direkt auf dem Mount.
  2. jDownloader2 lädt über den VPN-Tunnel herunter und entpackt nach jd/<Release>/.
  3. Event-Scripter (versioniert in git, ohne Secrets, per Ansible in die JD2-Config deployed):
    • Paket fertig: atomares mv jd/<Release>/ queue/<Profil>/<Release>/; .dlc nach jd/dlc_done/.
    • Download-Fehler: .dlc nach jd/failed/.
  4. Credentials liegen server-lokal in /etc/ghostnet-download/jdownloader/ (root-owned, Gruppe gn-download, Mode 0750, als /config gemountet). Im Repository liegt nur ein Platzhalter/Beispiel; Ansible prüft Existenz, nie Inhalt.
  5. Die Web-UI des Images wird nicht extern freigegeben; Zugriff nur über localhost/SSH-Tunnel.

VPN-Tunnel (gluetun-Sidecar):

  • Image qmcgaw/gluetun (Digest gepinnt), Konto gn-download; JD2-Container mit Network=container:<vpn> teilt den Netzwerk-Namespace — JD2 hat keine eigene IP, der gesamte Verkehr verlässt den Host über ProtonVPN.
  • ProtonVPN über WireGuard (VPN_SERVICE_PROVIDER=protonvpn); die Config (Private Key, Server, Land) stammt aus dem ProtonVPN-Account und liegt server-lokal in /etc/ghostnet-download/vpn.env (root-owned, 0640 root:gn-download, per EnvironmentFile= geladen). Im Repository nur vpn.env.example.
  • Kill Switch aktiv: gluetuns Firewall blockiert jeglichen Nicht-VPN-Traffic, sobald der Tunnel fällt.
  • Quadlet: NET_ADMIN + --device /dev/net/tun. TUN in der rootless User- Namespace wird bei der Umsetzung auf dem Host verifiziert; Fallback ist ein host-seitiger WireGuard-Service mit Policy-Routing für den Download-Pfad.
  • Hinweis: Hoster limitieren gelegentlich VPN-IPs; bei stockenden Downloads Serverland in vpn.env wechseln. Die Management-VPN (CT-105) bleibt davon unberührt.

6. Ablauf: Encoding-Worker

Pro Release (älteste zuerst, genau ein gleichzeitiger Encode), pro Videodatei sequenziell:

  1. Scan & Filter: queue/<Profil>/<Release>/ durchsuchen. Video-Endungen (case-insensitiv): mkv, mp4, m4v, avi, mov, wmv, flv, webm, mpg, mpeg, ts, m2ts, vob, 3gp, ogv. Dateien < 100 MB und Nicht-Video-Dateien werden ignoriert. Es gibt keine Hauptdatei; alle gültigen Videodateien werden encodet.
  2. Status queued → encoding in state/jobs/<job_id>.json (inkl. Fortschritt aus dem HandBrakeCLI-Output).
  3. Encode: HandBrakeCLI --import-preset <Profil>.json -Z "<Profil>" -i <quelle> -o work/<Release>/<stem>.mkv. Presets liegen unter profiles/, werden per Ansible auf den Host deployed und read-only gemountet.
  4. Validierung (am Ziel in work/, vor der Übergabe); Status verifying:
    • HandBrakeCLI-Exit 0
    • Zieldatei nicht leer
    • ffprobe findet mindestens einen Videostream
    • ffmpeg -v error -i <ziel> -f null -: Exit 0 und leere stderr (ffmpeg dekodiert tolerant; Fehlerzeilen auf stderr gelten als Defekt)
    • ffprobe-Dauerabweichung Ziel vs. Quelle ≤ 1 s
  5. Erfolg: atomares mv nach output/<Profil>/<Release>/, Quelldatei löschen, Status done. Fehler: Zieldatei löschen, Quelldatei nach output/_failed/<Release>/ verschieben, Status failed mit Fehlermeldung. Übrige Dateien des Releases werden weiter verarbeitet.
  6. Release-Ordner in queue/ löschen, sobald leer.

Zeitfenster: Vor jedem neuen Release prüft der Worker, ob 00:00–06:00 Uhr Europe/Berlin aktiv ist; außerhalb idlet er. Ein laufendes Release wird nicht abgebrochen und darf über 06:00 hinaus laufen.

Housekeeping (optional, standardmäßig aktiviert, deaktivierbar): Im Idle-Durchlauf löscht der Worker Dateien in output/, die älter als 30 Tage sind (Clean-Job als Sicherheitsnetz; die Abholung per mv räumt regulär auf).

7. Statusmodell

Eine JSON-Datei pro Videodatei unter state/jobs/<job_id>.json, atomar geschrieben (Schreibzugriff in state/tmp/, dann mv):

{
  "job_id": "<Release>__<Dateiname>",
  "release": "<Release>",
  "profile": "AV1 MKV 1080p30 RF25",
  "source": "queue/<Profil>/<Release>/<Datei>",
  "target": "output/<Profil>/<Release>/<Datei>.mkv",
  "status": "queued | encoding | verifying | done | failed",
  "progress_pct": 0.0,
  "source_duration_s": 7231.4,
  "target_duration_s": 7231.2,
  "source_bytes": 0,
  "target_bytes": 0,
  "started_at": "…",
  "finished_at": "…",
  "error": null
}

Lebenszyklus: queued → encoding → verifying → done | failed. Aufbewahrung: done nach 30 Tagen gelöscht; failed bis zur manuellen Prüfung.

state/system.json (Systemmetriken) wird vom Metrics-Collector des Monitoring-Bereichs bereitgestellt (GNMON-001).

8. Ressourcenlimits

Quadlet-/cgroup-Limits, in der User-Session durchgereicht:

Ressource Encoding-Worker jDownloader2 VPN-Sidecar
MemoryMax 16G 2G 256M
CPUWeight 100 50 50
Nice 10 10 —
IOWeight 50 20 —

Keine CPU-Obergrenze fürs Encoding. Feinjustierung nach einem repräsentativen Lasttest unter Inferenzlast (GNENC-001 Abschnitt 4).

9. Monitoring (Auslagerung)

Dashboard und Metrics-Collector werden im Monitoring-Bereich geplant und betrieben (GNMON-001, llm-server/monitoring/). Die Encoding-Pipeline stellt state/jobs/*.json bereit; der Collector schreibt state/system.json; das Dashboard mountet state/ und queue/ read-only und zeigt Ressourcen sowie Encoding-Übersicht. Das Dashboard hat keinen GPU- und keinen Schreibzugriff.

10. Images

Image Basis Inhalt Pinning
Worker linuxserver/handbrake:1.11.2-ls48 @ sha256:fda92bd1…48a3c7 + python3, ffmpeg Digest nach Bau in Ansible-Vars gepinnt; Build rootless als gn-encoding
jDownloader2 jlesage/jdownloader-2 — Digest gepinnt; Pull als gn-download
VPN-Sidecar qmcgaw/gluetun — Digest gepinnt; Pull als gn-download

Containerfiles unter llm-server/encoding/images/, Skripte unter llm-server/encoding/scripts/, Presets unter llm-server/encoding/profiles/. Skripte und Presets werden per Ansible auf den Host deployed und read-only in die Container gemountet (Updates ohne Image-Rebuild, versioniert über git).

11. Offene Punkte

  1. Feinjustierung der Ressourcenlimits nach Lasttest unter Inferenzlast (GNENC-001 Abschnitt 4).
  2. Rootless-TUN-Verifizierung für den VPN-Sidecar auf dem Host (Fallback: host-seitiger WireGuard mit Policy-Routing).
  3. UID/GID-Abgleich: Gruppe gn-media (GID 970) muss auf CT-101 identisch existieren; ghost Mitglied für die Abholung per mv (GNAI-017).