Skip to content

Instantly share code, notes, and snippets.

@yorickdowne
Last active May 1, 2026 18:19
Show Gist options
  • Select an option

  • Save yorickdowne/660a5d62916b4655f355934dc1df9cb8 to your computer and use it in GitHub Desktop.

Select an option

Save yorickdowne/660a5d62916b4655f355934dc1df9cb8 to your computer and use it in GitHub Desktop.
Reinstall Grub to all ESPs on Debian
#!/usr/bin/env bash
# NO WARRANTY, express or implied, for this script. It is risky: A bug in it may keep your system from booting
#
# This script is intended to help me when upgrading Debian on dual-disk OVH baremetal machines with multiple ESPs,
# installed before OVH switched to raid1 ESPs for Debian 13 installs on the 12th of November 2025, see
# https://community.ovhcloud.com/community/en/upcoming-change-efi-system-partition-over-raid1-for-linux-installations?id=community_question&sys_id=8602b59034ac7290f078f43c21c5a60d
#
# Assumptions
# 1) The UUID of the partition that holds /boot/grub is linked in <ESP-partition>/EFI/<bootloader-id>/grub.cfg .
# grub.cfg in EFI is generated only for signed images (shim + grub-efi-$arch-signed), amd64 and arm64 only.
# This is how the script determines whether a given ESP is managed by the currently running OS, and is safe to update.
# All other ESPs will be skipped
# 2) EFI boot entries, as seen by efibootmgr, exist for all ESPs that are managed by the currently running OS,
# and they have a the UUID of the ESP and a file path that contains \EFI\<bootloader-id> .
# verify_bootmgr() checks for that
# 3) The script can find the <bootloader-id> by reading GRUB_DISTRIBUTOR from /etc/default/grub
# 4) It's a Debian. The way grub is integrated is highly specific to the distribution, and this script would not be safe
# on anything else. Downstream Ubuntu handles multiple ESPs with its do-release-upgrade upgrader script already
set -euo pipefail
verify_bootmgr() {
local mgr_uuid
local mgr_path
local found=0
local uuid=$1
local grub_id=$2
while read -r line; do
# Extract the UUID (the string after GPT, up to the next comma)
mgr_uuid=$(echo "$line" | grep -oP 'GPT,\K[0-9a-fA-F-]{36}')
# Extract the EFI file path
mgr_path=$(echo "$line" | grep -oP 'File\(\K[^\)]+')
if [[ "$mgr_uuid" = "$uuid" && "$mgr_path" =~ ^\\EFI\\$grub_id ]]; then
found=1
fi
done < <(efibootmgr -v | grep "File(")
if [[ "$found" -eq 1 ]]; then
return 0
else
return 1
fi
}
esp_guid="c12a7328-f81f-11d2-ba4b-00a0c93ec93b"
. /etc/os-release
[ "$ID" = debian ] || { echo "You are not on Debian"; exit 1; }
[ -d /sys/firmware/efi ] || { echo "Not booted in UEFI mode"; exit 1; }
[ "$EUID" -eq 0 ] || { echo "Please run this as root, use sudo"; exit 1; }
case "$(dpkg --print-architecture)" in
amd64) target="x86_64-efi" ;;
arm64) target="arm64-efi" ;;
*)
echo "Unsupported architecture"
exit 1
;;
esac
if ! dpkg-query -W -f='${Status}' util-linux 2>/dev/null | grep -q "ok installed" \
|| ! dpkg-query -W -f='${Status}' coreutils 2>/dev/null | grep -q "ok installed" \
|| ! dpkg-query -W -f='${Status}' efibootmgr 2>/dev/null | grep -q "ok installed"; then
echo "Installing tools this script uses"
apt-get update && apt-get install --no-install-recommends -y \
util-linux \
coreutils \
efibootmgr
fi
if ! dpkg-query -W -f='${Status}' mawk 2>/dev/null | grep -q "ok installed" \
&& ! dpkg-query -W -f='${Status}' gawk 2>/dev/null | grep -q "ok installed"; then
echo "Installing mawk"
apt-get update && apt-get install --no-install-recommends -y mawk
fi
i=0
while read -r name parttype fstype; do
if [[ "$parttype" == "$esp_guid" && "$fstype" == "vfat" ]]; then
((i+=1))
fi
done < <(lsblk -rno NAME,PARTTYPE,FSTYPE)
if [[ "$i" -eq 1 ]]; then
echo "Only one EFI System Partition found. This script would not be helpful, and might be actively harmful"
exit 0
elif [[ "$i" -eq 0 ]]; then
echo "No EFI System Partitions found. Maybe this system uses raid1 for EFI. This script would not be helpful"
exit 0
fi
. /etc/default/grub
# Lowercase
grub_id="${GRUB_DISTRIBUTOR,,}"
if [[ "$grub_id" =~ [[:space:]] ]]; then
echo "GRUB_DISTRIBUTOR contains space. This script isn't sure it can find the correct bootloader-id"
echo "$GRUB_DISTRIBUTOR"
echo "Aborting"
exit 1
fi
# Fallback if empty
if [ -z "$grub_id" ]; then
echo "GRUB_DISTRIBUTOR is empty. This is unexpected. Defaulting bootloader-id to debian"
grub_id="debian"
fi
boot_grub_uuid=$(lsblk -no UUID $(df /boot/grub | tail -1 | awk '{print $1}'))
# Sanity check
lsblk -rno NAME,PARTTYPE,FSTYPE,PARTUUID | while read -r name parttype fstype esp_uuid; do
if [[ "$parttype" == "$esp_guid" && "$fstype" == "vfat" ]]; then
dev="/dev/$name"
mnt="$(mktemp -d)"
mount "$dev" "$mnt"
linked_uuid=$(grep "search.fs_uuid" "$mnt"/EFI/"$grub_id"/grub.cfg | awk '{print $2}')
if [[ "$linked_uuid" != "$boot_grub_uuid" ]]; then
continue
fi
if ! verify_bootmgr "$esp_uuid" "$grub_id"; then
echo "There is no matching entry in efibootmgr for the ESP on $dev"
echo "This requires manual intervention, the script is not sure this is safe"
echo "Aborting"
umount "$mnt"
rmdir "$mnt"
exit 1
fi
umount "$mnt"
rmdir "$mnt"
fi
done
# Loop again for real
lsblk -rno NAME,PARTTYPE,FSTYPE,PARTUUID | while read -r name parttype fstype; do
if [[ "$parttype" == "$esp_guid" && "$fstype" == "vfat" ]]; then
dev="/dev/$name"
mnt="$(mktemp -d)"
echo
echo "Checking ESP on $dev"
mount "$dev" "$mnt"
linked_uuid=$(grep "search.fs_uuid" "$mnt"/EFI/"$grub_id"/grub.cfg | awk '{print $2}')
if [[ "$linked_uuid" != "$boot_grub_uuid" ]]; then
echo "The ESP on $dev does not belong to this $NAME installation. Skipping"
continue
fi
echo "The ESP on $dev belongs to this $NAME installation. Installing GRUB to $dev"
grub-install \
--target="$target" \
--efi-directory="$mnt" \
--bootloader-id="$grub_id" \
--recheck
umount "$mnt"
rmdir "$mnt"
fi
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment