Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

DAWO Handbook

DAWO is a declarative control-plane for fleets of NixOS devices. Configuration is data in a git overlay; Nix turns that data into signed system closures; devices pull and converge (comin). Sextant is the human and API surface that edits the data safely, proves it builds, stages the rollout, and reports what each device actually runs. It is not MDM: declarative pull, no live command channel, every change an audited git commit.

This handbook is for operators running a DAWO fleet and for engineers working on it. It is built with mdBook and served self-hosted; it uses no external CDN.

The lifecycle at a glance

  1. Set up an imaging station - a NUC or mini-PC that boots devices over the network and reports what it sees to the console.
  2. Image a device - the console dispatches an image job; the station runs the install and reports progress until the device is on disk and converging.
  3. Manage it - configuration flows organisation -> group -> device; every change passes the Nix gate and can be reviewed as a change-request.
  4. Update it - a rollout ships a new revision in waves, each gated on health and soak, with an optional manual test gate.
  5. Retire or wipe it - an audited intent the device acts on locally; a crypto-wipe destroys the disk’s keys, and is armed per device.

Start with setting up an imaging station.

Set up an imaging station (NUC)

An imaging station (the “inspoelstraat”) is a small always-on box that boots devices over the network and reports what it sees to the console. You set one up a few times and then use it continuously.

Fresh hardware installs with a USB NixOS installer and nixos-anywhere: the USB brings the box up with SSH, and nixos-anywhere drives disko + install over SSH from your workstation. A fresh box (Secure Boot off, no TPM key enrolled) uses the -install variant: systemd-boot + a LUKS passphrase.

1. Write a NixOS minimal USB installer

lsblk                              # find the whole stick, e.g. /dev/sdb (NOT sdb1)
sudo dd if=<nixos-minimal>.iso of=/dev/sdX bs=4M status=progress oflag=sync

2. Boot the NUC and bring up SSH

Plug the USB and wired ethernet. Power on; the NUC boot menu is F10 if it does not auto-boot the stick. At the installer prompt:

sudo systemctl start sshd
sudo mkdir -p /root/.ssh
sudo tee /root/.ssh/authorized_keys < keys/id_ed25519.pub   # operator pubkey
ip -brief a                                                  # note the NUC's IP

3. Install

Pick a LUKS passphrase and store it in your password manager, then:

cd inspoelstraat-appliance
printf %s '<LUKS-passphrase>' > /tmp/luks.key
NIX_SSHOPTS="-o IdentitiesOnly=yes -o StrictHostKeyChecking=no" \
  nix run github:nix-community/nixos-anywhere -- \
    --flake .#dawo-inspoelstraat-install \
    --target-host root@<installer-ip> \
    -i ../keys/id_ed25519 \
    --generate-hardware-config nixos-generate-config ./hardware-configuration.nix \
    --phases disko,install \
    --disk-encryption-keys /tmp/luks.key /tmp/luks.key
rm -f /tmp/luks.key

--generate-hardware-config re-probes the NUC and writes its real kernel modules before building. Disko targets /dev/nvme0n1 (the NUC standard).

4. Make it a Sextant station

  1. In the console, under Imaging stations (owner), register a station and mint its report credential (shown once).
  2. Put the credential on the station (the runtime token file the station agent reads). It then reports discovered devices to the console.
  3. To image devices from it, wire the station runner to your fleet overlay - see Image a device from the console.

Later: enrol Secure Boot (-sb variant), then enrol TPM2 on the device, then run the steady-state dawo-inspoelstraat configuration.

Image a device from the console

Once a station is reporting, imaging a device is driven from the console.

  1. Discover - boot the target device on the station’s network. It appears under Enrollment for that station.
  2. Dispatch - on the Enrollment page, pick the target (or a whole rack in batch), choose its hardware profile (suggested from the make and model), and dispatch imaging. This creates the device record, captures its hardware specs, and queues an image job. The device stays visible with live status.
  3. Execute - the station runner claims the job, resolves the target’s IP from its DHCP lease, runs nixos-anywhere for that device’s generated configuration, and bakes the device’s one-time agent credential into the image. It reports progress back: pending -> imaging -> installed (or failed).
  4. Converge - the imaged device boots, checks in, and converges its configuration. It now shows on the device page with its facts and posture.

The guided flow shows brand-specific steps per hardware profile (firmware key, Secure Boot, disk layout) so an operator is walked through the process, and the whole run is auditable.

Update, retire and wipe

Update

Updates ship as a rollout: a new revision promotes through ordered waves, each gated on health and a soak window, with an optional manual test gate. See How a rollout ships.

Retire

Retiring a device keeps its record for audit but stops image builds, check-ins and rollout counting. Reactivation is an explicit, audited step.

Lock and wipe

Lock and wipe are intent-as-data: the console records the intent as an audited change; the device pulls it on check-in and acts locally. There is no live command channel.

  • Lock locks all sessions and persists across reboot; clear the intent to release.
  • Wipe cryptographically erases the device by destroying its LUKS key slots. It is irreversible and gated: the root executor refuses unless the device is explicitly armed (a per-device change) and locked first. Arm a device only when it is cleared to be wiped.

How a rollout ships

An update does not reach the whole fleet at once. It promotes through ordered waves. Each wave is a group of devices; the next wave only starts once the current one is converged healthy through its soak window. A wave can require a manual approval gate - a human checkpoint that the update was tested.

The rollout page shows the plan as a ladder: each wave with its device count (size it small first - a canary - then wider), soak, health floor and gate. Size a wave by its group and order; refine each wave with the gates.

An organisation can require a gated test wave before any rollout starts; an owner may skip it for a specific rollout, and that is logged.

Approval flows

How changes and updates are reviewed before they take effect is configurable per organisation, under Access -> Approval flows (owner):

  • Four-eyes - a change may not be merged by its own author.
  • Require change-request - configuration edits must go through a reviewed change, never a direct commit.
  • Require test wave - a rollout must have a gated test wave first; an owner can skip it per rollout.

Every configuration change is a git commit that passes the Nix gate first, so an edit that would not build never reaches the fleet.

Architecture overview

Sextant is a hexagonal Go application: a pure domain (model, resolver, policy compiler, filter evaluator) under application services, behind ports, with adapters for git, Nix, Postgres, LDAP and OIDC, and two thin transports - a server-rendered console and a JSON API over the same services.

  • Config plane - the git overlay (fleet.json + catalog) is the source of truth. Writes are serialized, pass the Nix eval gate, and commit with SSO-attributed authorship.
  • Observed plane - device check-ins, posture and hardware facts live in Postgres, tenant-namespaced.
  • Imaging plane - discovery and image jobs drive provisioning from a station.

See the decision records for the reasoning behind each choice.

Decision records

Architecture decisions are recorded as ADRs in the repository under docs/adr/. They cover the ground-up rebuild in Go, the policy model that compiles to the scope chain, Postgres-only durable state, the SSR + API contract, the generated settings catalog, assurance and approvals, API and credential security, tenant isolation, and language choice per workload.

Read them in docs/adr/ in the source repository.