docs: ADR-0014 Self-Service-ISO-Bau-Orchestrierung
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>
This commit is contained in:
parent
a2a34e568f
commit
a8e67b0c96
32
adr/0014-self-service-iso-bau-orchestrierung.md
Normal file
32
adr/0014-self-service-iso-bau-orchestrierung.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# 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.
|
||||||
@ -25,6 +25,7 @@ Die konkrete SQL-Implementierung und das Migrationssystem werden getrennt von di
|
|||||||
- Auftragszuweisung
|
- Auftragszuweisung
|
||||||
- Auftragsereignis
|
- Auftragsereignis
|
||||||
- Activation Code
|
- Activation Code
|
||||||
|
- ISO Build
|
||||||
- Network Profile
|
- Network Profile
|
||||||
- Secret Reference
|
- Secret Reference
|
||||||
|
|
||||||
@ -462,6 +463,31 @@ Beziehungen:
|
|||||||
|
|
||||||
- gehört zu genau einer Organization
|
- gehört zu genau einer Organization
|
||||||
|
|
||||||
|
## ISO Build
|
||||||
|
|
||||||
|
Ein ISO Build beschreibt einen Bauvorgang einer personalisierten Kunden-Installations-ISO (siehe ADR-0014, `13-live-provisioning-boot.md`). Läuft als Hintergrund-Thread in `provisioning-server`, diese Tabelle ist dessen Status-Speicher.
|
||||||
|
|
||||||
|
Nach jedem erfolgreichen Bau werden ältere ISO Builds derselben Organisation aufgeräumt (begrenzter Plattenplatz auf anode) — ein ISO Build ist damit kein dauerhaftes Archiv, sondern Status des jeweils aktuellen bzw. zuletzt versuchten Baus.
|
||||||
|
|
||||||
|
Attribute:
|
||||||
|
|
||||||
|
- id
|
||||||
|
- organization_id
|
||||||
|
- status (`pending`, `running`, `completed`, `failed`)
|
||||||
|
- output_filename (optional)
|
||||||
|
- error_message (optional)
|
||||||
|
- created_at
|
||||||
|
- started_at (optional)
|
||||||
|
- finished_at (optional)
|
||||||
|
|
||||||
|
Beziehungen:
|
||||||
|
|
||||||
|
- gehört zu genau einer Organization
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
- status ist eine der vier definierten Ausprägungen
|
||||||
|
|
||||||
## Relationales Schema v1
|
## Relationales Schema v1
|
||||||
|
|
||||||
### organizations
|
### organizations
|
||||||
@ -842,6 +868,27 @@ Constraints:
|
|||||||
|
|
||||||
- code ist Primärschlüssel
|
- code ist Primärschlüssel
|
||||||
|
|
||||||
|
### iso_builds
|
||||||
|
|
||||||
|
Spalten:
|
||||||
|
|
||||||
|
- id
|
||||||
|
- organization_id
|
||||||
|
- status (Standard: pending)
|
||||||
|
- output_filename (nullable)
|
||||||
|
- error_message (nullable)
|
||||||
|
- created_at
|
||||||
|
- started_at (nullable)
|
||||||
|
- finished_at (nullable)
|
||||||
|
|
||||||
|
Beziehungen:
|
||||||
|
|
||||||
|
- organization_id referenziert organizations.id
|
||||||
|
|
||||||
|
Constraints:
|
||||||
|
|
||||||
|
- status ist eine von pending/running/completed/failed (CHECK-Constraint)
|
||||||
|
|
||||||
## Beziehungskonsequenzen
|
## Beziehungskonsequenzen
|
||||||
|
|
||||||
Wird eine Organization gelöscht, dürfen zugehörige Devices nicht implizit mitgelöscht werden.
|
Wird eine Organization gelöscht, dürfen zugehörige Devices nicht implizit mitgelöscht werden.
|
||||||
@ -898,6 +945,7 @@ Für die relationale Umsetzung gilt daher:
|
|||||||
- device_merkmal_events.device_id → ON DELETE CASCADE
|
- device_merkmal_events.device_id → ON DELETE CASCADE
|
||||||
- device_merkmal_events.merkmal_id → ON DELETE RESTRICT
|
- device_merkmal_events.merkmal_id → ON DELETE RESTRICT
|
||||||
- activation_codes.organization_id → ON DELETE CASCADE
|
- activation_codes.organization_id → ON DELETE CASCADE
|
||||||
|
- iso_builds.organization_id → ON DELETE CASCADE
|
||||||
|
|
||||||
## ID-Erzeugung
|
## ID-Erzeugung
|
||||||
|
|
||||||
|
|||||||
@ -211,6 +211,20 @@ Noch nicht umgesetzt: `installer.sh` liest `tuxflotte.mode` noch nicht aus und d
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Mint/Casper-Payload-Mechanismus (04.–07.08.2026)
|
||||||
|
|
||||||
|
Der Fokus verschob sich nach den obigen Fedora-Untersuchungen auf Mint als primäres Backend (siehe ADR-0009-Folgeentscheidung) — Mints Live-/Install-Medium basiert auf `casper`/`live-build`, nicht auf Fedoras `dracut`. Für das `/updates`-Payload-Prinzip (siehe „WLAN-Zugangsdaten" oben — dieselbe Grundidee: Dateien direkt auf dem Boot-Medium, außerhalb des komprimierten Images, landen beim Live-Boot automatisch im Root) existiert bei Casper kein Äquivalent zu Dracuts `20-apply-live-updates.sh`-Hook.
|
||||||
|
|
||||||
|
**Falsche Annahme widerlegt:** Casper merged `/updates` vom ISO **nicht** automatisch ins gebootete Live-System — das war eine unverifizierte, projektweite Annahme (in Analogie zum Fedora/Dracut-Verhalten), die sich bei der tatsächlichen Umsetzung als falsch herausstellte.
|
||||||
|
|
||||||
|
Die funktionierende Lösung ist ein `casper-bottom`-Initrd-Hook (`initrd-hooks/casper-bottom/99casperboot`, eingebunden über denselben Cpio-Schicht-Mechanismus wie das Kexec-Preseed in Phase 1, siehe `scripts/lib/initrd.sh`) — mit einer nicht offensichtlichen Einschränkung: Ein **neuer** Hook-Skriptname wird von Casper nie ausgeführt. `initramfs-tools` erzeugt beim ISO-Bau eine `ORDER`-Datei (`scripts/casper-bottom/ORDER`) mit der fest verdrahteten Aufrufreihenfolge aller `casper-bottom`-Skripte; ein per Cpio-Schicht neu hinzugefügtes Skript taucht darin nicht auf und wird deshalb ignoriert, selbst wenn es im initramfs physisch vorhanden ist (real bestätigt durch Extraktion des tatsächlich gebauten `initrd.lz` mit `unmkinitramfs` — die erste Version dieses Fixes ist an genau diesem Punkt gescheitert). Zuverlässig funktioniert stattdessen das **Überschreiben eines bereits in `ORDER` referenzierten Pfads** — spätere Cpio-Schichten überschreiben Dateiinhalte am selben Pfad. Der Fix überschreibt deshalb das originale `99casperboot` (das im Original nur `touch /run/.casper-boot` macht) und hängt den `/updates`-Kopiervorgang dort an; `99casperboot` ist laut `ORDER` das letzte `casper-bottom`-Skript überhaupt, garantiert nach `05mountpoints` (das `/cdrom` nach `/root/cdrom` umhängt, Voraussetzung für den Kopiervorgang).
|
||||||
|
|
||||||
|
Dieselbe Erkenntnis (bestehenden, bereits referenzierten Pfad überschreiben statt neuen Hook-Namen einführen) gilt vermutlich für jeden künftigen Casper-Payload-Mechanismus, nicht nur für diesen einen Fix.
|
||||||
|
|
||||||
|
Im Rahmen desselben Self-Service-ISO-Ausbauschritts entstand die eigentliche Bau-Orchestrierung (Hintergrund-Thread in `provisioning-server`, `iso_builds`-Statustabelle, Kundenportal-Self-Service-UI) — siehe ADR-0014.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Zusammenfassung
|
## Zusammenfassung
|
||||||
|
|
||||||
Die Live-ISO ersetzt das direkte Booten in Anaconda durch eine vorgeschaltete, frei gestaltbare Live-Sitzung, in der `installer.sh` läuft.
|
Die Live-ISO ersetzt das direkte Booten in Anaconda durch eine vorgeschaltete, frei gestaltbare Live-Sitzung, in der `installer.sh` läuft.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user