Dokumentiert nachträglich das Phase-4/5-Ergebnis des Self-Service-ISO- Plans (04.-07.08.2026, laut Memory 'Phasen 1-5 abgeschlossen', bislang komplett undokumentiert): Hintergrund-Thread-Orchestrierung in provisioning-server, iso_builds-Statustabelle, Alt-Build-Aufräumung wegen anodes begrenztem Plattenplatz, PSK-Verschlüsselung in Kundenplattforms eigener DB statt provisioning-server, getrennte Aufladen-/Neubau-Aktionen, gestreamter Download-Proxy. 13-live-provisioning-boot.md um den Mint/Casper-Payload-Mechanismus ergänzt (Pendant zum dort bereits beschriebenen Fedora/Dracut-Weg, inkl. des gefundenen Casper-ORDER-Bugs: neue Hook-Skriptnamen werden nie ausgeführt, nur das Überschreiben eines bereits referenzierten Pfads funktioniert). 09-data-model-v1.md um iso_builds als neues Kernobjekt ergänzt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.9 KiB
ADR-0014: Self-Service-ISO-Bau-Orchestrierung
Status: Beschlossen Datum: 07.08.2026
Kontext
13-live-provisioning-boot.md beschreibt bereits das konkrete Modell für personalisierte ISOs mit eingebetteten WLAN-Zugangsdaten (Abschnitt „WLAN-Zugangsdaten", 21.07.2026): Self-Service, ausgelöst über einen Kundenprofil-Wunsch. Offen war zu diesem Zeitpunkt, wie der eigentliche ISO-Bau technisch orchestriert wird — er dauert mehrere Minuten (build_customer_iso.sh patcht die Quell-ISO mit xorriso), kann also nicht synchron innerhalb eines HTTP-Requests laufen, und muss sowohl vom internen Betrieb als auch später direkt von Kund:innen auslösbar sein.
Zusätzlich bestehen zwei praktische Randbedingungen: anodes Root-Dateisystem ist ein eng bemessener 28-GB-Loop-Mount (jede gebaute ISO mehrere GB groß), und der Browser einer Kund:in besitzt keinen TUXFLOTTE_KUNDENPLATTFORM_TOKEN, kann provisioning-server also nicht direkt für den Download ansprechen.
Entscheidung
Der ISO-Bau läuft als Background-Thread innerhalb von provisioning-server (run_iso_build(), per threading.Thread(daemon=True) aus dem auslösenden Request heraus gestartet), der build_customer_iso.sh als Subprocess aufruft. Eine neue Tabelle iso_builds (pending/running/completed/failed) ist der gemeinsame Status-Speicher zwischen dem auslösenden POST-Endpoint und den Status-/Download-Endpunkten — kein Message-Queue-System, weil ein einzelner Prozess (anode) sowohl Bau als auch Auslieferung übernimmt und die erwartete Nebenläufigkeit gering ist (kleine Kundenzahl, seltene Bau-Auslösung).
Nach jedem erfolgreichen Bau werden ältere ISO-Builds derselben Organisation aufgeräumt (cleanup_old_iso_builds()) — direkte Konsequenz aus anodes begrenztem Plattenplatz, nicht aus einer allgemeinen Aufbewahrungsrichtlinie.
WLAN-Zugangsdaten werden bewusst nicht in provisioning-servers Datenbank persistiert, nur transient während des Baus verwendet. Die dauerhafte, verschlüsselte Ablage (Fernet, installationsmedium_konfiguration.wifi_psk_encrypted) liegt in Kundenplattforms eigener, getrennter Datenbank (siehe ADR-0011) — ein WLAN-Passwort ist ein echtes Geheimnis, kein Hash-Fall, muss also entschlüsselbar bleiben, um erneut in einen Bau eingehen zu können; die Trennung der Datenbanken bedeutet aber, dass provisioning-server selbst kein Klartext-Passwort dauerhaft vorhält.
Zwei getrennte Aktionen statt einer: „Neues Installationsmedium erstellen" (neuer ISO-Bau, nötig bei geänderten WLAN-Zugangsdaten, da diese in die ISO eingebrannt sind) und „Aufladen" (PATCH /api/v1/enrollment-sessions/{id}, erweitert Gerätekontingent/Gültigkeit einer bestehenden Enrollment Session, siehe 11-device-enrollment.md, ohne die bereits verteilte ISO ungültig zu machen oder neu zu bauen). Eine bereits an Schüler:innen/Klassenräume verteilte ISO bleibt so nutzbar, auch wenn sich nur das Kontingent ändert.
Der Download läuft durch Kundenplattform hindurch gestreamt (GET /installationsmedium/download → anode_stream() → StreamingResponse), nicht als direkter Link auf provisioning-server — der Browser hat kein Service-Token, Kundenplattform ist der einzige Client, der beide Seiten (Kunden-Session, anode-Token) kennt. Kein Read-Timeout auf dieser Verbindung, da eine mehrere GB große Datei über die WireGuard-Verbindung zu anode länger dauern kann als der sonst genutzte 15-Sekunden-Timeout erlaubt.
GET /api/v1/organizations/{id} (Einzelabruf) wurde ergänzt, weil der bestehende Listen-Endpunkt alle Organisationen zurückgibt — für den internen, organisationsübergreifenden Admin-Bereich unproblematisch, für eine reguläre Kund:in im Kundenportal ein Datenleck.
Konsequenzen
ISO_BUILD_OUTPUT_DIR und MINT_ISO_PATH müssen zu absoluten Pfaden aufgelöst werden (Path(...).resolve() beim Modul-Laden) — run_iso_build() startet den Subprocess mit cwd=TUXFLOTTE_INSTALLER_DIR, ein relativer Default-Pfad würde dort statt relativ zum eigentlichen Arbeitsverzeichnis des Diensts gesucht. Ein erster Live-Test auf anode ist genau daran gescheitert (lokale Tests mit absoluten Pfaden hatten das nicht aufgedeckt).
Ein ISO-Bau blockiert den anfragenden Request nicht, aber es gibt aktuell keine Begrenzung gleichzeitig laufender Bau-Threads — bei der aktuellen Kundenzahl kein praktisches Problem, bei deutlich mehr gleichzeitigen Bauten (mehrere Organisationen zur gleichen Zeit) müsste das nachgerüstet werden (Warteschlange oder Parallelitätslimit).
Details zum technischen Payload-Mechanismus für das Mint/Casper-Backend (inklusive eines dabei gefundenen Casper-Bugs): 13-live-provisioning-boot.md. Details zum Datenmodell (iso_builds): 09-data-model-v1.md. installationsmedium_konfiguration (Kundenplattform-eigene Datenbank, WLAN-Zugangsdaten, gewählte Bereitstellungsvorlage) ist nicht Teil dieses provisioning-server-Datenmodells, siehe ADR-0011 zur Datenbanktrennung.