From 7abb171389b29c0bbaadc41bd68660e5e31bd44d Mon Sep 17 00:00:00 2001 From: David Thurstenson Date: Sun, 14 Mar 2021 20:12:53 -0500 Subject: [PATCH] Refactor to set up checks for systemd-boot and the firmware interface --- bin/nextboot | 180 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 128 insertions(+), 52 deletions(-) diff --git a/bin/nextboot b/bin/nextboot index 9d81793..a400f22 100755 --- a/bin/nextboot +++ b/bin/nextboot @@ -1,25 +1,80 @@ #!/usr/bin/env bash +# Pretty name for firmware interface +biosname="Firmware Configuration" + +# Check if systemd-boot is available +bootctl is-installed &> /dev/null && sdboot=1 + +# Check if rebooting to the firmware interface is supported +[[ "$(bootctl reboot-to-firmware)" == "supported" ]] && fwboot=1 + + +picksdboot() { + # Usage: picksdboot <prompt> + local entry PS3 COLUMNS + PS3="$2 " + COLUMNS=1 + + # Print title to stderr + printf "\n%s\n\n" "$1" >&2 + + # Put all systemd-boot loder entry confs into an array + mapfile -t sdbootlist < <(systemctl --boot-loader-entry=help) + + # Only add the firmware interface to the list if booting to it is supported + [[ -v fwboot ]] && sdbootlist+=("$biosname") + + select entry in "${sdbootlist[@]}"; do + break + done + + printf "%s" "$entry" +} + + +pickefi() { + # Usage: pickefi <title> <prompt> + local bootnext PS3 COLUMNS + PS3="$2 " + COLUMNS=1 + + # Print title to stderr + printf "\n%s\n\n" "$1" >&2 + + # This one is fun + # mapfile splits stdin on \n to an array + # The sed regex looks for an asterisk in the 8th position (0 indexed), + # which marks an efi boot entry as "active", so those are the only ones + # we care to add to the list. + # It *seems* like one could put mapfile at the end of the pipeline, but + # each command gets run in a subshell, so the array is lost. (ref: SC2031) + mapfile -t efientries < <(efibootmgr | sed -n '/.\{8\}\*/p') + + # Rebooting to the firmware interface only works with systemd-boot, so only add + # it to the EFI mode list if systemd-boot is available and it's supported + [[ -v sdboot && -v fwboot ]] && efientries+=("$biosname") + + select efiselection in "${efientries[@]}"; do + + # If they chose the firmware interface + if [[ "$efiselection" == "$biosname" ]]; then + break + else + # Take only character 4-8 of the entry, which is the BOOTNUM + bootnext="${efiselection:4:4}" + break + fi + done + + printf "%s" "$bootnext" +} -# The pretty name we want to give to the firmware entry -biosname="Firmware Interface" while getopts ":en" opt; do case $opt in e) - # This one is fun - # mapfile splits stdin on \n to an array - # The sed regex looks for an asterisk in the 8th position (0 indexed) - # It *seems* like one could put mapfile at the end of the pipeline, but - # each command gets run in a subshell, so the array is lost. (ref: SC2031) - mapfile -t efientries < <(efibootmgr | sed -n '/.\{8\}\*/p') - PS3="EFI entry to BootNext: " - COLUMNS=1 - select efiselection in "${efientries[@]}"; do - bootnext="${efiselection:4:4}" - break - done - sudo efibootmgr -q --bootnext "$bootnext" + efimode=1 ;; n) noreboot=1 @@ -31,45 +86,66 @@ while getopts ":en" opt; do esac done +# If -e wasn't set, and systemd-boot isn't installed, fail over to efimode anyways +if [[ ! -v efimode && ! -v sdboot ]]; then + echo "W: systemd-boot not installed. Choosing EFI boot entries instead..." + efimode=1 +fi -# If we are trying to set the EFI entry, this conditional isn't useful -if [[ -v bootnext ]]; then - : -# If we aren't trying to set the EFI entry, and systemd-boot is installed -elif [[ ! -v bootnext ]] && bootctl is-installed &> /dev/null; then - # Select prompt options - PS3="Select next boot entry: " - COLUMNS=1 - select entry in $(systemctl --boot-loader-entry=help) "$biosname"; do - if [[ "$entry" == "$biosname" ]]; then - sudo bootctl reboot-to-firmware true - break - else - sudo bootctl set-oneshot "$entry" - break - fi - done +# If we're in EFI mode +if [[ -v efimode ]]; then + title="Pick EFI boot entry" + prompt="Entry" + + if [[ -v noreboot ]]; then + prompt="$prompt for next boot:" + elif [[ ! -v noreboot ]]; then + prompt="$prompt to boot IMMEDIATELY:" + else + echo "E: idk how you got here..." + exit 1 + fi + + # Prompt for the user's choice + choice="$(pickefi "$title" "$prompt")" + + # A special case to boot into firmware interface + if [[ "$choice" == "$biosname" ]]; then + sudo bootctl reboot-to-firmware true + elif [[ "$choice" != "$biosname" ]]; then + sudo efibootmgr -q --bootnext "$choice" + fi + +# If we're in systemd-boot mode +elif [[ ! -v efimode ]]; then + title="Pick systemd-boot entry config" + prompt="Entry" + + if [[ -v noreboot ]]; then + prompt="$prompt for next boot:" + elif [[ ! -v noreboot ]]; then + prompt="$prompt to boot IMMEDIATELY:" + else + echo "E: idk how you got here either..." + exit 1 + fi + + # Prompt for the user's choice + choice="$(picksdboot "$title" "$prompt")" + + # A special case to boot into firmware interface + if [[ "$choice" == "$biosname" ]]; then + sudo bootctl reboot-to-firmware true + elif [[ "$choice" != "$biosname" ]]; then + sudo bootctl set-oneshot "$choice" + # Add an option to speed past the boot menu, since we've already + # chosen the desired entry. + rebootopts+=("--boot-loader-menu=1") + fi else - # If we've made it here, then we're not trying to set the EFI entry, and - # systemd-boot is not installed in the ESP, so bail. - echo "E: systemd-boot not installed. Exiting." + echo "E: you DEFINITELY don't belong here..." exit 1 fi -# If we do want to reboot now, and we aren't setting the EFI entry -if [[ ! -v noreboot && ! -v bootnext ]]; then - # Reboot, and set a low timeout on the systemd-boot menu for next boot only - sudo systemctl reboot --boot-loader-menu=1 -# If we do want to reboot now, and we are setting the EFI entry -elif [[ ! -v noreboot && -v bootnext ]]; then - # Get on with it - sudo systemctl reboot -# If we don't want to reboot now -elif [[ -v noreboot ]]; then - echo "Next boot set." - exit 0 -# If things are otherwise fucked -else - echo "E: You shouldn't be here..." - exit 1 -fi + +[[ ! -v noreboot ]] && sudo systemctl reboot "${rebootopts[@]}"