Release 0.1.0 - pilot baseline (proven on real hardware) #34

Open
bram.buijs wants to merge 39 commits from release/0.1.0 into main
Collaborator

DAWO 0.1.0, the pilot baseline. Brings release/0.1.0 to main.

Running on real government hardware, reproducible nixos-anywhere install, LUKS, NixOS 26.05, nl_NL:

  • HP ProBook 4 G1i (Intel Core Ultra 5 225U) - KDE Plasma
  • Lenovo ThinkPad T495s (AMD Ryzen 7 PRO 3700U) - GNOME (dev/comparison unit)

Screenshots attached; dawo-proof shows release 0.1.0, the flake revision, and the hardening services active.

What 0.1.0 adds and resolves:

  • hardening blocks with core/hardened tiers (#16)
  • declarative user management (#13), plus a local break-glass admin so a host can never become un-administerable
  • dawo.autoUpdate via comin (#24)
  • lean opt-in base (apps/shell/diagnostics) and the pilot app sets, sensible GNOME defaults
  • netboot (PXE) installer and a headless provisioning ISO
  • per-model hardware (generic base + per-model modules, HP ProBook 4 G1i / Lenovo T495s) and a nixos-facter path
  • roadmap and an on-device release marker

This contains and supersedes the open PRs #13, #16 and #24. Happy to land those individually first if you prefer - the proof here covers them either way.

AI assistance (per CONTRIBUTING AI Policy): drafted with Claude Code (Anthropic's Claude); reviewed, built and tested on real hardware by me.

DAWO 0.1.0, the pilot baseline. Brings release/0.1.0 to main. Running on real government hardware, reproducible nixos-anywhere install, LUKS, NixOS 26.05, nl_NL: - HP ProBook 4 G1i (Intel Core Ultra 5 225U) - KDE Plasma - Lenovo ThinkPad T495s (AMD Ryzen 7 PRO 3700U) - GNOME (dev/comparison unit) Screenshots attached; dawo-proof shows release 0.1.0, the flake revision, and the hardening services active. What 0.1.0 adds and resolves: - hardening blocks with core/hardened tiers (#16) - declarative user management (#13), plus a local break-glass admin so a host can never become un-administerable - dawo.autoUpdate via comin (#24) - lean opt-in base (apps/shell/diagnostics) and the pilot app sets, sensible GNOME defaults - netboot (PXE) installer and a headless provisioning ISO - per-model hardware (generic base + per-model modules, HP ProBook 4 G1i / Lenovo T495s) and a nixos-facter path - roadmap and an on-device release marker This contains and supersedes the open PRs #13, #16 and #24. Happy to land those individually first if you prefer - the proof here covers them either way. AI assistance (per CONTRIBUTING AI Policy): drafted with Claude Code (Anthropic's Claude); reviewed, built and tested on real hardware by me.
lanzaboote v1.0.0 sets boot.bootspec.enable, which nixpkgs 26.05 removed, so the
pinned tag fails to evaluate. Track master until a tagged release is compatible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refactor the hard-wired comin config into a dawo.autoUpdate block following the
block interface. A bare device tracks the upstream core on code.overheid.nl; a
workplace overrides dawo.autoUpdate.options.repoUrl to its own overlay flake
(which consumes the core as an input) so the chosen overlay rides along on every
update. Tunables: repoUrl, branch, poll interval (> 0 asserted). On by default in
profiles-dawo-generic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add users-hardened (opt-in, mutableUsers=false) for fully declarative, no-drift users. docs/users.md documents the workflow: one module per user under modules/users, hosts import the users they have, and passwords come from agenix (hashedPasswordFile) instead of plaintext hashes in git. agenix is already wired via the environment module.
Introduce the block interface from #8: each capability is a NixOS module
exposed as flake.modules.nixos.hardening-<name>, switched with
dawo.<block>.enable and tuned through dawo.<block>.options.<...>.

Two aggregate tiers:
- profiles-dawo-core: mandatory BIO/NCSC blocks, forced on with mkForce so a
  host cannot silently drop them (usb-control, ssh, sysctl-baseline, timesync,
  audit). Wired into profiles-dawo-generic.
- profiles-dawo-hardened: opt-in blocks, default off (apparmor for now).

Mandatory enforcement uses mkForce, suggested defaults use mkDefault, and an
empty or undefined option that would break the device asserts at build time
(ssh maxAuthTries > 0, timesync servers non-empty).

Supersedes the opt-in-only set in #12.

Ref: #6 #8

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Was nixos-unstable with a nixos-25.11 fallback that is nearly end of life. 26.05 is the current stable release; evaluates green across the config.
With nixpkgs pinned to the stable channel, expose nixos-unstable through an
overlay as pkgs.unstable so a single package can be pulled from unstable when a
newer version is needed, without moving the whole base off stable. Mirrors the
existing pkgs.stable overlay.

Ref: #11

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
End of support for 25.11 is near/passed, so the stable overlay it backed is
removed. Cherry-picking newer packages still works via the unstable overlay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the two desktops symmetric option-gated blocks so a host picks exactly one
and the other is never evaluated into its closure (no package, service, display
manager or theming collision - the coexistence concern raised in review).

- desktop-plasma: gate on dawo.desktop.plasma.enable; pair SDDM (desktop-sddm-bzk)
  to the same flag.
- desktop-gnome: GNOME with GDM, gated on dawo.desktop.gnome.enable.
- desktop-select: assert exactly one desktop is enabled.
- hardening-gnome: opt-in locked GNOME dconf profile (NCSC/CIS), declared in
  profiles-dawo-hardened.
- profiles-dawo-generic is now desktop-agnostic; hosts choose the desktop
  (dawo-t495s and dawo-hp-eb-850g7 default to plasma).

Proof: dawo-t495s evaluates with plasma (plasma6 on, gdm off); forcing gnome
flips to gdm+gnome with sddm/plasma off; enabling both trips the desktop-select
assertion.

Refs #21.
Standalone GNOME variant of the T495s host alongside dawo-t495s (Plasma), so both
desktops can be deployed and tested separately on the same machine without editing
a host file:
  nixos-rebuild switch --flake .#dawo-t495s         # KDE Plasma
  nixos-rebuild switch --flake .#dawo-t495s-gnome   # GNOME

Refs #21.
Default to plain systemd-boot so a fresh nixos-anywhere install boots without a
key ceremony. lanzaboote Secure Boot is gated on dawo.secureboot.enable and only
activates once sbctl keys are enrolled (docs/deploy.md), removing the fresh-install
case where a missing/unsigned boot entry left the device unbootable.

- new boot-loader block: systemd-boot by default, lanzaboote when enabled.
- drop boot-systemd / boot-secureboot (superseded by boot-loader).
- t495s, t495s-gnome and hp hosts use boot-loader; t495s defaults secureboot off.
- deploy.md: Secure Boot enrollment runbook plus a needs-improving note.

Proof: dawo-t495s and dawo-t495s-gnome instantiate with systemd-boot (lanzaboote
off); flipping dawo.secureboot.enable switches to lanzaboote.

Refs #19.
A minimal installer image that bakes in wifi credentials and an authorized SSH
key (both via build-time env, kept out of git, built with --impure), so the
stick boots, auto-joins wifi and brings up sshd unattended. nixos-anywhere then
drives the install from a workstation over the network, with no manual
wpa_supplicant or passwd in the live env.

Needed because nixos-anywhere's kexec path is blocked on an already-hardened
DAWO box: protectKernelImage sets kernel.kexec_load_disabled=1 (one-way until
reboot), so kexec_file_load returns EPERM. Booting this ISO sidesteps kexec
(--phases disko,install).

The module is org/person-neutral: provisioning specifics come from env and are
applied only when set, so the config stays valid under pure 'nix flake check'
(a build without the env vars yields a valid-but-useless ISO and is flagged by
a warning).
The dawo demo user shipped an initialHashedPassword whose plaintext was
unknown, so a freshly installed image could not be logged into. Set it to a
documented default (dawo / dawo) and document it in docs/users.md. The account
stays mutable (mutableUsers = true), so the password is meant to be changed
after first login; real deployments give the host its own gitops user
(hashedPasswordFile via agenix) and drop this one.
Per-device proof for screenshots and logs, without retyping long commands. Adds
the generic block environment-dawo-version, which:

- sets system.configurationRevision to the flake revision, so
  'nixos-version --configuration-revision' reports the exact source commit on
  every machine;
- writes /etc/dawo/release (flake rev, nixpkgs rev, nixos version, desktop);
- ships fastfetch and a single 'dawo-proof' command that prints a fastfetch
  panel (flake rev plus desktop) and a verification log (build path, users,
  hardening services, secure boot).

One word on the installed machine produces the whole proof. neofetch is archived
upstream, so fastfetch (its maintained successor) is used.
Replace em-dashes, curly quotes, arrows and decorative emoji with ASCII
equivalents across the docs and module comments, so the tree is plain ASCII.
GNOME ships with the minimize/maximize window buttons hidden and a few laptop
defaults off. Provide an unlocked dconf profile (users can still change it, no
extensions or gnome-tweaks required):

- window buttons: appmenu:minimize,maximize,close
- touchpad: tap-to-click and natural-scroll on
- top bar: show the date

The hardened profile (hardening-gnome) adds its own locked keys; dconf merges
both profiles.
Shell extensions run unvetted code in the user session. Add a locked
org/gnome/shell disable-user-extensions to the gnome-hardening dconf profile so
a hardened host blocks them and a user cannot re-enable them.
usbguard was forced on in the mandatory core, which blocks USB out of the box
(no stick or dongle works on a fresh device) - confirmed on a real T495s. That
breaks the user experience, so it does not belong in the mandatory floor.

Move hardening-usb-control from profiles-dawo-core to profiles-dawo-hardened
(opt-in, default off). The mandatory core now keeps only invisible controls
that cannot lock a user out (ssh, sysctl, timesync, audit). A workplace that
wants device-control enables it via the hardened tier or a compliance profile.
auditd is a no-op on nixpkgs 26.05 (an auditctl module bug rejects the generated
rules, breaking nixos-rebuild), yet it was forced on in the core - the contract
said 'audit' while nothing ran, a false sense of coverage.

Move hardening-audit out of profiles-dawo-core into the opt-in tier, and make
the block warn when enabled instead of silently doing nothing. journald covers
the interim log base. When the upstream bug is fixed, the real ruleset replaces
the warning and it returns to the mandatory core.
Lean the v0.1.1 baseline for the Zaanstad pilot (office workers reaching a VDI
over VPN/F5). Split the forced app list into opt-in dawo.apps.* blocks:
- dawo.apps.office (suite = libreoffice (default) | collabora; + Thunderbird)
- dawo.apps.comms (Element)
- dawo.apps.creative (GIMP, Inkscape, Krita, Penpot)
- dawo.apps.dev (VSCodium, Nix toolchain, gcc) - opt-in, off for the pilot

environment-dawo-pkgs shrinks to universal tools (+ vlc, freerdp for VDI/RDP).
Vendor apps (microsoft-edge, teams-for-linux) are removed from the core - they
belong in the consuming organisation's overlay. The reference T495s hosts (KDE +
GNOME) enable office + comms + creative.
Trim the always-on base to what an office/VDI end user needs and push the rest
behind opt-in blocks (lean core, productive pilot):

- zsh becomes opt-in (dawo.zsh.enable, default off); the dawo and deploy users
  use the default shell (bash). zsh is one flag away (brings its own fzf).
- environment-dawo-pkgs keeps only essentials (freerdp for VDI, exfat(progs)
  for USB, solaar, a GUI monitor, fastfetch); drops htop/lsd/fzf/age/nix-search/
  dmidecode/ethtool/pciutils/lshw/lm_sensors/libva-utils.
- new opt-in dawo.tools.diagnostics block (htop, lshw, pciutils, dmidecode,
  ethtool, lm_sensors, usbutils) for ops/support.
- vlc moves to an opt-in dawo.apps.media block; nix-search-cli to dawo.apps.dev.

The pilot hosts (dawo-t495s, dawo-t495s-gnome, dawo-hp-eb-850g7) enable office +
comms + creative + media so they are productive out of the box; dev and
diagnostics stay off.
Add WIFI_SSID2/WIFI_PSK2 (build-time env, optional) alongside the primary
network, so one provisioning stick can join from more than one location (e.g.
office plus a phone hotspot); wpa_supplicant connects to whichever is in range.
Stays valid under pure eval when unset.
dawo-installer-netboot imports netboot-minimal and bakes in sshd + the
provisioning key (no wifi - ethernet/DHCP). Produces kernel + initrd + an iPXE
script, so a fleet can be imaged over wired PXE without a USB stick per machine;
nixos-anywhere then installs over the same wired LAN (no AP client-isolation).
See netboot/README.md for the operator runbook.
Pull the bits duplicated across the per-model hardware modules into a generic
hardware-dawo-base (firmware, fwupd, bluetooth, initrd-systemd, platform),
imported by profiles-dawo-generic. The lenovo-t495s and hp-elitebook-850-g7
modules now carry only model specifics (their nixos-hardware profile + initrd).

Document the fleet convention in hardware.md: 1) pick a nixos-hardware profile
if the model is known, 2) look it up in the nixos-hardware catalog, 3) make it
via nixos-facter (auto-detect, no hand-written module) for unknown models. This
keeps adding a device near-zero hand-work - the basis for netboot/fleet imaging
of arbitrary hardware.
Capture the release plan toward a 1.0 production image: 0.1 pilot baseline
(active), 0.2 modularity + balanced hardening + identity, 0.3 three-layer
consumer overlays + strong auth (Secure Boot/TPM2/FIDO2), 1.0 production.

Add cross-cutting tracks that span releases: user provisioning (two users per
device - beheeraccount + person - plus RBAC and fleet management), the test and
update strategy (a release is gated on a proven on-device update path), and the
network provisioning pipeline.
dawo-proof and /etc/dawo/release showed only the flake revision. Add a
human-readable release name next to it, so a user or support desk can read the
release off the device without decoding a commit hash. The flake revision stays
the exact provenance.
With root locked (users-basics) and the dawo user not in wheel, a host with no
other admin and no deploy key cannot be administered locally at all - a lockout
we hit on a pilot device. Add dawo to wheel so the documented dawo/dawo login
can always sudo. A real deployment still replaces this with named admins
(wheel, agenix passwords) and can drop dawo.
The module and host were named hp-elitebook-850-g7, but the actual device is an
HP ProBook 4 G1i (16 inch Notebook AI PC, Intel Core Ultra 5 225U) - confirmed
on the installed machine. The config was already generic Intel (common-* + latest
kernel) so it boots fine; this only corrects the misleading name so the fleet and
the install proof match reality.

- modules/hardware/hp-elitebook-850-g7.nix -> hp-probook-4g1i.nix (block
  hardware-hp-probook-4g1i)
- modules/hosts/clients/dawo-hp-eb-850g7.nix -> dawo-hp-probook-4g1i.nix (host
  dawo-hp-probook-4g1i)
- hardware.md references updated.
Author
Collaborator

proof

proof
Capability in 0.1.0, off by default. When enabled per device
(dawo.diskUnlock.tpm2.enable) and enrolled on-device, systemd-stage-1 unlocks the
root from the TPM bound to PCR 7, so the user does not type the disk passphrase -
the UX win for the pilot. The passphrase stays as break-glass and TPM2 unlock
falls back to it. Best paired with dawo.secureboot.enable (already opt-in). The
on-device ceremony and recovery path are in docs/secureboot-tpm.md.
The six-laptop pilot is five KDE plus one GNOME, all on HP ProBook 4 G1i. Add the
GNOME host alongside the KDE one (same client recipe, only the desktop differs)
so the GNOME unit can be installed from the base image.
Collaborator

Adding WIP to header, please assign to me when ready for review.

Adding WIP to header, please assign to me when ready for review.
rutger.putter changed title from Release 0.1.0 - pilot baseline (proven on real hardware) to WIP: Release 0.1.0 - pilot baseline (proven on real hardware) 2026-06-23 10:52:52 +00:00
bram.buijs changed title from WIP: Release 0.1.0 - pilot baseline (proven on real hardware) to Release 0.1.0 - pilot baseline (proven on real hardware) 2026-06-23 16:29:35 +00:00
Author
Collaborator

@rutger.putter Ready for review

@rutger.putter Ready for review
Add dawo.bootstrapUser.enable (default true) so the documented dawo/dawo
login is a break-glass local admin a real deployment with named agenix
admins can switch off. Drop the working-tree-only pilot user import from
the HP ProBook client; named users belong in the org overlay, not the core.

Aligns release/0.1.0 with the image built and installed on the HP ProBook
4 G1i (GNOME) on 2026-06-23.
The generic core ships no SSH keys; admin/deploy keys belong in the
organisation overlay (ADR-0001/0003). Empty the deploy user's
authorizedKeys here - deploy@bitwarden and operator keys move to the
Zaanstad overlay.
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin release/0.1.0:release/0.1.0
git switch release/0.1.0
Sign in to join this conversation.
No reviewers
No milestone
No project
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
MinBZK/DAWO-NixOS!34
No description provided.