134 lines
5.6 KiB
Bash
134 lines
5.6 KiB
Bash
#!/bin/sh
|
|
# The fallback kernel's place on this machine's boot menu (module kernel, novox/hq ADR 0508 §9 and §10,
|
|
# issue 1841). Written by the mesh and run as root by mesh-kernel-boot-entries.service at boot and when the
|
|
# module is applied, and by the pacman hook 90-mesh-kernel-boot-entries.hook after a kernel is installed,
|
|
# upgraded or removed. Replaced on every push; change the module, never this file.
|
|
#
|
|
# boot-entries run [<root> [<this module's state> [<room needed in /boot, KiB>]]]
|
|
#
|
|
# The three are arguments only so the module's tests can run this against a stand-in tree.
|
|
#
|
|
# It reads from the machine which loader booted it, never from a setting:
|
|
# systemd-boot the EFI variable LoaderEntrySelected is present: the entry mesh-linux-lts.conf is written
|
|
# from the command line the machine booted with.
|
|
# GRUB /boot/grub/grub.cfg exists and that variable does not: the menu is generated again, only
|
|
# when the set of kernels in /boot differs from the menu's.
|
|
# neither it fails loudly. The default entry is never touched.
|
|
# And a pacman hook of someone else's that copies kernel images to one fixed name in /boot is moved into this
|
|
# module's state first (ADR 0508 §10): it would put the fallback kernel in the default kernel's place.
|
|
set -eu
|
|
|
|
root=/
|
|
state='${dir:state}'
|
|
[ $# -ge 2 ] && root=$(printf '%s' "$2" | sed 's|/$||')
|
|
[ $# -ge 3 ] && state=$3
|
|
boot=$root/boot
|
|
hooks=$root/etc/pacman.d/hooks
|
|
efivar=$root/sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
|
|
ours=90-mesh-kernel-boot-entries.hook
|
|
# The fallback kernel and its initramfs are about 72 MB in /boot (issue 1836): less room fails the unit
|
|
# before a package writes half of them.
|
|
need_kib=98304
|
|
[ $# -ge 4 ] && need_kib=$4
|
|
|
|
[ $# -ge 1 ] && [ "$1" = run ] || {
|
|
echo "usage: boot-entries run [<root> [<state directory> [<KiB needed>]]]" >&2
|
|
exit 2
|
|
}
|
|
|
|
# A hook that is not ours, triggered by kernel images, whose action copies to one fixed name in /boot.
|
|
copies_kernels() {
|
|
grep -Eqi '^[[:space:]]*Target[[:space:]]*=.*(vmlinuz|usr/lib/modules)' "$1" &&
|
|
grep -Eq '^[[:space:]]*Exec[[:space:]]*=.*(cp|install|ln|rsync)[[:space:]].*/boot/[A-Za-z0-9._+-]+([^A-Za-z0-9._+/%$*-]|$)' "$1"
|
|
}
|
|
|
|
if [ -d "$hooks" ]; then
|
|
for hook in "$hooks"/*.hook; do
|
|
[ -f "$hook" ] || continue
|
|
[ "$(basename "$hook")" = "$ours" ] && continue
|
|
copies_kernels "$hook" || continue
|
|
mkdir -p "$state/taken-over"
|
|
mv -f "$hook" "$state/taken-over/$(basename "$hook")"
|
|
echo "taken over: the pacman hook $(basename "$hook") copied kernel images to one fixed name in /boot; moved to $state/taken-over/ so each kernel keeps its own package's name"
|
|
done
|
|
fi
|
|
|
|
if [ -e "$efivar" ]; then
|
|
loader=systemd-boot
|
|
elif [ -f "$boot/grub/grub.cfg" ]; then
|
|
loader=grub
|
|
else
|
|
echo "no boot loader found: the machine was not booted by systemd-boot (no LoaderEntrySelected variable) and there is no $boot/grub/grub.cfg; the fallback kernel has no menu to be placed on" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Room first, only while the fallback kernel is not in /boot yet.
|
|
if [ ! -e "$boot/vmlinuz-linux-lts" ]; then
|
|
free=$(df -Pk "$boot" | awk 'NR==2 {print $4}')
|
|
[ -n "$free" ] || free=0
|
|
if [ "$free" -lt "$need_kib" ]; then
|
|
echo "$boot has $free KiB free and the fallback kernel needs $need_kib KiB: free room there before the kernel is installed" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
case "$loader" in
|
|
systemd-boot)
|
|
entries=$boot/loader/entries
|
|
cmdline=$(sed -e 's/BOOT_IMAGE=[^ ]*//g' -e 's/initrd=[^ ]*//g' "$root/proc/cmdline" | tr -s ' \n' ' ' | sed -e 's/^ //' -e 's/ $//')
|
|
[ -n "$cmdline" ] || {
|
|
echo "$root/proc/cmdline holds no command line to boot the fallback kernel with" >&2
|
|
exit 1
|
|
}
|
|
want=$(
|
|
echo "title Arch Linux (linux-lts, fallback)"
|
|
echo "linux /vmlinuz-linux-lts"
|
|
for ucode in intel-ucode.img amd-ucode.img; do
|
|
[ -e "$boot/$ucode" ] && echo "initrd /$ucode"
|
|
done
|
|
echo "initrd /initramfs-linux-lts.img"
|
|
echo "options $cmdline"
|
|
)
|
|
mkdir -p "$entries"
|
|
if [ "$(cat "$entries/mesh-linux-lts.conf" 2>/dev/null || true)" = "$want" ]; then
|
|
echo "systemd-boot: mesh-linux-lts.conf, as it was"
|
|
else
|
|
printf '%s\n' "$want" >"$entries/mesh-linux-lts.conf"
|
|
echo "systemd-boot: wrote $entries/mesh-linux-lts.conf from the command line the machine booted with; the default entry is as it was"
|
|
fi
|
|
;;
|
|
grub)
|
|
cfg=$boot/grub/grub.cfg
|
|
menu=$(sed -n 's|^[[:space:]]*linux[[:space:]]*\(/boot\)\{0,1\}/\(vmlinuz-[^[:space:]]*\).*|\2|p' "$cfg" | sort -u)
|
|
have=$(cd "$boot" && ls -d vmlinuz-* 2>/dev/null | sort -u || true)
|
|
if [ "$menu" = "$have" ]; then
|
|
echo "grub: the menu lists the kernels in /boot, left as it is"
|
|
else
|
|
# GRUB lists kernels in reverse version order, so the fallback kernel would come first and become the
|
|
# default entry: the kernel the machine booted is put first (GRUB_TOP_LEVEL; /etc/default/grub still
|
|
# overrides it when it sets one), the fallback kernel never.
|
|
top=$(tr ' ' '\n' <"$root/proc/cmdline" | sed -n 's|^BOOT_IMAGE=\(.*\)$|\1|p' | head -n 1)
|
|
top=$(printf '%s' "$top" | sed 's|.*/||')
|
|
case "$top" in
|
|
vmlinuz-linux-lts | "") top= ;;
|
|
esac
|
|
[ -n "$top" ] && [ -e "$boot/$top" ] || top=
|
|
if [ -z "$top" ]; then
|
|
if [ -e "$boot/vmlinuz-linux" ]; then
|
|
top=vmlinuz-linux
|
|
else
|
|
top=$(printf '%s\n' $have | grep -v '^vmlinuz-linux-lts$' | head -n 1 || true)
|
|
fi
|
|
fi
|
|
if [ -n "$top" ]; then
|
|
GRUB_TOP_LEVEL=/boot/$top grub-mkconfig -o "$cfg"
|
|
else
|
|
grub-mkconfig -o "$cfg"
|
|
fi
|
|
first=$top
|
|
[ -n "$first" ] || first="the menu's own order"
|
|
echo "grub: the kernels in /boot changed ($(echo $have | tr '\n' ' ')); the menu is generated again with $first first; the default entry is as it was"
|
|
fi
|
|
;;
|
|
esac
|