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
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/<distribution>/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
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)
`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/<distribution>/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/<distribution>/grub.cfg` in the ESP. Signed versions of grub
+ shipped by distributions will load their configuration from this
+ location by default.
`BiosBootloader=`, `--bios-bootloader=`