]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add grub for EFI support 2476/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Sun, 10 Mar 2024 21:15:39 +0000 (22:15 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 11 Mar 2024 09:06:34 +0000 (10:06 +0100)
We also rework the grub setup to not copy the grub modules into the
ESP anymore. We do this as grub for EFI booted in secure boot mode
does not load any unsigned modules for security reasons so we opt
to include all necessary modules into the grub image itself.

mkosi.conf.d/20-opensuse.conf
mkosi.conf.d/30-centos-fedora/mkosi.conf.d/20-uefi.conf
mkosi.conf.d/30-debian-ubuntu/mkosi.conf.d/20-x86-64.conf
mkosi/__init__.py
tests/test_boot.py

index f3d21cea109a47f23ea8bfe5a2e40831ee1d9f81..cfc37d9b112422c95579f4a5fe92961beda469d1 100644 (file)
@@ -22,6 +22,7 @@ Packages=
         erofs-utils
         grep
         grub2-i386-pc
+        grub2-x86_64-efi
         iproute
         iputils
         kernel-kvmsmall
index 7ce68d6473fcd59201464f6c8fa02cd655363dad..400758906a225422b96b4c91d973bf2ebedc3718 100644 (file)
@@ -10,3 +10,4 @@ Packages=
         pesign
         edk2-ovmf
         shim
+        grub2-efi-x64-modules
index 57c275e66fd38328853067add544ab70a804145f..48b054758ab0fc97ecfb8817e8de37a5332380be 100644 (file)
@@ -6,5 +6,6 @@ Architecture=x86-64
 [Content]
 Packages=
         amd64-microcode
-        grub-pc
+        grub-pc-bin
+        grub-efi-amd64
         intel-microcode
index 64d675da588648965d117b105bec12ad8abebff3..0c010d8af7ba1a1582a0f8c3a4c74c8787fd2c25 100644 (file)
@@ -1136,9 +1136,9 @@ def install_shim(context: Context) -> None:
     find_and_install_shim_binary(context, "mok", signed, unsigned, dst.parent)
 
 
-def find_grub_bios_directory(context: Context) -> Optional[Path]:
-    for d in ("usr/lib/grub/i386-pc", "usr/share/grub2/i386-pc"):
-        if (p := context.root / d).exists() and any(p.iterdir()):
+def find_grub_directory(context: Context, *, target: str) -> Optional[Path]:
+    for d in ("usr/lib/grub", "usr/share/grub2"):
+        if (p := context.root / d / target).exists() and any(p.iterdir()):
             return p
 
     return None
@@ -1156,11 +1156,9 @@ def want_grub_efi(context: Context) -> bool:
     if context.config.bootloader != Bootloader.grub:
         return False
 
-    if not any((context.root / "efi").rglob("grub*.efi")):
-        if context.config.bootable == ConfigFeature.enabled:
-            die("A bootable EFI image with grub was requested but grub for EFI is not installed in /efi")
-
-        return False
+    have = find_grub_directory(context, target="x86_64-efi") is not None
+    if not have and context.config.bootable == ConfigFeature.enabled:
+        die("An EFI bootable image with grub was requested but grub for EFI is not installed")
 
     return True
 
@@ -1178,7 +1176,7 @@ def want_grub_bios(context: Context, partitions: Sequence[Partition] = ()) -> bo
     if context.config.overlay:
         return False
 
-    have = find_grub_bios_directory(context) is not None
+    have = find_grub_directory(context, target="i386-pc") is not None
     if not have and context.config.bootable == ConfigFeature.enabled:
         die("A BIOS bootable image with grub was requested but grub for BIOS is not installed")
 
@@ -1232,24 +1230,17 @@ def prepare_grub_config(context: Context) -> Optional[Path]:
     return config
 
 
-def grub_bios_install(context: Context, partitions: Sequence[Partition]) -> None:
-    if not want_grub_bios(context, partitions):
-        return
-
-    # grub-install insists on opening the root partition device to probe it's filesystem which requires root
-    # so we're forced to reimplement its functionality. Luckily that's pretty simple, run grub-mkimage to
-    # generate the required core.img and copy the relevant files to the ESP.
-
+def grub_mkimage(context: Context, *, target: str, modules: Sequence[str] = (), output: Optional[Path] = None) -> None:
     mkimage = find_grub_binary("mkimage", root=context.config.tools())
     assert mkimage
 
-    directory = find_grub_bios_directory(context)
+    directory = find_grub_directory(context, target=target)
     assert directory
 
-    dst = context.root / "efi" / context.config.distribution.grub_prefix() / "i386-pc"
-    dst.mkdir(parents=True, exist_ok=True)
-
-    with tempfile.NamedTemporaryFile("w", prefix="grub-early-config") as earlyconfig:
+    with (
+        complete_step(f"Generating grub image for {target}"),
+        tempfile.NamedTemporaryFile("w", prefix="grub-early-config") as earlyconfig
+    ):
         earlyconfig.write(
             textwrap.dedent(
                 f"""\
@@ -1267,15 +1258,16 @@ def grub_bios_install(context: Context, partitions: Sequence[Partition]) -> None
                 "--directory", directory,
                 "--config", earlyconfig.name,
                 "--prefix", f"/{context.config.distribution.grub_prefix()}",
-                "--output", dst / "core.img",
-                "--format", "i386-pc",
+                "--output", output or (directory / "core.img"),
+                "--format", target,
                 *(["--verbose"] if ARG_DEBUG.get() else []),
-                # Modules required to find and read from the XBOOTLDR partition which has all the other modules.
                 "fat",
                 "part_gpt",
-                "biosdisk",
                 "search",
                 "search_fs_file",
+                "normal",
+                "linux",
+                *modules,
             ],
             sandbox=context.sandbox(
                 options=[
@@ -1285,18 +1277,30 @@ def grub_bios_install(context: Context, partitions: Sequence[Partition]) -> None
             ),
         )
 
-    for p in directory.glob("*.mod"):
-        shutil.copy2(p, dst)
 
-    for p in directory.glob("*.lst"):
-        shutil.copy2(p, dst)
+def install_grub(context: Context) -> None:
+    if not want_grub_bios(context) and not want_grub_efi(context):
+        return
 
-    shutil.copy2(directory / "modinfo.sh", dst)
-    shutil.copy2(directory / "boot.img", dst)
+    if want_grub_bios(context):
+        grub_mkimage(context, target="i386-pc", modules=("biosdisk",))
+
+    if want_grub_efi(context):
+        if context.config.shim_bootloader != ShimBootloader.none:
+            output = context.root / shim_second_stage_binary(context)
+        else:
+            output = context.root / efi_boot_binary(context)
+
+        with umask(~0o700):
+            output.parent.mkdir(parents=True, exist_ok=True)
+
+        grub_mkimage(context, target="x86_64-efi", output=output, modules=("chain",))
+        if context.config.secure_boot:
+            sign_efi_binary(context, output, output)
 
     dst = context.root / "efi" / context.config.distribution.grub_prefix() / "fonts"
     with umask(~0o700):
-        dst.mkdir(exist_ok=True)
+        dst.mkdir(parents=True, exist_ok=True)
 
     for d in ("grub", "grub2"):
         unicode = context.root / "usr/share" / d / "unicode.pf2"
@@ -1311,8 +1315,11 @@ def grub_bios_setup(context: Context, partitions: Sequence[Partition]) -> None:
     setup = find_grub_binary("bios-setup", root=context.config.tools())
     assert setup
 
+    directory = find_grub_directory(context, target="i386-pc")
+    assert directory
+
     with (
-        complete_step("Installing grub boot loader…"),
+        complete_step("Installing grub boot loader for BIOS…"),
         tempfile.NamedTemporaryFile(mode="w") as mountinfo,
     ):
         # grub-bios-setup insists on being able to open the root device that --directory is located on, which
@@ -1329,7 +1336,7 @@ def grub_bios_setup(context: Context, partitions: Sequence[Partition]) -> None:
             [
                 "sh", "-c", f"mount --bind {mountinfo.name} /proc/$$/mountinfo && exec $0 \"$@\"",
                 setup,
-                "--directory", context.root / "efi" / context.config.distribution.grub_prefix() / "i386-pc",
+                "--directory", directory,
                 *(["--verbose"] if ARG_DEBUG.get() else []),
                 context.staging / context.config.output_with_format,
             ],
@@ -3319,6 +3326,7 @@ def build_image(context: Context) -> None:
         configure_clock(context)
 
         install_systemd_boot(context)
+        install_grub(context)
         install_shim(context)
         run_sysusers(context)
         run_tmpfiles(context)
@@ -3344,7 +3352,6 @@ def build_image(context: Context) -> None:
     normalize_mtime(context.root, context.config.source_date_epoch)
     partitions = make_disk(context, skip=("esp", "xbootldr"), tabs=True, msg="Generating disk image")
     install_kernel(context, partitions)
-    grub_bios_install(context, partitions)
     normalize_mtime(context.root, context.config.source_date_epoch, directory=Path("boot"))
     normalize_mtime(context.root, context.config.source_date_epoch, directory=Path("efi"))
     partitions = make_disk(context, msg="Formatting ESP/XBOOTLDR partitions")
index 0ec42b7c70241aa679b816990dd2c3bc40e311e4..21614a12c2f55b06b12e9760248387dd2982f432 100644 (file)
@@ -5,7 +5,7 @@ import subprocess
 
 import pytest
 
-from mkosi.config import OutputFormat
+from mkosi.config import Bootloader, OutputFormat, QemuFirmware
 from mkosi.distributions import Distribution
 from mkosi.qemu import find_virtiofsd
 from mkosi.run import find_binary, run
@@ -25,7 +25,7 @@ def have_vmspawn() -> bool:
 
 
 @pytest.mark.parametrize("format", OutputFormat)
-def test_boot(config: Image.Config, format: OutputFormat) -> None:
+def test_format(config: Image.Config, format: OutputFormat) -> None:
     with Image(
         config,
         options=[
@@ -76,3 +76,27 @@ def test_boot(config: Image.Config, format: OutputFormat) -> None:
             return
 
         image.qemu(options=options + ["--qemu-firmware=bios"])
+
+
+@pytest.mark.parametrize("bootloader", Bootloader)
+def test_bootloader(config: Image.Config, bootloader: Bootloader) -> None:
+    if config.distribution == Distribution.rhel_ubi:
+        return
+
+    firmware = QemuFirmware.linux if bootloader == Bootloader.none else QemuFirmware.uefi
+
+    with Image(
+        config,
+        options=[
+            "--kernel-command-line=systemd.unit=mkosi-check-and-shutdown.service",
+            "--incremental",
+            "--ephemeral",
+            "--format=disk",
+            "--bootloader", str(bootloader),
+            "--qemu-firmware", str(firmware)
+        ],
+    ) as image:
+        image.summary()
+        image.genkey()
+        image.build()
+        image.qemu()