Настройка синхронизации EFI-разделов при использовании RAID1 и Grub

Введение

Прогресс не стоит на месте, инновации рано или поздно догоняют даже тех, кто их не очень ждёт. При установке Debian 12 по умолчанию для разметки дисков используется GPT, а для загрузки - UEFI.

UEFI расшифровывается как Unified Extensible Firmware Interface - унифицированный расширяемый интерфейс прошивок. Это обновлённый вариант BIOS, который умеет работать в 32- или 64-битном режиме процессора и умеет загружать с дисков загрузчики в формате EFI. Для этого на диске должна быть таблица разделов в формате GPT, один из разделов должен иметь тип ESP и отформатирован как файловая система FAT32. ESP расшифровывается как EFI System Partition - системный раздел EFI. Именно в этой файловой системе должны находиться файлы загрузчиков с расширением .efi, которые можно использовать для загрузки операционных систем. По умолчанию используется файл /EFI/Boot/bootx64.efi, однако UEFI позволяет прописать в NVRAM меню, позволяющее выбрать один из нескольких загрузчиков.

NVRAM расшифровывается как Non Volatile Random Access Memory - энергонезависимая память. Подобно тому, как UEFI являестся обновлённым вариантом BIOS, NVRAM является обновлённым вариантом CMOS. Настройки UEFI сохраняются в NVRAM, а батарейка на материнской плате теперь используется только в качестве источника питания для часов реального времени.

В NVRAM можно прописать меню, в котором можно указать список всех загрузчиков с расширением .efi, их текстовые описания и порядок использования.

Если нужно настроить загрузку с массива RAID1, то на каждом из дисков нужно создать раздел ESP, на который при установке будут записаны файлы с расширением .efi. Проблема в том, что такой раздел нужно не только создать, но и поддерживать актуальным на обоих дисках массива.

Управление NVRAM

Для управления меню, записанным в NVRAM, из Linux можно воспользоваться утилитой efibootmgr.

Вывести список всех возможных загрузчиков в меню:

# efibootmgr
BootCurrent: 0002
Timeout: 0 seconds
BootOrder: 0002,0000,0005,0001
Boot0000* Hard Drive
Boot0001  
Boot0002* debian
Boot0005  Internal EFI Shell

В строке BootCurrent указан номер строки, которая использовалась для загрузки текущей системы. В строке BootOrder приведён порядок загрузки. Звёздочками отмечены активные пункты меню.

Для вывода подробностей можно указать команде опцию -v:

# efibootmgr -v
BootNext: 0002
BootCurrent: 0002
Timeout: 0 seconds
BootOrder: 0004,0002,0000,0005,0001,0003
Boot0000* Hard Drive    BBS(HD,,0x0)P0: HGST HUS726T6TALE6L4      .
Boot0001    HD(1,MBR,0x34862533,0x11ac,0x4a20)/File()
Boot0002* debian    HD(1,GPT,f5414378-382e-459d-97da-f30daa245d1a,0x800,0xee000)/File(\EFI\debian\shimx64.efi)
Boot0003  default   HD(1,GPT,6dbda6de-bb78-4e7e-9bc5-c92ce9a7d2f7,0x800,0xee000)/File(\EFI\Boot\bootx64.efi)
Boot0004* debian    HD(1,GPT,6dbda6de-bb78-4e7e-9bc5-c92ce9a7d2f7,0x800,0xee000)/File(\EFI\debian\shimx64.efi)
Boot0005  Internal EFI Shell    VenMedia(5023b95c-db26-429b-a648-bd47664c8012)/FvFile(c57ad6b7-0515-40a8-9d21-551652854e37)

Активировать какой-либо пункт меню можно следующим образом:

# efibootmgr -b 0005 -a
BootCurrent: 0002
Timeout: 0 seconds
BootOrder: 0002,0000,0005,0001,0003
Boot0000* Hard Drive
Boot0001  
Boot0002* debian
Boot0003* default
Boot0005* Internal EFI Shell

Деактивировать можно вот так:

# efibootmgr -b 0005 -A
BootCurrent: 0002
Timeout: 0 seconds
BootOrder: 0002,0000,0005,0001,0003
Boot0000* Hard Drive
Boot0001  
Boot0002* debian
Boot0003* default
Boot0005  Internal EFI Shell

Добавить новый загрузчик и поместить его в начало списка можно следующим образом:

# efibootmgr -c -d /dev/sda -p 1 -L default -l '\EFI\Boot\bootx64.efi'
BootCurrent: 0002
Timeout: 0 seconds
BootOrder: 0003,0002,0000,0005,0001
Boot0000* Hard Drive
Boot0001  
Boot0002* debian
Boot0005  Internal EFI Shell
Boot0003* default

В опции -d указывается диск, в опции -p указывается номер раздела, в опции -L - название пункта меню, а в опции -l путь к используемому загрузчику.

Если нужно только добавить загрузчик, но не помещать его в начало списка загрузки, то опцию -c нужно заменить на -C.

К этой команде можно также добавить опцию -u, в которой указать текстовую строку, которая будет передана ядру операционной системы. В этой строке можно указать опции для настройки ядра при его запуске.

Удалить запись из этого списка можно с помощью следующей команды, указав ей номер удаляемого загрузчика:

# efibootmgr -B -b 0003

Для изменения порядка загрузки можно воспользоваться такой командой:

# efibootmgr -o 00002,0000,0005,0001,00003
BootCurrent: 0002
Timeout: 0 seconds
BootOrder: 0002,0000,0005,0001,0003
Boot0000* Hard Drive
Boot0001  
Boot0002* debian
Boot0003* default
Boot0005  Internal EFI Shell

С помощью опции -O можно вовсе удалить информацию о порядке загрузки. Видимо, в таком случае нужно будет выбрать используемый загрузчик из списка вручную.

Для того, чтобы при следующей загрузке компьютера использовать загрузчик с определённым номером, можно указать номер желаемого загрузчика следующей команде:

# efibootmgr -n 0002
BootNext: 0002
BootCurrent: 0002
Timeout: 0 seconds
Boot Order: 0004,0002,0000,0005,0001,0003
Boot0000* Hard Drive
Boot0001  
Boot0002* debian
Boot0003  default
Boot0004* debian
Boot0005  Internal EFI Shell

Как видно, выбранный загрузчик будет отображён в строке BootNext. Для отмены выбора можно воспользоваться опцией -N - будет использоваться порядок загрузки по умолчанию.

Чтобы изменить время ожидания ручного выбора загрузчика, можно воспользоваться командой такого вида:

# efibootmgr -t 5
BootNext: 0002
BootCurrent: 0002
Timeout: 5 seconds
BootOrder: 0004,0002,0000,0005,0001,0003
Boot0000* Hard Drive
Boot0001  
Boot0002* debian
Boot0003  default
Boot0004* debian
Boot0005  Internal EFI Shell

Для удаления времени ожидания ручного выбора можно воспользоваться опцией -T. Видимо, в таком случае будет использоваться загрузка в указанном порядке без возможности ручного выбора. Не совсем понятно, что произойдёт, если удалить порядок загрузки и выбранный следующий загрузчик. Видимо будет использоваться загрузка с настройками по умолчанию.

Все настраиваемые утилитой значения прописываются в переменные, доступные через файловый интерфейс:

# ls /sys/firmware/efi/efivars/Boot*
/sys/firmware/efi/efivars/Boot0000-8be4df61-93ca-11d2-aa0d-00e098032b8c  /sys/firmware/efi/efivars/Boot0003-8be4df61-93ca-11d2-aa0d-00e098032b8c  /sys/firmware/efi/efivars/BootCurrent-8be4df61-93ca-11d2-aa0d-00e098032b8c
/sys/firmware/efi/efivars/Boot0001-8be4df61-93ca-11d2-aa0d-00e098032b8c  /sys/firmware/efi/efivars/Boot0004-8be4df61-93ca-11d2-aa0d-00e098032b8c  /sys/firmware/efi/efivars/BootNext-8be4df61-93ca-11d2-aa0d-00e098032b8c
/sys/firmware/efi/efivars/Boot0002-8be4df61-93ca-11d2-aa0d-00e098032b8c  /sys/firmware/efi/efivars/Boot0005-8be4df61-93ca-11d2-aa0d-00e098032b8c  /sys/firmware/efi/efivars/BootOrder-8be4df61-93ca-11d2-aa0d-00e098032b8c

К сожалению, значения этих переменных имеют двоичный формат и их нельзя поменять, не прибегая к помощи утилиты efibootmgr.

Скрипт

Одно из решений, которое предлагают на просторах интернета - смонтировать раздел на втором диске в точку монитирования /boot/efi2 и обновлять его автоматически при вызове update-grub с помощью скрипта в каталоге /etc/grub.d/, который вызывается утилитой update-grub при каждом её запуске.

Например, можно создать файл /etc/grub.d/90_sync_efi2 со следующим содержимым:

#!/bin/sh
set -e

if mountpoint -q /boot/efi && mountpoint -q /boot/efi2 ; then
        efi=`find /boot/efi -type f -printf '%T+ %p\n' | sort | tail -n 1 | cut -d' ' -f2`
        efi2=`find /boot/efi2 -type f -printf '%T+ %p\n' | sort | tail -n 1 | cut -d' ' -f2`

        if [ -z "$efi" ] ; then
                rsync -t --recursive --delete /boot/efi2/ /boot/efi/
        elif [ -z "$efi2" ] ; then 
                rsync -t --recursive --delete /boot/efi/ /boot/efi2/
        elif [ "$efi" -nt "$efi2" ] ; then
                rsync -t --recursive --delete /boot/efi2/ /boot/efi/
        elif [ "$efi2" -nt "$efi" ] ; then
                rsync -t --recursive --delete /boot/efi/ /boot/efi2/
        fi
fi
exit 0

Нужно дать скрипту права на выполнение:

# chmod +x /etc/grub.d/90_sync_efi2

Не забудьте установить в систему пакет с утилитой rsync.

Скрипт находит в каждом из разделов самый новый файл. Если в каком-то из двух разделов файлов нет или самый новый файл старше, чем самый новый файл на другмо разделе, то выполняется синхронизация из раздела с более новым файлов в раздел с более старым файлом или без файлов.

RAID1

Другое решение - настроить зеркалирование разделов, не меняя в таблице GPT тип раздела. Для этого можно воспользоваться программным массивом, создаваемым утилитой mdadm. Для того, чтобы mdadm не затёрла начало раздела информацией о массиве - метаданными, нужно использовать метаданные версии 1.0, а не версии 1.2, которая используется при создании массивов по умолчанию. Метаданные версии 1.0 помещаются в конце раздела и таким образом не помешают загрузчику UEFI разобраться в структуре файловой системы и найти загрузочные файлы.

Пример команды:

# mdadm --create --verbose --level=1 --metadata=1.0 --raid-devices=2 /dev/md1 /dev/sda1 /dev/sdb1

Как обычно, после изменения массивов RAID нужно обновить содержимое файла /etc/mdadm/mdadm.conf, чтобы операционная система автоматически собрала массивы в процессе загрузки. Сделать это можно, например, при помощи следующей команды:

# mdadm --detail --scan > /etc/mdadm/mdadm.conf

Этот файл нужно добавить в образ файловой системы, используемой при загрузке ядра:

# update-initramfs -u -k all

Установка/восстановление GRUB

Для установки загрузчика GRUB в раздел ESP можно воспользоваться командой следующего вида:

# grub-install --target=x86_64-efi --efi-directory=/boot/efi/ --bootloader-id=debian

Где:

  • /boot/efi - точка монтирования раздела ESP,
  • debian - имя каталога, в котором будет размещён загрузчик, полный путь к файлам загрузчика на разделе ESP получится таким: \EFI\debian\.

Для загрузки GRUB принято использовать файл shimx64.efi - небольшой загрузчик, который был подписан сертификатами Microsoft, прописанными в UEFI всех производителей. При использовании этого файла возможно организовать загрузку в режиме SecureBoot, в котором код загрузчиков и ядра операционной системы проверяются с использованием сертификатов.

Загрузчик EFI из systemd

Вместе с системой systemd распространяется также пакет systemd-boot с утилитой, которая умеет устанавливать собственный простой EFI-загрузчик. Установим пакет:

# apt-get install systemd-boot

Если раздел ESP смонтирован в каталог /boot/efi, то установить в него загрузчик можно следующим образом:

# bootctl install --esp-path=/boot/efi/

bootctl проверяет, является ли указанный раздел разделом ESP в таблице разделов GPT. Если это не так, то он сообщит об ошибке. Например, в моём случае раздел /boot/efi находится на программном RAID, созданном средствами mdadm, как это описано ниже. Поэтому в моём случае при попытке воспользоваться приведённой выше командой я столкнулся с ошибкой:

File system "/dev/md1" is not located on a partitioned block device.

Чтобы отключить проверку типа раздела, нужно задать переменную окружения SYSTEMD_RELAX_ESP_CHECKS равной единице, вот так:

# SYSTEMD_RELAX_ESP_CHECKS=1 bootctl install --esp-path=/boot/efi/

Утилита добавил в раздел ESP файл \EFI\BOOT\BOOTX64.EFI и идентичный ему файл \EFI\systemd\systemd-bootx64.efi, а также создаст файлы с настройками загрузчика в каталоге loader. Кроме этого будет создан пустой каталог \EFI\Linux, который, видимо, предназначен для размещения драйверов файловых систем для UEFI.

Загрузчик EFI\systemd\systemd-bootx64.efi будет автоматически прописан первым загрузчиком в NVRAM. Чтобы утилиты не меняла порядок загрузки и не добавляла свой загрузчик в меню, можно указать ей опцию --no-variables.

Для удаления загрузчика можно воспользоваться такой командой:

# SYSTEMD_RELAX_ESP_CHECKS=1 bootctl remove --esp-path=/boot/efi/

Утилита также обладает другими возможностями, которые имеются в утилите efibootmgr: позволяет менять настройки таймаута, выбирать загрузчик, который будет использоваться при следующей загрузке и т.д.

Использованные материалы