Skip to content

Configuration as data

When an Arch update on a Tuesday morning requires a new workaround for a specific Intel iGPU — what does a traditional installer do? Nothing, until somebody patches the bash script that drives it, releases a new ISO (1–3 GB), and waits for users to download it. Days at best, weeks more commonly.

Ditana takes a fundamentally different approach: configuration as data.

Ditana’s architecture is strictly divided into two repositories:

  1. The Engine (ditana-installer) — a small, generic program that knows how to draw dialogs, partition disks, detect hardware, and run pacstrap.
  2. The Knowledge Base (ditana-config) — a structured database written in KDL v2. It declares every setting, every hardware quirk, every package dependency, every lifecycle script, and the logical relationships between them.

When you boot the Ditana ISO, the engine connects to GitHub and downloads the latest release of the knowledge base (falling back to an offline snapshot bundled with the ISO if you have no internet connection).

Three benefits, in order of immediate impact:

  • Zero-respin updates. If a user reports that a recent Arch update requires a new workaround, the fix lands in ditana-config as a small KDL edit. The very next person who boots a Ditana ISO downloads the updated logic automatically. The fix reaches users immediately, without anyone needing to download a new 2.6 GB ISO.
  • Transparent reasoning. Because settings are declared as structured data with explicit conditions, the rules are auditable end-to-end. Anyone can read the KDL files and see exactly why a package is installed, what combination of settings triggers a configuration file to be deployed, or which hardware property gates an option. No bash detective work.
  • Forkable contributions. Adding a hardware workaround, a new desktop tweak, or a packaging choice usually means editing a single KDL file. No installer engine changes, no Raku knowledge required. The ditana-config README walks contributors through the schema; most pull requests touch one file.

The knowledge base could have been any structured format. KDL was chosen because it has the properties Ditana’s configuration actually needs:

  • Native comments. YAML and JSON both struggle here — YAML has comments but tooling support is uneven; JSON has none at all. Ditana’s settings include extensive rationale comments explaining why a default is what it is. Those comments are the point.
  • Raw strings. Embedded shell snippets, sed expressions, and regex patterns are first-class. No double-escaping " and \ to satisfy the parser.
  • Hierarchical without ceremony. Children, properties, and arguments coexist cleanly. The same node can describe a setting and contain sub-nodes for its packages, scripts, and files.
  • Diff-friendly. Insertion order is preserved. A code review of a one-line addition shows a one-line diff, not a reordered structure.

The format is parsed by a small Rust converter (json-kdl-converter, also versioned alongside the schema) before the Raku installer consumes it as JSON. The next section explains how the data is validated end-to-end before any of that.

A complete, working setting in ditana-config looks like this:

- name="install-bubblewrap-suid" \
default-value="`(flatpak OR install-bubblejail)
AND (kernel-option-duurn
OR install-hardened-stable-kernel)`" {
// Without this, every Flatpak fails at startup
// with "bwrap: No permissions to create a new namespace".
arch-packages "bubblewrap-suid"
}

That node declares a derived setting (no user-facing dialog), a logical expression over four other settings, and one package to install if the expression is true. The reasoning lives next to the rule, where someone reviewing it can see why. The full schema documentation is in the ditana-config README.

Validation: catching mistakes before they ship

Section titled “Validation: catching mistakes before they ship”

A knowledge base this large needs guardrails. Every commit — both locally via pre-commit hooks and remotely on GitHub Actions — runs a layered validation pipeline that catches the mistakes a human reviewer might miss:

  • KDL syntax (kdlfmt) — the file parses at all.
  • Schema validation (ditana-schema.json) — every field has the right type, no unknown properties, no missing required fields. Catches typos in field names like arc-packages instead of arch-packages.
  • File reference integrity — every file referenced from a setting actually exists in folders/; conversely, every file in folders/ is referenced by at least one setting. Orphans get flagged. Directory references (which would silently do nothing because the installer uses cp without -R) are rejected.
  • Lifecycle correctnesschroot-script lines that touch /etc/skel/ are rejected, because the user is created after chroot-script runs — the correct field is early-chroot-script. A subtle bug that would otherwise produce a system where the new user’s home doesn’t have the expected files.
  • Shell script validity — every shell snippet in any of the script-list fields gets bash -n for syntax and shellcheck for quality. Same for standalone scripts under folders/. A missing quote or a [ "$x" = $y ] style bug gets caught at commit time, not at first install.
  • Logical expression integrity — backticks must balance; every setting name referenced in a default-value or available expression must actually exist somewhere in the knowledge base. A typo like install-cosmik instead of install-cosmic gets caught at commit time instead of producing a silent runtime crash.

The full check runs in seconds locally with pre-commit run --all-files, and again on every push to GitHub. Local green means CI green; broken commits never make it to a release.