Architecture
The high-level view: what keyboot is, what it isn’t, and where it sits relative
to GRUB and the booted OS. Extracted from SPEC §3/§4; the locked design
decisions live in CLAUDE.md and decisions/ (the ADRs). Start here, then read
unlock-flow.md for the mechanism.
What keyboot is
A greenfield boot stage that runs after GRUB and before the target OS’s init. In one boot it:
- decrypts an age-of-LUKS keyfile container inside its own initramfs,
- uses that keyfile’s payload to open every LUKS device backing the root zpool,
- imports the pool,
- lets the operator pick a boot environment (TTY or SSH),
- kexecs into that BE’s own kernel + initrd (the ZBM model).
The same artifact, selected by keyboot.mode= on the kernel cmdline, is also
a netbootable rescue shell, an unattended installer, and a memtest
launcher:
keyboot.mode= | role |
|---|---|
boot | the normal unlock → import → pick BE → kexec path |
rescue | full ZFS+LUKS toolkit + memtester, interactive shell |
install | unattended installer (partition → keyfile → pool → BE → distro) |
provision | unlock + import read-write → install-os --add-be shell |
memtest | kexec the ESP-staged memtest86+ (ADR 0014) |
It targets Gentoo (openrc), Debian (systemd), and Alpine (openrc), on UEFI and BIOS-MBR.
What keyboot is NOT
- Not the bootloader. GRUB stays. keyboot is a kernel+initramfs GRUB boots like any other entry; it never replaces GRUB.
- Not a fork. Greenfield, not a patched ZBM/dracut/genkernel — those are reference points only (SPEC §3).
- Not the init. It hands off (kexec) to the BE’s own kernel + init; it does
not
switch_rootinto the OS. - Not re-done by the OS. The booted OS does no LUKS or ZFS-import work — keyboot already opened the disks and (via the post-kexec payload handoff) the BE just re-opens + mounts. No legacy ZFS mounts in any supported distro.
Where it fits
firmware (UEFI / BIOS-MBR)
└─ GRUB ← stays the bootloader; one entry = keyboot
└─ keyboot (its own kernel + initramfs)
stage 1-9: unlock → import → pick BE → kexec
└─ kexec ──▶ the BE's OWN kernel + initrd
└─ BE initramfs: keyboot be-unlock (re-open + mount)
└─ the distro's init (systemd / openrc)
The GRUB seam
keyboot owns its artifacts; the distro owns grub.cfg. keyboot-install install
places /boot/keyboot-vmlinuz + /boot/keyboot-initramfs.img and drops an
executable /etc/grub.d/10_keyboot snippet, so the distro’s own grub-mkconfig
emits a menuentry 'keyboot'. On a keyboot-owned ESP (install-os) the grub.cfg
is rendered directly. Either way keyboot stays one entry inside GRUB.
The three environments (keep them straight)
A keyboot host has up to three distinct ZFS/LUKS contexts — see hetzner-deploy.md:
- keyboot’s unlock env — its own lockstep kernel +
zfs.ko+ the keyfile. Imports read-only, briefly. Small uniform ARC (ADR 0011). - the booted OS / BE — the distro’s kernel +
zfs.ko, the real workload, production ARC. - (transient) a vendor/USB rescue — only for the one-time bootstrap; after
install, keyboot’s own
provisionmode is the rescue (the “ENV-as-rescue” vision).
The architectural pillars (locked decisions, SPEC §3)
- Unlock = a LUKS1 keyfile container, not age. Its 32-byte payload is the AES-256 key for every data disk; multi-slot (daily/recovery/automation). LUKS1 for the container and the disks. See unlock-flow.md, SPEC §7.
- Handoff = kexec into the BE’s kernel (ADR 0003): the 32-byte payload is
carried RAM-only across the kexec so the BE re-unlocks with a single prompt;
keyboot.handoff=0falls back to an independent dropbear-over-SSH re-unlock (ADR 0002). - Devices open as
sn-<serial>so pool topology survives bus reordering; one Rust source for serial discovery, shared install-time and boot-time. - A/B ESP slots for keyboot itself (ADR 0009): two slots + a decrement-before-boot trial counter → a bad keyboot upgrade auto-reverts. The GL#44 one-shot provision builds on this.
- BE discovery enumerates any mountable dataset; no enforced layout. BE rollback is a pre-boot three-rung ladder (ADR 0007).
- ZFS in lockstep with upstream OpenZFS; keyboot’s embedded ZFS is the pool feature floor — upgrade keyboot before bumping pool features (ADR 0004).
- Delivery = CI-signed native packages (apk/ebuild/deb) in our own repos + a signed rescue USB; ansible manages the repo definitions (ADR 0008).
The pieces
| Path | What |
|---|---|
init/ | the /init entrypoint + numbered stages 1-9 + lib/ helpers (the unlock env runtime) |
tools/keyboot/ | the Rust core: keyboot (runtime: discover/unlock/be-unlock/slot-commit) + keyboot-install (installer: keyfile/partition/install/install-os/keyboot-slots) |
tools/keyboot-install-os/ | the install-os orchestrator (busybox/bash) + per-distro plugins + lib/ |
tools/keyboot-be-* | the BE-side lifecycle tools (autosnap, upgrade, rollback) |
kernel/, zfs/ | the embedded LTS kernel + the lockstep OpenZFS build |
pkg/, ci/ | native packaging + the build / QEMU / signing pipeline |
Design invariants (do not break)
- The decrypted keyfile/payload is RAM-only — tmpfs, never non-volatile storage, the cmdline, dmesg, or a crashdump; zeroed after use.
- GRUB stays the bootloader; keyboot is never it.
- The target OS does no LUKS/ZFS re-work; no legacy ZFS mounts.
- Disk-serial discovery has a single source (the Rust code), shared by install and boot.
See: unlock-flow.md, operator-guide.md,
disaster-recovery.md, decisions/ (the ADRs), and
SPEC.md (the design of record).