build_customer_iso.sh braucht seit der Squashfs-Schicht kein root mehr:
sudo-Aufruf und sudoers-Datei entfallen, NoNewPrivileges,
CapabilityBoundingSet/AmbientCapabilities und RestrictSUIDSGID wieder an
(Ausnahmen aus ADR-0027 zurueckgenommen).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bare-Metal-Fund (21.09.2026): dieselbe physische Testmaschine wurde mit
Aktivierungscodes zweier verschiedener Organisationen aktiviert (device_
fingerprint bleibt hardware-gebunden stabil, anders als bei VM-Testumgebungen
mit Snapshot-Reset zwischen Laeufen). load_or_create_device() wirft dafuer
bewusst einen RuntimeError, der bisher ungefangen bis zu FastAPI durchschlug
(HTTP 500 "Internal Server Error" statt einer klaren Ablehnung) - fuer den
Installer nicht von einem echten Serverfehler unterscheidbar. Jetzt sauber
abgefangen und als strukturierte success:false-Antwort zurueckgegeben, analog
zum bestehenden invalid_activation_code-Fall.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWKevK2QUK5tsGmqfSD6Yy
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWKevK2QUK5tsGmqfSD6Yy
Live gefunden direkt beim Nachprüfen des vorigen Fixes: "image/x-icon"
fehlt in Pythons mimetypes-Datenbank (guess_extension liefert None), der
Fallback-Default war fälschlich ".svg" statt ".ico" gesetzt (Kopierfehler
gegenüber _write_webapp_icon(), die genau dafür schon .ico als Default
nutzt). Ein Kolibri-Favicon (echtes ICO, von der neuen Magic-Bytes-
Erkennung korrekt als image/x-icon erkannt) landete dadurch weiterhin
mit .svg-Endung auf der Platte.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Live gefunden beim Icon-Backfill (Phase 6): learningequality.org liefert
sein Favicon mit Content-Type "image/svg+xml", tatsächlich sind die Bytes
ein PNG-in-ICO-Container - wäre im Browser als kaputtes Bild gelandet
(SVG-Parser auf Binärdaten). _erkanntes_bildformat() sniffed jetzt die
echten Magic Bytes (PNG/JPEG/GIF/ICO/WEBP/SVG-Erkennung), der HTTP-Header
dient nur noch als Fallback. Zentral in _fetch_url_bytes() gefixt, damit
sowohl Webapp- als auch Merkmal-Icons profitieren (gleicher Fetch-Pfad).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Migration 0045: 47 von 56 Merkmalen hatten keine Beschreibung (Datenbank-
Check 11.09.2026) - Kurzbeschreibungen ergänzt, gleicher Ton wie die 9
bestehenden. Neue Spalten homepage_url/icon_source_hint (nur real gegen
die jeweilige URL bzw. https://cdn.simpleicons.org/<slug> geprüft
gesetzt, sonst NULL statt geraten) sowie icon_filename/icon_content_type
(Cache, analog workspace_webapps).
Neuer Icon-Fetch: fetch_merkmal_icon() probiert zuerst den Simple-Icons-
Slug, sonst fetch_favicon() der Homepage (bestehende Funktion aus dem
Webapp-Mechanismus direkt wiederverwendet) - Kombi aus beidem war die
Nutzer-Entscheidung in der Plan-Klärungsrunde. aktualisiere_merkmal_icon()
ist ein eigener, wiederholbar aufrufbarer Schritt (POST /api/v1/merkmale/
{id}/icon/aktualisieren) statt Teil des Anlegens, weil Merkmale per
Migration entstehen, nicht per API-Call mit sofortigem Netzwerk-Kontext.
Auslieferung über GET /merkmal-icons/{filename}, gleiches Muster wie
/webapp-icons.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Live gefunden bei der Verifikation: die Geräteanzahl-Spalte filterte
schon archivierte Geräte heraus (FILTER WHERE archived_at IS NULL), die
"letzte Aktivität"-Spalte (MAX) nicht - eine Organisation mit 0 aktiven
Geräten konnte dadurch trotzdem ein Aktivitätsdatum zeigen (von einem
längst archivierten Gerät). Jetzt konsistent beide gefiltert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Neue fetch_customer_dashboard()/fetch_admin_dashboard() + Endpunkte
GET /api/v1/organizations/{id}/dashboard und GET /api/v1/dashboard/admin.
V1 bewusst nur aus bereits vorhandenen Daten (siehe Plan-Klärungsrunde
11.09.2026) - der Agent meldet Platten-/RAM-Auslastung schon länger bei
jedem Check-in (health_disk_used_percent/health_ram_used_percent), das
wird hier erstmals aggregiert statt nur pro Gerät angezeigt:
- "Nächste Schritte": eigener Workspace vorhanden, Installationsmedium
erstellt (das eigentliche iso_build_id lebt in kundenplattforms eigener
DB, dort ergänzt), erstes Gerät aktiv, Hostnamenkonvention eingerichtet.
- Flotten-Gesundheit: Geräte ohne Check-in seit 7 Tagen, Platten-/RAM-
Auslastung ab 85% - gemeinsame Auswertung _dashboard_flotten_gesundheit()
für Kunde (org-gefiltert) und Admin (alle Organisationen, mit Namens-
Präfix je Zeile).
- Letzte Aktionen: jüngste device_merkmal_events (applied/apply_failed).
- Admin zusätzlich: Organisationstabelle mit Geräteanzahl/letzter Aktivität.
Festplattenfehler-/SMART-Daten bleiben bewusst außen vor - der Agent
erfasst das aktuell nicht, wäre ein eigener Erfassungsmechanismus (siehe
Rückfrage-Runde im Plan).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Webapps bekommen zwei neue, optionale Eigenschaften (Migration 0043):
kategorie_id (FK auf kategorien, nullable) und kiosk_modus (Boolean).
Kein eigener "Webapps"-Menüpunkt (per Nutzer-Feedback in der Plan-Runde
verworfen) - stattdessen ordnet sich eine Webapp optional einer der
bestehenden App-Kategorien zu, KATEGORIE_ZU_XDG_CATEGORIES übersetzt das
in eine echte Freedesktop-Categories=-Zeile fürs .desktop-Menü (Default
unverändert Network;WebBrowser; ohne gewählte Kategorie).
Kiosk-Modus: bei aktiviertem kiosk_modus löst der Server (nicht das
Gerät) anhand der Workspace-Merkmal-Zuordnung auf, welcher Browser das
Kiosk-Fenster öffnet (Chrome bevorzugt, Chromium Fallback, sonst normaler
Tab) - _resolve_kiosk_browser() in fetch_workspace_webapps().
Migration 0044: Chromium + Microsoft Edge als neue Browser-Merkmale
(Google Chrome existiert bereits aus Migration 0031). Chromium bewusst
NUR für debian-image/mint-image blueprint-verknüpft, nicht ubuntu-image -
recherchiert: Ubuntus "chromium"-Paket ist seit 19.10 nur ein
transitionelles Paket, das im Hintergrund Snap nachzieht (passt nicht zu
unserem Modell), während Debian und Mint ein echtes .deb bauen. Microsoft
Edge über das offizielle, distributionsunabhängige Microsoft-Repo auf
allen drei Backends.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Live gefundene Lücke (11.09.2026): das neue Besitz-Modell von
routers/workspace.py bindet Webapps strikt an Eigentum - ein globaler,
schreibgeschützter Standard-Workspace hat nie eigene Webapps, und eine
Organisation ohne einen einzigen eigenen Workspace hatte dadurch gar
keinen Weg mehr zu Webapps (Regression gegenüber dem alten, aber
unsicheren Verhalten, das jede nutzende Organisation an geteilten
Workspaces mitschreiben liess und damit ein Cross-Org-Leck riskierte).
clone_workspace() kopiert die Merkmal-Zusammensetzung eines sichtbaren
Workspace in einen neuen, garantiert der aufrufenden Organisation
gehörenden Workspace - der Standard selbst bleibt für alle unangetastet.
Neuer Service-Endpunkt POST /api/v1/workspaces/{id}/clone (Service-Token
wie die übrigen Workspace-Endpunkte).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
- Migration 0041: "Buero & Produktivitaet" -> "Büro & Produktivität"
(ASCII-Ersatzschreibweise, key unveraendert - Fremdschluessel laufen
ueber kategorie_id, nicht ueber key).
- Migration 0042: fedora-Backend nach Nutzer-Bestaetigung komplett
entfernt (siehe Rueckfrage bei Migration 0040) - dazu erst die eine
darauf verweisende Bereitstellungsvorlage ("Schulcomputer (Fedora)")
und ihre 4 historischen Zuweisungen an bereits archivierte Testgeraete
geloescht (sonst ON DELETE RESTRICT), dann das Backend selbst
(Blueprints kaskadieren automatisch mit).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
- debug_mode_until wurde beim Ablaufen nie geloescht - Auftragskatalog-
Seite und Geraeteliste zeigten den Debug-Modus dadurch dauerhaft als
aktiv an, auch Tage nach Ablauf (live an zwei echten Geraeten gefunden,
abgelaufen am 02./09.09., trotzdem noch "aktiv" angezeigt).
_health_felder_aus_row() liefert das Feld jetzt nur noch, wenn es
tatsaechlich noch in der Zukunft liegt.
- fetch_unassigned_devices() (Flows "Unbestaetigte Geraete") hatte den
archived_at-Filter nie bekommen, den fetch_devices_for_organization()/
fetch_all_devices() schon hatten - bereits geloeschte Geraete ohne
Zuweisung erschienen dadurch dauerhaft als offene Aufgabe.
- Migration 0040: Backend-Namen gekuerzt (Debian/Mint/Ubuntu statt
"... (Golden Image) ..."), altes "mint"-Backend (vor dem Golden-Image-
Pivot, keine Bereitstellungsvorlage nutzt es mehr) geloescht. "fedora"
bewusst NICHT angefasst - echte Bereitstellungsvorlage+Zuweisungen
haengen dran, siehe Rueckfrage an den Nutzer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
- Migration 0039: organizations.hostname_praefix/-stellen/-nummer_modus/
-naechste_sequenz. Leerer Präfix = keine Konvention (unverändertes
Verhalten, Client bestimmt den Hostnamen selbst).
- generate_hostname_for_organization(): erzeugt "<praefix>-<nummer>",
sequenziell (Zeilen-Lock via FOR UPDATE) oder zufällig (freie Nummer
per secrets.randbelow(), Kollisionsprüfung gegen vorhandene Hostnamen,
Fallback auf nächste freie sequenzielle Nummer nach 20 Versuchen).
- resolve_hostname_for_activation(): greift nur bei einem GENUIN NEUEN
Gerät (device_fingerprint noch unbekannt) - eine Re-Aktivierung eines
bekannten Geräts behält immer den vom Client gemeldeten Hostnamen,
überschreibt also keine manuelle Umbenennung.
- agent_checkin(): liefert jetzt auch "hostname" (devices.hostname) an
den Agenten.
- Neue Endpunkte GET/PUT /api/v1/organizations/{id}/hostname-konvention.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
- Migration 0038: gruppen.workspace_id (nullable FK), device_merkmale
Tabelle entfernt (keine Datenzeilen auf anode, geprüft vor Drop).
- set_gruppe_workspace()/clear_gruppe_workspace(): weist einer
Gerätegruppe einen alternativen Workspace zu, ersetzt die komplette
Baseline für Mitgliedsgeräte statt nur einzelner Merkmale. Prüft
Organisationszugehörigkeit des Workspace (global oder eigene Org) und
die Exklusivitätsregel (ein Gerät nur in einer workspace-tragenden
Gruppe gleichzeitig) - sowohl beim Zuweisen als auch beim Hinzufügen
eines Geräts zu einer bereits workspace-tragenden Gruppe.
- resolve_effective_workspace_id(): neue Auflösungsstufe zwischen
Bereitstellungsvorlage und OE-/Gruppen-Merkmal-Overrides.
- fetch_auftragskatalog_state()/_listing(): device_merkmale-Override-Ebene
entfernt (gewann bisher immer), select_auftrag/deselect_auftrag-
Endpunkte + set_auftrag_selection() entfernt. get_device_auftragskatalog
bleibt als reine Ist-Zustand-Anzeige.
- Neuer Endpunkt GET /api/v1/organizations/{id}/workspaces (global +
eigene, org-gefiltert - fetch_workspaces() bleibt admin-only, würde
sonst fremde private Workspaces preisgeben).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Zwei zusammenhängende Fixes, beide Voraussetzung für die neue
optionen-Eingabe-UI in kundenplattform (struktur.py/gruppen.py):
1. set_auftrag_selection()/set_oe_auswahl()/set_gruppe_auswahl()
ersetzten optionen bisher komplett (kein natives JSONB-Merge im
UPSERT) - ein im Formular leer gelassenes Passwort-Feld
(parse_optionen_form() lässt solche Keys bewusst weg, siehe
optionen_felder.py) hätte ein zuvor gesetztes Passwort beim nächsten
Bearbeiten stillschweigend gelöscht. Neue merge_optionen()-Funktion:
bestehenden Wert holen, mit den neu eingegebenen Feldern
zusammenführen, erst dann verschlüsseln (encrypt_sensitive_optionen()
dafür idempotent gemacht - erkennt bereits "enc:"-präfigierte Werte
und verschlüsselt nicht doppelt).
2. /api/v1/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/select
und die Gruppen-Entsprechung nahmen bisher gar keinen Payload-Body
entgegen (hartcodiert leeres optionen-Dict) - jetzt wie der
geräteweise Endpunkt mit AuftragSelectRequest. Ohne diesen Fix hätte
die neue OE-/Gruppen-UI (Kundenplattform) eingegebene AD-Domäne o.ä.
nie beim Server ankommen können.
Zusätzlich: fetch_oe_merkmale_listing()/fetch_gruppe_merkmale_listing()
liefern jetzt auch optionen (maskiert, siehe mask_sensitive_optionen())
für die "bereits gesetzt"-Anzeige im Formular.
ECHT gegen die Produktions-DB getestet (nicht nur Code-Review): OE-Ebene
erst mit Domain+Passwort setzen, dann NUR die Domain erneut setzen ohne
Passwort-Feld - Passwort blieb korrekt erhalten, Domain wurde aktualisiert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Fund beim Vorbereiten der AD/OIDC/Fileserver-Merkmale (10.09.2026):
resolve_katalog_overrides() selektierte nie die optionen-Spalte aus
oe_merkmale/gruppen_merkmale, obwohl set_oe_auswahl()/set_gruppe_auswahl()
sie langst speichern konnten - nur device_merkmale.optionen erreichte je
ein Geraet. Da die Geraete-Einzel-Ebene laut Entscheidung vom 09.09.2026
nicht der vorgesehene Ort fuer Merkmal-Konfiguration ist (siehe
project_phase2_app_store_merkmale-Memory), waere OE-/Gruppen-seitig
gesetzte Konfiguration (z.B. eine AD-Domaene) nie beim Geraet angekommen.
resolve_katalog_overrides() liefert jetzt (aktiv, quelle, quelle_id,
optionen) statt nur (aktiv, quelle, quelle_id) - beide Aufrufer
(fetch_auftragskatalog_state fuer den Agenten,
fetch_auftragskatalog_listing fuer die Kundenportal-Anzeige) angepasst.
Zusaetzlich neue At-Rest-Verschluesselung fuer optionen-Werte, deren Key
auf _passwort/_password/_secret/_token endet (Namenskonvention statt
Allowlist - neue Merkmale mit einem Zugangsdaten-Feld brauchen nur den
passenden Key-Namen, keine Codeaenderung). Gleiches Fernet-Muster wie
kundenplattform/encryption.py fuer den WLAN-PSK, aber eigenes Modul/eigener
Schluessel (TUXFLOTTE_ENCRYPTION_KEY) - provisioning-server muss bei jedem
Agent-Checkin entschluesseln koennen, anders als der einmalige ISO-Build-
Transfer dort. Kundenportal-Katalog-Listing zeigt sensible Werte nur als
maskierten Boolean (mask_sensitive_optionen()), nie im Klartext oder auch
nur verschluesselt im Browser.
Neue Abhaengigkeit: cryptography==50.0.0 (gleiche Version wie
kundenplattform), TUXFLOTTE_ENCRYPTION_KEY live auf anode gesetzt.
ECHT gegen die laufende Produktions-Datenbank getestet (nicht nur
Syntax-Check): OE-Ebene (verschluesselter Roh-DB-Wert, korrekter
Klartext im Agent-Checkin-Pfad, korrekt maskiert im Listing-Pfad) UND
Geraete-Ebene als Regressionstest (bestehender, meistgenutzter Pfad
funktioniert unveraendert weiter) - beide bestanden, Testdaten wieder
entfernt.
Voraussetzung fuer die anstehenden AD/OIDC/Fileserver-Merkmale (siehe
project_phase2_app_store_merkmale-Memory), die auf OE-/Gruppen-Ebene mit
echten Zugangsdaten konfiguriert werden sollen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Ergaenzung zum bisherigen rein kundeneigenen workspace_webapps: Admin
kann jetzt zusaetzlich global wiederverwendbare Vorlagen anlegen
('bereitstellen und zur Auswahl stellen'), Kunden aktivieren sie pro
Workspace zusaetzlich zu weiterhin frei erstellbaren eigenen
Eintraegen ('Kunden koennen selbst welche erstellen').
Neue Tabelle webapp_templates (Migration 0033, diesmal korrekt ueber
TUXFLOTTE_DATABASE_URL statt sudo-postgres angewendet - siehe Lehre aus
dem vorherigen Owner-Bug). workspace_webapps bekommt ein nullable
template_id (ON DELETE SET NULL) zur Aktivierungs-Nachverfolgung.
activate_webapp_template() kopiert Name/URL/Icon-Datei PHYSISCH unter
einem neuen, vom eigenen Eintrag abgeleiteten Dateinamen - keine
laufende Referenz auf die Vorlagen-Icon-Datei, sonst wuerde ein
spaeteres Loeschen/Aktualisieren der Vorlage die schon aktivierte Kopie
mit betreffen.
Neue Endpunkte: /api/v1/webapp-templates (CRUD),
/api/v1/workspaces/{id}/webapp-templates/available (noch nicht
aktivierte Vorlagen fuer einen Workspace), .../activate.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Nachtrag: Migration 0030 (App-Store-Batch-1-Merkmale) war im letzten
Commit versehentlich nicht mit dabei - jetzt nachgeholt (git status hat
es aufgedeckt).
Neue Tabelle workspace_webapps (Migration 0032): kundeneigene
{name, url}-Eintraege pro Workspace, kein fester Katalog wie bei
Merkmalen - jede Organisation/jeder Workspace hat ihre/seine eigene
Liste (Nutzer-Anforderung 09.09.2026: 'der Kunde hat diverse URLs').
app.py-Aenderungen:
- fetch_favicon(): bestes-Effort-Ermittlung des Website-Favicons
(erst <link rel=icon> per Regex aus dem HTML, sonst /favicon.ico an
der Domain-Wurzel) - bewusst ohne neue pip-Abhaengigkeit (nur
urllib, passt zum bisher sehr schlanken requirements.txt).
- CRUD-Endpunkte /api/v1/workspaces/{id}/webapps[/{webapp_id}]
(GET/POST/PUT/DELETE), Icon-Serving unter /webapp-icons/{filename}
(unauthentifiziert wie /golden-images/, gleicher
Path-Traversal-Schutz).
- Checkin-Response um 'webapps' erweitert (fetch_workspace_webapps(),
gleiches assignments->bereitstellungsvorlagen-Join-Muster wie
fetch_assigned_blueprints()).
- WICHTIG: Server komponiert nie eine eigene oeffentliche URL (ANODE_URL
ist nur client-seitig bekannt, siehe agent.py/backend.sh) - Checkin
liefert nur den relativen icon_path, der Agent haengt ANODE_URL davor.
Gehört zusammen mit admin/ansible-content (neue Rolle
webapp-shortcuts, echt end-to-end getestet) und admin/kundenplattform
(Admin-UI unter /admin/technik/workspaces/{id}, Icon-Proxy analog zum
bestehenden ISO-Download-Proxy-Muster).
Live auf anode angewendet: Migration 0032 (workspace_webapps-Tabelle).
app.py-Deployment (git pull + Neustart) folgt separat.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Fund beim Start von Phase 2 (Merkmal-Ausbau): bestehende App-Store-
Merkmale (brave, google-earth, guest-session, libreoffice, onlyoffice)
waren nur fuer die alten Backends mint/fedora verdrahtet, nicht fuer
die inzwischen tatsaechlich genutzten Golden-Image-Backends
(mint-image/debian-image/ubuntu-image) - der Auftragskatalog lieferte
diese Merkmale fuer jedes real provisionierte Geraet nicht aus. Per
INSERT...SELECT nachgezogen (gleicher Ansible-Rollenname, alle Rollen
sind bereits distributionsunabhaengig).
Zusaetzlich acht neue App-Store-Unterkategorien fuer die anstehende
App-Auswahl (siehe Plan-Datei/Memory project_phase2_app_store_merkmale).
Bestehende vier Merkmale bleiben vorerst bei der groben 'app-store'-
Kategorie, Umsortierung ist ein separater Schritt.
Live auf anode angewendet + verifiziert (15 Blueprint-Zeilen, 8 neue
Kategorien).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Registriert das neue backends/debian-image/backend.sh (siehe
tuxflotte-installer@63df40d) in der backends-Tabelle und legt eine
Test-/Entwicklungsvorlage "Tuxflotte-Debian-Entwicklung-v0.1" in der
Liebherr-Org/Entwickler-Workspace an, analog zur bestehenden
Tuxflotte-Mint-Entwicklung-v0.1 - fuer den anstehenden E2E-Nachweis von
Phase 1 (zweite Referenz-Distribution).
Noch NICHT auf anode angewendet (DB-Schreibzugriff braucht User-Freigabe,
siehe Session).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpNs8yZeQkHgMWx6WNT1QH
Neues Feld installation_directives.partitioning.target_disk_rank (Default
1 = groesste Platte) auf CreateBereitstellungsvorlageRequest/
create_bereitstellungsvorlage() - ersetzt den bisherigen impliziten
Default in backends/mint-image/backend.sh ("erste Platte in
lsblk-Erkennungsreihenfolge", nicht deterministisch bei mehreren internen
Datentraegern). Migration 0026 setzt den neuen Default in der
partitioning-Spalte und zieht bestehende Zeilen nach.
Siehe platform-docs/roadmap/multi-distro-merkmal-workspace-creator.md,
Phase 0 (Mehrplatten-Heuristik), 08.09.2026 mit dem Nutzer entschieden.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpNs8yZeQkHgMWx6WNT1QH
provisioning-server.service, provisioning-server-iso-build.sudoers und ein
erstes README.md - vorher existierte ueberhaupt kein Unit-File und keine
Deploy-Dokumentation im Repo, nur der Env-Datei-Pfad war erwaehnt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMczXJkjyv59Ds3TsDw8H3
provisioning-server laeuft auf der neuen anode-VM nicht mehr als root
(siehe kundenplattform.service als Vorbild fuer die Haertung). Der
ISO-Build braucht weiterhin root (CAP_CHOWN/CAP_FOWNER fuers Erhalten
der Datei-Eigentuemer im Squashfs, siehe Kommentar in
build_customer_iso.sh) - deshalb hier per eng gefasster NOPASSWD-
sudo-Regel (/etc/sudoers.d/provisioning-server-iso-build, nur genau
dieser Skriptpfad) statt den ganzen Dienst wieder privilegiert laufen
zu lassen. End-to-End getestet: echter ISO-Build ueber denselben
systemd-Namespace wie der Produktionsdienst, inkl. Loeschbarkeit der
root-erzeugten Ausgabedatei durch den provisioning-server-User.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMczXJkjyv59Ds3TsDw8H3
Neue has_completed_installation() prueft device_installation_events auf
ein 'installation_completed'-Ereignis - device_created=False allein reicht
nicht (ein bekanntes Geraet koennte auch an einem frueheren Commit-Gate
abgebrochen sein). Wird jetzt im /api/v1/activate-Antwortobjekt
mitgeliefert (.device.previously_installed).
Nutzer-Wunsch (02.09.2026): "Was ist eigentlich, wenn der Stick bei einem
bereits installierten System erneut ausgeführt wird?" - bisher gar
nichts, der Auto-Modus haette die Zielplatte stillschweigend erneut
komplett geloescht. tuxflotte-installer liest dieses Feld jetzt in
25_installation_confirm.sh und erzwingt eine echte Rueckfrage, auch im
Auto-Modus (siehe dortigen Commit).
Vor dem UPDATE geprueft, ob agent_secret_issued_at noch NIE gesetzt war -
nur dann (echte Neuinstallation, nicht ein spaeterer Self-Heal-Re-
Bootstrap bei 401 auf checkin) wird debug_mode_until gleich mit auf
jetzt+DEBUG_MODE_DURATION_HOURS gesetzt.
Nutzer-Wunsch (02.09.2026): kurz nach dem ersten Boot des fertig
installierten Systems soll ein frisch zugewiesenes Merkmal ohne
stundenlange Wartezeit (normales Poll-Intervall: 6 Stunden) ankommen -
der Debug-Modus (60s-Intervall) ist dafuer schon vorhanden, sprang bisher
aber nie automatisch an.
load_or_create_device() aktualisierte bei einem bekannten device_fingerprint
bisher nur hostname/last_seen - archived_at/deprovisioned_at blieben
unangetastet.
Live gefunden (02.09.2026, echter Hardware-Test): ein per "Gerät löschen"
archiviertes Geraet (archive_device(), Soft Delete - Historie bleibt
erhalten) blieb nach einer frischen Neuinstallation derselben physischen
Hardware (gleicher device_fingerprint, neue /api/v1/activate-Anfrage)
dauerhaft aus fetch_devices_for_organization() verschwunden, weil
archived_at nie zurueckgesetzt wurde - fuer den Nutzer sah es aus, als
waere das neu registrierte Geraet nie angekommen.
Ein erfolgreicher /api/v1/activate-Aufruf ist der eindeutigste mögliche
Beweis, dass ein Geraet wieder aktiv genutzt wird - setzt jetzt beide
Felder explizit auf NULL zurueck.
Neue Tabelle (Migration 0025) + POST /api/v1/devices/{device_id}/
installation-events (device_fingerprint-authentifiziert statt Service-
Token - das Geraet hat waehrend der Installation noch kein agent_secret,
gleiches Vertrauensniveau wie /api/v1/activate) + GET-Gegenstueck
(Service-Token, fuer die Kundenplattform).
Nutzer-Wunsch (01.09.2026, nach dem ersten echten Hardware-Test):
Fehlschlaege waehrend der Installation sichtbar machen, ohne am
Bildschirm mitschreiben zu muessen ("das ist kein Flow"). Bewusst
getrennt von device_merkmal_events (andere Lebensphase, andere Auth,
kein Merkmal-Bezug) und zunaechst nur admin-/operatorseitig ausgewertet -
ob/wie umfangreich der Kunde das in seiner eigenen GUI sieht, ist noch
offen (Nutzer: "das können wir noch wann anders diskutieren").
Neue Spalte size_bytes (Migration 0024, analog zu sha256/Migration 0023) -
wird direkt nach erfolgreichem Bau einmalig berechnet (output_path.stat().
st_size) und zusammen mit sha256 gespeichert, kein Neuberechnen bei jedem
Seitenaufruf noetig.
Nutzer-Feedback (01.09.2026, echter Hardware-Test): "Bitte nach ISO-Build
auch die Größe anzeigen lassen" - kundenplattform zeigt sie jetzt auf
/installationsmedium an (iso_build.size_bytes | filesizeformat).
Analog zur bestehenden /installers/fedora-workstation/ks.cfg-Route:
GET /golden-images/{filename} liefert eine Datei aus data/golden-images/
(Env-Override TUXFLOTTE_GOLDEN_IMAGES_DIR), ohne Auth-Token - das
Zielgeraet hat beim Laden des Golden Image in backend_launch() noch
keins. Kein personalisierter Inhalt (anders als die ISO-Build-Downloads),
deshalb bewusst keine Authentifizierung noetig. Path-Traversal ueber den
Dateinamen wird per Path(...).name-Vergleich abgelehnt (400).
Vorbereitung fuer ADR-0024 Phase 3 (backends/mint-image/backend.sh
GOLDEN_IMAGE_URL zeigt als naechstes hierher).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Siehe Fund #7 im heutigen Self-Service-Flow-Test (Memory). Migration 0023
(iso_builds.sha256), compute_file_sha256() haest die Datei einmalig direkt
nach erfolgreichem Bau (gestreamt, nicht komplett im Speicher - ISOs sind
mehrere GB), update_iso_build() nimmt den Wert jetzt entgegen. Ermoeglicht
Integritaetspruefung unabhaengig vom Kundenplattform-Download-Proxy-Pfad.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Siehe neue ADR (platform-docs). Migration 0022 (devices.debug_mode_until +
6 Health-Spalten). AGENT_POLL_INTERVAL_SECONDS-Default 300 -> 21600 (6h,
Jitter kommt agentenseitig). agent_checkin() liefert dynamisch das kurze
Debug-Intervall statt des Standards, solange debug_mode_until in der
Zukunft liegt - reiner Zeitvergleich, kein Cron zum Zurücksetzen nötig.
Neue Endpunkte POST/DELETE /api/v1/devices/{id}/debug-mode. Check-in
nimmt optional health-Objekt entgegen (Disk/RAM/Uptime/Load), schreibt es
kombiniert mit agent_last_checkin in einem UPDATE. fetch_devices_for_
organization()/fetch_all_devices() liefern debug_mode_until + Health jetzt
mit aus (Kundenplattform braucht sie für Anzeige/Toggle-Zustand, kein
Zusatz-Request nötig).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
compute_iso_volid() sanitisiert den Organisationsnamen ISO9660-konform
(Grossbuchstaben/Ziffern/Unterstrich, max. 32 Zeichen, NFKD-Fallback fuer
Umlaute, 'ORG'-Fallback falls kein ASCII-Zeichen uebrig bleibt) und haengt
das Bau-Datum (UTC) an. run_iso_build() reicht das Ergebnis als sechsten
Parameter an build_customer_iso.sh durch, statt des bisher fest codierten
'TUXFLOTTE'-Labels.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neue Spalten kontakt_name, adresse, telefon, kontakt_email, notizen -
bewusst nur einfache Kontaktdaten, keine Richtlinien-/Sicherheitsfelder
(Network Profile/Secret Reference bleiben laut ADR-0001 weiterhin
unspezifiziert, eigenständiges Thema). Bestehender PATCH-Endpoint
(/api/v1/organizations/{id}) wird nur breiter, keine neue Route.
fetch_organizations() (Liste, fuer den Admin-Bereich) und
fetch_organization() (Einzelabruf, fuer Self-Service) beide erweitert,
damit intern und im Kundenportal dieselben Daten sichtbar sind.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>