#!/bin/sh # # setup-boot - Generate boot configuration for an installed system # rawnix.org - https://rawnix.org # # Usage: setup-boot # setup-boot /mnt # # Writes one file into the target: # # /etc/limine-sync.conf cmdline for the generated limine entries # # Encrypted volumes other than root are genluks's job -- it owns # /etc/crypttab and /etc/cryptkeys. This only warns if it finds some # and no crypttab exists. # # Run it after setup-filesystem and before installing the kernel. # The kernel package's boot/ trigger fires limine-sync, which reads # limine-sync.conf; with no cmdline the generated entries have no # root= and the install boots to a panic. # # Everything here is derived from what is mounted under # right now, exactly as genfstab does it. Mount the target the way it # will be at runtime before running this. # # LUKS root # --------- # If the target's root is a /dev/mapper/ device, the mapping is # traced back to its LUKS partition and the cmdline becomes # # cryptroot=UUID= root=/dev/mapper/ ro # # tinyrd creates the mapping under exactly the name in root=, so the # name you chose with `cryptsetup open` is the name that must survive # into the installed system. Nothing else records it. set -e die() { echo "ERROR: $*" >&2; exit 1; } say() { printf '>>> %s\n' "$*"; } warn() { printf ' ! %s\n' "$*" >&2; } [ "$(id -u)" = 0 ] || die "Must run as root" [ -n "$1" ] || die "Usage: setup-boot " [ -d "$1" ] || die "Directory not found: $1" MNT="${1%/}" [ -n "$MNT" ] || MNT="/" mountpoint -q "$MNT" || die "$MNT is not a mountpoint" [ -d "$MNT/etc" ] || die "$MNT/etc does not exist — run setup-filesystem $MNT first" # The ESP not being mounted is the failure that looks like success: # limine-sync refuses outright (good), but only at kernel-install time, # several minutes and one download later. Say so now. if ! mountpoint -q "$MNT/boot/efi"; then warn "$MNT/boot/efi is not a mountpoint" warn "mount the ESP there before installing the kernel, or limine-sync will refuse" else # A FAT partition with the wrong GPT type GUID takes the install all # the way to the end: mkfs works, the bootloader is copied, every # step reports success, and then firmware doing a spec-compliant # removable-media scan finds no partition of type ESP and boots # nothing. Firmware booting from an NVRAM entry that names the # partition directly never notices, which is what makes this # survive testing on one machine. _esp=$(findmnt -n -o SOURCE --target "$MNT/boot/efi" | head -1) _pt=$(lsblk -n -o PARTTYPE "$_esp" 2>/dev/null | head -1) case "$_pt" in c12a7328-f81f-11d2-ba4b-00a0c93ec93b|C12A7328-F81F-11D2-BA4B-00A0C93EC93B) ;; "") warn "cannot read the partition type of $_esp — check it is an EFI System Partition" ;; *) warn "$_esp is partition type $_pt, not an EFI System Partition" warn "some firmware will not boot it; fix with: sgdisk -t N:ef00 /dev/DISK" ;; esac fi # ── Root device ─────────────────────────────────────────────── ROOTDEV=$(findmnt -n -o SOURCE --target "$MNT" | head -1) [ -n "$ROOTDEV" ] || die "cannot determine the root device of $MNT" CRYPTLINE="" MAPNAME="" ROOTSPEC="" case "$ROOTDEV" in /dev/mapper/*) MAPNAME=${ROOTDEV#/dev/mapper/} command -v cryptsetup >/dev/null 2>&1 || \ die "$ROOTDEV is a device-mapper device but cryptsetup is not installed" cryptsetup status "$MAPNAME" >/dev/null 2>&1 || \ die "cryptsetup does not know the mapping $MAPNAME" # "type: LUKS2" — a plain dm-crypt or an LVM volume has no # header for tinyrd to find, and would boot to the 30s timeout. _type=$(cryptsetup status "$MAPNAME" | awk '$1 == "type:" { print $2 }') case "$_type" in LUKS*) ;; *) die "$MAPNAME is $_type, not LUKS — tinyrd can only unlock LUKS" ;; esac BACKING=$(cryptsetup status "$MAPNAME" | awk '$1 == "device:" { print $2 }') [ -n "$BACKING" ] || die "cannot find the backing device of $MAPNAME" LUKSUUID=$(cryptsetup luksUUID "$BACKING") [ -n "$LUKSUUID" ] || die "cannot read the LUKS UUID of $BACKING" CRYPTLINE="cryptroot=UUID=$LUKSUUID " # The mapper name is stable by construction, so no UUID needed: # tinyrd creates the mapping under exactly this name. ROOTSPEC="$ROOTDEV" say "Encrypted root: $BACKING -> $MAPNAME ($LUKSUUID)" ;; *) # Prefer the filesystem UUID. A device path is whatever the # kernel happened to call the disk during the install, and that # name is not a property of the disk: adding a drive, moving a # cable, or booting the same image with a different controller # renames it, and the installed system then stops at # "tinyrd: waiting for /dev/sdb2" for a partition that is # present under another name. tinyrd resolves UUID= itself by # reading superblocks -- there is no blkid or udev that early. _uuid=$(blkid -s UUID -o value "$ROOTDEV" 2>/dev/null || true) if [ -n "$_uuid" ]; then ROOTSPEC="UUID=$_uuid" say "Plain root: $ROOTDEV ($ROOTSPEC)" else ROOTSPEC="$ROOTDEV" warn "cannot read a filesystem UUID from $ROOTDEV" warn "falling back to the device path, which breaks if the disk is renamed" fi ;; esac # ── limine-sync.conf ────────────────────────────────────────── say "Writing $MNT/etc/limine-sync.conf" cat > "$MNT/etc/limine-sync.conf" << EOF # /etc/limine-sync.conf - generated by setup-boot # # Read by limine-sync when it regenerates the managed block of # limine.conf on the ESP. Keys are uppercase; an unrecognised one is # warned about and ignored. Other keys, with their defaults: # # TITLE=rawnix # MICROCODE=amd-ucode.img # INITRD=tinyrd.img (set empty to boot with no initramfs) # XEN_KERNEL= (series pin, e.g. 6.12) # # Edit and re-run limine-sync to apply. The hand-written part of the # menu is /etc/limine.conf.in, a different file. CMDLINE=${CRYPTLINE}root=$ROOTSPEC ro quiet EOF # ── crypttab ────────────────────────────────────────────────── # Not written here. genluks owns /etc/crypttab and /etc/cryptkeys, and # two tools writing one file means whichever runs last wins: setup-boot # re-run after genluks would put every volume back to "none", and the # first sign of it would be a boot prompting for volumes that used to # open by themselves. # # So this only looks, and says what it sees. if command -v cryptsetup >/dev/null 2>&1; then extras="" for src in $(findmnt -R -n -o SOURCE "$MNT" | sort -u); do case "$src" in /dev/mapper/*) ;; *) continue ;; esac name=${src#/dev/mapper/} [ "$name" != "$MAPNAME" ] || continue cryptsetup status "$name" >/dev/null 2>&1 || continue extras="$extras $name" done if [ -n "$extras" ]; then if [ -f "$MNT/etc/crypttab" ]; then say "Encrypted volumes besides root:$extras" say " already listed in $MNT/etc/crypttab" else warn "encrypted volumes besides root:$extras" warn "no $MNT/etc/crypttab — they will not be opened at boot" warn "run: genluks $MNT" fi fi fi echo "" say "Boot configuration ready:" sed -n 's/^CMDLINE=/ cmdline: /p' "$MNT/etc/limine-sync.conf" echo "" echo " Install the kernel last: its boot/ trigger runs limine-sync," echo " which writes limine.conf on the ESP from what is in /boot." echo ""