From 7e20590dc4378ee78d9f28be95902362ac51e738 Mon Sep 17 00:00:00 2001 From: TobiPeterG Date: Tue, 5 Mar 2024 01:40:46 +0100 Subject: [PATCH] feat: add bash completions This will complete started commands and provide a list of possible commands and options. --- completion/sdbootutil | 70 +++++++++++++++++ sdbootutil | 170 ++++++++++++++++++++++++------------------ sdbootutil.spec | 7 ++ 3 files changed, 175 insertions(+), 72 deletions(-) create mode 100755 completion/sdbootutil diff --git a/completion/sdbootutil b/completion/sdbootutil new file mode 100755 index 0000000..f8aa56d --- /dev/null +++ b/completion/sdbootutil @@ -0,0 +1,70 @@ +err() { + : +} + +find_kernels() +{ + local fn kv + found_kernels=() + + for fn in ""/usr/lib/modules/*/"$image"; do + kv="${fn%/*}" + kv="${kv##*/}" + found_kernels+=("$kv") + done +} + +eval "$(sdbootutil _print_bash_completion_data)" + +_sdbootutil_completion() { + local cur prev words cword subvol_prefix image + COMPREPLY=() + _get_comp_words_by_ref -n : cur prev words cword + + local command_found=0 + eval_bootctl + set_image_name + define_commands + local i=0 + + for word in "${words[@]:1:$cword-1}"; do + if [[ ${command_types["$word"]} =~ ^(command|command_needing_kernel)$ ]]; then + command_found=1 + fi + if [[ " --arch " == " $word " ]]; then + firmware_arch="${words[i+2]}" + set_image_name + fi + if [[ " --image " == " $word " ]]; then + image="${words[i+2]}" + fi + ((i++)) + done + + if [[ ${command_types["$prev"]} == "command_needing_kernel" ]]; then + find_kernels + local kernel_versions="${found_kernels[@]}" + COMPREPLY=( $(compgen -W "${kernel_versions}" -- ${cur}) ) + elif [[ "$prev" == "--arch" ]]; then + COMPREPLY=( $(compgen -W "x64 aa64" -- ${cur}) ) + elif [[ "$prev" == "--image" ]]; then + COMPREPLY=( $(compgen -W "vmlinuz Image" -- ${cur}) ) + elif [[ $cur == -* ]]; then + local opts_and_args + for key in "${!command_types[@]}"; do + if [[ ${command_types["$key"]} =~ ^(option|argument)$ ]]; then + opts_and_args+="$key " + fi + done + COMPREPLY=( $(compgen -W "${opts_and_args}" -- ${cur}) ) + elif [[ $command_found -eq 0 ]]; then + local commands + for key in "${!command_types[@]}"; do + if [[ ${command_types["$key"]} =~ ^(option|argument|command|command_needing_kernel)$ ]]; then + commands+="$key " + fi + done + COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) ) + fi +} +complete -F _sdbootutil_completion sdbootutil \ No newline at end of file diff --git a/sdbootutil b/sdbootutil index d44a8b7..89193ce 100755 --- a/sdbootutil +++ b/sdbootutil @@ -832,6 +832,21 @@ find_kernels() done } +set_image_name() +{ + case "$firmware_arch" in + x64) image=vmlinuz ;; + aa64) image=Image ;; + *) err "Unsupported architecture $firmware_arch" ;; + esac +} + +eval_bootctl() +{ + # XXX: bootctl should have json output for that too + eval "$(bootctl 2>/dev/null | sed -ne 's/Firmware Arch: *\(\w\+\)/firmware_arch="\1"/p;s/ *token: *\(\w\+\)/entry_token="\1"/p;s, *\$BOOT: *\([^ ]\+\).*,boot_root="\1",p')" +} + # map that uses expected path on the ESP for each installed kernel as key. The # value is the entry id if an entry exists. declare -A installed_kernels @@ -1498,8 +1513,33 @@ main_menu() done } +define_commands() { + declare -gA command_types=( + [install]="command" [needs-update]="command" [update]="command" [force-update]="command" + [add-kernel]="command_needing_kernel" [remove-kernel]="command_needing_kernel" + [set-default-snapshot]="command" [add-all-kernels]="command" + [remove-all-kernels]="command" [is-installed]="command" + [list-snapshots]="command" [list-entries]="command" + [list-kernels]="command" [is-bootable]="command" + [update-predictions]="command" [kernels]="command_interactive" + [snapshots]="command_interactive" [entries]="command_interactive" [mkinitrd]="command" + [bootloader]="command" + [--help]="option" [--verbose]="option" [--flicker]="option" + [--esp-path]="argument" [--arch]="argument" [--entry-token]="argument" + [--image]="argument" [--no-variables]="option" [--no-reuse-initrd]="option" + [--no-random-seed]="option" [--all]="option" + ) +} + ####### main ####### +if [[ "$1" == "_print_bash_completion_data" ]]; then + declare -f set_image_name + declare -f eval_bootctl + declare -f define_commands + exit 0 +fi + getopttmp=$(getopt -o hc:v --long help,flicker,verbose,esp-path:,entry-token:,arch:,image:,no-variables,no-reuse-initrd,no-random-seed,all -n "${0##*/}" -- "$@") eval set -- "$getopttmp" @@ -1521,11 +1561,13 @@ while true ; do esac done -case "$1" in - install|needs-update|update|force-update|add-kernel|remove-kernel|set-default-snapshot|add-all-kernels|mkinitrd|remove-all-kernels|is-installed|list-snapshots|list-entries|list-kernels|is-bootable|update-predictions|bootloader) ;; - kernels|snapshots|entries|"") stty_size; interactive=1 ;; - *) err "unknown command $1" ;; -esac +define_commands +if [[ -z "$1" || ${command_types["$1"]} == "command_interactive" ]]; then + stty_size + interactive=1 +elif [[ -z ${command_types["$1"]} ]]; then + err "unknown command $1" +fi for i in /etc/os-release /usr/lib/os-release; do [ -f "$i" ] || continue @@ -1534,9 +1576,7 @@ for i in /etc/os-release /usr/lib/os-release; do done [ -n "$arg_esp_path" ] && export SYSTEMD_ESP_PATH="$arg_esp_path" - -# XXX: bootctl should have json output for that too -eval "$(bootctl 2>/dev/null | sed -ne 's/Firmware Arch: *\(\w\+\)/firmware_arch="\1"/p;s/ *token: *\(\w\+\)/entry_token="\1"/p;s, *\$BOOT: *\([^ ]\+\).*,boot_root="\1",p')" +eval_bootctl read -r root_uuid root_device < <(findmnt / -v -r -n -o UUID,SOURCE) root_subvol=$(btrfs subvol show / 2>/dev/null|head -1) subvol_prefix="${root_subvol%/.snapshots/*}" @@ -1554,73 +1594,59 @@ settle_entry_token [ -n "$root_device" ] || err "Can't determine root device" [ -n "$entry_token" ] || err "No entry token. sd-boot not installed?" [ -n "$firmware_arch" ] || err "Can't determine firmware arch" -case "$firmware_arch" in - x64) image=vmlinuz ;; - aa64) image=Image ;; - *) err "Unsupported architecture $firmware_arch" ;; -esac +set_image_name root_snapshot="${root_subvol#"${subvol_prefix}"/.snapshots/}" root_snapshot="${root_snapshot%/snapshot}" -# XXX: Unify both in /EFI/opensuse? -if is_sdboot; then - boot_dst="/EFI/systemd" -elif is_grub2; then - boot_dst="/EFI/opensuse" -else - err "Bootloader not detected" -fi - -if [ "$1" = "install" ]; then - install_bootloader "${2:-$root_snapshot}" -elif [ "$1" = "needs-update" ]; then - bootloader_needs_update "${2:-$root_snapshot}" -elif [ "$1" = "update" ]; then - if bootloader_needs_update "${2:-$root_snapshot}"; then install_bootloader "${2:-$root_snapshot}"; else :; fi -elif [ "$1" = "force-update" ]; then - if is_installed; then install_bootloader "${2:-$root_snapshot}"; else :; fi -elif [ "$1" = "bootloader" ]; then - bootloader_name "${2:-$root_snapshot}" -elif [ "$1" = "add-kernel" ]; then - install_kernel "${3:-$root_snapshot}" "$2" -elif [ "$1" = "add-all-kernels" ]; then - install_all_kernels "${2:-$root_snapshot}" -elif [ "$1" = "mkinitrd" ]; then - arg_no_reuse_initrd=1 - install_all_kernels "${2:-$root_snapshot}" -elif [ "$1" = "remove-kernel" ]; then - remove_kernel "${3:-$root_snapshot}" "$2" -elif [ "$1" = "remove-all-kernels" ]; then - remove_all_kernels "${2:-$root_snapshot}" -elif [ "$1" = "set-default-snapshot" ]; then - set_default_snapshot "${2:-$root_snapshot}" -elif [ "$1" = "is-installed" ]; then - if is_installed; then - log_info "systemd-boot was installed using sdbootutil" - exit 0 - else - log_info "not installed using this tool" - exit 1 - fi -elif [ "$1" = "list-kernels" ]; then - list_kernels "${2:-$root_snapshot}" -elif [ "$1" = "list-entries" ]; then - list_entries "${2:-}" -elif [ "$1" = "list-snapshots" ]; then - list_snapshots -elif [ "$1" = "is-bootable" ]; then - is_bootable "${2:-$root_snapshot}" -elif [ "$1" = "update-predictions" ]; then - update_predictions=1 -elif [ "$1" = "kernels" ]; then - show_kernels "${2:-$root_snapshot}" -elif [ "$1" = "snapshots" ]; then - show_snapper -elif [ "$1" = "entries" ]; then - show_entries -else - main_menu -fi +case "$1" in + "install") + install_bootloader "${2:-$root_snapshot}" ;; + "needs-update") + bootloader_needs_update "${2:-$root_snapshot}" ;; + "update") + if bootloader_needs_update "${2:-$root_snapshot}"; then install_bootloader "${2:-$root_snapshot}"; fi ;; + "force-update") + if is_installed; then install_bootloader "${2:-$root_snapshot}"; else :; fi ;; + "bootloader") + bootloader_name "${2:-$root_snapshot}" ;; + "add-kernel") + install_kernel "${3:-$root_snapshot}" "$2" ;; + "add-all-kernels" | "mkinitrd") + [[ "$1" = "mkinitrd" ]] && arg_no_reuse_initrd=1 + install_all_kernels "${2:-$root_snapshot}" ;; + "remove-kernel") + remove_kernel "${3:-$root_snapshot}" "$2" ;; + "remove-all-kernels") + remove_all_kernels "${2:-$root_snapshot}" ;; + "set-default-snapshot") + set_default_snapshot "${2:-$root_snapshot}" ;; + "is-installed") + if is_installed; then + log_info "systemd-boot was installed using sdbootutil" + exit 0 + else + log_info "not installed using this tool" + exit 1 + fi ;; + "list-kernels") + list_kernels "${2:-$root_snapshot}" ;; + "list-entries") + list_entries "${2:-}" ;; + "list-snapshots") + list_snapshots ;; + "is-bootable") + is_bootable "${2:-$root_snapshot}" ;; + "update-predictions") + update_predictions=1 ;; + "kernels") + show_kernels "${2:-$root_snapshot}" ;; + "snapshots") + show_snapper ;; + "entries") + show_entries ;; + *) + main_menu ;; +esac [ -z "$update_predictions" ] || generate_tpm2_predictions diff --git a/sdbootutil.spec b/sdbootutil.spec index 9488b1f..f341ec2 100644 --- a/sdbootutil.spec +++ b/sdbootutil.spec @@ -86,6 +86,10 @@ package may disable other plugin scripts that are incompatible. %install install -D -m 755 sdbootutil %{buildroot}%{_bindir}/sdbootutil +#bash completions +mkdir -p %{buildroot}%{_datadir}/bash-completion/completions +install -D -m 755 completion/sdbootutil %{buildroot}%{_datadir}/bash-completion/completions/sdbootutil + # services for i in sdbootutil-update-predictions.service; do install -D -m 644 "$i" %{buildroot}%{_unitdir}/"$i" @@ -137,6 +141,9 @@ sdbootutil update %files %license LICENSE %{_bindir}/sdbootutil +%dir %{_datadir}/bash-completion +%dir %{_datadir}/bash-completion/completions +%{_datadir}/bash-completion/completions/sdbootutil %{_unitdir}/sdbootutil-update-predictions.service %files rpm-scriptlets