summary refs log tree commit diff
path: root/os/etc/kernel/efi.sh
diff options
context:
space:
mode:
Diffstat (limited to 'os/etc/kernel/efi.sh')
-rwxr-xr-xos/etc/kernel/efi.sh351
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 $?