Hi all i created a script that will allow you to use the Systemd-Boot loader even with SecureBoot enabled.
I have also cross-posted a link to here in @github: systemd/issues/16457.
Explanation of what this script does:
Current latest version is: zz-update-systemd-boot.txt (Remove extension and make executable)
Hope it is useful to the community,
Maybe Kubuntu devs can use this in it's distro
Output from my current running system:
I have also cross-posted a link to here in @github: systemd/issues/16457.
Explanation of what this script does:
- Automatically generate a boot-entry called "Linux Secure-Bootloader" for your UEFI-Bios boot menu.
And Automatically update (as needed) the Shim, MokManager and Systemd-boot used to boot your system in the ESP, see installUEFIbootEntry(). - When ever you install a new kernel, or install/update any software/drivers that causes the ramdisk to be regenarated, the script will automatically update the ones used in the ESP partition to boot your OS.
These files are in a separate 'kubuntu' vendor-dir in the ESP and does not interfere with those that your distro installs. - Automatically generate boot entries (as needed) in Systemd-boot's boot menu that you can choose from:
- The latest kernel and ramdisk.
This entry is generated with a boot-try counter of 3 to detect boot failures, see getLoaderVars(). - The latest kernel and previous ramdisk.
- The old kernel and old ramdisk.
- The latest kernel and ramdisk.
- Automatically adds the kernel commandline options used from current boot, including the current swap partition to resume from.
At moment it also always adds "intel_iommu=on", see getCmdLineOptions() if you don't want or modify this behaviour.
Warning: First boot/After each update of Systemd-boot you need to either:
(Maybe i will automate this step later on)
- Enroll the hash of <ESP>/EFI/systemd/grubx64.efi using the MokManager that will come-up after boot.
- OR automatically sign <ESP>/EFI/systemd/grubx64.efi with an accepted key for your SecureBoot system (eg. in db, KEK or MokList).
You can't use /var/lib/shim-signed/mok/MOK.key to sign it because it is only valid for kernel-drivers...
(Maybe i will automate this step later on)
Current latest version is: zz-update-systemd-boot.txt (Remove extension and make executable)
Code:
#!/usr/bin/env bash # This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. # https://creativecommons.org/licenses/by-sa/4.0/ # # @version 2020.07.26 # @author ©TriMoon™ <https://gitlab.com/TriMoon> # @copyright (C) 2020+ {@link https://gitlab.com/TriMoon|©TriMoon™} # @license CC-BY-SA-4.0 # # To install this script execute with 'installme' as argument. # Manual test after installing can be done by using `update-initramfs -u` # # shellcheck disable=SC2046 if test $(id -u) -ne 0; then printf "%s\n" \ "Need ROOT, aborting !" exit 1 fi # Global variables used in this script... declare -i \ bTestFlags \ BootTries \ versionFound \ latestFound \ prevFound \ oldFound declare -a \ KERNEL_CMDLINE declare \ ESP \ MACHINE_ID \ SWAP_PAR_UUID \ ROOT_PAR_UUID \ KERNEL \ KERNEL_LATEST \ VERSION_KERNEL_LATEST \ KERNEL_OLD \ VERSION_KERNEL_OLD \ RAMDISK \ RAMDISK_LATEST \ VERSION_RAMDISK_LATEST \ RAMDISK_OLD \ VERSION_RAMDISK_OLD \ LOADERID \ VERSION \ loader_entries \ loader_conf \ log_name \ log_func_name \ log_header_start \ log_header_end \ log_func_start \ log_func_end \ log_header \ log_func # shellcheck disable=SC2034 source /etc/os-release # Install this script function installScript(){ local -a \ opts \ extradirs local \ mainDest log_func_name="Install" log_func="${log_func_start}${log_func_name}${log_func_end}" printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Installing helper..." extradirs=( # One for the kernel's postrm /etc/kernel/postrm.d # Another for the initramfs's post-update /etc/initramfs/post-update.d ) opts=( --verbose --parents ) # Not always available by default... mkdir "${opts[@]}" \ /etc/initramfs/post-update.d # Install a fresh script in main place if needed. mainDest="/etc/kernel/postinst.d/zz-update-systemd-boot" if test ! -f "$mainDest" \ || ! diff "$0" "$mainDest" >/dev/null then # copy into place opts=( --verbose --archive ) cp "${opts[@]}" "$0" "$mainDest" # Set access permissions opts=( --modify "u::rwx,g::rx,o::r,g:adm:rwx" ) setfacl "${opts[@]}" "$mainDest" # Remove the symlinks, if any, to reflect timestamp on new ones. for d in "${extradirs[@]}"; do rm --force "$d/zz-update-systemd-boot" done fi # Link the other needed scripts to the main one. opts=( --verbose --symbolic --force ) for d in "${extradirs[@]}"; do if test ! -L "$d/zz-update-systemd-boot"; then ln "${opts[@]}" \ "$mainDest" \ "$d/zz-update-systemd-boot" fi done # opts=( # -l # --all # "--color=auto" # ) # for d in /etc/kernel/postinst.d "${extradirs[@]}"; do # ls "${opts[@]}" "$d/zz-update-systemd-boot" # done } function checkNeededBins(){ local -i missingBins local -a neededBins local Bin log_func_name="check needed bins" log_func="${log_func_start}${log_func_name}${log_func_end}" printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Check needed bins..." # Check for needed binaries for functionality. neededBins=( # pkg: coreutils "/bin/cp" "/bin/mv" "/bin/rm" "/usr/bin/head" "/usr/bin/mktemp" "/usr/bin/readlink" "/usr/bin/test" "/usr/bin/touch" # pkg: sed "/bin/sed" # pkg: grep "/bin/grep" # pkg: libc-bin # To create the BOOTX64.CSV "/usr/bin/iconv" # pkg: mount # To determine swap partition. "/sbin/swapon" # pkg: util-linux # To get UUID of root and swap partitions "/bin/lsblk" # pkg: systemd # To determine root partition "/bin/systemctl" # Systemd-boot "/usr/lib/systemd/boot/efi/systemd-bootx64.efi" # pkg: shim # MokManager "/usr/lib/shim/mmx64.efi" # Unsigned-Shim loader "/usr/lib/shim/shimx64.efi" # pkg: shim-signed # Signed-Shim loader # This package is optional because you can sign your own, # but if you DO sign your own then place it here: # "/usr/lib/shim/shimx64.efi.signed" # pkg: efibootmgr # For EFI-boot entries "/bin/efibootmgr" # pkg: systemd # For determining ESP "/usr/bin/bootctl" # pkg: gcc # To determine machine architecture "/usr/bin/cc" # pkg: diffutils # To determine differences of binary files "/usr/bin/cmp" ) missingBins=0 for Bin in "${neededBins[@]}"; do if test ! -f "$Bin"; then missingBins=$((missingBins+1)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "'$Bin' missing !" fi done if test $missingBins -gt 0; then printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Missing $missingBins needed binaries, aborting !" exit 1 else printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "All ok" fi } # Grab current Machine ID. function getMachineID(){ MACHINE_ID=$(</etc/machine-id) } # Grab the UUID of the root partition. function getRootUUID(){ local ROOT_PAR # ROOT_PAR=$(\ # df -l --output=source / \ # | tail -n +2\ # ) # Ask systemd for the device of the root partition. ROOT_PAR=$(\ systemctl status -- -.mount \ | sed -En "/What:/ {s|^.*\s(.*)$|\1|; p}"\ ) # get the UUID of the ROOT_PAR ROOT_PAR_UUID=$(\ lsblk \ --fs \ --noheadings \ --output UUID \ "$ROOT_PAR" \ ) } # Grab the UUID of the resume partition. function getResumeUUID(){ local SWAP_PAR # Ask swapon for the first swap partition. # FIXME: We should check that its a partition ! SWAP_PAR=$(\ swapon \ --noheadings \ --show=NAME \ | head --lines=1\ ) # Grab the UUID of the SWAP_PAR SWAP_PAR_UUID=$(\ lsblk \ --fs \ --noheadings \ --output=UUID \ "$SWAP_PAR"\ ) } # Grab current ESP prtition. function getESP(){ # Ask bootctl for the esp path. ESP=$(\ bootctl --print-esp-path\ ) } # Grab the command line options for the kernel. function getCmdLineOptions(){ KERNEL_CMDLINE=() # Iterate over the current command line options provided to the kernel. for arg in $(</proc/cmdline); do case $arg in # Grub uses this, loader entries define it differently... BOOT_IMAGE=*) ;& # We set this resume==*) ;& # We set this root=*) ;& # We override this intel_iommu=*) ;& # Just a dummy to be stepped-into by above. --non-existing-option--) # We skip continue ;; # Add rest *) KERNEL_CMDLINE+=( "$arg" ) esac done # We override and always add these # Comment out if not wanted... KERNEL_CMDLINE+=( "intel_iommu=on" ) # echo "${KERNEL_CMDLINE[@]}" } # Grab the subdir to be used for the loader entries # This function needs these variables defined before calling: # ESP function getLoaderVars(){ local d loader_entries="/loader/entries" BootTries=3 log_func_name="Loader Vars" log_func="${log_func_start}${log_func_name}${log_func_end}" # ID is populated from /etc/os-release # These tests are just a few ideas at moment. case "${ID,,}" in "ubuntu") # Below only works when already in a GUI, # thats why we fix it below for now... if test\ "$DESKTOP_SESSION" = "plasma" \ -a "$XDG_SESSION_DESKTOP" = "KDE" \ -a "$XDG_CURRENT_DESKTOP" = "KDE" then LOADERID=kubuntu fi LOADERID=kubuntu ;; esac if test -z "$LOADERID"; then printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Could not compose a loaderID, aborting !" exit 1 fi # Full path to the loader entry config file(s) without extension, # so that we can append to it as needed. loader_conf="${ESP}${loader_entries}/${LOADERID}" # Create the needed directories when absent. for d in "${ESP}${loader_entries}" "${ESP}/${LOADERID}"; do test ! -d "$d" && mkdir --verbose "$d" done } # Fill the kernel and ramdisk variables. # This function needs these variables defined before calling: # KERNEL # RAMDISK function getKernelAndRamdiskVars(){ ### Find the Real filename and versions of: # Latest kernel KERNEL_LATEST=$(readlink --canonicalize-existing "/boot/${KERNEL}") if test -n "$KERNEL_LATEST"; then VERSION_KERNEL_LATEST="${KERNEL_LATEST#*-}" VERSION_KERNEL_LATEST="${VERSION_KERNEL_LATEST%-*}" fi # Latest ramdisk RAMDISK_LATEST=$(readlink --canonicalize-existing "/boot/${RAMDISK}") if test -n "$RAMDISK_LATEST"; then VERSION_RAMDISK_LATEST="${RAMDISK_LATEST#*-}" VERSION_RAMDISK_LATEST="${VERSION_RAMDISK_LATEST%-*}" fi # Old kernel KERNEL_OLD=$(readlink --canonicalize-existing "/boot/${KERNEL}.old") if test -n "$KERNEL_OLD"; then VERSION_KERNEL_OLD="${KERNEL_OLD#*-}" VERSION_KERNEL_OLD="${VERSION_KERNEL_OLD%-*}" fi # Old ramdisk RAMDISK_OLD=$(readlink --canonicalize-existing "/boot/${RAMDISK}.old") if test -n "$RAMDISK_OLD"; then VERSION_RAMDISK_OLD="${RAMDISK_OLD#*-}" VERSION_RAMDISK_OLD="${VERSION_RAMDISK_OLD%-*}" fi # echo "$VERSION_KERNEL_LATEST" # echo "$VERSION_RAMDISK_LATEST" # echo "$VERSION_KERNEL_OLD" # echo "$VERSION_RAMDISK_OLD" # exit 123 } # This function needs these variables defined before calling: # ESP function installUEFIbootEntry(){ local -a \ opts \ bootEntries local -i \ needToCreateEntry local \ destDir \ entryLabel \ vendor \ shimLib \ bootEntry \ bootEntryLabel vendor="systemd" entryLabel="Linux Secure-Bootloader" destDir="${ESP}/EFI/$vendor" log_func_name="UEFI bootEntry" log_func="${log_func_start}${log_func_name}${log_func_end}" printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Preparing..." # Create the destination dir if non-existant. test ! -d "${destDir}" && mkdir -v "${destDir}" opts=( --verbose # Does NOT work because of ESP-FS's timestamp precision # --update --archive "--no-preserve=ownership" ) shimLib="/usr/lib/shim" # Copy the Signed-Shim loader. if test -f "${shimLib}/shimx64.efi.signed"; then # Preventing un-needed writes. if ! cmp "${shimLib}/shimx64.efi.signed" \ "${destDir}/shimx64.efi" \ >/dev/null then cp "${opts[@]}" \ "${shimLib}/shimx64.efi.signed" \ "${destDir}/shimx64.efi" fi # Else copy the Unsigned-Shim loader. elif test -f "${shimLib}/shimx64.efi"; then # Preventing un-needed writes. if ! cmp "${shimLib}/shimx64.efi" \ "${destDir}/shimx64.efi" \ >/dev/null then cp "${opts[@]}" \ "${shimLib}/shimx64.efi" \ "${destDir}" fi else # This should NEVER happen, # because we already tested in checkNeededBins() printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Unable to find a Shim binary, aborting !" exit 1 fi # Copy the Shim loader's mokManager. # Preventing un-needed writes. if ! cmp "${shimLib}/mmx64.efi" \ "${destDir}/mmx64.efi" \ >/dev/null then cp "${opts[@]}" \ "${shimLib}/mmx64.efi" \ "${destDir}" fi # Copy the Systemd-boot loader. # Preventing un-needed writes. if ! cmp "/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \ "${destDir}/grubx64.efi" \ >/dev/null then cp "${opts[@]}" \ "/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \ "${destDir}/grubx64.efi" fi # Create the 'BOOTX64.CSV' file printf "%s,%s,%s,%s\n" \ "shimx64.efi" \ "$entryLabel" \ "" \ "This is the SecureBoot entry for Systemd-Boot" \ | iconv -t UCS-2 --output="${destDir}/BOOTX64.CSV" # Grab EFI-bootloader entries we are interested in. mapfile -t bootEntries < <( efibootmgr --verbose \ | grep --ignore-case --regexp="\\\\$vendor\\\\" \ | sed -E 's|^Boot([[:digit:]]+).*|\1|' ) # Check if we need to create a fresh UEFI bootEntry. needToCreateEntry=1 if test "${#bootEntries[@]}" -gt 0; then for bootEntry in "${bootEntries[@]}"; do bootEntryLabel=$( efibootmgr \ | sed -En "/^Boot$bootEntry/ {s|Boot$bootEntry(\*)?\s+(.*)|\2|; p}" ) case "$bootEntryLabel" in "Linux Bootloader") printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Found default systemd-boot entry" # Just informational. ;; "$entryLabel") printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "UEFI SecureBoot entry already exists..." needToCreateEntry=0 # efibootmgr -B -b "$bootEntry" >/dev/null ;; *) # We should normally not end here... printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Found extra entry $bootEntry = '$bootEntryLabel'" esac done fi if test "${needToCreateEntry}" -eq 1; then printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Creating UEFI SecureBoot entry..." efibootmgr \ --create \ --label "$entryLabel" \ --loader "/EFI/systemd/shimx64.efi" \ >/dev/null fi } # Outputs a loader entry config # $1 = (latest|old|prev) # $2 = version # $3 = Kernel filename (without path) # $4 = Ramdisk filename (without path) function createLoaderEntry(){ local -i \ fieldWidth local -a \ contents \ loader_options local \ txtLine \ EFI_ARCH fieldWidth=12 contents=() # Taken from shim's Makefile EFI_ARCH=$( cc -dumpmachine \ | cut -f1 -d- \ | sed \ -e 's,aarch64,aa64,' \ -e 's,arm.*,arm,' \ -e 's,i[3456789]86,ia32,' \ -e 's,x86_64,x64,' \ ) # Header printf -v txtLine "# %s/%s_" \ "${loader_entries}" "${LOADERID}" # Don't add $1 for latest test "$1" != "latest" && txtLine+="$1" contents+=( "${txtLine}.conf" ) # Title (Captialized first char) printf -v txtLine "%-*s %s" $fieldWidth \ "title" "${LOADERID^} ${VERSION}" # Add to total output. contents+=( "${txtLine}" ) # Version printf -v txtLine "%-*s %s" $fieldWidth \ "version" "$2" # Not needed anymore because non-prev versions don't need it, # and prev-version adds it self while transforming... # # Don't add $1 for old. # test "$1" != "old" && txtLine+="-$1" # Add to total output. contents+=( "${txtLine}" ) # Machine ID printf -v txtLine "%-*s %s" $fieldWidth \ "machine-id" "$MACHINE_ID" # Add to total output. contents+=( "${txtLine}" ) # Architecture printf -v txtLine "%-*s %s" $fieldWidth \ "architecture" "$EFI_ARCH" # Add to total output. contents+=( "${txtLine}" ) # Kernel printf -v txtLine "%-*s %s" $fieldWidth \ "linux" "/${LOADERID}/$3" # Add to total output. contents+=( "${txtLine}" ) # Ramdisk printf -v txtLine "%-*s %s" $fieldWidth \ "initrd" "/${LOADERID}/$4" # Add to total output. contents+=( "${txtLine}" ) # Options loader_options=() # TODO: Resume is restricted when in SecureBoot, but how to resume in that case? # Always add the resume partion. loader_options+=( "resume=UUID=$SWAP_PAR_UUID" ) # Add the resume partion when SecureBoot is NOT enabled # # test $(mokutil --sb-state >/dev/null) && \ # # loader_options+=( "resume=UUID=$SWAP_PAR_UUID" ) # test "SecureBoot enabled" != $(mokutil --sb-state) && \ # loader_options+=( "resume=UUID=$SWAP_PAR_UUID" ) # Add root partition to use test -n "$ROOT_PAR_UUID" && \ loader_options+=( "root=UUID=$ROOT_PAR_UUID" ) # Add the remaining options loader_options+=( "${KERNEL_CMDLINE[@]}" ) printf -v txtLine "%-*s%s" $fieldWidth \ "options" \ "$(\ printf " %s" \ "${loader_options[@]}" \ )" # Add to total output. contents+=( "${txtLine}" ) # Output the total output. printf "%s\n" "${contents[@]}" } # Old-kernel-logic function oldKernelLogic(){ local -a cp_opts log_func_name="Old Logic" log_func="${log_func_start}${log_func_name}${log_func_end}" bTestFlags=0 # Can't use -nt/-ot because of timestamp precision diffs of FS's... # The tests: (Binary flags) # 0001 = Old kernel does NOT exist in ESP. # or DIFFERS from the one in ESP. if test ! -f "${ESP}/${LOADERID}/${KERNEL}.old" \ || ! cmp "${KERNEL_OLD}" "${ESP}/${LOADERID}/${KERNEL}.old" >/dev/null then bTestFlags=$((bTestFlags | 2#0001)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Old kernel is not in ESP or has changed" fi # 0010 = Old ramdisk does NOT exist in ESP. # or DIFFERS from the one in ESP. if test ! -f "${ESP}/${LOADERID}/${RAMDISK}.old" \ || ! cmp "${RAMDISK_OLD}" "${ESP}/${LOADERID}/${RAMDISK}.old" >/dev/null then bTestFlags=$((bTestFlags | 2#0010)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Old ramdisk is not in ESP or has changed" fi # 0100 = Prev kernel exists in ESP. if test -f "${ESP}/${LOADERID}/${RAMDISK}.prev"; then bTestFlags=$((bTestFlags | 2#0100)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Prev kernel exists in ESP" fi # 1000 = Prev ramdisk exists in ESP. if test -f "${ESP}/${LOADERID}/${RAMDISK}.prev"; then bTestFlags=$((bTestFlags | 2#1000)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Prev ramdisk exists in ESP" fi # We need to create an old entry when: (All true) # Versions of old kernel/ramdisk are same. # Versions of old/latest of kernel&ramdisk are DIFFERENT. # Versions of all kernels&ramdisks are non-empty. if test \ "$VERSION_KERNEL_OLD" = "$VERSION_RAMDISK_OLD" \ -a "$VERSION_KERNEL_OLD" != "$VERSION_KERNEL_LATEST" \ -a "$VERSION_RAMDISK_OLD" != "$VERSION_RAMDISK_LATEST" \ -a -n "$VERSION_KERNEL_OLD" \ -a -n "$VERSION_RAMDISK_OLD" \ -a -n "$VERSION_KERNEL_LATEST" \ -a -n "$VERSION_RAMDISK_LATEST" then # Create an old-entry from a prev-entry when: (All true) # prevFound set. # 0001 = Old kernel does NOT exist in ESP. # or DIFFERS from the one in ESP. # 0010 = Old ramdisk does NOT exist in ESP. # or DIFFERS from the one in ESP. # 0100 = Prev kernel exists in ESP. # 1000 = Prev ramdisk exists in ESP. if test \ $((versionFound & prevFound)) -gt 0 \ -a $((bTestFlags ^ 2#1111)) -eq 0 then printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Creating old-entry from prev-entry..." # Rename the kernel/ramdisk in ESP. mv \ --verbose \ "${ESP}/${LOADERID}/${KERNEL}" \ "${ESP}/${LOADERID}/${KERNEL}.old" mv \ --verbose \ "${ESP}/${LOADERID}/${RAMDISK}.prev" \ "${ESP}/${LOADERID}/${RAMDISK}.old" # Adjust the loader entry-config appropriately into an old version. # Change the comment at top to reflect new filename. # Remove our extension from the version (not needed anymore) # Change the kernel used. # Change the ramdisk used. # (Don't use -i so we can preserve timestamp...) sed -E \ -e "s|(^#.*)_prev.conf|\1_old.conf|" \ -e "s|(^version.*)_prev|\1|" \ -e "s|(^linux.*)${KERNEL}|\1${KERNEL}.old|" \ -e "s|(^initrd.*)${RAMDISK}.prev|\1${RAMDISK}.old|" \ "${loader_conf}_prev.conf" \ >"${loader_conf}_old.conf" # Update timestamp of config to that of the ramdisk. touch \ --reference="${ESP}/${LOADERID}/${RAMDISK}.old" \ "${loader_conf}_old.conf" # Remove the loader entry-config for prev, # it will be re-created as needed... rm \ --verbose \ --force \ "${loader_conf}_prev.conf" # Generate a FRESH old-entry when: (Any one true) # ! prevFound set. # 0001 = Old kernel does NOT exist in ESP. # or DIFFERS from the one in ESP. # 0010 = Old ramdisk does NOT exist in ESP. # or DIFFERS from the one in ESP. # ! 0100 = Prev kernel exists in ESP. # ! 1000 = Prev ramdisk exists in ESP. elif test \ $((versionFound & prevFound)) -eq 0 \ -a $((bTestFlags ^ 2#0011)) -eq 0 then printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Generating fresh old-entry..." # Copy the old kernel and it's ramdisk. cp_opts=( --verbose --update --archive "--no-preserve=ownership" ) cp "${cp_opts[@]}" \ "${KERNEL_OLD}" \ "${ESP}/${LOADERID}/${KERNEL}.old" cp "${cp_opts[@]}" \ "${RAMDISK_OLD}" \ "${ESP}/${LOADERID}/${RAMDISK}.old" # Create the loader entry-config. createLoaderEntry \ "old" \ "$VERSION_KERNEL_OLD" \ "${KERNEL}.old" \ "${RAMDISK}.old" \ >"${loader_conf}_old.conf" # Update timestamp of config to that of the ramdisk. touch \ --reference="${ESP}/${LOADERID}/${RAMDISK}.old" \ "${loader_conf}_old.conf" else printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Nothing to be done..." fi else printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Skip..." fi } # Prev-kernel-logic function prevKernelLogic(){ local -a cp_opts log_func_name="Previous Logic" log_func="${log_func_start}${log_func_name}${log_func_end}" bTestFlags=0 # Can't use -nt/-ot because of timestamp precision diffs of FS's... # The tests: (Binary flags) # 00001 = Latest kernel did NOT boot succesfully (yet) # This will prevent creating a prev loader-entry from latest-entry # when it has a boot-try extension, either fresh or failed. if test ! -f "${loader_conf}_.conf"; then bTestFlags=$((bTestFlags | 2#00001)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Latest kernel did NOT boot succesfully (yet)" # Only test rest when successully booted at least once. else # 00010 = Versions of latest kernel/ramdisk are SAME. if test "$VERSION_KERNEL_LATEST" = "$VERSION_RAMDISK_LATEST"; then bTestFlags=$((bTestFlags | 2#00010)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Versions of latest kernel/ramdisk are same" fi # 00100 = The Latest ramdisk in ESP does exist. # AND DIFFERS from the latest ramdisk. # This only happens when ramdisk is updated... if test -f "${ESP}/${LOADERID}/${RAMDISK}" \ && ! cmp "${RAMDISK_LATEST}" "${ESP}/${LOADERID}/${RAMDISK}" >/dev/null then bTestFlags=$((bTestFlags | 2#00100)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Latest ramdisk is in ESP and has changed" fi # 01000 = Prev ramdisk in ESP does NOT exist. # OR DIFFERS from the latest ramdisk. # This will prevent updating the Prev ramdisk in ESP # with the latest ramdisk in ESP when re-run... if test ! -f "${ESP}/${LOADERID}/${RAMDISK}.prev" \ || ! cmp "${RAMDISK_LATEST}" "${ESP}/${LOADERID}/${RAMDISK}.prev" >/dev/null then bTestFlags=$((bTestFlags | 2#01000)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Prev ramdisk is not in ESP or has changed" fi # 10000 = Versions of latest kernel and old ramdisk are SAME. # I once noticed this, not sure if it was a glitch... if test "$VERSION_KERNEL_LATEST" = "$VERSION_RAMDISK_OLD"; then bTestFlags=$((bTestFlags | 2#10000)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Versions of latest kernel and old ramdisk are same" fi fi # We need to create a prev entry when: (All true) # ! 00001 = Latest kernel did NOT boot succesfully (yet) # This will prevent creating a prev loader-entry from latest-entry # when it has a boot-try extension, either fresh or failed. # 00010 = Versions of latest kernel/ramdisk are SAME. # 00100 = The Latest ramdisk in ESP does exist. # AND DIFFERS from the latest ramdisk. # This only happens when ramdisk is updated... # 01000 = Prev ramdisk in ESP does NOT exist. # OR DIFFERS from the latest ramdisk. # This will prevent updating the Prev ramdisk in ESP # with the latest ramdisk in ESP when re-run... if test $(((bTestFlags & 2#01111) ^ 2#01110)) -eq 0; then # We need to UPDATE the prev ramdisk only when: (All true) # latestFound set. # prevFound set. if test \ $((versionFound & latestFound)) -gt 0 \ -a $((versionFound & prevFound)) -gt 0 then # Only update the prev ramdisk. printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Updateing prev-entry ramdisk..." # Rename the ramdisk in ESP. mv \ --verbose \ "${ESP}/${LOADERID}/${RAMDISK}" \ "${ESP}/${LOADERID}/${RAMDISK}.prev" # Update timestamp of config to that of the ramdisk. touch \ --reference="${ESP}/${LOADERID}/${RAMDISK}.prev" \ "${loader_conf}_prev.conf" # Remove the loader entry-config for latest kernel, # it will be re-created fresh... rm \ --verbose \ --force \ "${loader_conf}_.conf" # We need to create a FRESH prev entry from latest-entry when: (All true) # latestFound set. # ! prevFound set. elif test \ $((versionFound & latestFound)) -gt 0 \ -a $((versionFound & prevFound)) -eq 0 then # Create a fresh Prev loader entry-config. printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Creating prev-entry from latest-entry..." # Rename the ramdisk in ESP. mv \ --verbose \ "${ESP}/${LOADERID}/${RAMDISK}" \ "${ESP}/${LOADERID}/${RAMDISK}.prev" # Adjust the loader entry-config appropriately into a prev version. # Change the comment at top to reflect new filename. # Change the version. # Change the ramdisk used. # (Don't use -i so we can preserve timestamp...) sed -E \ -e "s|(^#.*)_.conf|\1_prev.conf|" \ -e "s|(^version.*)|\1-prev|" \ -e "s|(^initrd.*)${RAMDISK}|\1${RAMDISK}.prev|" \ "${loader_conf}_.conf" \ >"${loader_conf}_prev.conf" # Update timestamp of config to that of the ramdisk. touch \ --reference="${ESP}/${LOADERID}/${RAMDISK}.prev" \ "${loader_conf}_prev.conf" # Remove the loader entry-config for latest kernel, # it will be re-created as needed... rm \ --verbose \ --force \ "${loader_conf}_.conf" else printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Nothing to be done..." fi else printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Skip..." fi } # Latest-kernel-logic function latestKernelLogic(){ local -a cp_opts log_func_name="Latest Logic" log_func="${log_func_start}${log_func_name}${log_func_end}" bTestFlags=0 # Can't use -nt/-ot because of timestamp precision diffs of FS's... # The tests: (Binary flags) # 00001 = Versions of latest kernel/ramdisk are same. if test "$VERSION_KERNEL_LATEST" = "$VERSION_RAMDISK_LATEST"; then bTestFlags=$((bTestFlags | 2#00001)) fi # 00010 = Versions of latest kernel/ramdisk are non-empty. if test -n "$VERSION_KERNEL_LATEST" -a -n "$VERSION_RAMDISK_LATEST"; then bTestFlags=$((bTestFlags | 2#00010)) fi # 00100 = Latest ramdisk does NOT exist in ESP. # or DIFFERS from the one in ESP. if test ! -f "${ESP}/${LOADERID}/${RAMDISK}" \ || ! cmp "${RAMDISK_LATEST}" "${ESP}/${LOADERID}/${RAMDISK}" >/dev/null then bTestFlags=$((bTestFlags | 2#00100)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Latest ramdisk is not in ESP or has changed" fi # 01000 = Latest kernel does NOT exist in ESP. # or DIFFERS from the one in ESP. if test ! -f "${ESP}/${LOADERID}/${KERNEL}" \ || ! cmp "${KERNEL_LATEST}" "${ESP}/${LOADERID}/${KERNEL}" >/dev/null then bTestFlags=$((bTestFlags | 2#01000)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Latest kernel is not in ESP or has changed" fi # 10000 = Loader entry-config for latest kernel NOT found. if test ! -f "${loader_conf}_.conf" -a ! -f "${loader_conf}_+"*.conf; then bTestFlags=$((bTestFlags | 2#10000)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Loader entry-config for latest kernel NOT found" fi # Generate a fresh latest-entry when: (All true) # 00001 = Versions of latest kernel/ramdisk are same. # 00010 = Versions of latest kernel/ramdisk are non-empty. # 00100 = Latest ramdisk does NOT exist in ESP. # or DIFFERS from the one in ESP. # Don't check for kernel here because only ramdisk might have been # updated, and it will be checked inside while copying. if test $(((bTestFlags & 2#00111) ^ 2#00111)) -eq 0; then printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Generating fresh latest-entry..." # Copy the latest kernel and it's ramdisk. cp_opts=( --verbose --archive "--no-preserve=ownership" ) cp "${cp_opts[@]}" \ "${RAMDISK_LATEST}" \ "${ESP}/${LOADERID}/${RAMDISK}" # Only copy latest kernel when it was different or non-existant in ESP. # This will prevent un-needed writes. # 01000 = Latest kernel does NOT exist in ESP. # or DIFFERS from the one in ESP. if test $(((bTestFlags & 2#01000) ^ 2#01000)) -eq 0; then cp "${cp_opts[@]}" \ "${KERNEL_LATEST}" \ "${ESP}/${LOADERID}/${KERNEL}" fi # Create the loader entry-config. createLoaderEntry \ "latest" \ "$VERSION_KERNEL_LATEST" \ "${KERNEL}" \ "${RAMDISK}" \ >"${loader_conf}_+${BootTries}.conf" # Update timestamp of config to that of the ramdisk. touch \ --reference="${ESP}/${LOADERID}/${RAMDISK}" \ "${loader_conf}_+${BootTries}.conf" # Re-generate a fresh latest-entry when: (All true) # 00001 = Versions of latest kernel/ramdisk are same. # 00010 = Versions of latest kernel/ramdisk are non-empty. # ! 00100 = Latest ramdisk does NOT exist in ESP. # or DIFFERS from the one in ESP. # ! 01000 = Latest kernel does NOT exist in ESP. # or DIFFERS from the one in ESP. # 10000 = Loader entry-config for latest kernel NOT found. elif test $(((bTestFlags & 2#11111) ^ 2#10011)) -eq 0; then printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Re-generating fresh latest-entry..." # Create the loader entry-config. createLoaderEntry \ "latest" \ "$VERSION_KERNEL_LATEST" \ "${KERNEL}" \ "${RAMDISK}" \ >"${loader_conf}_+${BootTries}.conf" # Update timestamp of config to that of the ramdisk. touch \ --reference="${ESP}/${LOADERID}/${RAMDISK}" \ "${loader_conf}_+${BootTries}.conf" else printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Nothing to be done..." fi } # Loader-configs-logic function loaderConfigsLogic(){ log_func_name="Loader Configs" log_func="${log_func_start}${log_func_name}${log_func_end}" if test ! -d "${ESP}/EFI/systemd"; then printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "systemd-boot is not installed, skipping !" exit 0 fi latestFound=$((2#0001)) prevFound=$((2#0010)) oldFound=$((2#0100)) # Don't chain these, they need to be checked individualy for later logic... # Set oldFound when: (All true) # Loader entry-config for old exist. # Old kernel EXIST in ESP already. # Old ramdisk EXIST in ESP already. if test \ -f "${loader_conf}_old.conf" \ -a -f "${ESP}/${LOADERID}/${KERNEL}.old" \ -a -f "${ESP}/${LOADERID}/${RAMDISK}.old" then versionFound=$((versionFound | oldFound)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Valid entry for OLD found" # else # printf "%b %b: %s\n" \ # "${log_header}" \ # "${log_func}" \ # "NO valid entry for OLD found" fi # Set prevFound when: (All true) # Loader entry-config for prev exist. # Latest kernel EXIST in ESP already. # Prev ramdisk EXIST in ESP already. # (Will be result of Prev-kernel-logic below) if test \ -f "${loader_conf}_prev.conf" \ -a -f "${ESP}/${LOADERID}/${KERNEL}" \ -a -f "${ESP}/${LOADERID}/${RAMDISK}.prev" then versionFound=$((versionFound | prevFound)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Valid entry for PREVIOUS found" # else # printf "%b %b: %s\n" \ # "${log_header}" \ # "${log_func}" \ # "NO valid entry for PREVIOUS found" fi # ONLY check for successfully booted configs. # eg: no boot-try extensions see: systemd-boot(7)#BOOT COUNTING # Set latestFound when: (All true) # Loader entry-config for latest exist. # Latest kernel/ramdisk EXIST in ESP already. # (Will be result of Latest-kernel-logic below) if test \ -f "${loader_conf}_.conf" \ -a -f "${ESP}/${LOADERID}/${KERNEL}" \ -a -f "${ESP}/${LOADERID}/${RAMDISK}" then versionFound=$((versionFound | latestFound)) printf "%b %b: %s\n" \ "${log_header}" \ "${log_func}" \ "Valid entry for LATEST found" # else # printf "%b %b: %s\n" \ # "${log_header}" \ # "${log_func}" \ # "NO valid entry for LATEST found" fi oldKernelLogic prevKernelLogic latestKernelLogic } KERNEL="vmlinuz" RAMDISK="initrd.img" log_name="systemd-boot" log_header_start="\e[2m" log_header_end="\e[0m" log_func_start="(\e[32m" log_func_end="\e[0m)" log_header="${log_header_start}${log_name}${log_header_end}" bTestFlags=0 BootTries=0 versionFound=0 latestFound=0 prevFound=0 oldFound=0 case "$1" in installme) printf "%b: %s\n" \ "${log_header}" \ "Requested install of helper" checkNeededBins installScript ;; installBoot) printf "%b: %s\n" \ "${log_header}" \ "Requested install Boot loader-entry" checkNeededBins getESP installUEFIbootEntry ;; *) checkNeededBins getESP getMachineID getRootUUID getResumeUUID getCmdLineOptions getLoaderVars getKernelAndRamdiskVars loaderConfigsLogic installUEFIbootEntry esac
Maybe Kubuntu devs can use this in it's distro
Output from my current running system:
-
Code:
> bootctl | xsel -ib System: Firmware: UEFI 2.40 (American Megatrends 5.11) Secure Boot: enabled Setup Mode: user Current Boot Loader: Product: systemd-boot 245.4-4ubuntu3.2 Features: ✓ Boot counting ✓ Menu timeout control ✓ One-shot menu timeout control ✓ Default entry control ✓ One-shot entry control ✓ Support for XBOOTLDR partition ✓ Support for passing random seed to OS ✓ Boot loader sets ESP partition information ESP: /dev/disk/by-partuuid/07aa2f88-2546-4101-95d4-59e76a04f93c File: └─/EFI/SYSTEMD/SHIMX64.EFI Random Seed: Passed to OS: no System Token: set Exists: yes Available Boot Loaders on ESP: ESP: /boot/efi (/dev/disk/by-partuuid/07aa2f88-2546-4101-95d4-59e76a04f93c) File: └─/EFI/systemd/systemd-bootx64.efi (systemd-boot 245.4-4ubuntu3.1) File: └─/EFI/systemd/shimx64.efi File: └─/EFI/systemd/mmx64.efi File: └─/EFI/systemd/grubx64.efi (systemd-boot 245.4-4ubuntu3.2) File: └─/EFI/BOOT/BOOTX64.EFI Boot Loaders Listed in EFI Variables: Title: Linux Secure-Bootloader ID: 0x0000 Status: active, boot-order Partition: /dev/disk/by-partuuid/07aa2f88-2546-4101-95d4-59e76a04f93c File: └─/EFI/SYSTEMD/SHIMX64.EFI Title: ubuntu ID: 0x0002 Status: inactive, boot-order Partition: /dev/disk/by-partuuid/07aa2f88-2546-4101-95d4-59e76a04f93c File: └─/EFI/UBUNTU/SHIMX64.EFI Title: UEFI OS ID: 0x0004 Status: inactive, boot-order Partition: /dev/disk/by-partuuid/07aa2f88-2546-4101-95d4-59e76a04f93c File: └─/EFI/BOOT/BOOTX64.EFI Title: ubuntu ID: 0x0005 Status: inactive, boot-order Partition: /dev/disk/by-partuuid/07aa2f88-2546-4101-95d4-59e76a04f93c File: └─/EFI/UBUNTU/GRUBX64.EFI Boot Loader Entries: $BOOT: /boot/efi (/dev/disk/by-partuuid/07aa2f88-2546-4101-95d4-59e76a04f93c) Default Boot Loader Entry: title: Kubuntu 20.04.1 LTS (Focal Fossa) id: kubuntu_.conf source: /boot/efi/loader/entries/kubuntu_.conf version: 5.4.0-42 machine-id: 80479e7d268341ec858db9de810df978 architecture: x64 linux: /kubuntu/vmlinuz initrd: /kubuntu/initrd.img options: resume=UUID=734867ce-811d-4fb3-ac4c-44a61fb1fca9 root=UUID=deb6ca8a-f40e-44c8-aa8d-db16d41219a9 ro quiet splash vt.handoff=7 intel_iommu=on
-
Code:
> bootctl list | xsel -ib Boot Loader Entries: title: EFI Tools id: efitools-keytool.conf source: /boot/efi/loader/entries/efitools-keytool.conf version: KeyTool title: Kubuntu Grub-loader id: grub-kubuntu.conf source: /boot/efi/loader/entries/grub-kubuntu.conf version: Grub title: Kubuntu 20.04 LTS (Focal Fossa) (5.4.0-40) id: kubuntu_old.conf source: /boot/efi/loader/entries/kubuntu_old.conf version: 5.4.0-40 machine-id: 80479e7d268341ec858db9de810df978 architecture: x64 linux: /kubuntu/vmlinuz.old initrd: /kubuntu/initrd.img.old options: resume=UUID=734867ce-811d-4fb3-ac4c-44a61fb1fca9 root=UUID=deb6ca8a-f40e-44c8-aa8d-db16d41219a9 ro quiet splash vt.handoff=7 intel_iommu=on title: Kubuntu 20.04 LTS (Focal Fossa) (5.4.0-42-prev) id: kubuntu_prev.conf source: /boot/efi/loader/entries/kubuntu_prev.conf version: 5.4.0-42-prev machine-id: 80479e7d268341ec858db9de810df978 architecture: x64 linux: /kubuntu/vmlinuz initrd: /kubuntu/initrd.img.prev options: resume=UUID=734867ce-811d-4fb3-ac4c-44a61fb1fca9 root=UUID=deb6ca8a-f40e-44c8-aa8d-db16d41219a9 ro quiet splash vt.handoff=7 intel_iommu=on title: Kubuntu 20.04.1 LTS (Focal Fossa) (default) id: kubuntu_.conf source: /boot/efi/loader/entries/kubuntu_.conf version: 5.4.0-42 machine-id: 80479e7d268341ec858db9de810df978 architecture: x64 linux: /kubuntu/vmlinuz initrd: /kubuntu/initrd.img options: resume=UUID=734867ce-811d-4fb3-ac4c-44a61fb1fca9 root=UUID=deb6ca8a-f40e-44c8-aa8d-db16d41219a9 ro quiet splash vt.handoff=7 intel_iommu=on title: EFI Default Loader id: auto-efi-default source: /sys/firmware/efi/efivars/LoaderEntries-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f title: Reboot Into Firmware Interface id: auto-reboot-to-firmware-setup source: /sys/firmware/efi/efivars/LoaderEntries-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
Comment