Zum Inhalt springen

Konfiguration als Daten

Wenn eine Arch-Aktualisierung an einem Dienstagmorgen einen neuen Workaround für eine bestimmte Intel-iGPU erfordert – was tut ein herkömmliches Installationsprogramm? Nichts, bis jemand das Bash-Skript korrigiert, das es steuert, ein neues ISO (1–3 GB) veröffentlicht und darauf wartet, dass die Benutzerinnen und Benutzer es herunterladen. Im besten Fall Tage, meist eher Wochen.

Ditana verfolgt einen grundlegend anderen Ansatz: Konfiguration als Daten.

Die Architektur von Ditana ist auf drei Repositorys aufgeteilt:

  1. Die Engine (ditana-installer) – ein kleines, generisches Programm, das weiss, wie man Dialoge zeichnet, Datenträger partitioniert, Hardware erkennt und pacstrap ausführt.

  2. Die Wissensbasis (ditana-config) – eine strukturierte Datenbank in KDL v2. Sie deklariert jede Einstellung, jede Hardware-Eigenheit, jede Paketabhängigkeit, jedes Lebenszyklus-Skript und die logischen Beziehungen zwischen ihnen.

  3. Die Paket-Pipeline (ditana-build) – das, was die Pakete baut, die die Wissensbasis nennt. Die Ditana-Paketquelle setzt auf der von Arch auf, und die Rezepte darin werden von dieser Pipeline aus dem Quellcode neu gebaut, geprüft und signiert, statt so übernommen zu werden, wie sie kommen.

Wenn Sie das Ditana-ISO starten, verbindet sich die Engine mit GitHub und lädt die neueste Version der Wissensbasis herunter (ohne Internetverbindung greift sie auf einen Offline-Schnappschuss zurück, der im ISO enthalten ist).

Wissenswert ist auch, was das Laden zur Laufzeit nicht abdeckt. Die Engine selbst und der Konverter, mit dem sie KDL liest, befinden sich auf dem Medium und ändern sich nur mit einem neuen Medium. Und der Kanal transportiert einen Fehler ebenso bereitwillig wie eine Korrektur: Nennt eine Konfiguration ein Paket, das die Paketquelle eines älteren Mediums nicht liefern kann, ist mit diesem Medium keine Installation mehr möglich – genau das ist geschehen, als Arch im September 2026 bubblewrap-suid zurückgezogen hat.

Drei Vorteile, geordnet nach ihrer unmittelbaren Wirkung:

  • Aktualisierungen ohne neues ISO. Meldet jemand, dass eine kürzlich erschienene Arch-Aktualisierung einen neuen Workaround erfordert, landet die Korrektur als kleine KDL-Änderung in ditana-config. Schon die nächste Person, die ein Ditana-ISO startet, lädt die aktualisierte Logik automatisch herunter. Die Korrektur erreicht die Benutzerinnen und Benutzer sofort, ohne dass jemand ein neues ISO herunterladen muss.
  • Nachvollziehbare Begründungen. Da Einstellungen als strukturierte Daten mit ausdrücklichen Bedingungen deklariert sind, lassen sich die Regeln durchgängig überprüfen. Alle können die KDL-Dateien lesen und genau sehen, warum ein Paket installiert wird, welche Kombination von Einstellungen dazu führt, dass eine Konfigurationsdatei eingerichtet wird, oder von welcher Hardware-Eigenschaft eine Option abhängt. Keine Detektivarbeit in Bash.
  • Beiträge über Forks. Einen Hardware-Workaround, eine neue Desktop-Anpassung oder eine neue Entscheidung zur Paketierung hinzuzufügen, bedeutet in der Regel, eine einzige KDL-Datei zu ändern. Keine Änderungen an der Engine des Installationsprogramms – und ausserhalb der Hardware-Detektoren, die einzeilige Raku-Ausdrücke sind, überhaupt kein Raku. Die README-Datei von ditana-config führt Mitwirkende durch das Schema; die meisten Pull-Requests betreffen eine einzige Datei.

Warum KDL und nicht JSON oder YAML

Abschnitt «Warum KDL und nicht JSON oder YAML»

Die Wissensbasis hätte in jedem beliebigen strukturierten Format vorliegen können. KDL wurde gewählt, weil es die Eigenschaften hat, die Ditanas Konfiguration tatsächlich braucht:

  • Native Kommentare. YAML und JSON tun sich hier beide schwer – YAML hat Kommentare, aber die Unterstützung durch Werkzeuge ist uneinheitlich; JSON hat überhaupt keine. Ditanas Einstellungen enthalten ausführliche Kommentare mit Begründungen, die erklären, warum ein Standardwert so ist, wie er ist. Genau um diese Kommentare geht es.
  • Rohe Zeichenketten. Eingebettete Shell-Schnipsel, sed-Ausdrücke und Regex-Muster sind vollwertig. Kein doppeltes Maskieren von " und \, nur um den Parser zufriedenzustellen.
  • Hierarchisch ohne Umstände. Kindknoten, Eigenschaften und Argumente bestehen sauber nebeneinander. Derselbe Knoten kann eine Einstellung beschreiben und Unterknoten für ihre Pakete, Skripte und Dateien enthalten.
  • Diff-freundlich. Die Einfügereihenfolge bleibt erhalten. Ein Code-Review einer einzeiligen Ergänzung zeigt einen einzeiligen Diff, keine umsortierte Struktur.

Das Format wird von einem kleinen Konverter in Rust (json-kdl-converter, ebenfalls zusammen mit dem Schema versioniert) eingelesen, bevor das Installationsprogramm in Raku es als JSON verarbeitet. Der nächste Abschnitt erklärt, wie die Daten vor alldem durchgängig validiert werden.

Wie das in der Praxis aussieht

Abschnitt «Wie das in der Praxis aussieht»

Eine vollständige, funktionierende Einstellung in ditana-config sieht so aus:

// XFCE fallback for Wayland-only primary terminals (foot, cosmic-term).
// Fires when the user picks a Wayland-native terminal AND also installs
// XFCE. Writes to xfce-xdg-terminals.list only; xdg-terminals.list still
// points at the primary terminal for Wayland sessions.
- name="fallback-kitty-for-xfce" \
default-value="`(install-foot OR install-cosmic-term) AND install-xfce`" {
arch-packages "kitty" \
"imagemagick" \
"python-pygments" \
"libcanberra" \
"xdg-terminal-exec-git"
files "/etc/xdg/kitty/kitty.conf" \
"/usr/share/pixmaps/ditana-logo-tiny.png"
chroot-script "mkdir -p /etc/xdg" \
"echo kitty.desktop >/etc/xdg/xfce-xdg-terminals.list"
}

Dieser Knoten deklariert eine abgeleitete Einstellung ohne eigenen Dialog, einen logischen Ausdruck über drei andere Einstellungen, die Pakete, die installiert werden, wenn er zutrifft, und die Datei, die XFCE dazu bringt, sie zu verwenden. Die Begründung steht neben der Regel; wer diese prüft, sieht dort, warum. Die vollständige Dokumentation des Schemas steht in der README-Datei von ditana-config.

Dieselbe Form trägt auch Dinge, die gar keine Pakete sind. Ein Programm, das für seine Sandbox einen unprivilegierten Benutzer-Namensraum (user namespace) braucht, gibt das direkt bei sich selbst an:

- name="flatpak" default-value=#true {
arch-packages "flatpak"
// Every Flatpak, not only the browsers, runs bwrap to build its outer
// sandbox, and bwrap needs a user namespace. This is the declaration
// that buys it one.
userns-allow "/usr/bin/bwrap"
}

Ditana gibt unprivilegierte Benutzer-Namensräume nicht jedem Programm frei. Ein minimales BPF-Programm, ditana-userns-guard, ist an den Hook userns_create des Kernels selbst angehängt und verweigert jeder ausführbaren Datei einen Namensraum, sofern keine Einstellung ihn für sie angefordert hat. Ein solcher Hook kann nur verweigern und nie gewähren; deshalb wird die sysctl-Einstellung, die Namensräume abschaltet, erst angehoben, wenn das Programm geladen ist – ein Rechner, der es nicht laden kann, lässt sie vollständig abgeschaltet. Das ist die Voreinstellung einer Option im Abschnitt System Hardening; schalten Sie sie aus, stehen Namensräume allem offen, wie auf den meisten Distributionen.

Ditana verweigert Benutzer-Namensräume allem, was nicht danach gefragt hat. Praktikabel wird das dadurch, dass die Anforderung lokal ist: Sie steht dort, wo das Paket ausgewählt wird, sodass keine synchronisierte zentrale Liste gepflegt werden muss, und wenn die Einstellung ausgeschaltet wird, fällt die Berechtigung mit ihr weg. Das Installationsprogramm sammelt die Deklarationen der Einstellungen, die am Ende aktiviert bleiben, und erzeugt daraus die Positivliste.

Validierung: Fehler abfangen, bevor sie ausgeliefert werden

Abschnitt «Validierung: Fehler abfangen, bevor sie ausgeliefert werden»

Eine so grosse Wissensbasis braucht Leitplanken. Bei jedem Commit läuft – sowohl lokal über Pre-Commit-Hooks als auch entfernt auf GitHub Actions – eine mehrstufige Validierungs-Pipeline, die die Fehler abfängt, die bei einer Prüfung durch einen Menschen übersehen werden könnten:

  • KDL-Syntax (kdlfmt) – die Datei lässt sich überhaupt parsen.
  • Schema-Validierung (ditana-schema.json) – jedes Feld hat den richtigen Typ, keine unbekannten Eigenschaften, keine fehlenden Pflichtfelder. Fängt Tippfehler in Feldnamen ab, etwa arc-packages statt arch-packages.
  • Integrität der Dateiverweise – jede Datei, auf die eine Einstellung verweist, existiert tatsächlich in folders/; umgekehrt wird auf jede Datei in folders/ von mindestens einer Einstellung verwiesen. Verwaiste Dateien werden markiert. Verweise auf Verzeichnisse (die stillschweigend nichts bewirken würden, weil das Installationsprogramm cp ohne -R verwendet) werden abgelehnt.
  • Korrektheit des Lebenszyklus – Zeilen in chroot-script, die /etc/skel/ betreffen, werden abgelehnt, weil das Benutzerkonto erst nach der Ausführung von chroot-script angelegt wird – das richtige Feld ist early-chroot-script. Ein subtiler Fehler, der sonst ein System ergäbe, in dem das Benutzerverzeichnis des neuen Kontos nicht die erwarteten Dateien enthält.
  • Gültigkeit der Shell-Skripte – jeder Shell-Schnipsel in einem der Felder mit Skriptlisten wird mit bash -n auf Syntax und mit shellcheck auf Qualität geprüft. Ein fehlendes Anführungszeichen oder ein Fehler nach dem Muster [ "$x" = $y ] wird beim Commit erkannt, nicht erst bei der ersten Installation.
  • Deklarierte Berechtigungen und Kernel-Einstellungen – ein Eintrag userns-allow muss ein absoluter Pfad sein, und ein Eintrag sysctl muss etwas nennen, das ein sysctl-Schlüssel ist, und ihm einen Wert geben, den der Kernel annimmt. Beide Felder kamen mit 0.9.4 hinzu und sind in ditana-schema.json deklariert.
  • Widersprüchliche Kernel-Einstellungen – zwei Einstellungen, die denselben sysctl-Schlüssel mit unterschiedlichen Werten schreiben, halten die Installation an, und die Meldung nennt beide. Als das noch als Shell-Text zusammengesetzt wurde, wie vor 0.9.4, gewann einfach die zuletzt geschriebene Zeile, und niemand erfuhr davon.
  • Integrität der logischen Ausdrücke – Backticks müssen paarweise stehen; jeder Name einer Einstellung, auf den in einem Ausdruck in default-value oder available verwiesen wird, muss irgendwo in der Wissensbasis tatsächlich existieren. Ein Tippfehler wie install-cosmik statt install-cosmic wird beim Commit abgefangen, statt zur Laufzeit einen stillen Absturz zu verursachen.

Die vollständige Prüfung läuft lokal mit pre-commit run --all-files in Sekunden und bei jedem Push zu GitHub erneut. Lokal grün heisst CI grün; fehlerhafte Commits gelangen nie in eine veröffentlichte Version.

Diese Übersetzung wurde maschinell erstellt. Leserinnen und Leser verbessern sie auf Weblate.