#!/bin/bash # Build a QEMU-bootable, LUKS-encrypted Debian image that reproduces lim's Tor # unlock stack for the initramfs-tools backend: the REAL hooks from # lim/configuration/initramfs-tools/, cryptsetup-initramfs + dropbear-initramfs, # offline onion keys, and a boot-ok marker service. Boots on a virtio machine # (models the software stack, not the Raspberry Pi board). # # Debian amd64 on an x86_64 host — no qemu-user needed. Needs root (debootstrap, # loop device, cryptsetup, chroot). See tests/e2e/qemu/README.md. # # Same env contract and image.env output as build_image.sh. set -euo pipefail : "${WORK_DIR:?set WORK_DIR}" : "${REPO_ROOT:?set REPO_ROOT}" : "${PASSPHRASE:?set PASSPHRASE}" : "${SSH_PUBKEY:?set SSH_PUBKEY}" SUITE="${DEBIAN_SUITE:-bookworm}" MIRROR="${DEBIAN_MIRROR:-http://deb.debian.org/debian}" MAPPER_NAME="cryptroot" ROOTFS="$WORK_DIR/rootfs" IMAGE="$WORK_DIR/image.raw" HOOK_SRC="$REPO_ROOT/lim/configuration/initramfs-tools" banner() { printf "\n========== %s ==========\n" "$1"; } # Debian keeps tools in /usr/sbin, which an Arch host's PATH (merged /usr/bin) # omits, so chroot can't find update-initramfs. Force a Debian-style PATH. in_chroot() { chroot "$ROOTFS" /usr/bin/env \ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin "$@" } cleanup() { set +e for m in dev/pts dev proc sys; do mountpoint -q "$ROOTFS/$m" && umount -l "$ROOTFS/$m" done mountpoint -q "$WORK_DIR/mnt" && umount -R "$WORK_DIR/mnt" [ -e "/dev/mapper/$MAPPER_NAME" ] && cryptsetup close "$MAPPER_NAME" [ -n "${LOOP:-}" ] && losetup -d "$LOOP" } trap cleanup EXIT banner "bootstrapping Debian rootfs with debootstrap ($SUITE)" mkdir -p "$ROOTFS" debootstrap --arch=amd64 --variant=minbase \ --include=linux-image-amd64,cryptsetup,cryptsetup-initramfs,dropbear-initramfs,tor,busybox,systemd-sysv,udev,ifupdown,isc-dhcp-client \ "$SUITE" "$ROOTFS" "$MIRROR" banner "binding chroot filesystems" cp /etc/resolv.conf "$ROOTFS/etc/resolv.conf" for m in proc sys dev dev/pts; do mkdir -p "$ROOTFS/$m" mount --bind "/$m" "$ROOTFS/$m" done banner "creating the LUKS image (UUID must exist before update-initramfs)" truncate -s 6G "$IMAGE" LOOP="$(losetup -f --show "$IMAGE")" printf '%s' "$PASSPHRASE" | cryptsetup luksFormat --type luks2 --batch-mode \ --pbkdf-memory 262144 "$LOOP" - LUKS_UUID="$(cryptsetup luksUUID "$LOOP")" banner "configuring crypttab / fstab / networking" # `initramfs` bakes the mapping in so cryptsetup-initramfs unlocks before pivot. echo "$MAPPER_NAME UUID=$LUKS_UUID none luks,initramfs" > "$ROOTFS/etc/crypttab" echo "/dev/mapper/$MAPPER_NAME / ext4 defaults,noatime 0 1" > "$ROOTFS/etc/fstab" echo "lim-e2e" > "$ROOTFS/etc/hostname" # initramfs-tools includes virtio via MODULES=most (default); be explicit. sed -i 's/^MODULES=.*/MODULES=most/' "$ROOTFS/etc/initramfs-tools/initramfs.conf" banner "authorizing the ssh key for dropbear-initramfs" install -D -m0600 "$SSH_PUBKEY" "$ROOTFS/etc/dropbear/initramfs/authorized_keys" banner "installing the real lim initramfs-tools tor hooks" install -D -m0755 "$HOOK_SRC/tor_hook" "$ROOTFS/etc/initramfs-tools/hooks/tor" install -D -m0755 "$HOOK_SRC/tor_premount" "$ROOTFS/etc/initramfs-tools/scripts/init-premount/tor" install -D -m0755 "$HOOK_SRC/tor_bottom" "$ROOTFS/etc/initramfs-tools/scripts/init-bottom/tor" install -D -m0644 "$HOOK_SRC/torrc" "$ROOTFS/etc/tor/initramfs-torrc" if [ -n "${TOR_TEST_NETWORK_CONF:-}" ]; then cat "$TOR_TEST_NETWORK_CONF" >> "$ROOTFS/etc/tor/initramfs-torrc" fi banner "generating onion keys offline" install -d -m0700 "$ROOTFS/etc/tor/initramfs-onion" : > "$WORK_DIR/keygen-torrc" tor -f "$WORK_DIR/keygen-torrc" --DisableNetwork 1 \ --DataDirectory "$WORK_DIR/keygen-data" \ --HiddenServiceDir "$ROOTFS/etc/tor/initramfs-onion" \ --HiddenServicePort "22 127.0.0.1:22" \ --SocksPort 0 --RunAsDaemon 0 --Log "notice stderr" & keygen_pid=$! for _ in $(seq 1 30); do [ -s "$ROOTFS/etc/tor/initramfs-onion/hostname" ] && break sleep 1 done kill "$keygen_pid" 2>/dev/null || true ONION_ADDRESS="$(cat "$ROOTFS/etc/tor/initramfs-onion/hostname")" banner "baking the boot-ok marker service + serial getty" cat > "$ROOTFS/etc/systemd/system/lim-boot-ok.service" <<'EOF' [Unit] Description=Signal a successful LUKS-unlock boot to the serial console After=multi-user.target [Service] Type=oneshot ExecStart=/bin/sh -c 'echo LIM_UNLOCK_BOOT_OK > /dev/console' [Install] WantedBy=multi-user.target EOF in_chroot systemctl enable lim-boot-ok.service serial-getty@ttyS0.service banner "generating the initramfs (update-initramfs)" in_chroot update-initramfs -c -k all || in_chroot update-initramfs -u -k all banner "exporting kernel + initramfs" KERNEL_IMG="$(find "$ROOTFS/boot" -maxdepth 1 -name 'vmlinuz-*' | sort | tail -1)" INITRAMFS_IMG="$(find "$ROOTFS/boot" -maxdepth 1 -name 'initrd.img-*' | sort | tail -1)" [ -n "$KERNEL_IMG" ] || { echo "No kernel in $ROOTFS/boot" >&2; exit 1; } [ -n "$INITRAMFS_IMG" ] || { echo "No initramfs in $ROOTFS/boot" >&2; exit 1; } echo "Using kernel $KERNEL_IMG and initramfs $INITRAMFS_IMG" cp "$KERNEL_IMG" "$WORK_DIR/kernel" cp "$INITRAMFS_IMG" "$WORK_DIR/initramfs" banner "unbinding chroot filesystems" for m in dev/pts dev proc sys; do umount -l "$ROOTFS/$m"; done banner "copying the rootfs into the encrypted volume" printf '%s' "$PASSPHRASE" | cryptsetup open "$LOOP" "$MAPPER_NAME" - mkfs.ext4 -q "/dev/mapper/$MAPPER_NAME" mkdir -p "$WORK_DIR/mnt" mount "/dev/mapper/$MAPPER_NAME" "$WORK_DIR/mnt" cp -a "$ROOTFS/." "$WORK_DIR/mnt/" sync umount -R "$WORK_DIR/mnt" cryptsetup close "$MAPPER_NAME" losetup -d "$LOOP" LOOP="" banner "writing image.env" cat > "$WORK_DIR/image.env" <