diff options
Diffstat (limited to 'os/etc/kernel/efi.sh')
| -rwxr-xr-x | os/etc/kernel/efi.sh | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/os/etc/kernel/efi.sh b/os/etc/kernel/efi.sh new file mode 100755 index 0000000..b1d5e05 --- /dev/null +++ b/os/etc/kernel/efi.sh @@ -0,0 +1,351 @@ +#!/bin/bash +set -e +[ -n "$DEBUG" ] && set -x + +. /lib/gentoo/functions.sh + +EFI_PARTITION=/boot/efi +EFI_DIR=/efi/gentoo + +_() { + if [ "$NOOP" ]; then + echo "+" "$*" + else + echo "+" "$*" + "$@" + fi +} + +mount_efi() { + mount "${EFI_PARTITION}" || true + mount -o remount,rw /sys/firmware/efi/efivars || true + trap '{ umount ${EFI_PARTITION}; mount -o remount,ro /sys/firmware/efi/efivars; }' EXIT +} +path_from_efi() { + sed -e 's,\\,/,g' -e "s,^,${EFI_PARTITION}," +} +path_to_efi() { + sed -e "s,${EFI_PARTITION},," -e 's,/,\\,g' +} +path_to_boot() { + sed -e "s,${EFI_PARTITION}${EFI_DIR},/boot," +} +hashsum() { + sha1sum | cut -d' ' -f1 +} + +parse_efi() ( + local bootorder bootcurrent + bootorder=$(efibootmgr -u | grep '^BootOrder' | sed -e 's,BootOrder: ,,' -e 's/,/\n/g' | awk '{print $0 "\t" NR}') + bootcurrent=$(efibootmgr -u | grep '^BootCurrent' | sed -e 's,BootCurrent: ,,') + + efibootmgr -u | + grep -E '^Boot[0-9]' | + while read -r ln; do + local hash entry loader id + + hash=$(hashsum <<<"$ln") + ln=$(sed -e 's|Boot\([^\*]*\)\*\s\(.*\)\sHD(\([0-9]*\),[^,]*,\([^,]*\),.*)/File(\([^)]*\))\(.*\)$|\1\t\2\t\3\t\4\t\5\t\6|' <<<"$ln") + entry=$(cut -d $'\t' -f1 <<<"$ln") + loader=$(cut -d $'\t' -f5 <<<"$ln") + version=$(sed -e 's,^.*vmlinuz-\(.*\)\.efi$,\1,' <<<"$loader") + if [ -f "$(path_from_efi <<<"$loader")" ]; then + jq -cn -S \ + --arg hash "$hash" \ + --arg entry "$entry" \ + --arg loader "$loader" \ + --arg version "$version" \ + --argjson bootorder "$(grep "$entry" <<<"$bootorder" | cut -d $'\t' -f2)" \ + --argjson current "$(test "$entry" = "$bootcurrent" && echo true || echo false)" \ + --arg disk "$(cut -d $'\t' -f4 <<<"$ln")" \ + --arg label "$(cut -d $'\t' -f2 <<<"$ln")" \ + --argjson part "$(cut -d $'\t' -f3 <<<"$ln")" \ + --arg args "$(cut -d $'\t' -f6 <<<"$ln")" \ + '{version:$version,entry:$entry,label:$label,loader:$loader,args:$args,bootorder:$bootorder,current:$current,disk:$disk,part:$part,hash:$hash}' + fi + done | + jq -s 'sort_by(.bootorder)' +) + +create_efi() ( + jq -rc '.[]' | + while read -r ln; do + local entry hash + + entry=$(jq -r '.entry' <<<"$ln" || echo none) + hash=$(jq -r '.hash' <<<"$ln" || echo none) + loader=$(jq -r '.loader' <<<"$ln") + bootorder=$(jq -r '.bootorder' <<<"$ln") + label=$(jq -r '.label' <<<"$ln") + args=$(jq -r '.args' <<<"$ln") + disk=$(findmnt -no SOURCE --target "$(path_from_efi <<<"$loader")") + part=$(sed -e 's,^.*p\([0-9]\)$,\1,' <<<"$disk") + disk=${disk//p${part}/} + + _ efibootmgr -u --remove-dups --create --disk "$disk" --part "$part" --label "$label" --loader "$loader" -u "$args" --index "$bootorder" + done +) + +delete_efi() ( + local entry=$1 + _ efibootmgr -u --delete-bootnum --bootnum "$entry" +) +bootnext_efi() ( + local entry=$1 + _ efibootmgr -u --bootnext "$entry" +) +activate_efi() ( + local entry=$1 + _ efibootmgr -u --active --bootnum "$entry" +) +deactivate_efi() ( + local entry=$1 + _ efibootmgr -u --inactive --bootnum "$entry" +) + +parse_kernels() ( + local kernel_dir=${1:-/boot} + # test -n "$kernel_dir" || eerror "need kernel directory argument" + + local current_kernel current_hash + current_kernel=$(parse_efi | jq -rc '.[]|select(.current == false)|.loader' | path_from_efi) + current_hash=$(hashsum <"$current_kernel") + + find "$kernel_dir" -maxdepth 1 -type f -name 'vmlinuz*' -print | + while read -r ln; do + local file mtime ver ext hash + + file=$ln + hash=$(hashsum <"$file") + mtime=$(stat -c '%Y' "$file") + ln=$(sed -e "s,^${kernel_dir}/\([^-]*\)-\([0-9]*\.[0-9]*\.[0-9]*[^\.]*\)\(.*\)\$,\1\t\2\t\3," <<<"$ln") + ver=$(cut -d $'\t' -f2 <<<"$ln") + ext=$(cut -d $'\t' -f3 <<<"$ln") + jq -cn -S \ + --arg file "$file" \ + --argjson old "$(grep -q old <<<"$ext" && echo true || echo false)" \ + --argjson current "$(test "$hash" = "$current_hash" && echo true || echo false)" \ + --arg version "$ver" \ + --argjson mtime "$mtime" \ + --arg hash "$hash" \ + '{file:$file,old:$old,version:$version,mtime:$mtime,hash:$hash,current:$current}' + done | + jq -s 'sort_by(.mtime)|reverse' +) + +kernel_to_initramfs() { + local file + + file=$(dirname "$1")/$(basename "$1" | sed -e 's,^vmlinuz,initramfs,') + if grep -Eq '\.efi$' <<<"$file"; then + sed -e 's,\.efi$,.img,' <<<"$file" + else + sed -e 's,$,.img,' <<<"$file" + fi +} + +install_kernel() { + local file=$1 target=${EFI_PARTITION}${EFI_DIR} newfile + newfile=$target/$(basename "$file").efi + + _ cp -vf "$file" "$newfile" >&2 + _ cp -vf "$(kernel_to_initramfs "$file")" "$target/" >&2 + + echo "$newfile" +} + +remove_kernel() { + local file=$1 initramfs + initramfs=$(kernel_to_initramfs "$file") + + _ rm -vf "$file" >&2 + _ rm -vf "$initramfs" >&2 + + # _ rm -vf "$(path_to_boot <<<"$file")" >&2 + # _ rm -vf "$(path_to_boot <<<"$file").old" >&2 + # + # _ rm -vf "$(path_to_boot <<<"$initramfs")" >&2 + # _ rm -vf "$(path_to_boot <<<"$initramfs").old" >&2 + # + # _ rm -vf "$(path_to_boot <<<"$file" | sed -e 's,vmlinuz,System.map,')" >&2 + # _ rm -vf "$(path_to_boot <<<"$file" | sed -e 's,vmlinuz,System.map,').old" >&2 + # + # _ rm -vf "$(path_to_boot <<<"$file" | sed -e 's,vmlinuz,config,')" >&2 + # _ rm -vf "$(path_to_boot <<<"$file" | sed -e 's,vmlinuz,config,').old" >&2 +} + +# $1 : new kernel version, e.g. 6.1.27-gentoo-dist +# $2 : new kernel path, e.g. /boot/vmlinuz-6.1.27-gentoo-dist +new_kernel() { + local version=$1 kernel=$2 + local kernel_dir efi_kernel efi_entries + + test -f "$kernel" || eerror "kernel needs to be a file" + + einfo "New kernel version ${version}" + + kernel_dir=$(dirname "$kernel") + efi_kernel=${EFI_PARTITION}${EFI_DIR}/$(basename "$kernel").efi + efi_entries=$(parse_efi | jq -rc) + + efi_entries_dup=$(jq -rc --arg version "$version" '.[]|select(.version==$version)|{entry:.entry,loader:.loader}' <<<"$efi_entries") + ebegin "Remove duplicate kernel and boot entry with version $version" + for efi in $efi_entries_dup; do + delete_efi "$(jq -rc '.entry' <<<"$efi")" + remove_kernel "$(jq -rc '.loader' <<<"$efi" | path_from_efi)" + done + eend + + # ebegin "Create fallback boot entry" + # local bootcurrent_entry bootcurrent + # bootcurrent_entry=$(efibootmgr -u | grep '^BootCurrent' | sed -e 's,BootCurrent: ,,') + # bootcurrent=$(jq -rc --arg current "$bootcurrent_entry" '.[]|select(.entry == $current)' <<<"$efi_entries") + # if [ -f "$(jq -r '.loader' <<<"$bootcurrent" | path_from_efi)" ]; then + # if [ "$efi_kernel" = "$(jq -r '.loader' <<<"$bootcurrent" | path_from_efi)" ]; then + # efibootmgr -u --bootnum "$bootcurrent_entry" --delete-bootnum + # + # local bootcurrent_kernel bootcurrent_initramfs + # bootcurrent_kernel=$(jq -rc '.loader' <<<"$bootcurrent" | path_from_efi) + # bootcurrent_initramfs=$(dirname "$bootcurrent_kernel")/$(basename "$bootcurrent_kernel" | sed -e 's,^vmlinuz,initramfs,' -e 's,efi$,img,') + # mv -v "$bootcurrent_kernel" "$bootcurrent_kernel.old" >&2 + # mv -v "$bootcurrent_initramfs" "$bootcurrent_initramfs.old" >&2 + # + # jq -cnS \ + # --arg loader "$(path_to_efi <<<"$bootcurrent_kernel")" \ + # --argjson bootorder 1 \ + # --arg label "Gentoo Linux (old)" \ + # --arg args "initrd=$(path_to_efi <<<"$bootcurrent_initramfs")" \ + # '[{label:$label,loader:$loader,args:$args,bootorder:$bootorder}]' | + # create_efi + # else + # eerror "can't demote boot entry" + # exit 2 + # #efibootmgr -u --bootnum "$bootcurrent_entry" --index 2 + # fi + # else + # local oldkernel + # oldkernel=$(parse_kernels "/boot" | jq -rc '[.[]|select(.current != true)]|first|.file') + # efi_oldkernel=$(install_kernel "$oldkernel") + # jq -cnS \ + # --arg loader "$(path_to_efi <<<"$efi_oldkernel")" \ + # --argjson bootorder 1 \ + # --arg label "Gentoo Linux (old)" \ + # --arg args "initrd=$(kernel_to_initramfs "$efi_oldkernel" | path_to_efi)" \ + # '[{label:$label,loader:$loader,args:$args,bootorder:$bootorder}]' | + # create_efi + # fi + # eend + + ebegin "Install new kernel" + efi_kernel=$(install_kernel "$kernel") + if [ ! -e "$efi_kernel" ]; then + eerror "Installing kernel to $efi_kernel failed!" + fi + eend + + ebegin "Create new boot entry (debug)" + jq -cnS \ + --arg loader "$(path_to_efi <<<"$efi_kernel")" \ + --arg label "Gentoo Linux $version (debug)" \ + --arg args "initrd=$(kernel_to_initramfs "$efi_kernel" | path_to_efi) rd.cmdline=ask" \ + '[{label:$label,loader:$loader,args:$args}]' | + create_efi + eend + + ebegin "Create new boot entry (default)" + jq -cnS \ + --arg loader "$(path_to_efi <<<"$efi_kernel")" \ + --arg label "Gentoo Linux $version" \ + --arg args "initrd=$(kernel_to_initramfs "$efi_kernel" | path_to_efi)" \ + '[{label:$label,loader:$loader,args:$args}]' | + create_efi + eend +} + +# check for cli usage +efi=1 +kernel= +interactive= +list= +json= +while getopts KEhlij opt; do + case ${opt} in + E) efi=1 kernel= ;; + K) kernel=1 efi= ;; + i) interactive=1 ;; + l) list=1 ;; + j) json=1 ;; + h | ?) + printf "usage: %s [-hilj]\n" "$(basename "$0")" + printf "\n" + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ "$(id -u)" -ne 0 ]; then + eerror "need to be root" +fi + +# always mount efivarfs first +mount_efi + +# run list +if [ -n "$list" ]; then + [ -n "$efi" ] && parse_efi | + jq '[.[] | {boot:([(if .current then "*" else " " end), .entry]|join("")),label:.label}]' | + /home/robert/bin/jq-table + [ -n "$kernel" ] && parse_kernels | + jq '[.[] | {hash:([(if .current then "*" else " " end), .hash[:6]]|join("")),version:.version}]' | + /home/robert/bin/jq-table + exit $? +fi + +if [ -n "$json" ]; then + [ -n "$efi" ] && parse_efi + [ -n "$kernel" ] && parse_kernels + exit $? +fi + +# run interactive +if [ -n "$interactive" ]; then + export BEMENU_BACKEND=curses + + entry=$(parse_efi | + jq '[.[] | {boot:([(if .current then "*" else " " end), .entry]|join("")),label:.label}]' | + /home/robert/bin/jq-table | + tail -n+2 | + bemenu -p ' efi: select boot entry ' | + cut -c2-) + [ -z "$entry" ] && exit + + bootnum=$(cut -d' ' -f1 <<<"$entry") + [ -z "$bootnum" ] && exit + + echo ">>> $(cut -d' ' -f2 <<<"$entry")" + + action=$(cat <<-EOF | bemenu -p " efi: $(cut -d' ' -f2 <<<"$entry") " + delete + activate + deactivate + bootnext +EOF + ) + [ -z "$action" ] && exit + + case "$action" in + delete) delete_efi "$bootnum" ;; + activate) activate_efi "$bootnum" ;; + deactivate) deactivate_efi "$bootnum" ;; + bootnext) bootnext_efi "$bootnum" ;; + esac + exit $? +fi + + +# run install routine, used by installkernel +test -n "$1" || eerror "need version argument" +test -n "$2" || eerror "need kernel argument" +new_kernel "$1" "$2" +exit $? |