From 676e3243816b2eff01888cb035b548bd67af52a0 Mon Sep 17 00:00:00 2001 From: Thomas Stallinger Date: Sat, 19 Sep 2026 14:52:13 +0200 Subject: [PATCH] Bereitstellungsvorlage: custom-Partitionierungsschema per API/GUI CreateBereitstellungsvorlageRequest bekommt scheme + extra_partitions (Mountpoint/Prozent/Dateisystem je Zusatzpartition), mit serverseitiger Validierung (Prozentsumme < 100, keine doppelten Mountpoints, nur ext4/btrfs). Bisher war "custom" laut Code-Kommentar nur per direkter DB-Pflege erreichbar - jetzt vollstaendig ueber das bestehende Admin- Formular (Organisationen -> Bereitstellungsvorlagen). fetch_templates() liefert partitioning jetzt mit aus, damit die Admin- Tabelle das gewaehlte Schema anzeigen kann. Migration 0046 dokumentiert die neue JSONB-Form im Spalten-Default und zieht bestehende Zeilen nach (Muster wie Migration 0026). Teil der E2E-Testrunde, siehe platform-docs/roadmap/ e2e-testrunde-lokale-adressierung.md Phase 3. Gegenstueck im Install- Backend (image_deploy_partition_custom() etc.) ist in tuxflotte-installer bereits umgesetzt. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01DWKevK2QUK5tsGmqfSD6Yy --- app.py | 50 ++++++++++++++++--- ...tellungsvorlage_custom_partitionierung.sql | 30 +++++++++++ 2 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 migrations/0046_bereitstellungsvorlage_custom_partitionierung.sql diff --git a/app.py b/app.py index 86fc9c0..a3d623f 100644 --- a/app.py +++ b/app.py @@ -244,6 +244,12 @@ class UpdateWebappTemplateRequest(BaseModel): description: str | None = None +class ExtraPartitionRequest(BaseModel): + mountpoint: str + percent: int + filesystem: str + + class CreateBereitstellungsvorlageRequest(BaseModel): workspace_id: str backend_id: str @@ -257,6 +263,12 @@ class CreateBereitstellungsvorlageRequest(BaseModel): # _mint_image_detect_target_disk() und platform-docs/roadmap/ # multi-distro-merkmal-workspace-creator.md, Phase 0 (08.09.2026). target_disk_rank: int = 1 + # scheme:"custom" + extra_partitions (siehe platform-docs/roadmap/ + # e2e-testrunde-lokale-adressierung.md Phase 3, 19.09.2026) - loest die + # bisherige Einschraenkung ab, dass ein custom-Schema nur per direkter + # DB-Pflege moeglich war (siehe Kommentar in create_bereitstellungsvorlage()). + scheme: str = "single" + extra_partitions: list[ExtraPartitionRequest] = [] class CreateEnrollmentSessionRequest(BaseModel): @@ -641,7 +653,8 @@ def fetch_templates(organization_id: str) -> list[dict]: SELECT bv.id, bv.label, bv.is_default, w.id, w.name, - b.id, b.name, b.version + b.id, b.name, b.version, + bv.partitioning FROM bereitstellungsvorlagen bv JOIN workspaces w ON w.id = bv.workspace_id JOIN backends b ON b.id = bv.backend_id @@ -659,6 +672,7 @@ def fetch_templates(organization_id: str) -> list[dict]: "workspace": {"id": str(row[3]), "name": row[4]}, "backend": {"id": str(row[5]), "name": row[6], "version": row[7]}, "is_default": row[2], + "partitioning": row[8], } for row in rows ] @@ -673,22 +687,25 @@ def create_bereitstellungsvorlage( root_filesystem: str, secure_boot_required: bool, target_disk_rank: int = 1, + scheme: str = "single", + extra_partitions: list[dict] | None = None, ) -> dict: """ Bislang gab es dafuer keinen API-/UI-Weg - Bereitstellungsvorlagen kamen nur per Seed-Migration (0005/0011) fuer die "Default Lab"-Organisation zustande (siehe Phase 5 des Self-Service-ISO-Plans, dort als offene - Luecke notiert). partitioning bekommt bewusst nur eine einfache - "root_filesystem"-Wahl im single-scheme (siehe Migration 0015 fuer die - volle Form) - ein custom-Schema mit extra_partitions ist Sache der - direkten DB-Pflege, nicht dieses Formulars. + Luecke notiert). Seit 19.09.2026 (siehe platform-docs/roadmap/ + e2e-testrunde-lokale-adressierung.md Phase 3) deckt dieses Formular auch + scheme:"custom" + extra_partitions ab - Validierung liegt im aufrufenden + Endpoint (create_bereitstellungsvorlage_endpoint()). """ vorlage_id = uuid4() partitioning = { - "scheme": "single", + "scheme": scheme, "root_filesystem": root_filesystem, "target_disk_rank": target_disk_rank, + "extra_partitions": extra_partitions or [], } with get_database_connection() as conn: @@ -4843,6 +4860,25 @@ def create_bereitstellungsvorlage_endpoint( if payload.target_disk_rank < 1: return {"success": False, "error": "invalid_request", "message": "target_disk_rank muss mindestens 1 sein."} + if payload.scheme not in ("single", "custom"): + return {"success": False, "error": "invalid_request", "message": "scheme muss 'single' oder 'custom' sein."} + + if payload.scheme == "custom": + mountpoints = [p.mountpoint for p in payload.extra_partitions] + if len(mountpoints) != len(set(mountpoints)): + return {"success": False, "error": "invalid_request", "message": "extra_partitions enthält doppelte Mountpoints."} + if sum(p.percent for p in payload.extra_partitions) >= 100: + return {"success": False, "error": "invalid_request", "message": "Summe der extra_partitions-Prozente muss unter 100 bleiben."} + for p in payload.extra_partitions: + if not p.mountpoint.startswith("/"): + return {"success": False, "error": "invalid_request", "message": f"Mountpoint '{p.mountpoint}' muss mit '/' beginnen."} + if p.percent <= 0: + return {"success": False, "error": "invalid_request", "message": f"percent für '{p.mountpoint}' muss positiv sein."} + if p.filesystem not in ("ext4", "btrfs"): + return {"success": False, "error": "invalid_request", "message": f"Nicht unterstütztes Dateisystem für '{p.mountpoint}': {p.filesystem}"} + elif payload.extra_partitions: + return {"success": False, "error": "invalid_request", "message": "extra_partitions ist nur bei scheme='custom' erlaubt."} + vorlage = create_bereitstellungsvorlage( organization_id, payload.workspace_id, @@ -4853,6 +4889,8 @@ def create_bereitstellungsvorlage_endpoint( payload.root_filesystem, payload.secure_boot_required, payload.target_disk_rank, + payload.scheme, + [p.model_dump() for p in payload.extra_partitions], ) return {"success": True, "bereitstellungsvorlage": vorlage} diff --git a/migrations/0046_bereitstellungsvorlage_custom_partitionierung.sql b/migrations/0046_bereitstellungsvorlage_custom_partitionierung.sql new file mode 100644 index 0000000..e3f2c78 --- /dev/null +++ b/migrations/0046_bereitstellungsvorlage_custom_partitionierung.sql @@ -0,0 +1,30 @@ +BEGIN; + +-- Erweitert installation_directives.partitioning um scheme:"custom" (siehe +-- tuxflotte-installer Phase 3, image_deploy_partition_custom()): neben dem +-- bisherigen "single" kann jetzt eine Liste zusaetzlicher Partitionen +-- angegeben werden, z.B. +-- {"scheme": "custom", "root_filesystem": "ext4", "target_disk_rank": 1, +-- "extra_partitions": [{"mountpoint": "/home", "percent": 20, "filesystem": "ext4"}, +-- {"mountpoint": "/var", "percent": 10, "filesystem": "btrfs"}]} +-- extra_partitions-Prozente sind Anteile der GESAMTEN Platte (nicht des +-- verbleibenden Rests nach Abzug der ESP), Summe muss < 100 bleiben (Rest +-- geht an root). filesystem aktuell nur ext4/btrfs (zfs/Fedora bewusst +-- nicht abgedeckt, siehe platform-docs/roadmap/ +-- e2e-testrunde-lokale-adressierung.md Phase 3). Validiert wird sowohl im +-- Admin-Formular (kundenplattform/routers/admin_kunden.py) als auch +-- nochmal defensiv im Install-Backend (backend_validate() in +-- tuxflotte-installer/backends/*/backend.sh). +ALTER TABLE bereitstellungsvorlagen + ALTER COLUMN partitioning SET DEFAULT + '{"scheme": "single", "root_filesystem": "ext4", "target_disk_rank": 1, "extra_partitions": []}'::jsonb; + +-- Bestehende Zeilen explizit nachziehen statt sich auf den Laufzeit- +-- Fallback "// []" im Backend zu verlassen - self-documenting statt +-- stillschweigend implizit (gleiches Muster wie Migration 0026 fuer +-- target_disk_rank). +UPDATE bereitstellungsvorlagen + SET partitioning = partitioning || '{"extra_partitions": []}'::jsonb + WHERE NOT (partitioning ? 'extra_partitions'); + +COMMIT;