Using systemd-boot on Debian Bullseye

This post describes how to replace Grub with the systemd-boot UEFI boot manager. The setup is very similar to the method described in the article Replace GRUB2 on Ubuntu 10.04 with a few simplifications (no secure boot) and with a slightly improved kernel installer hook script.

Start by creating a boot config file /boot/efi/loader/loader.conf with the following content.

default systemd
timeout 1
editor 1

Create the directory that will hold the configuration files for the installed kernel images

mkdir -p /boot/efi/loader/entries

and install the boot loader with the bootctl command

bootctl install --path=/boot/efi

You can then check if the installation was successful with the efibootmgr. This command is also useful to change the boot order and to delete old boot entries. The systemd-boot entry should show up as "Linux Boot Manager".

Now systemd-boot is installed, but it does not know about the installed kernel images in the system.

Use the kernel-install command to add and remove the kernel to and from the systemd-boot directory structure. Simply create a /etc/kernel/postinst.d/zz-update-systemd-boot file with the following content:

#!/bin/sh
set -e

/usr/bin/kernel-install add "$1" "$2"

exit 0

Create a /etc/kernel/postrm.d/zz-update-systemd-boot file with the following content:

#!/bin/sh
set -e

/usr/bin/kernel-install remove "$1"

exit 0

Create the /boot/efi/<machine-id> directory if it doesn't exist (find the machine-id from the file /etc/machine-id) and run kernel-install manually to force the initial install. That command will look like kernel-install add `uname -r` /boot/vmlinuz-`uname -r`.

Now you should see all installed kernel images in the systemd-boot menu.

It is not necessary to remove grub, as long as it appears after systemd-boot in the output of efibootmgr. This also provides a useful fallback in case systemd-boot is having a problem.

Legacy install script

If the kernel-install command is not available, use the following script to update the systemd-boot configuration with the installed kernel images. Save the script to a system location, like /usr/local/bin/update-systemd-boot.sh and call it every time a new kernel is installed or removed from the system.

Also check the kernel parameters in the flags variable if you need special arguments passed to the kernel.

#!/bin/sh
#
# This is a simple kernel hook to populate the systemd-boot entries
# whenever kernel images are added or removed.
#


# The disk containing the root partition; also see `sudo blkid`
root_disk="UUID=$(findmnt / -o UUID -n)"

# The linux kernel arguments
flags="ro quiet"

prog_name=`basename $0`
boot_dir="/boot"
sd_boot_dir="loader/entries"
sd_boot_path="$boot_dir/efi/$sd_boot_dir"
title=
prefix=
force=0
verbose=0


usage() {
    echo "$prog_name [OPTIONS]"
}

help() {
    usage
    echo
    echo "OPTIONS"
    echo "  -h          show this help page"
    echo "  -f          force overwrite"
    echo "  -v          make verbose"
}

while getopts hfv opt; do
    case "$opt" in
        h)  help
            exit 0;;
        f)  force=1;;
        v)  verbose=1;;
        \?)     # unknown flag
            usage >&2
            exit 1;;
    esac
done
shift `expr $OPTIND - 1`

if [ $# -ne 0 ]; then
    echo >&2 "No extra argument allowed"
    exit 1
fi


sd_boot_entry() {
    cat <<-EOF
	title   $title
	version $version
	linux   /$sd_boot_dir/vmlinuz-$version
	initrd  /$sd_boot_dir/initrd.img-$version
	options root=$root_disk $flags
	EOF
}


if [ ! -h "/dev/disk/by-uuid/${root_disk#UUID=}" ]; then
    echo >&2 "error: root disk '$root_disk' not found"
    exit 1
fi

if [ ! -d "$sd_boot_path" ]; then
    echo >&2 "error: directory '$sd_boot_path' not found"
    exit 1
fi

if [ ! -f /etc/machine-id ]; then
    echo >&2 "error: machine id file '/etc/machine-id' not found"
    exit 1
fi
prefix="`cat /etc/machine-id`-v"

if [ -f /etc/os-release ]; then
    title=`sed -ne '/^PRETTY_NAME=/s/.*"\(.*\)".*/\1/p' /etc/os-release`
else
    title=Debian
fi


echo "Updating Systemd boot entries"

# Copy images from the Debian install directory to the EFI partition
find "$boot_dir" -maxdepth 1 -type f -name '*.dpkg-tmp' -prune -o -name 'vmlinuz-*' -exec cp -u {} "$sd_boot_path" \;
find "$boot_dir" -maxdepth 1 -type f -name '*.dpkg-tmp' -prune -o -name 'initrd.img-*' -exec cp -u {} "$sd_boot_path" \;

# Remove files from the EFI if they are missing from the Debian install directory
find "$sd_boot_path" -maxdepth 1 -type f -name 'vmlinuz-*' | while read i; do
    kernel="$boot_dir/`basename $i`"
    if [ ! -f "$kernel" ]; then
        rm -f "$i"
    fi
done
find "$sd_boot_path" -maxdepth 1 -type f -name 'initrd.img-*' | while read i; do
    initrd="$boot_dir/`basename $i`"
    if [ ! -f "$initrd" ]; then
        rm -f "$i"
    fi
done

# Remove the conf file
find "$sd_boot_path" -maxdepth 1 -type f -name "$prefix*.conf" | sort -Vr | while read i; do
    version=`basename $i | sed -e "s/$prefix//; s/.conf$//"`
    kernel="$sd_boot_path/vmlinuz-$version"
    initrd="$sd_boot_path/initrd.img-$version"
    if [ ! -f "$kernel" -o ! -f "$initrd" ]; then
        echo "Removing kernel v$version"
        rm -f "$i" "$kernel" "$initrd"
    fi
done

# Add new kernel entries
find "$boot_dir" -maxdepth 1 -type f -name '*.dpkg-tmp' -prune -o -name 'vmlinuz-*' -print | sort -Vr | while read i; do
    version=`basename $i | sed -e 's/vmlinuz-//'`
    initrd="$boot_dir/initrd.img-$version"
    file="$sd_boot_path/$prefix$version.conf"
    echo "Found kernel `basename $i`"
    if [ ! -f "$initrd" ]; then
        echo "Ignoring $i"
    elif [ $force -eq 1 -o ! -f "$file" ]; then
        echo "Adding $file"
        sd_boot_entry "$version" > "$file"
        cp -v $i "$initrd" "$sd_boot_path"
    fi
done

Calling the update script every time a kernel is installed or removed can be tedious, and luckily this can be automated. Simply create the following two scripts /etc/kernel/postinst.d/zz-update-systemd-boot and /etc/kernel/postrm.d/zz-update-systemd-boot with this content:

#! /bin/sh
set -e

sh /usr/local/bin/update-systemd-boot.sh
exit 0

Update 25 February 2021

Find the disk UUID using the findmnt command. Thanks to Björn Beckendorf for this tip.

Update 29 August 2021

Use the kernel-install command to add and remove the kernel image. Thanks to Alan MacLeod and Andrea Pappacoda for this tip.