Unverified Commit a587a6a3 authored by Will Fancher's avatar Will Fancher Committed by GitHub
Browse files

Merge pull request #285401 from sdht0/systemd-boot-xbootldr

 nixos/systemd-boot: Add support for an XBOOTLDR partition
parents 03197858 b470b443
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -17174,6 +17174,12 @@
      fingerprint = "E173 237A C782 296D 98F5  ADAC E13D FD4B 4712 7951";
    }];
  };
  sdht0 = {
    email = "nixpkgs@sdht.in";
    github = "sdht0";
    githubId = 867424;
    name = "Siddhartha Sahu";
  };
  sdier = {
    email = "scott@dier.name";
    matrix = "@sdier:matrix.org";
+4 −0
Original line number Diff line number Diff line
@@ -283,6 +283,10 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m

- Cinnamon has been updated to 6.0. Please beware that the [Wayland session](https://blog.linuxmint.com/?p=4591) is still experimental in this release.

- New `boot.loader.systemd-boot.xbootldrMountPoint` allows setting up a separate [XBOOTLDR partition](https://uapi-group.org/specifications/specs/boot_loader_specification/) to store boot files. Useful on systems with a small EFI System partition that cannot be easily repartitioned.

- `boot.loader.systemd-boot` will now verify that `efiSysMountPoint` (and `xbootldrMountPoint` if configured) are mounted partitions.

- `services.postgresql.extraPlugins` changed its type from just a list of packages to also a function that returns such a list.
  For example a config line like ``services.postgresql.extraPlugins = with pkgs.postgresql_11.pkgs; [ postgis ];`` is recommended to be changed to ``services.postgresql.extraPlugins = ps: with ps; [ postgis ];``;

+39 −16
Original line number Diff line number Diff line
@@ -56,6 +56,14 @@ This partition table type uses GPT and:
- creates an FAT32 ESP partition from 8MiB to specified `bootSize` parameter (256MiB by default), set it bootable ;
- creates an primary ext4 partition starting after the boot partition and extending to the full disk image

#### `efixbootldr`

This partition table type uses GPT and:

- creates an FAT32 ESP partition from 8MiB to 100MiB, set it bootable ;
- creates an FAT32 BOOT partition from 100MiB to specified `bootSize` parameter (256MiB by default), set `bls_boot` flag ;
- creates an primary ext4 partition starting after the boot partition and extending to the full disk image

#### `hybrid`

This partition table type uses GPT and:
@@ -111,19 +119,7 @@ To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$imag
  # When setting one of `user' or `group', the other needs to be set too.
  contents ? []

, # Type of partition table to use; either "legacy", "efi", or "none".
  # For "efi" images, the GPT partition table is used and a mandatory ESP
  #   partition of reasonable size is created in addition to the root partition.
  # For "legacy", the msdos partition table is used and a single large root
  #   partition is created.
  # For "legacy+gpt", the GPT partition table is used, a 1MiB no-fs partition for
  #   use by the bootloader is created, and a single large root partition is
  #   created.
  # For "hybrid", the GPT partition table is used and a mandatory ESP
  #   partition of reasonable size is created in addition to the root partition.
  #   Also a legacy MBR will be present.
  # For "none", no partition table is created. Enabling `installBootLoader`
  #   most likely fails as GRUB will probably refuse to install.
, # Type of partition table to use; described in the `Image Partitioning` section above.
  partitionTableType ? "legacy"

, # Whether to invoke `switch-to-configuration boot` during image creation
@@ -193,11 +189,11 @@ To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$imag
  additionalPaths ? []
}:

assert (lib.assertOneOf "partitionTableType" partitionTableType [ "legacy" "legacy+gpt" "efi" "hybrid" "none" ]);
assert (lib.assertOneOf "partitionTableType" partitionTableType [ "legacy" "legacy+gpt" "efi" "efixbootldr" "hybrid" "none" ]);
assert (lib.assertMsg (fsType == "ext4" && deterministic -> rootFSUID != null) "In deterministic mode with a ext4 partition, rootFSUID must be non-null, by default, it is equal to rootGPUID.");
  # We use -E offset=X below, which is only supported by e2fsprogs
assert (lib.assertMsg (partitionTableType != "none" -> fsType == "ext4") "to produce a partition table, we need to use -E offset flag which is support only for fsType = ext4");
assert (lib.assertMsg (touchEFIVars -> partitionTableType == "hybrid" || partitionTableType == "efi" || partitionTableType == "legacy+gpt") "EFI variables can be used only with a partition table of type: hybrid, efi or legacy+gpt.");
assert (lib.assertMsg (touchEFIVars -> partitionTableType == "hybrid" || partitionTableType == "efi" || partitionTableType == "efixbootldr" || partitionTableType == "legacy+gpt") "EFI variables can be used only with a partition table of type: hybrid, efi, efixbootldr, or legacy+gpt.");
  # If only Nix store image, then: contents must be empty, configFile must be unset, and we should no install bootloader.
assert (lib.assertMsg (onlyNixStore -> contents == [] && configFile == null && !installBootLoader) "In a only Nix store image, the contents must be empty, no configuration must be provided and no bootloader should be installed.");
# Either both or none of {user,group} need to be set
@@ -225,6 +221,7 @@ let format' = format; in let
    legacy = "1";
    "legacy+gpt" = "2";
    efi = "2";
    efixbootldr = "3";
    hybrid = "3";
  }.${partitionTableType};

@@ -266,6 +263,23 @@ let format' = format; in let
          $diskImage
      ''}
    '';
    efixbootldr = ''
      parted --script $diskImage -- \
        mklabel gpt \
        mkpart ESP fat32 8MiB 100MiB \
        set 1 boot on \
        mkpart BOOT fat32 100MiB ${bootSize} \
        set 2 bls_boot on \
        mkpart ROOT ext4 ${bootSize} -1
      ${optionalString deterministic ''
          sgdisk \
          --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
          --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC  \
          --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F  \
          --partition-guid=3:${rootGPUID} \
          $diskImage
      ''}
    '';
    hybrid = ''
      parted --script $diskImage -- \
        mklabel gpt \
@@ -436,7 +450,7 @@ let format' = format; in let
    diskImage=nixos.raw

    ${if diskSize == "auto" then ''
      ${if partitionTableType == "efi" || partitionTableType == "hybrid" then ''
      ${if partitionTableType == "efi" || partitionTableType == "efixbootldr" || partitionTableType == "hybrid" then ''
        # Add the GPT at the end
        gptSpace=$(( 512 * 34 * 1 ))
        # Normally we'd need to account for alignment and things, if bootSize
@@ -570,6 +584,15 @@ let format' = format; in let

        ${optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
      ''}
      ${optionalString (partitionTableType == "efixbootldr") ''
        mkdir -p /mnt/{boot,efi}
        mkfs.vfat -n ESP /dev/vda1
        mkfs.vfat -n BOOT /dev/vda2
        mount /dev/vda1 /mnt/efi
        mount /dev/vda2 /mnt/boot

        ${optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
      ''}

      # Install a configuration.nix
      mkdir -p /mnt/etc/nixos
+49 −20
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@ from dataclasses import dataclass

# These values will be replaced with actual values during the package build
EFI_SYS_MOUNT_POINT = "@efiSysMountPoint@"
BOOT_MOUNT_POINT = "@bootMountPoint@"
LOADER_CONF = f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf"  # Always stored on the ESP
NIXOS_DIR = "@nixosDir@"
TIMEOUT = "@timeout@"
EDITOR = "@editor@" == "1"
CONSOLE_MODE = "@consoleMode@"
@@ -28,6 +31,7 @@ CONFIGURATION_LIMIT = int("@configurationLimit@")
CAN_TOUCH_EFI_VARIABLES = "@canTouchEfiVariables@"
GRACEFUL = "@graceful@"
COPY_EXTRA_FILES = "@copyExtraFiles@"
CHECK_MOUNTPOINTS = "@checkMountpoints@"

@dataclass
class BootSpec:
@@ -87,7 +91,7 @@ def generation_conf_filename(profile: str | None, generation: int, specialisatio


def write_loader_conf(profile: str | None, generation: int, specialisation: str | None) -> None:
    with open(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf.tmp", 'w') as f:
    with open(f"{LOADER_CONF}.tmp", 'w') as f:
        if TIMEOUT != "":
            f.write(f"timeout {TIMEOUT}\n")
        f.write("default %s\n" % generation_conf_filename(profile, generation, specialisation))
@@ -96,7 +100,7 @@ def write_loader_conf(profile: str | None, generation: int, specialisation: str
        f.write(f"console-mode {CONSOLE_MODE}\n")
        f.flush()
        os.fsync(f.fileno())
    os.rename(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf.tmp", f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf")
    os.rename(f"{LOADER_CONF}.tmp", LOADER_CONF)


def get_bootspec(profile: str | None, generation: int) -> BootSpec:
@@ -126,9 +130,9 @@ def copy_from_file(file: str, dry_run: bool = False) -> str:
    store_file_path = os.path.realpath(file)
    suffix = os.path.basename(store_file_path)
    store_dir = os.path.basename(os.path.dirname(store_file_path))
    efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix)
    efi_file_path = f"{NIXOS_DIR}/{store_dir}-{suffix}.efi"
    if not dry_run:
        copy_if_not_exists(store_file_path, f"{EFI_SYS_MOUNT_POINT}%s" % (efi_file_path))
        copy_if_not_exists(store_file_path, f"{BOOT_MOUNT_POINT}{efi_file_path}")
    return efi_file_path

def write_entry(profile: str | None, generation: int, specialisation: str | None,
@@ -145,7 +149,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None

    try:
        if bootspec.initrdSecrets is not None:
            subprocess.check_call([bootspec.initrdSecrets, f"{EFI_SYS_MOUNT_POINT}%s" % (initrd)])
            subprocess.check_call([bootspec.initrdSecrets, f"{BOOT_MOUNT_POINT}%s" % (initrd)])
    except subprocess.CalledProcessError:
        if current:
            print("failed to create initrd secrets!", file=sys.stderr)
@@ -155,7 +159,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None
                  f'for "{title} - Configuration {generation}", an older generation', file=sys.stderr)
            print("note: this is normal after having removed "
                  "or renamed a file in `boot.initrd.secrets`", file=sys.stderr)
    entry_file = f"{EFI_SYS_MOUNT_POINT}/loader/entries/%s" % (
    entry_file = f"{BOOT_MOUNT_POINT}/loader/entries/%s" % (
        generation_conf_filename(profile, generation, specialisation))
    tmp_path = "%s.tmp" % (entry_file)
    kernel_params = "init=%s " % bootspec.init
@@ -202,14 +206,14 @@ def get_generations(profile: str | None = None) -> list[SystemIdentifier]:


def remove_old_entries(gens: list[SystemIdentifier]) -> None:
    rex_profile = re.compile(r"^" + re.escape(EFI_SYS_MOUNT_POINT) + "/loader/entries/nixos-(.*)-generation-.*\.conf$")
    rex_generation = re.compile(r"^" + re.escape(EFI_SYS_MOUNT_POINT) + "/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$")
    rex_profile = re.compile(r"^" + re.escape(BOOT_MOUNT_POINT) + "/loader/entries/nixos-(.*)-generation-.*\.conf$")
    rex_generation = re.compile(r"^" + re.escape(BOOT_MOUNT_POINT) + "/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$")
    known_paths = []
    for gen in gens:
        bootspec = get_bootspec(gen.profile, gen.generation)
        known_paths.append(copy_from_file(bootspec.kernel, True))
        known_paths.append(copy_from_file(bootspec.initrd, True))
    for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/loader/entries/nixos*-generation-[1-9]*.conf"):
    for path in glob.iglob(f"{BOOT_MOUNT_POINT}/loader/entries/nixos*-generation-[1-9]*.conf"):
        if rex_profile.match(path):
            prof = rex_profile.sub(r"\1", path)
        else:
@@ -220,11 +224,18 @@ def remove_old_entries(gens: list[SystemIdentifier]) -> None:
            continue
        if not (prof, gen_number, None) in gens:
            os.unlink(path)
    for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/*"):
    for path in glob.iglob(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/*"):
        if not path in known_paths and not os.path.isdir(path):
            os.unlink(path)


def cleanup_esp() -> None:
    for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/loader/entries/nixos*"):
        os.unlink(path)
    if os.path.isdir(f"{EFI_SYS_MOUNT_POINT}/{NIXOS_DIR}"):
        shutil.rmtree(f"{EFI_SYS_MOUNT_POINT}/{NIXOS_DIR}")


def get_profiles() -> list[str]:
    if os.path.isdir("/nix/var/nix/profiles/system-profiles/"):
        return [x
@@ -255,6 +266,9 @@ def install_bootloader(args: argparse.Namespace) -> None:
    # flags to pass to bootctl install/update
    bootctl_flags = []

    if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT:
        bootctl_flags.append(f"--boot-path={BOOT_MOUNT_POINT}")

    if CAN_TOUCH_EFI_VARIABLES != "1":
        bootctl_flags.append("--no-variables")

@@ -263,8 +277,8 @@ def install_bootloader(args: argparse.Namespace) -> None:

    if os.getenv("NIXOS_INSTALL_BOOTLOADER") == "1":
        # bootctl uses fopen() with modes "wxe" and fails if the file exists.
        if os.path.exists(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf"):
            os.unlink(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf")
        if os.path.exists(LOADER_CONF):
            os.unlink(LOADER_CONF)

        subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["install"])
    else:
@@ -291,13 +305,15 @@ def install_bootloader(args: argparse.Namespace) -> None:
            print("updating systemd-boot from %s to %s" % (installed_version, available_version))
            subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["update"])

    os.makedirs(f"{EFI_SYS_MOUNT_POINT}/efi/nixos", exist_ok=True)
    os.makedirs(f"{EFI_SYS_MOUNT_POINT}/loader/entries", exist_ok=True)
    os.makedirs(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}", exist_ok=True)
    os.makedirs(f"{BOOT_MOUNT_POINT}/loader/entries", exist_ok=True)

    gens = get_generations()
    for profile in get_profiles():
        gens += get_generations(profile)

    remove_old_entries(gens)

    for gen in gens:
        try:
            bootspec = get_bootspec(gen.profile, gen.generation)
@@ -315,9 +331,15 @@ def install_bootloader(args: argparse.Namespace) -> None:
            else:
                raise e

    for root, _, files in os.walk(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files", topdown=False):
        relative_root = root.removeprefix(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files").removeprefix("/")
        actual_root = os.path.join(f"{EFI_SYS_MOUNT_POINT}", relative_root)
    if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT:
        # Cleanup any entries in ESP if xbootldrMountPoint is set.
        # If the user later unsets xbootldrMountPoint, entries in XBOOTLDR will not be cleaned up
        # automatically, as we don't have information about the mount point anymore.
        cleanup_esp()

    for root, _, files in os.walk(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/.extra-files", topdown=False):
        relative_root = root.removeprefix(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/.extra-files").removeprefix("/")
        actual_root = os.path.join(f"{BOOT_MOUNT_POINT}", relative_root)

        for file in files:
            actual_file = os.path.join(actual_root, file)
@@ -330,7 +352,7 @@ def install_bootloader(args: argparse.Namespace) -> None:
            os.rmdir(actual_root)
        os.rmdir(root)

    os.makedirs(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files", exist_ok=True)
    os.makedirs(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/.extra-files", exist_ok=True)

    subprocess.check_call(COPY_EXTRA_FILES)

@@ -340,6 +362,8 @@ def main() -> None:
    parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help=f"The default {DISTRO_NAME} config to boot")
    args = parser.parse_args()

    subprocess.check_call(CHECK_MOUNTPOINTS)

    try:
        install_bootloader(args)
    finally:
@@ -347,7 +371,12 @@ def main() -> None:
        # it can leave the system in an unbootable state, when a crash/outage
        # happens shortly after an update. To decrease the likelihood of this
        # event sync the efi filesystem after each update.
        rc = libc.syncfs(os.open(f"{EFI_SYS_MOUNT_POINT}", os.O_RDONLY))
        rc = libc.syncfs(os.open(f"{BOOT_MOUNT_POINT}", os.O_RDONLY))
        if rc != 0:
            print(f"could not sync {BOOT_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr)

        if BOOT_MOUNT_POINT != EFI_SYS_MOUNT_POINT:
            rc = libc.syncfs(os.open(EFI_SYS_MOUNT_POINT, os.O_RDONLY))
            if rc != 0:
                print(f"could not sync {EFI_SYS_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr)

+50 −9
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@ let

  efi = config.boot.loader.efi;

  systemdBootBuilder = pkgs.substituteAll {
  systemdBootBuilder = pkgs.substituteAll rec {
    src = ./systemd-boot-builder.py;

    isExecutable = true;
@@ -28,23 +28,40 @@ let

    inherit (efi) efiSysMountPoint canTouchEfiVariables;

    bootMountPoint = if cfg.xbootldrMountPoint != null
      then cfg.xbootldrMountPoint
      else efi.efiSysMountPoint;

    nixosDir = "/EFI/nixos";

    inherit (config.system.nixos) distroName;

    memtest86 = optionalString cfg.memtest86.enable pkgs.memtest86plus;

    netbootxyz = optionalString cfg.netbootxyz.enable pkgs.netbootxyz-efi;

    checkMountpoints = pkgs.writeShellScript "check-mountpoints" ''
      fail() {
        echo "$1 = '$2' is not a mounted partition. Is the path configured correctly?" >&2
        exit 1
      }
      ${pkgs.util-linuxMinimal}/bin/findmnt ${efiSysMountPoint} > /dev/null || fail efiSysMountPoint ${efiSysMountPoint}
      ${lib.optionalString
        (cfg.xbootldrMountPoint != null)
        "${pkgs.util-linuxMinimal}/bin/findmnt ${cfg.xbootldrMountPoint} > /dev/null || fail xbootldrMountPoint ${cfg.xbootldrMountPoint}"}
    '';

    copyExtraFiles = pkgs.writeShellScript "copy-extra-files" ''
      empty_file=$(${pkgs.coreutils}/bin/mktemp)

      ${concatStrings (mapAttrsToList (n: v: ''
        ${pkgs.coreutils}/bin/install -Dp "${v}" "${efi.efiSysMountPoint}/"${escapeShellArg n}
        ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/"${escapeShellArg n}
        ${pkgs.coreutils}/bin/install -Dp "${v}" "${bootMountPoint}/"${escapeShellArg n}
        ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/"${escapeShellArg n}
      '') cfg.extraFiles)}

      ${concatStrings (mapAttrsToList (n: v: ''
        ${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${efi.efiSysMountPoint}/loader/entries/"${escapeShellArg n}
        ${pkgs.coreutils}/bin/install -D $empty_file "${efi.efiSysMountPoint}/efi/nixos/.extra-files/loader/entries/"${escapeShellArg n}
        ${pkgs.coreutils}/bin/install -Dp "${pkgs.writeText n v}" "${bootMountPoint}/loader/entries/"${escapeShellArg n}
        ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/loader/entries/"${escapeShellArg n}
      '') cfg.extraEntries)}
    '';
  };
@@ -99,6 +116,18 @@ in {
      '';
    };

    xbootldrMountPoint = mkOption {
      default = null;
      type = types.nullOr types.str;
      description = lib.mdDoc ''
        Where the XBOOTLDR partition is mounted.

        If set, this partition will be used as $BOOT to store boot loader entries and extra files
        instead of the EFI partition. As per the bootloader specification, it is recommended that
        the EFI and XBOOTLDR partitions be mounted at `/efi` and `/boot`, respectively.
      '';
    };

    configurationLimit = mkOption {
      default = null;
      example = 120;
@@ -108,7 +137,7 @@ in {
        Useful to prevent boot partition running out of disk space.

        `null` means no limit i.e. all generations
        that were not garbage collected yet.
        that have not been garbage collected yet.
      '';
    };

@@ -200,7 +229,7 @@ in {
      '';
      description = lib.mdDoc ''
        Any additional entries you want added to the `systemd-boot` menu.
        These entries will be copied to {file}`/boot/loader/entries`.
        These entries will be copied to {file}`$BOOT/loader/entries`.
        Each attribute name denotes the destination file name,
        and the corresponding attribute value is the contents of the entry.

@@ -217,9 +246,9 @@ in {
        { "efi/memtest86/memtest.efi" = "''${pkgs.memtest86plus}/memtest.efi"; }
      '';
      description = lib.mdDoc ''
        A set of files to be copied to {file}`/boot`.
        A set of files to be copied to {file}`$BOOT`.
        Each attribute name denotes the destination file name in
        {file}`/boot`, while the corresponding
        {file}`$BOOT`, while the corresponding
        attribute value specifies the source file.
      '';
    };
@@ -243,6 +272,18 @@ in {

  config = mkIf cfg.enable {
    assertions = [
      {
        assertion = (hasPrefix "/" efi.efiSysMountPoint);
        message = "The ESP mount point '${efi.efiSysMountPoint}' must be an absolute path";
      }
      {
        assertion = cfg.xbootldrMountPoint == null || (hasPrefix "/" cfg.xbootldrMountPoint);
        message = "The XBOOTLDR mount point '${cfg.xbootldrMountPoint}' must be an absolute path";
      }
      {
        assertion = cfg.xbootldrMountPoint != efi.efiSysMountPoint;
        message = "The XBOOTLDR mount point '${cfg.xbootldrMountPoint}' cannot be the same as the ESP mount point '${efi.efiSysMountPoint}'";
      }
      {
        assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub;
        message = "This kernel does not support the EFI boot stub";
Loading