platform-docs/architecture/09-data-model-v1.md
Thomas Stallinger 217f672ddd docs: design Auftragskatalog as ad-hoc, revocable Merkmal assignment
ADR-0010 decides that an Auftrag is technically a Merkmal, not a new
parallel entity: it reuses the existing Merkmal/Blueprint/ansible-role
mechanism from ADR-0002, but adds a device-level, independently
toggleable assignment path (device_merkmale) alongside the existing
workspace-level one. Deselecting an Auftrag needs to actually revert
it, so checkin will need to declare a full present/absent state per
catalog entry rather than just an additive list; that role-level and
API-level work is deferred, this only settles the data model.

Also adds a single generic, append-only event log
(device_merkmal_events) for both selection changes and agent-reported
execution results, instead of several purpose-specific tables, so
future event types don't require schema changes. Extends
09-data-model-v1.md and the glossary accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 23:29:12 +02:00

16 KiB

Tuxflotte Datenmodell v1

Zweck

Das Datenmodell beschreibt die persistenten Kernobjekte der Tuxflotte-Plattform und ihre Beziehungen.

Die konkrete SQL-Implementierung und das Migrationssystem werden getrennt von diesem fachlichen Modell behandelt.

Kernobjekte

  • Organization
  • Device
  • Hardware Snapshot
  • Network Interface
  • Storage Device
  • Workspace
  • Kategorie
  • Merkmal
  • Backend
  • Blueprint
  • Bereitstellungsvorlage
  • Assignment
  • Auftragszuweisung
  • Auftragsereignis
  • Activation Code
  • Network Profile
  • Secret Reference

Network Profile und Secret Reference sind weiterhin nur benannt, aber fachlich noch nicht spezifiziert — das bleibt bewusst außerhalb des aktuellen Arbeitsschritts (Provisioning-Server-Datenmodell für Workspace/Merkmal/Backend/Blueprint/Bereitstellungsvorlage).

Organization

Eine Organization beschreibt eine organisatorische Einheit innerhalb der Tuxflotte-Plattform.

Eine Organization besitzt eigene Geräte, Workspaces, Netzwerkprofile und Richtlinien.

Attribute:

  • id
  • name
  • created_at
  • updated_at

Beziehungen:

  • besitzt mehrere Devices
  • besitzt optional eigene Workspaces (zusätzlich zu global bereitgestellten Workspaces, siehe Workspace)
  • besitzt mehrere Bereitstellungsvorlagen
  • besitzt mehrere Activation Codes
  • besitzt mehrere Network Profiles
  • referenziert Secrets

Device

Ein Device repräsentiert ein dauerhaft bekanntes physisches oder virtuelles Gerät.

Die interne Geräte-ID ist unabhängig von Hardwaremerkmalen.

Der Hardware-Fingerprint dient der Wiedererkennung während des Provisioning-Handshakes.

Attribute:

  • id
  • organization_id
  • device_fingerprint
  • hostname
  • created_at
  • last_seen

Beziehungen:

  • gehört zu genau einer Organization
  • besitzt mehrere Hardware Snapshots
  • besitzt optional ein Assignment

Hardware Snapshot

Ein Hardware Snapshot beschreibt den zu einem bestimmten Zeitpunkt erfassten Hardwarezustand eines Devices.

Hardware Snapshots sind zeitbezogene Inventardaten und nicht Bestandteil der stabilen Geräteidentität.

Attribute:

  • id
  • device_id
  • collected_at
  • architecture
  • manufacturer
  • product_name
  • product_version
  • system_uuid
  • system_serial
  • board_vendor
  • board_name
  • board_serial
  • bios_vendor
  • bios_version
  • boot_mode
  • secure_boot
  • tpm_version
  • cpu_model
  • cpu_logical_count
  • memory_bytes

Beziehungen:

  • gehört zu genau einem Device
  • besitzt mehrere Network Interfaces
  • besitzt mehrere Storage Devices

Network Interface

Ein Network Interface beschreibt eine zum Zeitpunkt des Hardware Snapshots vorhandene Netzwerkschnittstelle.

Attribute:

  • id
  • hardware_snapshot_id
  • name
  • type
  • mac_address

Beziehungen:

  • gehört zu genau einem Hardware Snapshot

Storage Device

Ein Storage Device beschreibt einen zum Zeitpunkt des Hardware Snapshots vorhandenen Massenspeicher.

Attribute:

  • id
  • hardware_snapshot_id
  • model
  • serial
  • size_bytes
  • transport
  • name

Beziehungen:

  • gehört zu genau einem Hardware Snapshot

Workspace

Ein Workspace beschreibt einen distributionsunabhängigen Arbeitsplatz-Zielzustand aus einer Menge von Merkmalen (siehe 02-workspace-model.md).

Ein Workspace steht zunächst global bereit. Eine Organization kann zusätzlich eigene Workspaces definieren.

Attribute:

  • id
  • organization_id (optional; NULL bedeutet global bereitgestellt)
  • key
  • name
  • description
  • created_at
  • updated_at

Beziehungen:

  • gehört optional zu genau einer Organization
  • besteht aus mehreren Merkmalen
  • wird in mehreren Bereitstellungsvorlagen referenziert

Notiz für später (30.07.2026): Ein Workspace ist zwar distributionsunabhängig benannt, aber nicht jedes Merkmal hat für jeden Backend ein Blueprint (siehe unten). Für den noch nicht gebauten, selbst-bedienten Workspace-Creator (Kunde stellt sich einen eigenen Workspace aus Merkmalen zusammen) folgt daraus eine UX-Reihenfolge, keine Schema-Änderung: erst Backend wählen, dann Merkmale — die Merkmalsauswahl im Creator sollte auf blueprints-Zeilen für den bereits gewählten Backend gefiltert sein, damit kein Workspace entsteht, der auf der gewünschten Distribution gar nicht realisierbar ist. Nicht dringend, betrifft nur den noch nicht priorisierten Self-Service-Teil (siehe project-tuxflotte-pilot-roadmap).

Kategorie

Eine Kategorie gruppiert Merkmale, die im Auftragskatalog (siehe ADR-0010) zur geräteweisen Ad-hoc-Auswahl stehen, für die Darstellung in einer Katalog-Übersicht.

Eine Kategorie hat keine fachliche Wirkung auf die Anwendung eines Merkmals; sie dient ausschließlich der Organisation im Auftragskatalog.

Attribute:

  • id
  • key
  • name
  • description
  • sort_order

Beziehungen:

  • wird von mehreren Merkmalen referenziert

Constraints:

  • key ist eindeutig

Merkmal

Ein Merkmal ist eine einzelne fachliche Eigenschaft eines Workspace, distributionsunabhängig benannt (siehe 12-feature-blueprint-model.md).

Ein Merkmal kann zusätzlich zur Workspace-Zugehörigkeit für die geräteweise Ad-hoc-Zuweisung über den Auftragskatalog freigegeben sein (im_auftragskatalog, siehe ADR-0010, Auftragszuweisung unten). Das ist unabhängig davon, ob das Merkmal auch (oder ausschließlich) über einen Workspace zugewiesen wird.

Attribute:

  • id
  • key
  • name
  • description
  • kategorie_id (optional; nur relevant, wenn im_auftragskatalog = true)
  • im_auftragskatalog (Standard: false)

Beziehungen:

  • gehört zu mehreren Workspaces
  • besitzt je Backend höchstens einen Blueprint
  • gehört optional zu genau einer Kategorie
  • wird optional mehreren Devices direkt zugewiesen (Auftragszuweisung)

Backend

Ein Backend beschreibt die distributionsspezifische Umsetzung eines Workspace (siehe 04-backend-model.md).

Attribute:

  • id
  • key
  • name
  • installer_type (zum Beispiel kickstart, autoinstall, calamares, preseed)

Beziehungen:

  • besitzt Blueprints für Merkmale
  • wird in mehreren Bereitstellungsvorlagen referenziert

Blueprint

Ein Blueprint beschreibt die technische Umsetzung genau eines Merkmals für genau ein Backend (siehe 12-feature-blueprint-model.md).

Attribute:

  • id
  • merkmal_id
  • backend_id
  • ansible_role

Beziehungen:

  • gehört zu genau einem Merkmal
  • gehört zu genau einem Backend

Constraints:

  • je Merkmal und Backend existiert höchstens ein Blueprint

Bereitstellungsvorlage

Eine Bereitstellungsvorlage beschreibt eine von einer Organization vorkonfigurierte Kombination aus Workspace und Backend, einschließlich der installationszeitlichen Vorgaben, die kein Bestandteil eines Blueprints sind (siehe 03-organization-model.md, 12-feature-blueprint-model.md).

Attribute:

  • id
  • organization_id
  • workspace_id
  • backend_id
  • label
  • is_default
  • disk_encryption
  • partitioning
  • secure_boot_required
  • created_at
  • updated_at

Beziehungen:

  • gehört zu genau einer Organization
  • referenziert genau einen Workspace
  • referenziert genau ein Backend

Constraints:

  • höchstens eine Bereitstellungsvorlage je Organization mit is_default = true

Assignment

Ein Assignment beschreibt die konkrete Zuordnung eines Device zu der Bereitstellungsvorlage, mit der es provisioniert wurde.

Attribute:

  • id
  • device_id
  • bereitstellungsvorlage_id
  • created_at

Beziehungen:

  • gehört zu genau einem Device
  • referenziert genau eine Bereitstellungsvorlage

Constraints:

  • ein Device besitzt höchstens ein Assignment

Auftragszuweisung

Eine Auftragszuweisung beschreibt, ob ein katalogfähiges Merkmal (im_auftragskatalog = true) für ein einzelnes Device aktuell ausgewählt ist — unabhängig von dessen Workspace-Zugehörigkeit über die Bereitstellungsvorlage (siehe ADR-0010).

Anders als bei einem Assignment ist das Vorhandensein einer Zeile nicht gleichbedeutend mit „aktiv": Ab- und erneutes Zuwählen ändern das aktiv-Flag, statt die Zeile zu löschen und neu anzulegen, damit die Historie über das Ereignisprotokoll (Auftragsereignis) nachvollziehbar bleibt.

Attribute:

  • id
  • device_id
  • merkmal_id
  • aktiv
  • created_at
  • updated_at

Beziehungen:

  • gehört zu genau einem Device
  • referenziert genau ein Merkmal

Constraints:

  • (device_id, merkmal_id) ist eindeutig
  • merkmal_id muss auf ein Merkmal mit im_auftragskatalog = true verweisen (anwendungsseitig geprüft)

Auftragsereignis

Ein Auftragsereignis ist ein unveränderlicher, anhängender Protokolleintrag zu einem Merkmal auf einem Device — sowohl für Auswahländerungen als auch für vom Provisioning Agent gemeldete Ausführungsergebnisse (siehe ADR-0010).

Ein einziges, generisches Ereignisprotokoll deckt beide Fälle über das event_type-Feld ab, damit künftige Ereignisarten keine neue Tabelle erfordern.

Attribute:

  • id
  • device_id
  • merkmal_id
  • event_type (selected, deselected, applied, apply_failed, removed, remove_failed)
  • detail (optional, strukturierte Zusatzinformation)
  • occurred_at

Beziehungen:

  • gehört zu genau einem Device
  • referenziert genau ein Merkmal

Constraints:

  • keine (append-only, kein Unique-Constraint über die fachlichen Spalten)

Activation Code

Ein Activation Code autorisiert den initialen Provisionierungsvorgang eines Geräts und bestimmt die zugeordnete Organization (siehe 08-provisioning-api.md, 11-device-enrollment.md).

Der Activation Code ist laut 11-device-enrollment.md ein Übergangsmechanismus und wird langfristig durch Enrollment Sessions ersetzt.

Attribute:

  • code
  • organization_id
  • active
  • created_at

Beziehungen:

  • gehört zu genau einer Organization

Relationales Schema v1

organizations

Spalten:

  • id
  • name
  • created_at
  • updated_at

devices

Spalten:

  • id
  • organization_id
  • device_fingerprint
  • hostname
  • created_at
  • last_seen

Beziehungen:

  • organization_id referenziert organizations.id

Constraints:

  • device_fingerprint ist eindeutig

hardware_snapshots

Spalten:

  • id
  • device_id
  • collected_at
  • architecture
  • manufacturer
  • product_name
  • product_version
  • system_uuid
  • system_serial
  • board_vendor
  • board_name
  • board_serial
  • bios_vendor
  • bios_version
  • boot_mode
  • secure_boot
  • tpm_version
  • cpu_model
  • cpu_logical_count
  • memory_bytes

Beziehungen:

  • device_id referenziert devices.id

network_interfaces

Spalten:

  • id
  • hardware_snapshot_id
  • name
  • type
  • mac_address

Beziehungen:

  • hardware_snapshot_id referenziert hardware_snapshots.id

storage_devices

Spalten:

  • id
  • hardware_snapshot_id
  • name
  • model
  • serial
  • size_bytes
  • transport

Beziehungen:

  • hardware_snapshot_id referenziert hardware_snapshots.id

workspaces

Spalten:

  • id
  • organization_id (nullable)
  • key
  • name
  • description
  • created_at
  • updated_at

Beziehungen:

  • organization_id referenziert organizations.id (optional)

Constraints:

  • (organization_id, key) ist eindeutig

kategorien

Spalten:

  • id
  • key
  • name
  • description
  • sort_order

Constraints:

  • key ist eindeutig

merkmale

Spalten:

  • id
  • key
  • name
  • description
  • kategorie_id (nullable)
  • im_auftragskatalog (Standard: false)

Beziehungen:

  • kategorie_id referenziert kategorien.id (optional)

Constraints:

  • key ist eindeutig

workspace_merkmale

Spalten:

  • workspace_id
  • merkmal_id

Beziehungen:

  • workspace_id referenziert workspaces.id
  • merkmal_id referenziert merkmale.id

Constraints:

  • (workspace_id, merkmal_id) ist Primärschlüssel

backends

Spalten:

  • id
  • key
  • name
  • installer_type

Constraints:

  • key ist eindeutig

blueprints

Spalten:

  • id
  • merkmal_id
  • backend_id
  • ansible_role

Beziehungen:

  • merkmal_id referenziert merkmale.id
  • backend_id referenziert backends.id

Constraints:

  • (merkmal_id, backend_id) ist eindeutig

bereitstellungsvorlagen

Spalten:

  • id
  • organization_id
  • workspace_id
  • backend_id
  • label
  • is_default
  • disk_encryption
  • partitioning
  • secure_boot_required
  • created_at
  • updated_at

Beziehungen:

  • organization_id referenziert organizations.id
  • workspace_id referenziert workspaces.id
  • backend_id referenziert backends.id

Constraints:

  • höchstens eine Zeile je organization_id mit is_default = true (partieller Unique-Index)

assignments

Spalten:

  • id
  • device_id
  • bereitstellungsvorlage_id
  • created_at

Beziehungen:

  • device_id referenziert devices.id
  • bereitstellungsvorlage_id referenziert bereitstellungsvorlagen.id

Constraints:

  • device_id ist eindeutig

device_merkmale

Spalten:

  • id
  • device_id
  • merkmal_id
  • aktiv
  • created_at
  • updated_at

Beziehungen:

  • device_id referenziert devices.id
  • merkmal_id referenziert merkmale.id

Constraints:

  • (device_id, merkmal_id) ist eindeutig

device_merkmal_events

Spalten:

  • id
  • device_id
  • merkmal_id
  • event_type
  • detail (nullable)
  • occurred_at

Beziehungen:

  • device_id referenziert devices.id
  • merkmal_id referenziert merkmale.id

activation_codes

Spalten:

  • code
  • organization_id
  • active
  • created_at

Beziehungen:

  • organization_id referenziert organizations.id

Constraints:

  • code ist Primärschlüssel

Beziehungskonsequenzen

Wird eine Organization gelöscht, dürfen zugehörige Devices nicht implizit mitgelöscht werden.

Wird ein Device gelöscht, werden zugehörige Hardware Snapshots mitgelöscht.

Wird ein Hardware Snapshot gelöscht, werden zugehörige Network Interfaces und Storage Devices automatisch mitgelöscht.

Wird eine Organization gelöscht, dürfen ihre Bereitstellungsvorlagen und Activation Codes nicht implizit erhalten bleiben — sie verlieren mit der Organization ihre Grundlage.

Wird ein Workspace, ein Backend oder ein Merkmal gelöscht, das noch von einer Bereitstellungsvorlage beziehungsweise einem Blueprint referenziert wird, muss das gesperrt werden, um Bereitstellungsvorlagen nicht unbemerkt ungültig zu machen.

Wird ein Device gelöscht, wird sein Assignment mitgelöscht. Wird eine Bereitstellungsvorlage gelöscht, auf die noch ein Assignment verweist, muss das gesperrt werden.

Wird ein Device gelöscht, werden seine Auftragszuweisungen und Auftragsereignisse mitgelöscht (konsistent mit dem Umgang mit Hardware Snapshots). Wird ein Merkmal gelöscht, das noch von einer Auftragszuweisung oder einem Auftragsereignis referenziert wird, muss das gesperrt werden. Wird eine Kategorie gelöscht, verlieren zugehörige Merkmale lediglich ihre Kategoriezuordnung; sie bleiben davon unabhängig im Auftragskatalog bestehen.

Notiz für später (03.08.2026): Das Kaskadieren des Auftragsereignis-Protokolls bei Device-Löschung folgt bewusst dem bestehenden Muster (siehe Hardware Snapshot), ist aber im Sinne einer lückenlosen Historie eine Abwägung, keine zwingende Konsequenz — ein gelöschtes Device nimmt damit auch seine Ausführungshistorie mit. Sollte künftig eine Anforderung entstehen, Auftragshistorie auch nach Device-Löschung aufzubewahren (zum Beispiel für Audit-Zwecke über die Gerätelebensdauer hinaus), ist das eine Änderung der Löschregel für device_merkmal_events.device_id (z. B. auf ON DELETE SET NULL mit einer zusätzlichen, vom Device losgelösten Identifikationsspalte), keine Änderung an der Ereignis-Tabellenstruktur selbst.

Für die relationale Umsetzung gilt daher:

  • devices.organization_id → ON DELETE RESTRICT
  • hardware_snapshots.device_id → ON DELETE CASCADE
  • network_interfaces.hardware_snapshot_id → ON DELETE CASCADE
  • storage_devices.hardware_snapshot_id → ON DELETE CASCADE
  • workspaces.organization_id → ON DELETE CASCADE
  • workspace_merkmale.workspace_id → ON DELETE CASCADE
  • workspace_merkmale.merkmal_id → ON DELETE RESTRICT
  • merkmale.kategorie_id → ON DELETE SET NULL
  • blueprints.merkmal_id → ON DELETE CASCADE
  • blueprints.backend_id → ON DELETE CASCADE
  • bereitstellungsvorlagen.organization_id → ON DELETE CASCADE
  • bereitstellungsvorlagen.workspace_id → ON DELETE RESTRICT
  • bereitstellungsvorlagen.backend_id → ON DELETE RESTRICT
  • assignments.device_id → ON DELETE CASCADE
  • assignments.bereitstellungsvorlage_id → ON DELETE RESTRICT
  • device_merkmale.device_id → ON DELETE CASCADE
  • device_merkmale.merkmal_id → ON DELETE RESTRICT
  • device_merkmal_events.device_id → ON DELETE CASCADE
  • device_merkmal_events.merkmal_id → ON DELETE RESTRICT
  • activation_codes.organization_id → ON DELETE CASCADE

ID-Erzeugung

Persistente Plattformobjekte verwenden UUIDs als interne IDs.

UUIDs werden durch die Anwendung erzeugt.

Die Datenbank validiert und speichert die IDs, erzeugt sie jedoch nicht selbst.

Dadurch stehen Objekt-IDs bereits vor dem Datenbank-INSERT zur Verfügung und können innerhalb einer Transaktion für abhängige Datensätze verwendet werden.