Last active
May 1, 2026 18:19
-
-
Save yorickdowne/660a5d62916b4655f355934dc1df9cb8 to your computer and use it in GitHub Desktop.
Reinstall Grub to all ESPs on Debian
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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