fix(deploy): dropbear at boot on initramfs-tools, with dracut's image as the way back (ADR 0039, D20)

Ubuntu 26.04 builds its boot images with dracut, and dropbear-initramfs needs
initramfs-tools, which replaces it. The host's step 2 now installs
initramfs-tools with cryptsetup-initramfs beside dropbear, sets the boot
image's address in a conf.d file of its own, and checks every new image for
what opens the disk, dropbear, its key and the adapter's driver before any
reboot. Before switching, dracut's image of the running kernel is kept as an
entry in GRUB's menu (shown 5 s, the boot in text), so a failed image is
undone at the iMac without a USB stick.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-08 21:10:22 +01:00
commit 0144cea380
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
6 changed files with 119 additions and 9 deletions

View file

@ -10,6 +10,9 @@ end of the tunnel.
| `sshd-halkyon.conf` | `/etc/ssh/sshd_config.d/10-halkyon.conf` | keys alone, two users (D17) |
| `dropbear.conf` | `/etc/dropbear/initramfs/dropbear.conf` | the passphrase over SSH at boot (D20) |
| `initramfs-modules` | appended to `/etc/initramfs-tools/modules` | the USB network adapter at boot (D20) |
| `initramfs-ip` | `/etc/initramfs-tools/conf.d/halkyon-ip` | its address by DHCP at boot (D20) |
| `grub-halkyon.cfg` | `/etc/default/grub.d/halkyon.cfg` | GRUB's menu for 5 s, the boot in text (D20) |
| `grub-dracut-fallback` | `/etc/grub.d/41_dracut_fallback` | the way back to dracut's image (D20) |
| `fstab-docker` | appended to `/etc/fstab` | Docker's and containerd's data on the HDD (D12) |
| `docker-after-data.conf` | `docker.service.d/` and `containerd.service.d/` | both wait for it (D12) |
| `wg-halkyon.conf` | `/etc/wireguard/wg-halkyon.conf` | home's end of the tunnel (D8) |
@ -45,22 +48,53 @@ for the later.
(*Heimnetz › Netzwerk › balthasar › Diesem Netzwerkgerät immer die gleiche
IPv4-Adresse zuweisen*), so that 192.168.178.53 stays its address.
2. **The passphrase over SSH at boot** (D20):
2. **The passphrase over SSH at boot** (D20). Ubuntu 26.04 builds its boot images with
dracut; `dropbear-initramfs` needs initramfs-tools, which replaces it. So first the
way back: today's image, dracut's, kept for the kernel running now, as an entry in
GRUB's menu, which shows for 5 seconds from now on:
```sh
sudo apt install dropbear-initramfs
sudo cp /boot/initrd.img-$(uname -r) /boot/initrd.img-$(uname -r).dracut
sudo install -m 644 deploy/host/grub-halkyon.cfg /etc/default/grub.d/halkyon.cfg
sudo install -m 755 deploy/host/grub-dracut-fallback /etc/grub.d/41_dracut_fallback
sudo update-grub # "Found the way back: … with dracut's image"
grep -c 'the way back' /boot/grub/grub.cfg # 1
```
Then initramfs-tools, with what opens the root disk (`cryptsetup-initramfs`) and
dropbear. apt lists `dracut` and `dracut-core` to be removed, and nothing else:
```sh
sudo apt install initramfs-tools cryptsetup-initramfs dropbear-initramfs
sudo install -m 644 deploy/host/dropbear.conf /etc/dropbear/initramfs/dropbear.conf
# melchior's public key alone (from ~/.ssh/authorized_keys, step 1), restricted:
printf 'no-port-forwarding,no-agent-forwarding,no-X11-forwarding,command="/usr/bin/cryptroot-unlock" %s\n' \
"$(cat ~/.ssh/authorized_keys)" | sudo tee /etc/dropbear/initramfs/authorized_keys
grep -v '^#' deploy/host/initramfs-modules | sudo tee -a /etc/initramfs-tools/modules
echo 'IP=::::balthasar:enx0050b6bffcff:dhcp' | sudo tee -a /etc/initramfs-tools/initramfs.conf
sudo update-initramfs -u
sudo install -m 644 deploy/host/initramfs-ip /etc/initramfs-tools/conf.d/halkyon-ip
sudo update-initramfs -u -k all
```
At the next boot, at home: `ssh -p 2222 root@192.168.178.53` from melchior asks for the
root disk's passphrase, and the boot goes on. The keyboard at the iMac still works as
before, so a mistake here costs no more than the walk to it.
Before any reboot, each new image must hold what opens the disk, dropbear, its key
and the adapter's driver:
```sh
for image in /boot/initrd.img-*-generic; do
echo "== $image"
sudo lsinitramfs "$image" | grep -E 'cryptroot/crypttab$|sbin/cryptsetup$|sbin/dropbear$|/authorized_keys$|cdc_ncm\.ko|sbin/lvm$'
done # for each: crypttab, cryptsetup, dropbear, authorized_keys, cdc_ncm, lvm
sudo sh -c 'unmkinitramfs /boot/initrd.img-$(uname -r) /tmp/initrd &&
find /tmp/initrd -path "*cryptroot/crypttab" -exec cat {} +; rm -rf /tmp/initrd'
# dm_crypt-0 UUID=… none luks…
sudo dropbearkey -y -f /etc/dropbear/initramfs/dropbear_ed25519_host_key | grep -i fingerprint
```
The last line is dropbear's own host key, which melchior sees on port 2222 at the
next boot. At that boot, at home: `ssh -p 2222 root@192.168.178.53` from melchior asks
for the root disk's passphrase, and the boot goes on. The keyboard at the iMac still
works as before. Should the new image not open the disk, choose *the way back* in
GRUB's menu at the iMac: it boots as before this step. Once the new images have
booted: `sudo rm /etc/grub.d/41_dracut_fallback /boot/initrd.img-*.dracut && sudo update-grub`.
3. **No desktop** (D12): it stops starting at boot, and Docker Desktop, which runs a VM of
its own beside Docker, goes. Multipass is switched off while no VM is needed: its

View file

@ -0,0 +1,27 @@
#!/bin/sh
# The way back to the boot image dracut made (ADR 0039, D20), as
# /etc/grub.d/41_dracut_fallback (root, 0755) on the instance's host at home, which
# update-grub runs. Ubuntu 26.04 builds its boot images with dracut, and
# dropbear-initramfs needs initramfs-tools, which replaces it: should an image of
# initramfs-tools not open the root disk, this entry boots a kernel with the image
# dracut had made for it, kept beside it as initrd.img-<version>.dracut. Chosen at the
# iMac in GRUB's menu (grub-halkyon.cfg). Once initramfs-tools' images have booted,
# the copy and this file may go.
set -e
boot=$(findmnt -no UUID /boot)
for image in /boot/initrd.img-*.dracut; do
[ -e "$image" ] || continue
version=${image#/boot/initrd.img-}
version=${version%.dracut}
[ -e "/boot/vmlinuz-$version" ] || continue
echo "Found the way back: $version with dracut's image" >&2
cat <<EOF
menuentry 'Ubuntu, $version with dracut (the way back)' {
insmod part_gpt
insmod ext2
search --no-floppy --fs-uuid --set=root $boot
linux /vmlinuz-$version root=/dev/mapper/ubuntu--vg-ubuntu--lv ro
initrd /initrd.img-$version.dracut
}
EOF
done

View file

@ -0,0 +1,7 @@
# GRUB on the instance's host at home (ADR 0039, D20), as /etc/default/grub.d/halkyon.cfg:
# its menu shown for 5 seconds, so that at the iMac the way back (grub-dracut-fallback)
# can be chosen, and the boot in text, without a splash: no desktop, and the
# passphrase's prompt is plain on the screen as over SSH.
GRUB_TIMEOUT_STYLE=menu
GRUB_TIMEOUT=5
GRUB_CMDLINE_LINUX_DEFAULT=""

4
deploy/host/initramfs-ip Normal file
View file

@ -0,0 +1,4 @@
# The boot image's network (ADR 0039, D20), as /etc/initramfs-tools/conf.d/halkyon-ip
# on the instance's host at home: the USB adapter by DHCP, so dropbear is reached at
# balthasar's fixed address (the FRITZ!Box keeps it).
IP=::::balthasar:enx0050b6bffcff:dhcp

View file

@ -191,7 +191,13 @@
never clashes with sshd's in a `known_hosts`). It takes melchior's key alone, which may
run nothing but `cryptroot-unlock`. The USB network adapter's driver (`cdc_ncm`) is
added to the initramfs. From away, a reboot waits for someone at home, as the tunnel
isn't up that early. The keyboard still works as before.
isn't up that early. The keyboard still works as before. Ubuntu 26.04 builds its boot
images with dracut, for which Ubuntu packages no SSH at boot; so initramfs-tools
replaces it, with `cryptsetup-initramfs` and Ubuntu's `dropbear-initramfs` (the
developer's choice of 8 Nov 2026, over the unpackaged dracut-sshd and over no
unlocking by SSH). dracut's last image stays in GRUB's menu, shown for 5 seconds, as
the way back until initramfs-tools' images have booted, so a failed image costs a
choice at the iMac rather than a USB stick.
## Consequences

View file

@ -270,8 +270,40 @@ void main() {
expect(options, containsAll(['-c', 'cryptroot-unlock']));
});
test("loads the USB network adapter's driver at boot", () {
test("loads the USB network adapter's driver at boot, and takes its "
'address by DHCP there', () {
expect(lines(read('deploy/host/initramfs-modules')), contains('cdc_ncm'));
expect(lines(read('deploy/host/initramfs-ip')), [
'IP=::::balthasar:enx0050b6bffcff:dhcp',
]);
});
group("keeps a way back to dracut's image, which initramfs-tools "
'replaces', () {
final fallback = read('deploy/host/grub-dracut-fallback');
test("as GRUB's entry for each kernel that has a copy of it", () {
expect(
fallback,
contains('for image in /boot/initrd.img-*.dracut; do'),
);
expect(
fallback,
contains(r'[ -e "/boot/vmlinuz-$version" ] || continue'),
);
expect(fallback, contains(r'initrd /initrd.img-$version.dracut'));
expect(
File('deploy/host/grub-dracut-fallback').statSync().mode & 0x49,
0x49,
reason: 'executable, as update-grub runs it',
);
});
test("in GRUB's menu, shown long enough to choose it at the iMac", () {
final grub = lines(read('deploy/host/grub-halkyon.cfg'));
expect(grub, contains('GRUB_TIMEOUT_STYLE=menu'));
expect(grub, contains('GRUB_TIMEOUT=5'));
});
});
test("keeps Docker's data on the HDD, which Docker waits for", () {