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>
33 lines
4.9 KiB
Markdown
33 lines
4.9 KiB
Markdown
# 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-server`s 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.
|