- pve_nfs_export: NFSv4-only via /etc/nfs.conf ([nfsd] vers2/vers3=n; Debian-13-Unit startet nfsd ohne EnvironmentFile, RPCNFSDARGS wirkungslos), Restart-Handler, selbstheilende nfsd-Versionspruefung; verify-production um Export-Root- und Versions-Assertions erweitert - Autorisierte Konvergenz auf dem PVE-Host: Export-Root 100000:970 2775, /proc/fs/nfsd/versions -3 +4 +4.1 +4.2, verify ok=17 changed=0, Idempotenz changed=0 - Berechtigungsmodell (Betreiberentscheidung 30.10.2026): Mode 2777 auf input/- und output/-Baum der Pipeline (LXC-idmap-Diskrepanz guest-root vs. UID 982/983:970) - GNENC-000/001/002, GNAI-017/018/019 auf NFS-auf-PVE-Host-Stand gebracht; CT-101-Verifikation ok=47 changed=0 dokumentiert; AGENTS.md-Seccomp-Hinweis korrigiert Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
17 KiB
Finaler Architekturplan
Dokumenten-ID: GNENC-001-PLAN Titel: CPU-basiertes Video-Encoding auf dem KI-Server Stand: 2026-10-30 Status: Finaler Plan nach Planungsabschluss am 19.10.2026; externe Abhängigkeiten (CT-101, PVE-Host-NFS) umgesetzt (GNAI-019); die operative Pipeline ist in GNENC-002 spezifiziert, das host-weite Monitoring in GNMON-001
1. Zweck und Abgrenzung
Der physische Server ghostnet-ki soll neben den KI-Workloads einen getrennten
Workload zum Encoding von Videodateien ausführen. Der Encoding-Workload gehört
nicht zum LLM-Stack. Seine fachliche Architektur, Bedienung und späteren SOPs
werden deshalb in diesem eigenen Dokumentationsbereich geführt.
Ansible, Podman, Betriebssystem und hostweite Ressourcenregeln bleiben gemeinsame Plattformbestandteile. Sie werden weiterhin ausschließlich im Infrastructure-Repository verwaltet, damit nicht mehrere Repositories unabhängig konkurrierende Sollzustände auf denselben Host anwenden.
Die Pipeline im Detail — Komponenten, Konten, Verzeichnislayout, Datenfluss,
Validierung, Statusmodell — ist in
GNENC-002-PLAN_Pipeline.md spezifiziert. Das
host-weite Monitoring (Systemmetriken, Dashboard) wird im Monitoring-Bereich
unter docs/platform/llm-server/monitoring/ geführt (GNMON). Die
Samba-Anbindung der Pipeline auf CT-101 ist in
GNAI-017
beschrieben.
2. Bestätigte Entscheidungen
| Thema | Entscheidung | Nachweisgrenze |
|---|---|---|
| Repository | Host- und Deployment-Ansible bleibt im bestehenden Infrastructure-Repository | angenommen |
| Dokumentation | Encoding liegt unter docs/platform/llm-server/encoding/ und verwendet die Kennung GNENC; Monitoring unter docs/platform/llm-server/monitoring/ mit der Kennung GNMON; CT 101 unter docs/platform/proxmox-server/lxc/ct-101-samba/ mit GNAI-017 |
umgesetzt |
| Ausführungsidentitäten | drei gesperrte Konten: gn-encoding (UID/GID 982, Worker), gn-download (983, jDownloader2 + VPN-Sidecar), gn-dashboard (984, Dashboard, Monitoring-Bereich); gemeinsame Gruppe gn-media (GID 970) für die Übergabe-Verzeichnisse |
gn-encoding eingerichtet und geprüft; weitere geplant |
| Containertrennung | je Konto ein eigener rootless Podman-Store und ein eigener rootverwalteter Quadlet-Pfad | gn-encoding eingerichtet und verifiziert; weitere geplant |
| Rechenressource | Encoding ist CPU-basiert ohne GPU-Zugriff; der host-seitige Metrics-Collector (Monitoring-Bereich) liest GPU-Metriken ausschließlich lesend für das Dashboard | Planungsentscheidung |
| Parallelität | darf parallel zu AI Production oder AI Testing laufen | Planungsentscheidung |
| Zeitsteuerung | dauerhaft laufende Dienste; der Encoding-Worker startet neue Releases nur nächtlich 00:00–06:00 Uhr Europe/Berlin, laufende Jobs laufen über das Fenster hinaus (kein Abbruch am Fensterende) | Betreiberentscheidung 19.10.2026; geändert gegenüber dem Erststand |
| Download-Stufe | jDownloader2-Container (jlesage/jdownloader-2, gepinnter Digest) liest .dlc aus input/<Profil>/; Event-Scripter verschiebt den Release-Ordner atomar in die Queue |
Betreiberentscheidung 19.10.2026 |
| VPN | der gesamte jDownloader2-Verkehr läuft über einen ProtonVPN-WireGuard-Tunnel: gluetun-Sidecar (qmcgaw/gluetun, gepinnt) unter gn-download, JD2 teilt dessen Netzwerk-Namespace, Kill Switch aktiv (kein Traffic ohne Tunnel); WireGuard-Credentials server-lokal in /etc/ghostnet-download/vpn.env, nicht in git |
Betreiberentscheidung 19.10.2026 |
| Anwendung | Worker-Image als Eigenbau auf Basis des gepinnten HandBrake-Images linuxserver/handbrake:1.11.2-ls48 (sha256:fda92bd1…48a3c7) mit python3 und ffmpeg; die Digests von Worker- und VPN-Image werden nach dem Bau gepinnt |
Betreiberentscheidung 19.10.2026 |
| Speicherort | Pipeline-Daten liegen auf dem PVE-Host-Medienverbund (/media/datenhdd3; per LXC-Mount auch in CT-101 sichtbar): dort liegt die Medienbibliothek; ghostnet-ki selbst hat nur eine 2-TB-NVMe (OS + LLM) |
Betreiberentscheidung 19.10.2026; NFS seit 30.10.2026 auf dem PVE-Host (GNAI-019 Entscheidung 2) |
| Ein-/Ausgabe | NFS-Export 10.0.0.2:/media/datenhdd3/Encoding (PVE-Host, Rolle pve_nfs_export, NFSv4-only, nur für 10.0.0.90, root_squash), gemountet auf /var/lib/ghostnet-encoding; Verzeichnislayout input/, jd/, queue/, work/, output/, state/ |
Betreiberentscheidung 19.10.2026; Umsetzung GNAI-019 (Revision 30.10.2026); Mount-Abnahme durch das Encoding-Ansible ausstehend |
| Abholung | fertige Outputs per SMB über CT-101: Share [Encode] (output/, RW für ghost, lesend für daidalaw/debugofen) — Entnahme per mv in die Bibliotheks-Shares räumt output/ auf; optionaler Clean-Job löscht Dateien nach 30 Tagen aus output/; kein smbd auf ghostnet-ki |
Betreiberentscheidung 19.10.2026 |
| Samba-Integration (CT-101) | CT-101 ist per Ansible adoptiert (GNAI-019; verifiziert 30.10.2026, ok=47 changed=0): Shares [Encode]/[Encode-In]/[Encoding], Gruppe gn-media (GID 970) mit ghost als Mitglied; .dlc-Drop über [Encode-In]. NFS liegt nicht auf CT-101 (PVE-Host); die guest-seitige GID 970 gleicht die Host-GID 970 wegen des LXC-idmap nicht ab — Berechtigungsmodell: Mode 2777 auf den SMB-berührten Verzeichnissen (Betreiberentscheidung 30.10.2026, GNENC-002 Abschnitt 4) |
Betreiberentscheidung 19.10.2026; GNAI-017/GNAI-019 |
| Profile | drei versionierte HandBrake-Presets (SVT-AV1 10-bit, MKV, Audio-Passthrough) unter llm-server/encoding/profiles/; Auswahl über gleichnamige Ordner unter input/ bzw. queue/ |
Betreiberentscheidung 19.10.2026; Presets versioniert |
| Warteschlange | dateibasiert: .dlc-Drop in input/<Profilname>/, jDownloader2 befüllt queue/<Profilname>/<Release>/, der Worker arbeitet die Queue mit genau einem gleichzeitigen Encode ab |
Betreiberentscheidung 19.10.2026 |
| Filter | Nicht-Video-Dateien und Dateien < 100 MB werden ignoriert; alle übrigen Videodateien eines Releases werden encodet (keine Hauptdatei-Auswahl) | Betreiberentscheidung 19.10.2026 |
| Validierung | HandBrakeCLI-Exit 0, nicht-leere Zieldatei, vorhandener Videostream, fehlerfreie ffmpeg-Volldecodierung (leere stderr) und ffprobe-Dauerabweichung ≤ 1 s | Betreiberentscheidung 19.10.2026 |
| Fehlerbehandlung | fehlerhafte Zieldatei löschen, Quelldatei nach output/_failed/<Release>/ verschieben (Quarantäne pro Datei); erfolgreiche Dateien desselben Releases bleiben verschont |
Betreiberentscheidung 19.10.2026; geändert gegenüber nächtlichem Retry |
| Aufbewahrung | Quelldatei wird nach erfolgreichem Encoding und Validierung gelöscht; Output bleibt bis zur Abholung; work/ wird pro Lauf geleert; done-Status wird nach 30 Tagen gelöscht |
Betreiberentscheidung 19.10.2026 |
| Statusmodell | eine JSON-Datei pro Videodatei unter state/jobs/, atomar geschrieben (Encoding); Systemmetriken in state/system.json werden vom Metrics-Collector des Monitoring-Bereichs bereitgestellt |
Betreiberentscheidung 19.10.2026 |
| Statusanzeige | Dashboard im Monitoring-Bereich (GNMON-001): Container unter gn-dashboard (Python/Flask), nur lesende Mounts; erreichbar über NPM (CT-103) mit Keycloak-Absicherung |
Betreiberentscheidung 19.10.2026 |
| Ressourcenlimits | Quadlet-/cgroup-Limits: Worker MemoryMax 16G, Nice 10, IOWeight 50; jDownloader2 MemoryMax 2G, CPUWeight 50, IOWeight 20, Nice 10; VPN-Sidecar MemoryMax 256M, CPUWeight 50; keine CPU-Obergrenze fürs Encoding | Betreiberentscheidung 19.10.2026; Feinjustierung nach Lasttest offen |
| Zugangsdaten | jDownloader2-Credentials und VPN-WireGuard-Config liegen server-lokal unter /etc/ghostnet-download/ (jdownloader/, vpn.env; root-owned, nicht in git); im Repository nur Platzhalter/Beispiele |
Betreiberentscheidung 19.10.2026 |
3. Zielbild
flowchart LR
HOST[ghostnet-ki]
PROD[gnai-production\nrootless Podman]
TEST[gnai-testing\nrootless Podman]
ENC[gn-encoding\nrootless Podman]
DL[gn-download\nrootless Podman]
DASH[gn-dashboard\nrootless Podman]
NAS[PVE-Host NFS 10.0.0.2\nCT-101 SMB 10.0.0.9]
GPU[beide GPUs]
CPU[CPU / I-O Budget]
TIMER[nächtliche Fensterlogik\nim Worker]
HOST --> PROD
HOST --> TEST
HOST --> ENC
HOST --> DL
HOST --> DASH
HOST -->|"NFS-Mount /var/lib/ghostnet-encoding"| NAS
PROD --> GPU
TEST --> GPU
ENC --> CPU
DL --> CPU
DASH --> CPU
TIMER --> ENC
AI Production und AI Testing bleiben untereinander exklusiv. Der Encoding-Workload ist von dieser KI-Umschaltung unabhängig und darf parallel zur jeweils aktiven KI-Umgebung laufen. Hostwartung kann weiterhin einen Stopp aller Workloads erfordern.
Der Datenfluss der Pipeline folgt dem Filesystem-Prinzip: Jede Komponente liest
den Output-Ordner des vorangegangenen Schritts; Übergaben erfolgen per atomarem
mv innerhalb des NFS-Mounts. Der Download-Verkehr von jDownloader2 läuft
ausschließlich über den VPN-Tunnel.
flowchart LR
DLC["input/<Profil>/*.dlc"]
VPN["gluetun-Sidecar (ProtonVPN, Kill Switch)"]
JD2["jDownloader2-Container (gn-download)"]
QUEUE["queue/<Profil>/<Release>/"]
WORKER["Encoding-Worker (gn-encoding)"]
OUT["output/<Profil>/<Release>/"]
FAILED["output/_failed/<Release>/"]
STATE["state/jobs/*.json"]
DASH["Dashboard (gn-dashboard, Monitoring)"]
DLC --> JD2
JD2 -.->|"teilt Netzwerk-Namespace"| VPN
JD2 -->|"atomares mv"| QUEUE
QUEUE --> WORKER
WORKER -->|"validiert"| OUT
WORKER -->|"Fehler"| FAILED
WORKER --> STATE
STATE --> DASH
4. Ressourcen- und Zeitsteuerung
Die Encoding-Pipeline läuft als dauerhaft laufende systemd-User-Dienste der
Konten gn-encoding (Worker), gn-download (jDownloader2 und VPN-Sidecar) mit
Restart=on-failure. Der Worker prüft vor jedem neuen Release, ob das
nächtliche Fenster (00:00–06:00 Uhr Europe/Berlin) aktiv ist, und idlet
außerhalb des Fensters. Ein laufendes Release wird am Fensterende nicht
abgebrochen, sondern zu Ende geführt (Betreiberentscheidung 19.10.2026; ersetzt
den früheren geordneten Stopp mit nächtlichem Neustart).
Da der Worker außerhalb des Fensters idlet, entsteht tagsüber praktisch keine Last durch das Encoding. Die Ressourcenlimits schützen daher vor allem gegen Fehlerfälle und stellen sicher, dass selbst ein versehentlich tagsüber laufender Encode die Inferenz nicht verdrängt:
| Ressource | Encoding-Worker | jDownloader2 | VPN-Sidecar | Dashboard |
|---|---|---|---|---|
| MemoryMax | 16G | 2G | 256M | 512M |
| CPUWeight | 100 | 50 | 50 | 50 |
| Nice | 10 | 10 | — | — |
| IOWeight | 50 | 20 | — | — |
Es gibt keine CPU-Obergrenze für den Worker: Nachts stehen alle Kerne zur
Verfügung. Konkrete Grenzwerte werden nach einem repräsentativen Encoding-Test
unter gleichzeitiger Inferenzlast feinjustiert (Abschnitt 7). gn-encoding
erhält keine Mitgliedschaft in GPU-Gruppen und keine /dev/kfd- oder
/dev/dri-Weitergabe; gn-dashboard ebenso nicht — der host-seitige
Metrics-Collector liest GPU-Metriken ausschließlich lesend für das Dashboard.
5. Repository-Grenzen
Am 19.10.2026 wurde das LLM-Server-Projekt fachlich in ein Hauptverzeichnis pro
Aufgabe aufgeteilt; die Struktur folgt den Bereichen unter
docs/platform/llm-server/:
llm-server/
├── host/ansible/ # gemeinsame Plattform des physischen Servers
│ ├── playbooks/
│ │ ├── host.yml # host_baseline, podman_runtime (shared), workload_accounts, amd_gpu_access
│ │ ├── preflight.yml
│ │ ├── bootstrap-deploy-account.yml
│ │ ├── verify-deploy-access.yml
│ │ └── verify-rocm-container.yml
│ └── roles/
│ ├── host_baseline/
│ ├── workload_accounts/
│ ├── amd_gpu_access/
│ └── rocm_container_probe/
├── llm/ # LLM-Stack; entsteht mit dem AI-Vertical-Slice (GNAI-001 Meilenstein 2)
├── encoding/
│ ├── ansible/ # Encoding-Workload (Playbooks + Rollen)
│ ├── images/ # Containerfiles (Worker)
│ ├── profiles/ # versionierte HandBrake-Presets
│ └── scripts/ # Worker- und Event-Scripter-Skripte
└── monitoring/
├── ansible/ # Monitoring (Collector-Service, Dashboard-Quadlet)
├── images/ # Containerfile (Dashboard)
└── scripts/ # Metrics-Collector
Die Samba-LXC CT-101 wird als eigener Bereich unter proxmox/lxc/ct-101-samba/
geführt (Dokumentation GNAI-017 unter docs/platform/proxmox-server/lxc/ct-101-samba/).
host.yml verwaltet ausschließlich gemeinsame Plattformzustände; die
gemeinsame Rolle workload_accounts legt dabei auch die Konten gn-encoding,
gn-download und gn-dashboard an. video-encoding.yml verwaltet den
Encoding-spezifischen Zustand — Pfade, Images, Quadlets und Health Checks —
und setzt die Host-Baseline voraus. Ein künftiges ai.yml im llm/-Projekt
darf nur KI-Zustände verändern; ein monitoring.yml im monitoring/-Projekt
verwaltet den Monitoring-Zustand (Konto gn-dashboard, Collector, Dashboard).
Ein späteres site.yml kann die unterstützte Gesamtkonfiguration in definierter
Reihenfolge anwenden.
Eigene Programme liegen versioniert unter llm-server/encoding/scripts/ bzw.
llm-server/monitoring/scripts/, Containerfiles unter images/ und die
Presets unter llm-server/encoding/profiles/ (Betreiberentscheidung 19.10.2026).
Ein separates Quellcode-Repository wird erst benötigt, wenn eine Oberfläche oder
ein umfangreicher Dienst eigenständig entwickelt wird. Ein solches Repository
erzeugt ein versioniertes Artefakt oder Image; ausschließlich das
Infrastructure-Repository legt fest, welche Version auf dem Server läuft.
6. Aktueller Implementierungsstand
Die gemeinsame Rolle workload_accounts hat am 20.09.2026 eingerichtet:
Konto: gn-encoding (UID/GID 982)
Konfiguration: /etc/ghostnet-encoding
Daten: /var/lib/ghostnet-encoding
Eingang: /var/lib/ghostnet-encoding/input
Ausgang: /var/lib/ghostnet-encoding/output
Arbeitsdaten: /var/lib/ghostnet-encoding/work
Quadlets: /etc/containers/systemd/users/982
Podman-Store: /var/lib/ghostnet-encoding/.local/share/containers/storage
Passwort und interaktive Shell sind gesperrt. Der systemd-User-Manager läuft dank Linger unabhängig von einer Anmeldung. Der Podman-Store wurde als rootless und getrennt von beiden KI-Stores geprüft. Es wurden noch keine Images oder Container für den Encoding-Workload angelegt.
Am 19.10.2026 wurde das Encoding-Ansible-Projekt unter
llm-server/encoding/ansible/ eingerichtet. video-encoding.yml verwaltet und
verifiziert seitdem den Encoding-Grundzustand: Konto-Identität und Passwort-
sperre, subordinate ID-Bereiche, Verzeichnisstruktur, Quadlet-Verzeichnis,
Linger und aktiver User-Manager, getrennter rootless Podman-Store sowie die
CPU-Isolation (keine GPU-Gruppenmitgliedschaft, kein Lesezugriff auf /dev/kfd
oder DRM-Render-Nodes). Solange die Rolle kein Encoder-Image gesetzt hat, stellt
sie zusätzlich sicher, dass weder Quadlet-Dateien noch Container für
gn-encoding existieren. Check-Mode und zwei aufeinanderfolgende Läufe meldeten
jeweils ok=30 changed=0 failed=0. Das gepinnte HandBrake-Image
linuxserver/handbrake:1.11.2-ls48 (sha256:fda92bd1…48a3c7) wurde
anschließend in den rootless Store von gn-encoding gezogen; der Pull
bestätigte den Manifest-Digest.
Am 19.10.2026 wurde die Planung abgeschlossen (Abschnitte 2 und 7). Die
externen Abhängigkeiten sind umgesetzt: CT-101 ist per Ansible adoptiert
(GNAI-019, verifiziert 30.10.2026), der NFS-Export läuft auf dem PVE-Host
(Rolle pve_nfs_export); der PVE-Host ist konvergiert (Export-Root
100000:970 2775, NFSv4-only wirksam, verify-production ok=17 changed=0,
Idempotenz changed=0; autorisierter Lauf 30.10.2026). Das
Berechtigungsmodell für die SMB-Schreibpfade ist entschieden (Mode 2777,
GNENC-002 Abschnitt 4); offener Restpunkt ist die NFSv4.2-Mount-Abnahme von
ghostnet-ki. Die Implementierungsphase auf
ghostnet-ki baut als Nächstes auf: die Konten gn-download und gn-dashboard
samt Gruppe gn-media (GID 970), den NFS-Mount von 10.0.0.2, die
Image-Bauten mit Digest-Pinning (Worker, VPN-Sidecar, Dashboard), die
Quadlet-Units für jDownloader2, VPN-Sidecar, Worker und Dashboard, den
host-seitigen Metrics-Collector sowie die Skripte aus scripts/. Die Rolle
video_encoding stellt bis dahin sicher, dass weder Quadlet-Dateien noch
Container für gn-encoding existieren.
7. Offene Entscheidungen
- Feinjustierung der Ressourcenlimits nach einem repräsentativen Encoding-Test unter gleichzeitiger Inferenzlast (Abschnitt 4).
Diese Werte werden nicht vorab in Playbooks angenommen. Die am 30.10.2026 offenen Punkte Berechtigungsmodell (Mode 2777, GNENC-002 Abschnitt 4) und PVE-Host-Konvergenz (GNAI-017 Abschnitt 6.9) sind entschieden beziehungsweise erledigt.