From: Daan De Meyer Date: Fri, 18 Aug 2023 11:58:08 +0000 (+0200) Subject: Add grub EFI support X-Git-Tag: v16~52^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F1797%2Fhead;p=thirdparty%2Fmkosi.git Add grub EFI support Note that we only generate the necessary menu entries for the grub configuration to chainload into our generated UKIs, we do not yet install grub for EFI ourselves as this is a distribution specific mess that we still need to figure out. On Fedora, because the shim and grub2-efi packages install directly to /boot which we redirect to /efi, this is sufficient to boot with grub on EFI by simply installing the shim and grub2-efi packages. For other distributions, a post install or finalize script will be necessary that installs grub (and optionally shim) to the correct locations in the ESP. --- diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 0ed91837e..0c82ec4d8 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -581,6 +581,22 @@ def find_grub_prefix(state: MkosiState) -> Optional[str]: return "grub2" if "grub2" in os.fspath(path) else "grub" +def want_grub_efi(state: MkosiState) -> bool: + if state.config.bootable == ConfigFeature.disabled: + return False + + if state.config.bootloader != Bootloader.grub: + return False + + if not any((state.root / "efi").rglob("grub*.efi")): + if state.config.bootable == ConfigFeature.enabled: + die("A bootable EFI image with grub was requested but grub for EFI is not installed in /efi") + + return False + + return True + + def want_grub_bios(state: MkosiState, partitions: Sequence[Partition] = ()) -> bool: if state.config.bootable == ConfigFeature.disabled: return False @@ -637,9 +653,42 @@ def prepare_grub_config(state: MkosiState) -> Optional[Path]: with umask(~0o600), config.open("w") as f: f.write("set timeout=0\n") + # Signed EFI grub shipped by distributions reads its configuration from /EFI//grub.cfg in + # the ESP so let's put a shim there to redirect to the actual configuration file. + efi = state.root / "efi/EFI" / state.config.distribution.name / "grub.cfg" + with umask(~0o700): + efi.parent.mkdir(parents=True, exist_ok=True) + + # Read the actual config file from the root of the ESP. + efi.write_text(f"configfile /{prefix}/grub.cfg\n") + return config +def prepare_grub_efi(state: MkosiState) -> None: + if not want_grub_efi(state): + return + + config = prepare_grub_config(state) + assert config + + with config.open("a") as f: + f.write('if [ "${grub_platform}" == "efi" ]; then\n') + + for uki in (state.root / "efi/EFI/Linux").glob("*.efi"): + f.write( + textwrap.dedent( + f"""\ + menuentry "{uki.stem}" {{ + chainloader /{uki.relative_to(state.root / "efi")} + }} + """ + ) + ) + + f.write("fi\n") + + def prepare_grub_bios(state: MkosiState, partitions: Sequence[Partition]) -> None: if not want_grub_bios(state, partitions): return @@ -1744,6 +1793,7 @@ def build_image(args: MkosiArgs, config: MkosiConfig) -> None: partitions = make_image(state, skip=("esp", "xbootldr")) install_unified_kernel(state, partitions) + prepare_grub_efi(state) prepare_grub_bios(state, partitions) partitions = make_image(state) install_grub_bios(state, partitions) diff --git a/mkosi/config.py b/mkosi/config.py index 997734b4b..2cbbad51a 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -119,6 +119,7 @@ class Bootloader(StrEnum): none = enum.auto() uki = enum.auto() systemd_boot = enum.auto() + grub = enum.auto() class BiosBootloader(StrEnum): diff --git a/mkosi/resources/mkosi.md b/mkosi/resources/mkosi.md index 3b7f71cdb..1818da0b4 100644 --- a/mkosi/resources/mkosi.md +++ b/mkosi/resources/mkosi.md @@ -783,13 +783,29 @@ they should be specified with a boolean argument: either "1", "yes", or "true" t `Bootloader=`, `--bootloader=` -: Takes one of `none`, `systemd-boot` or `uki`. Defaults to +: Takes one of `none`, `systemd-boot`, `uki` or `grub`. Defaults to `systemd-boot`. If set to `none`, no EFI bootloader will be installed into the image. If set to `systemd-boot`, systemd-boot will be installed and for each installed kernel, a UKI will be generated and stored in `EFI/Linux` in the ESP. If set to `uki`, a single UKI will be generated for the latest installed kernel (the one with the highest - version) which is installed to `EFI/BOOT/BOOTX64.EFI` in the ESP. + version) which is installed to `EFI/BOOT/BOOTX64.EFI` in the ESP. If + set to `grub`, for each installed kernel, a UKI will be generated and + stored in `EFI/Linux` in the ESP. For each generated UKI, a menu entry + is appended to the grub configuration in `grub/grub.cfg` in the ESP + which chainloads into the UKI. A shim grub.cfg is also written to + `EFI//grub.cfg` in the ESP which loads `grub/grub.cfg` + in the ESP for compatibility with signed versions of grub which load + the grub configuration from this location. + +: Note that we do not yet install grub to the ESP when `Bootloader=` is + set to `grub`. This has to be done manually in a postinst or finalize + script. The grub EFI binary should be installed to + `/efi/EFI/BOOT/BOOTX64.EFI` (or similar depending on the architecture) + and should be configured to load its configuration from + `EFI//grub.cfg` in the ESP. Signed versions of grub + shipped by distributions will load their configuration from this + location by default. `BiosBootloader=`, `--bios-bootloader=`