Skip to content

Instantly share code, notes, and snippets.

@openglfreak
Last active April 2, 2021 17:17
Show Gist options
  • Save openglfreak/77851807a36538f947607df0ed87bce2 to your computer and use it in GitHub Desktop.
Save openglfreak/77851807a36538f947607df0ed87bce2 to your computer and use it in GitHub Desktop.
My own EFISTUB setup/update script - Development has moved to https://github.com/openglfreak/efistubmgr
VERBOSE=1
_cmdline_quiet='quiet vga=current loglevel=3 rd.systemd.show_status=auto rd.udev.log_priority=3'
_cmdline_misc='rw splash add_efi_memmap threadirqs sysrq_always_enabled=1 systemd.unified_cgroup_hierarchy=1'
_cmdline_nospec='pti=off nopti spectre_v1=off nospectre_v1 spectre_v2=off nospectre_v2 spectre_v2_app2app=off nospectre_v2_app2app l1tf=off nol1tf kvm-intel.vmentry_l1d_flush=never spec_store_bypass_disable=off nospec_store_bypass_disable no_stf_barrier mitigations=off clearcpuid=514'
_cmdline_perf='nowatchdog nmi_watchdog=0 intel_iommu=igfx_off workqueue.power_efficient=0 intel_pstate=hwp_only libahci.ignore_sss=1 scsi_mod.use_blk_mq=1 cryptomgr.notests elevator=bfq noreplace-smp page_alloc.shuffle=1 rcupdate.rcu_expedited=1 skew_tick=1'
_cmdline_gfx='i915.fastboot=1 i915.modeset=1 i915.enable_fbc=0 i915.enable_guc=0 i915.alpha_support=1 i915.disable_power_well=1 i915.lvds_channel_mode=2 nouveau.modeset=0 nvidia-drm.modeset=1'
_cmdline_fixes='acpi_osi=Linux acpi_sleep=nonvs iomem=relaxed slab_common.usercopy_fallback=y'
CMDLINE="$_cmdline_quiet $_cmdline_misc $_cmdline_nospec $_cmdline_perf $_cmdline_gfx $_cmdline_fixes"
_LABEL='Arch Linux (Default) (fallback initramfs)'
_KERNEL=vmlinuz-linux
_INITRD=initramfs-linux-fallback.img
add_boot_entry
_LABEL='Arch Linux (Default)'
_KERNEL=vmlinuz-linux
_INITRD=initramfs-linux.img
add_boot_entry
_LABEL='Arch Linux (TkG 5.8 PDS) (fallback initramfs)'
_KERNEL=vmlinuz-linux58-tkg-pds
_INITRD=initramfs-linux58-tkg-pds-fallback.img
add_boot_entry
_LABEL='Arch Linux (TkG 5.8 PDS)'
_KERNEL=vmlinuz-linux58-tkg-pds
_INITRD=initramfs-linux58-tkg-pds.img
add_boot_entry
_LABEL='Arch Linux (TkG 5.9 PDS) (fallback initramfs)'
_KERNEL=vmlinuz-linux59-tkg-pds
_INITRD=initramfs-linux59-tkg-pds-fallback.img
add_boot_entry
_LABEL='Arch Linux (TkG 5.9 PDS)'
_KERNEL=vmlinuz-linux59-tkg-pds
_INITRD=initramfs-linux59-tkg-pds.img
add_boot_entry
_LABEL='Arch Linux (TkG 5.10 PDS) (fallback initramfs)'
_KERNEL=vmlinuz-linux510-tkg-pds
_INITRD=initramfs-linux510-tkg-pds-fallback.img
add_boot_entry
_LABEL='Arch Linux (TkG 5.10 PDS)'
_KERNEL=vmlinuz-linux510-tkg-pds
_INITRD=initramfs-linux510-tkg-pds.img
add_boot_entry
#!/bin/sh
set -e 2>/dev/null ||:
set +C 2>/dev/null ||:
set +f 2>/dev/null ||:
set -u 2>/dev/null ||:
# description:
# Escapes a string for usage in a sed pattern.
# Sed expression copied from https://stackoverflow.com/a/2705678
# params:
# [literal]: string
# The string to escape. If omitted it's read from stdin
# [separator]: char
# The separator char. Defaults to a / (slash)
# outputs:
# The escaped string
sed_escape_pattern() {
if [ $# -gt 2 ]; then
echo 'sed_escape_pattern: Too many arguments' >&2
return 1
fi
if [ $# -ge 1 ]; then
# Shellcheck bug.
# shellcheck disable=SC2221,SC2222
case "${2:-}" in
??*)
echo 'sed_escape_pattern: Separator too long' >&2
return 2;;
[]\\\$\*\.^[]) set -- "$1" '';;
''|*) set -- "$1" "${2:-/}"
esac
# shellcheck disable=SC1003
printf '%s\n' "$1" | sed -e 's/[]\\'"$2"'$*.^[]/\\&/g' -e '$!s/$/\\/'
else
sed -e 's/[]\\/$*.^[]/\\&/g' -e '$!s/$/\\/'
fi
}
# description:
# Finds a boot entry by the label and returns the entry's bootnum
# params:
# name: string
# The label of the boot entry to find
# outputs:
# The bootnums of the found boot entries as 4-digit hexadecimal numbers,
# one per line, or nothing if no entry is found
find_bootnum_from_label() {
if [ $# -ne 1 ]; then
if [ $# -lt 1 ]; then
echo 'find_bootnum_from_label: Not enough arguments' >&2
return 1
else
echo 'find_bootnum_from_label: Too many arguments' >&2
return 2
fi
fi
LC_ALL=C efibootmgr | sed -n -e 's/^Boot\([0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]\)\*\? '"$(sed_escape_pattern "$1")"'$/\1/p'
}
# description:
# Deletes a list of boot entries by their bootnums
# params:
# [bootnums...]: string
# The bootnums of the boot entries to delete
delete_bootnums() {
while [ $# -ge 1 ]; do
efibootmgr --bootnum "$1" --delete-bootnum >/dev/null || return
shift
done
}
# description:
# Finds a boot entry by the label and deletes it
# params:
# name: string
# The label of the boot entry to delete
# outputs:
# The number of boot entries found
delete_bootentry_by_label() {
if [ $# -ne 1 ]; then
if [ $# -lt 1 ]; then
echo 'delete_bootentry_by_label: Not enough arguments' >&2
return 1
else
echo 'delete_bootentry_by_label: Too many arguments' >&2
return 2
fi
fi
# shellcheck disable=SC2046
set -- $(find_bootnum_from_label "$1")
printf '%i\n' $#
delete_bootnums ${1+"$@"}
}
# description:
# Creates or updates a boot entry with new parameters.
# See efibootmgr(8) for the default values of optional parameters.
# Empty optional parameters are handled like unset
# params:
# label: string
# The label of the boot entry
# loader: string
# The loader for the boot entry
# [cmdline]: string
# Command line arguments for the loader
# [disk]: string
# The disk containing the loader
# [part]: string
# The partition containing the loader
# outputs:
# The efibootmgr output
update_bootentry() {
if [ $# -lt 2 ]; then
echo 'update_bootentry: Not enough arguments' >&2
return 1
fi
if [ $# -gt 5 ]; then
echo 'update_bootentry: Too many arguments' >&2
return 2
fi
delete_bootentry_by_label "$1" >/dev/null || return
# Separate presence checks for option and value because Zsh doesn't do
# word splitting by default.
efibootmgr --create ${4:+--disk} ${4:+"$4"} \
${5:+--part} ${5:+"$5"} \
--label "$1" \
--loader "$2" \
${3:+--unicode} ${3:+"$3"}
}
_is_dry_run() { [ "x${dry_run:-${DRY_RUN:-0}}" = 'x1' ] || return; }
_is_verbose() { [ "x${verbose:-${VERBOSE:-0}}" = 'x1' ] || return; }
_is_true() {
set -- "$(printf '%s\n' "$1" | tr '[:upper:]' '[:lower:]')"
case "$1" in
true|1|y|yes|on) :;;
*) return 1
esac
}
_load_config() {
! _is_verbose || printf 'Loading config file %s\n' "$1"
# shellcheck source=/etc/xdg/efistubmgr.conf
# shellcheck disable=SC1091,SC2034
if ! . "$1"; then
printf 'error: Error while processing config file %s\n' "$1" >&2
return 1
fi
}
_load_configs_dir() {
if [ -e "$1/efistubmgr.conf" ]; then
_load_config "$1/efistubmgr.conf"
fi
for config_file in "$1/efistubmgr.conf.d"/*.conf; do
[ -e "${config_file}" ] || continue
_load_config "${config_file}" 2>&3 3>&-
done 3>&2 2>/dev/null || :
}
_load_configs() {
if [ "x${single_config+set}" = 'xset' ]; then
# shellcheck disable=SC1090
if ! . "${single_config}"; then
printf 'error: Error while processing config file %s\n' "${single_config}" >&2
return 1
fi
return
fi
! [ -d /boot ] || _load_configs_dir /boot
IFS=':' eval 'set -- ${XDG_CONFIG_DIRS:-/etc/xdg}'
i=$#; while [ "${i}" -ge 1 ]; do
eval "_load_configs_dir \"\${${i}}\""
i="$((i-1))"
done
}
_create_data_dir() {
mkdir -p /var/lib || return
# shellcheck disable=SC2174
mkdir -p -m 700 /var/lib/efistubmgr
}
_check_managed_entry_list_writable() {
if ! _is_dry_run; then
if [ -e /var/lib/efistubmgr/managed_entries ]; then
if ! [ -w /var/lib/efistubmgr/managed_entries ]; then
echo 'error: State file not writable' >&2
return 1
fi
else
if ! [ -w /var/lib/efistubmgr ]; then
echo 'error: State directory not writable' >&2
return 2
fi
fi
fi
}
_load_managed_entry_list() {
_check_managed_entry_list_writable || return
! _is_verbose || echo 'Loading managed entry list'
if [ -e /var/lib/efistubmgr/managed_entries ]; then
if ! managed_entries="$(cat /var/lib/efistubmgr/managed_entries)"; then
echo 'error: State file not readable' >&2
return 1
fi
else
managed_entries=
fi
}
_update_entry() {
eval "label=\"\${LABEL_$1:-}\""
! _is_verbose || printf 'Creating/updating boot entry "%s"\n' "${label}"
eval "kernel=\"\${KERNEL_$1:-}\""
eval "cmdline=\"\${CMDLINE_$1:-\${CMDLINE:-}}\""
eval "no_autodetect_ucode=\"\${NO_AUTODETECT_UCODE_$1:-\${NO_AUTODETECT_UCODE:-}}\""
if eval "[ \"x\${INITRD_$1+set}\" = 'xset' ]"; then
eval "initrds=\"initrd=\\\\\${INITRD_$1}\""
else
initrds=
j=0; while eval "[ \"x\${INITRD_$1_${j}+set}\" = 'xset' ]"; do
eval "initrds=\"\${initrds} initrd=\\\\\${INITRD_$1_${j}}\""
j="$((j+1))"
done
initrds="${initrds# }"
fi
# shellcheck disable=SC2154
if [ "x${initrds:+set}" = 'xset' ] && _is_true "${no_autodetect_ucode}"; then
if [ "x${ucode_initrds+set}" = 'xset' ]; then
! _is_verbose || echo 'Searching for microcode initrds'
ucode_initrds=
for ucode_initrd in /boot/*-ucode.img; do
[ -e "${ucode_initrd}" ] || continue
! _is_verbose || printf 'Found microcode initrd %s\n' "${ucode_initrd}"
ucode_initrds="initrd=\\${ucode_initrd#/boot/} ${ucode_initrds}"
done
fi
initrds="${ucode_initrds}${initrds}"
fi
if [ "x${initrds:+set}" = 'xset' ]; then
cmdline="root=UUID=${rootuuid=$(findmnt -vkno UUID /)} rootfstype=${rootfstype=$(findmnt -vkno FSTYPE /)} rootflags=${rootflags=$(findmnt -vkno OPTIONS /)} ${initrds} ${cmdline}"
else
cmdline="root=${rootdev=$(findmnt -vkno SOURCE /)} rootfstype=${rootfstype=$(findmnt -vkno FSTYPE /)} rootflags=${rootflags=$(findmnt -vkno OPTIONS /)} ${cmdline}"
fi
if ! _is_dry_run; then
# shellcheck disable=SC2154
if ! update_bootentry "${label}" "\\${kernel}" "${cmdline}" >/dev/null; then
printf 'error: Failed to create/update boot entry "%s"\n' "${label}" >&2
return 1
fi
fi
}
_update_entries() {
_new_managed_entries=
i=0; while eval "[ \"x\${LABEL_${i}+set}\" = 'xset' ]"; do
_update_entry "${i}" || return
_new_managed_entries="${_new_managed_entries}
${label}"
i="$((i+1))"
done
new_managed_entries="${_new_managed_entries#?}"
}
_get_tmpdir() {
if [ "x${XDG_RUNTIME_DIR:+set}" = 'xset' ]; then
tmpdir="${XDG_RUNTIME_DIR}"
# shellcheck disable=SC2174
mkdir -p -m 700 "${tmpdir}"
else
tmpdir="${TMPDIR:-${TEMPDIR:-/tmp}}"
# shellcheck disable=SC2174
mkdir -p -m 777 "${tmpdir}"
fi
}
_remove_old_entries() {
_get_tmpdir
printf '%s\n' "${managed_entries}" | LC_ALL=C sort -u >"${tmpdir}/efistubmgr-old-managed" || return
printf '%s\n' "${new_managed_entries}" | LC_ALL=C sort -u >"${tmpdir}/efistubmgr-new-managed" || return
entries_to_remove="$(LC_ALL=C comm -23 "${tmpdir}/efistubmgr-old-managed" "${tmpdir}/efistubmgr-new-managed")"
[ "x${entries_to_remove:+set}" = 'xset' ] || return 0
_failed_to_remove=
while IFS= read -r label; do
! _is_verbose || printf 'Removing boot entry "%s"\n' "${label}"
if ! _is_dry_run; then
if ! delete_bootentry_by_label "${label}" >/dev/null; then
! _is_verbose || printf 'Failed to remove boot entry "%s"\n' "${label}"
_failed_to_remove="${_failed_to_remove}${label}
"
fi
fi
done <<EOF
${entries_to_remove}
EOF
new_managed_entries="${_failed_to_remove}${new_managed_entries}"
}
_write_new_managed_entry_list() {
! _is_verbose || echo 'Saving new managed entry list'
if ! _is_dry_run; then
if ! printf '%s\n' "${new_managed_entries}" >/var/lib/efistubmgr/managed_entries; then
echo 'error: Could not write new managed boot entry list' >&2
return 1
fi
fi
}
_update_entries_and_state() {
_create_data_dir || return
_load_managed_entry_list || return
if _update_entries; then
_remove_old_entries
else
if [ "x${new_managed_entries:+set}" = 'xset' ]; then
new_managed_entries="${managed_entries}
${new_managed_entries}"
else
new_managed_entries="${managed_entries}"
fi
fi
_write_new_managed_entry_list
}
_save_entry_data() {
for varname in LABEL KERNEL CMDLINE NO_AUTODETECT_UCODE; do
if eval "[ \"x\${_${varname}+set}\" = 'xset' ]"; then
eval "${varname}_${1}=\"\${_${varname}}\""
fi
done
if [ "x${_INITRD+set}" = 'xset' ]; then
eval "INITRD_${1}=\"\${_INITRD}\""
else
j=0; while eval "[ \"x\${_INITRD_${j}+set}\" = 'xset' ]"; do
eval "INITRD_$1_${j}=\"\${_INITRD_${j}}\""
j="$((j+1))"
done
fi
}
_clear_entry_data() {
for varname in LABEL KERNEL CMDLINE NO_AUTODETECT_UCODE; do
if eval "[ \"x\${_${varname}+set}\" = 'xset' ]"; then
eval "unset _${varname}"
fi
done
unset _INITRD
i=0; while eval "[ \"x\${_INITRD_${i}+set}\" = 'xset' ]"; do
eval "unset \${_INITRD_${i}}"
i="$((i+1))"
done
}
add_boot_entry() {
i=0; while eval "[ \"x\${LABEL_${i}+set}\" = 'xset' ]"; do
i="$((i+1))"
done
_save_entry_data "${i}" || return
_clear_entry_data
}
# description:
# The main function of the program
# params:
# [options...]: string
# The command line options for the program
# outputs:
# Log messages, if enabled
main() {
while [ $# -ge 1 ]; do
case "$1" in
--config)
single_config="$2"
shift 2;;
--dry-run)
DRY_RUN=1
shift;;
--verbose)
VERBOSE=1
shift;;
--help)
cat <<EOF
Usage: ${0##*/} [options]
Options:
--config <file> Use an alternate config file
--dry-run Do not update any files or efi variables
--verbose Output status messages while working
--help Show this help message
EOF
return;;
*)
printf 'error: Unrecognized parameter: %s\n' "$1"
return 1
esac
done
_load_configs || return
_update_entries_and_state
}
main ${1+"$@"}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment