#!/bin/sh # # Purpose {{{ # This script will reboot on Windows system # 1. Try to get UEFI entry number for Windows. # 2. Define it as default only for next boot. # 3. Reboot (default) or poweroff the system. # … # # 2023-08-18 # }}} # Flags {{{ ## Exit on error {{{ set -o errexit ## }}} ## Exit on unset var {{{ ### Use "${VARNAME-}" to test a var that may not have been set set -o nounset ## }}} ## Pipeline command is treated as failed {{{ ### Not available in POSIX sh − https://github.com/koalaman/shellcheck/wiki/SC3040 #set -o pipefail ## }}} ## Help with debugging {{{ ### Call the script by prefixing it with "TRACE=1 ./script.sh" if [ "${TRACE-0}" -eq 1 ]; then set -o xtrace; fi ## }}} # }}} # Vars {{{ PROGNAME=$(basename "${0}"); readonly PROGNAME PROGDIR=$(readlink --canonicalize-missing $(dirname "${0}")); readonly PROGDIR ARGS="${*}"; readonly ARGS readonly NBARGS="${#}" [ -z "${DEBUG-}" ] && DEBUG=1 ## Export DEBUG for sub-script export DEBUG ## Default values for some vars readonly FLAG_REBOOT_DEFAULT="0" readonly FLAG_POWEROFF_DEFAULT="1" readonly WINDOWS_BASE_LABEL_DEFAULT="Windows" ## Colors readonly PURPLE='\033[1;35m' readonly RED='\033[0;31m' readonly RESET='\033[0m' readonly COLOR_DEBUG="${PURPLE}" # }}} usage() { # {{{ cat <<- HELP usage: $PROGNAME [-d|-h|-l|-r|-s] Try to reboot to Windows system. EXAMPLES : - Reboot to Windows system ${PROGNAME} - Define Windows for next boot but poweroff the system ${PROGNAME} --poweroff - Search for a specific Windows label ${PROGNAME} --label "Windows Boot Manager" OPTIONS : -d,--debug Enable debug messages. -h,--help Print this help message. -l,--label,--windows-label Define a different label to search for Windows UEFI entry. (default: ${WINDOWS_BASE_LABEL_DEFAULT}) -r,--reboot Reboot the system (default behaviour). -s,--shutdown,--poweroff,--halt Ask to shutdown the system. -u,--uefi,--uefi-entry Define the UEFI entry to use. (default: first matching from efibootmgr + windows-label) HELP } # }}} debug_message() { # {{{ local_debug_message="${1}" ## Print message if DEBUG is enable (=0) [ "${DEBUG}" -eq "0" ] && printf '\e[1;35m%-6b\e[m\n' "DEBUG − ${PROGNAME} : ${local_debug_message}" unset local_debug_message return 0 } # }}} error_message() { # {{{ local_error_message="${1}" local_error_code="${2}" ## Print message printf '%b\n' "ERROR − ${PROGNAME} : ${RED}${local_error_message}${RESET}" >&2 unset local_error_message exit "${local_error_code:=66}" } # }}} define_vars() { # {{{ debug_message "-- Define vars BEGIN" # If flag_reboot wasn't defined (argument) {{{ if [ -z "${flag_reboot-}" ]; then ## Use default value readonly flag_reboot="${FLAG_REBOOT_DEFAULT}" fi # }}} # If flag_poweroff wasn't defined (argument) {{{ if [ -z "${flag_poweroff-}" ]; then ## Use default value readonly flag_poweroff="${FLAG_POWEROFF_DEFAULT}" fi # }}} # If windows_base_label wasn't defined (argument) {{{ if [ -z "${windows_base_label-}" ]; then ## Use default value readonly windows_base_label="${WINDOWS_BASE_LABEL_DEFAULT}" fi # }}} # If windows_uefi_entry wasn't defined (argument) {{{ if [ -z "${windows_uefi_entry-}" ]; then ## Try to get the first matching entry with efibootmgr + windows_base_label debug_message "||define_vars − \ Try to get Windows UEFI entry with 'efibootmgr' + windows-label (${windows_base_label})." windows_uefi_entry=$(efibootmgr | grep --extended-regexp --max-count=1 -- ".*${windows_base_label}.*" | cut --characters=5-8) fi # }}} # If windows_uefi_entry remains empty {{{ if [ -z "${windows_uefi_entry-}" ]; then error_message "windows_uefi_entry seems empty! Verify the windows_base_label used (${windows_base_label}) and your UEFI configuration ('efibootmgr -v')." 11 elif [ -n "${windows_uefi_entry}" ]; then debug_message "||define_vars − \ The ${RED}${windows_uefi_entry}${COLOR_DEBUG} UEFI entry will be used for Windows." else error_message "I am not supposed to happen!" 12 fi # }}} debug_message "-- Define vars END" } # }}} is_command_available() { # {{{ local_command_available_cmd="${1}" debug_prefix="${2:-}" ## Return False by default return_command_available="1" if [ "$(command -v ${local_command_available_cmd})" ]; then debug_message "${debug_prefix}is_command_available − \ ${RED}${local_command_available_cmd}${COLOR_DEBUG} seems present on this host." return_command_available="0" else debug_message "${debug_prefix}is_command_available − \ ${RED}${local_command_available_cmd}${COLOR_DEBUG} is not available on this host." return_command_available="1" fi unset local_command_available_cmd unset debug_prefix return "${return_command_available}" } # }}} main() { # {{{ debug_message "--- MAIN BEGIN" ## If a command is missing {{{ ### Exit with error message is_command_available "efibootmgr" "| " \ || error_message "No test_me command available. Please install efibootmgr package with your package manager." 01 ## }}} ## Define all vars define_vars ## If the expected UEFI entry doesn't exists {{{ if ! sudo efibootmgr | grep --quiet -- "Boot${windows_uefi_entry}"; then error_message "The expected UEFI entry (${windows_uefi_entry}) doesn't seem to exist…" 21 fi ## }}} ## Define next boot entry sudo efibootmgr --quiet --bootnext "${windows_uefi_entry}" ## If BootNext entry doesn't match {{{ if ! sudo efibootmgr | grep --quiet -- "BootNext: ${windows_uefi_entry}"; then error_message "The expected UEFI entry (${windows_uefi_entry}) doesn't seem to be the next one (see 'sudo efibootmgr' BootNext entry)." 22 fi ## }}} printf '%b' "Reboot/shutdown as requested to winbouse partition\\n" [ "${flag_reboot}" -eq "0" ] && sudo systemctl reboot [ "${flag_poweroff}" -eq "0" ] && sudo systemctl poweroff debug_message "--- MAIN END" } # }}} # Manage arguments # {{{ # This code can't be in a function due to argument management if [ ! "${NBARGS}" -eq "0" ]; then manage_arg="0" ## If the first argument ask for help (h|help|-h|-help|-*h|-*help) {{{ if printf -- '%s' "${1-}" | grep --quiet --extended-regexp -- "^-*h(elp)?$"; then usage exit 0 fi ## }}} ## If the first argument is not an option if ! printf -- '%s' "${1}" | grep --quiet --extended-regexp -- "^-+"; then ## Print help message and exit printf '%b\n' "${RED}Invalid option: ${1}${RESET}" printf '%b\n' "---" usage exit 1 fi # Parse all options (start with a "-") one by one while printf -- '%s' "${1-}" | grep --quiet --extended-regexp -- "^-+"; do case "${1}" in -d|--debug ) ## debug DEBUG=0 debug_message "--- Manage argument BEGIN" ;; -h|--help ) ## help message (if it's not the first option) usage exit 0 ;; -l|--label|--windows-label ) ## Set windows_base_label var ## Move to the next argument shift ## Define var readonly windows_base_label="${1}" ;; -r|--reboot ) ## If a reboot was requested (default behaviour) flag_reboot="0" flag_poweroff="1" ;; -s|--shutdown|--poweroff|--halt ) ## If a poweroff was requested flag_reboot="1" flag_poweroff="0" ;; -u|--uefi|--uefi-entry ) ## Set windows_uefi_entry var ## Move to the next argument shift ## Define var readonly windows_uefi_entry="${1}" ;; * ) ## unknow option printf '%b\n' "${RED}Invalid option: ${1}${RESET}" printf '%b\n' "---" usage exit 1 ;; esac debug_message "| ${RED}${1}${COLOR_DEBUG} option managed." ## Move to the next argument shift manage_arg=$((manage_arg+1)) done debug_message "| ${RED}${manage_arg}${COLOR_DEBUG} argument(s) successfully managed." else debug_message "| No arguments/options to manage." fi debug_message "--- Manage argument END" # }}} main exit 255