现在很多Linux发行版都是“开箱即用”,即安装好之后已经包含了大部分常用的软件包,一套完整的桌面环境(如果有),并且已经完成的大部分系统配置。而Arch Linux并非如此,它讲究的是高度的个性化,以及和上游保持高度一致。因此,Arch Linux若要做到“日常用起来舒适”,则需要大量的经验、能力、时间、想法去做配置,甚至有的时候会遇到坑。
GRUB就是一个典型的代表。在其他大部分Linux发行版(如Debian,Rocky Linux),都对自带的GRUB进行了少量修改,并会在安装期间或更新软件包期间自动维护GRUB的相关文件。除非需要,使用这些发行版的用户完全感知不到GRUB配置的存在。
而Arch Linux上的GRUB,只是提供了一个非常接近上游的最新软件包版本。相信大家在安装Arch Linux时都有印象,若要使用GRUB,安装GRUB相关软件包之后,在arch-chroot
中需要手动使用grub-install
和grub-mkconfig
来生成GRUB EFI文件,并生成启动配置文件。
有些Arch Linux用户可能也希望能开启Secure Boot(安全启动),来保证Windows 11上的一些功能能正常使用。而此时,Arch Linux的一个特性,即“只提供了一个非常接近上游的最新软件包版本”,有时候也会变成一个坑。
在 这篇文章中我提到过的一个事情,若按照Arch Wiki的指南,一步一步将GRUB安全启动配置好,虽然最后确实能启动起来,但是可能会遇到其他Linux发行版遇不到的一个问题:字体渲染问题,即是Arch Linux这个特性的具象表现。
这篇文章,就把我在Arch Linux上配置GRUB遇到的一些问题,解决方案,以及做的一些优化,分享出来。
这次为了方便,直接使用Hyper-V虚拟机进行实际演示,Arch Linux不安装任何界面软件,仅保留tty命令行。全程操作通过SSH进行。
实际演示效果参考
【BiliBili】 打开安全启动时的Arch Linux/Windows 11双系统切换+GRUB字体正常加载效果演示
先决条件,以及一些说明
本文已经假定,你已经按照常规的关闭安全启动的方式,已经成功安装Arch Linux,并且使用UEFI GRUB引导启动Arch Linux。
本文会把安全启动相关的配置文件存放在/etc/secureboot
文件夹下,如果你需要修改保存路径,请注意本文提供的相关配置命令和文件的路径设置。
如果操作过程中发现了没有创建的文件夹,请手动创建。
进行过程中,请随身备好一个Arch Linux启动盘,以在操作错误时及时还原配置。
文中所涉及的所有命令和配置文本建议认真且逐行阅读,并且了解每条命令和配置文件的实际用途。
请不要直接照抄配置文件,请根据你的实际情况进行修改。因为你的Arch Linux只有你最熟悉应该怎么去配置。
安全启动(Secure Boot)配置
正常情况下,安装和使用Arch Linux,都是推荐关闭安全启动的。不过本文开头也提到了,部分情况下会存在打开安全启动使用Arch Linux的情况。
即便之前的一篇文章提到过常规的安全启动配置方式,这篇文章会重新概述一下配置方法,以及一些配置细节。
GRUB侧的配置
首先,安装相应的软件包:shim-signed
(AUR包),sbsigntools
,mokutil
。
使用OpenSSL生成一对安全启动签名密钥,记得妥善保管。
sudo mkdir /etc/secureboot/keys
# Generate key pair
KEYPAIR_PATH='/etc/secureboot/keys'
sudo openssl req -newkey rsa:4096 -nodes -keyout "$KEYPAIR_PATH/MOK.key" -new -x509 -sha256 -days 3650 -subj "/CN=My Arch Linux Machine Owner Key/" -out "$KEYPAIR_PATH/MOK.crt"
sudo openssl x509 -outform DER -in "$KEYPAIR_PATH/MOK.crt" -out "$KEYPAIR_PATH/MOK.cer"
现在,我们来编写具有GRUB EFI生成和自动签名脚本。
之前有篇文章中的mok_sign
函数,我拆分到了其他目录中,方便复用。
/etc/secureboot/libs/mok_sign.sh
文件内容,注意修改签名密钥的路径:
mok_sign() {
KEYPAIR_PATH='/etc/secureboot/keys'
# sign if not already done so.
if ! /usr/bin/sbverify --list "$1" 2>/dev/null | /usr/bin/grep -q "signature certificates"; then
printf 'Signing %s...\n' "$1"
sudo sbsign --key "$KEYPAIR_PATH/MOK.key" --cert "$KEYPAIR_PATH/MOK.crt" --output "$1" "$1"
else
printf 'Skip sign: %s\n' "$1"
fi
}
现在,回到/etc/secureboot
这个文件夹,新建update-sb-grub-efi.sh
文件,内容如下(记得chmod +x
给予可执行权限):
#! /bin/bash
set -u
BASIC_MODULES="all_video boot btrfs cat chain configfile echo efifwsetup efinet ext2 fat
font gettext gfxmenu gfxterm gfxterm_background gzio halt help hfsplus iso9660 jpeg
keystatus loadenv loopback linux ls lsefi lsefimmap lsefisystab lssal memdisk minicmd
normal ntfs part_apple part_msdos part_gpt password_pbkdf2 png probe read reboot regexp
search search_fs_uuid search_fs_file search_label sleep smbios squash4 test true video videoinfo
xfs zfs zstd zfscrypt zfsinfo cpuid play tpm usb tar"
GRUB_MODULES="$BASIC_MODULES cryptodisk crypto gcry_arcfour gcry_blowfish gcry_camellia
gcry_cast5 gcry_crc gcry_des gcry_dsa gcry_idea gcry_md4 gcry_md5 gcry_rfc2268 gcry_rijndael
gcry_rmd160 gcry_rsa gcry_seed gcry_serpent gcry_sha1 gcry_sha256 gcry_sha512 gcry_tiger
gcry_twofish gcry_whirlpool luks lvm mdraid09 mdraid1x raid5rec raid6rec"
SCRIPT_PATH="$(dirname "$(realpath $0)")"
sudo grub-mkimage -c "$SCRIPT_PATH/grub-sb-stub/grub-pre.cfg" \
-o /boot/efi/EFI/arch/grubx64.efi -O x86_64-efi \
--sbat "$SCRIPT_PATH/grub-sbat.csv" \
-m "$SCRIPT_PATH/grub-sb-stub/memdisk.tar" \
$GRUB_MODULES
source "$(dirname "$0")/libs/mok_sign.sh"
mok_sign /boot/efi/EFI/arch/grubx64.efi
这份文件是根据Ubuntu官方编译脚本,并做了适当修改制作的(这个在ArchWiki中也有提及)。也许你已经发现了,我并没有直接使用grub-install
生成EFI文件,而是使用grub-mkimage
实现更加定制的配置。
复制/usr/share/grub/sbat.csv
到/etc/secureboot/grub-sbat.csv
,并可对文件做部分修改,以避免出现SBAT问题。
新建目录/etc/secureboot/grub-sb-stub
,我们接下来要在这里面放一些GRUB的配置数据。
GRUB MemDisk和预加载脚本
新建文件夹/etc/secureboot/grub-sb-stub/memdisk
,然后在里面新建fonts
文件夹。将你需要的字体的PF2文件(比如/usr/share/grub/unicode.pf2
)复制到fonts
文件夹中。
随后修改当前路径到/etc/secureboot/grub-sb-stub
,执行tar -cf memdisk.tar -C memdisk .
。该命令会创建一个memdisk,包含我们的字体文件数据,并给前面我们创建的签名脚本使用。
创建文件/etc/secureboot/grub-sb-stub/grub-pre.cfg
,根据前面的脚本的配置的设置,这个GRUB脚本文件将在GRUB启动时立刻执行。
insmod part_msdos
insmod part_gpt
insmod font
loadfont /fonts/unicode.pf2
search.fs_uuid a5c1a3cb-ff2c-43a8-83d7-9c8e8a13a33f root hd0,gpt2
set prefix=($root)/boot/grub
configfile grub.cfg
上面的配置首先加载相应的模块,读取memdisk中的字体数据(如果不考虑复杂的OpenGPG签名加载方式,这是目前安全启动下GRUB读取字体的最好办法),之后通过UUID搜索包含GRUB配置文件的分区,并立刻读取其中的grub.cfg内容。因此,你必须将search.fs_uuid
中的硬盘UUID换成你系统硬盘的真实UUID。
可以通过lsblk -fs
命令快速确认包含GRUB配置文件的分区UUID。
此外,如果你使用的是BTRFS,请注意set prefix
那一行的/boot/grub
,应该修改为/boot/grub
相对于BTRFS主分区的完整路径。比如我的Arch Linux实体机系统将系统文件放在名字为@
的子卷上,GRUB的/boot/grub
数据也在此子卷中,则需要改为set prefix=($root)/@/boot/grub
。
如果之后希望读取更多字体,只需要将相应的PF2文件复制到上面创建的memdisk中,并在grub-pre.cfg
中使用loadfont
命令加载,并重新生成GRUB EFI文件,即可正常显示对应字体。
完成上述操作之后,回到/etc/secureboot
文件夹,执行update-sb-grub-efi.sh
。不出意外的话你会看到下面两行输出,即代表没有问题:
$ sudo ./update-sb-grub-efi.sh
Signing /boot/efi/EFI/arch/grubx64.efi...
Signing Unsigned original image
内核签名
现在,我们要对内核进行签名。可以在ArchWiki的这个章节找到相应的说明,不过为了方便,这里也同样说明一下我的做法。
新建/etc/initcpio/post/kernel-sbsign
,内容如下,并同时使用chmod +x
给予可执行权限。
#!/usr/bin/env bash
kernel="$1"
[[ -n "$kernel" ]] || exit 0
# use already installed kernel if it exists
[[ ! -f "$KERNELDESTINATION" ]] || kernel="$KERNELDESTINATION"
keypairs=(/etc/secureboot/keys/MOK.key /etc/secureboot/keys/MOK.crt)
for (( i=0; i<${#keypairs[@]}; i+=2 )); do
key="${keypairs[$i]}" cert="${keypairs[(( i + 1 ))]}"
if ! sbverify --cert "$cert" "$kernel" &>/dev/null; then
sbsign --key "$key" --cert "$cert" --output "$kernel" "$kernel"
fi
done
之后,立刻使用pacman重新安装所有已经安装的内核,不仅可以给内核打上安全启动签名,还可以确认脚本的正确性。如果在重新安装内核时,确认有下面的输出,即算配置正确。
==> Initcpio image generation successful
==> Running post hooks
-> Running post hook: [kernel-sbsign]
Signing Unsigned original image
==> Post processing done
准备重启
在你的EFI分区下,放入之前创建的签名密钥的cer文件。我将其放入到/EFI/arch/keys/MOK.cer
中。
同时复制Shim相关的已签名EFI,并添加相关的引导项。
# 复制cer文件
sudo mkdir /boot/efi/EFI/arch/keys
sudo cp /etc/secureboot/keys/MOK.cer /boot/efi/EFI/arch/keys/
# 或使用mokutil进行签名导入
mokutil --import /etc/secureboot/keys/MOK.cer
# 复制mmx64.efi和shimx64.efi
sudo cp /usr/share/shim-signed/mmx64.efi /boot/efi/EFI/arch/
sudo cp /usr/share/shim-signed/shimx64.efi /boot/efi/EFI/arch/
# 添加Shim引导选项
# /dev/sda记得改为你的EFI分区所在硬盘对应的block文件
# --part后面的1记得改成EFI分区所在分区的位置(以1开始)
sudo efibootmgr --unicode --disk /dev/sda --part 1 --create --label "arch-shim" --loader /EFI/arch/shimx64.efi
一切完成之后,重启,进入UEFI配置选项,打开安全启动,并经由arch-shim
启动项启动GRUB。
在这个界面,选中我们复制的MOK.cer,并导入到Machine Owner Key列表中,重新启动,配置即可完成。
自动更新GRUB的EFI文件和配置数据
首先,准备一下update-grub
脚本。可以通过AUR包的形式安装(包名为update-grub
),也可以在/usr/local/bin
下新建一个。文件的内容可以参考这里。
在/etc/pacman.d/hooks
文件夹下(没有则新建),新建两个文件(pacman hooks)。
/etc/pacman.d/hooks/1-update-grub-efi.hook
,用于实时更新GRUB EFI文件:
[Trigger]
Operation=Install
Operation=Upgrade
Type=Package
Target=grub
[Action]
Description=Update GRUB UEFI binaries
When=PostTransaction
NeedsTargets
Exec=/bin/sh -c '/etc/secureboot/update-sb-grub-efi.sh'
/etc/pacman.d/hooks/999-update-grub-cfg.hook
,用于在适时的时候重新生成/boot/grub/grub.cfg
:
[Trigger]
Operation=Install
Operation=Upgrade
Operation=Remove
Type=Package
Target=grub
Target=linux
Target=linux-lts
Target=linux-zen
Target=linux-hardened
[Action]
Description=Update GRUB configuration file
When=PostTransaction
NeedsTargets
Exec=/bin/sh -c '/usr/bin/update-grub'
Depends=grub
记得将/usr/bin/update-grub
改为你的update-grub
的实际路径。
重新安装GRUB,看看是否有执行pacman hook,如果成功执行则配置成功。
操作演示视频
Coming soon...