diff --git a/nixos/modules/installer/tools/nixos-enter.sh b/nixos/modules/installer/tools/nixos-enter.sh
index 6469d9faa038..115b3d7a7c5e 100644
--- a/nixos/modules/installer/tools/nixos-enter.sh
+++ b/nixos/modules/installer/tools/nixos-enter.sh
@@ -104,4 +104,6 @@ chroot_add_resolv_conf "$mountPoint" || print "ERROR: failed to set up resolv.co
chroot "$mountPoint" systemd-tmpfiles --create --remove --exclude-prefix=/dev 1>&2 || true
)
+unset TMPDIR
+
exec chroot "$mountPoint" "${command[@]}"
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index e9697b5f0e64..2980b4ba6857 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -45,16 +45,6 @@ initrd {initrd}
options {kernel_params}
"""
-# The boot loader entry for memtest86.
-#
-# TODO: This is hard-coded to use the 64-bit EFI app, but it could probably
-# be updated to use the 32-bit EFI app on 32-bit systems. The 32-bit EFI
-# app filename is BOOTIA32.efi.
-MEMTEST_BOOT_ENTRY = """title MemTest86
-efi /efi/memtest86/BOOTX64.efi
-"""
-
-
def generation_conf_filename(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
pieces = [
"nixos",
@@ -283,23 +273,24 @@ def main() -> None:
except OSError as e:
print("ignoring profile '{}' in the list of boot entries because of the following error:\n{}".format(profile, e), file=sys.stderr)
- memtest_entry_file = "@efiSysMountPoint@/loader/entries/memtest86.conf"
- if os.path.exists(memtest_entry_file):
- os.unlink(memtest_entry_file)
- shutil.rmtree("@efiSysMountPoint@/efi/memtest86", ignore_errors=True)
- if "@memtest86@" != "":
- mkdir_p("@efiSysMountPoint@/efi/memtest86")
- for path in glob.iglob("@memtest86@/*"):
- if os.path.isdir(path):
- shutil.copytree(path, os.path.join("@efiSysMountPoint@/efi/memtest86", os.path.basename(path)))
- else:
- shutil.copy(path, "@efiSysMountPoint@/efi/memtest86/")
+ for root, _, files in os.walk('@efiSysMountPoint@/efi/nixos/.extra-files', topdown=False):
+ relative_root = root.removeprefix("@efiSysMountPoint@/efi/nixos/.extra-files").removeprefix("/")
+ actual_root = os.path.join("@efiSysMountPoint@", relative_root)
- memtest_entry_file = "@efiSysMountPoint@/loader/entries/memtest86.conf"
- memtest_entry_file_tmp_path = "%s.tmp" % memtest_entry_file
- with open(memtest_entry_file_tmp_path, 'w') as f:
- f.write(MEMTEST_BOOT_ENTRY)
- os.rename(memtest_entry_file_tmp_path, memtest_entry_file)
+ for file in files:
+ actual_file = os.path.join(actual_root, file)
+
+ if os.path.exists(actual_file):
+ os.unlink(actual_file)
+ os.unlink(os.path.join(root, file))
+
+ if not len(os.listdir(actual_root)):
+ os.rmdir(actual_root)
+ os.rmdir(root)
+
+ mkdir_p("@efiSysMountPoint@/efi/nixos/.extra-files")
+
+ subprocess.check_call("@copyExtraFiles@")
# Since fat32 provides little recovery facilities after a crash,
# it can leave the system in an unbootable state, when a crash/outage
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index 0f76d7d6b24a..c07567ec82ea 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -29,6 +29,22 @@ let
inherit (efi) efiSysMountPoint canTouchEfiVariables;
memtest86 = if cfg.memtest86.enable then pkgs.memtest86-efi else "";
+
+ netbootxyz = if cfg.netbootxyz.enable then pkgs.netbootxyz-efi else "";
+
+ copyExtraFiles = pkgs.writeShellScript "copy-extra-files" ''
+ empty_file=$(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}
+ '') 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}
+ '') cfg.extraEntries)}
+ '';
};
checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" {
@@ -125,6 +141,74 @@ in {
true.
'';
};
+
+ entryFilename = mkOption {
+ default = "memtest86.conf";
+ type = types.str;
+ description = ''
+ systemd-boot orders the menu entries by the config file names,
+ so if you want something to appear after all the NixOS entries,
+ it should start with o or onwards.
+ '';
+ };
+ };
+
+ netbootxyz = {
+ enable = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Make netboot.xyz available from the
+ systemd-boot menu. netboot.xyz
+ is a menu system that allows you to boot OS installers and
+ utilities over the network.
+ '';
+ };
+
+ entryFilename = mkOption {
+ default = "o_netbootxyz.conf";
+ type = types.str;
+ description = ''
+ systemd-boot orders the menu entries by the config file names,
+ so if you want something to appear after all the NixOS entries,
+ it should start with o or onwards.
+ '';
+ };
+ };
+
+ extraEntries = mkOption {
+ type = types.attrsOf types.lines;
+ default = {};
+ example = literalExpression ''
+ { "memtest86.conf" = '''
+ title MemTest86
+ efi /efi/memtest86/memtest86.efi
+ '''; }
+ '';
+ description = ''
+ Any additional entries you want added to the systemd-boot menu.
+ These entries will be copied to /boot/loader/entries.
+ Each attribute name denotes the destination file name,
+ and the corresponding attribute value is the contents of the entry.
+
+ systemd-boot orders the menu entries by the config file names,
+ so if you want something to appear after all the NixOS entries,
+ it should start with o or onwards.
+ '';
+ };
+
+ extraFiles = mkOption {
+ type = types.attrsOf types.path;
+ default = {};
+ example = literalExpression ''
+ { "efi/memtest86/memtest86.efi" = "''${pkgs.memtest86-efi}/BOOTX64.efi"; }
+ '';
+ description = ''
+ A set of files to be copied to /boot.
+ Each attribute name denotes the destination file name in
+ /boot, while the corresponding
+ attribute value specifies the source file.
+ '';
};
graceful = mkOption {
@@ -148,15 +232,64 @@ in {
assertions = [
{
assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub;
-
message = "This kernel does not support the EFI boot stub";
}
- ];
+ ] ++ concatMap (filename: [
+ {
+ assertion = !(hasInfix "/" filename);
+ message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries within folders are not supported";
+ }
+ {
+ assertion = hasSuffix ".conf" filename;
+ message = "boot.loader.systemd-boot.extraEntries.${lib.strings.escapeNixIdentifier filename} is invalid: entries must have a .conf file extension";
+ }
+ ]) (builtins.attrNames cfg.extraEntries)
+ ++ concatMap (filename: [
+ {
+ assertion = !(hasPrefix "/" filename);
+ message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: paths must not begin with a slash";
+ }
+ {
+ assertion = !(hasInfix ".." filename);
+ message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: paths must not reference the parent directory";
+ }
+ {
+ assertion = !(hasInfix "nixos/.extra-files" (toLower filename));
+ message = "boot.loader.systemd-boot.extraFiles.${lib.strings.escapeNixIdentifier filename} is invalid: files cannot be placed in the nixos/.extra-files directory";
+ }
+ ]) (builtins.attrNames cfg.extraFiles);
boot.loader.grub.enable = mkDefault false;
boot.loader.supportsInitrdSecrets = true;
+ boot.loader.systemd-boot.extraFiles = mkMerge [
+ # TODO: This is hard-coded to use the 64-bit EFI app, but it could probably
+ # be updated to use the 32-bit EFI app on 32-bit systems. The 32-bit EFI
+ # app filename is BOOTIA32.efi.
+ (mkIf cfg.memtest86.enable {
+ "efi/memtest86/BOOTX64.efi" = "${pkgs.memtest86-efi}/BOOTX64.efi";
+ })
+ (mkIf cfg.netbootxyz.enable {
+ "efi/netbootxyz/netboot.xyz.efi" = "${pkgs.netbootxyz-efi}";
+ })
+ ];
+
+ boot.loader.systemd-boot.extraEntries = mkMerge [
+ (mkIf cfg.memtest86.enable {
+ "${cfg.memtest86.entryFilename}" = ''
+ title MemTest86
+ efi /efi/memtest86/BOOTX64.efi
+ '';
+ })
+ (mkIf cfg.netbootxyz.enable {
+ "${cfg.netbootxyz.entryFilename}" = ''
+ title netboot.xyz
+ efi /efi/netbootxyz/netboot.xyz.efi
+ '';
+ })
+ ];
+
system = {
build.installBootLoader = checkedSystemdBootBuilder;
diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix
index c3899b58d6b3..51cfd82e6c4b 100644
--- a/nixos/tests/systemd-boot.nix
+++ b/nixos/tests/systemd-boot.nix
@@ -110,4 +110,145 @@ in
assert "updating systemd-boot from (000.0-1-notnixos) to " in output
'';
};
+
+ memtest86 = makeTest {
+ name = "systemd-boot-memtest86";
+ meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+
+ machine = { pkgs, lib, ... }: {
+ imports = [ common ];
+ boot.loader.systemd-boot.memtest86.enable = true;
+ nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
+ "memtest86-efi"
+ ];
+ };
+
+ testScript = ''
+ machine.succeed("test -e /boot/loader/entries/memtest86.conf")
+ machine.succeed("test -e /boot/efi/memtest86/BOOTX64.efi")
+ '';
+ };
+
+ netbootxyz = makeTest {
+ name = "systemd-boot-netbootxyz";
+ meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+
+ machine = { pkgs, lib, ... }: {
+ imports = [ common ];
+ boot.loader.systemd-boot.netbootxyz.enable = true;
+ };
+
+ testScript = ''
+ machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf")
+ machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
+ '';
+ };
+
+ entryFilename = makeTest {
+ name = "systemd-boot-entry-filename";
+ meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+
+ machine = { pkgs, lib, ... }: {
+ imports = [ common ];
+ boot.loader.systemd-boot.memtest86.enable = true;
+ boot.loader.systemd-boot.memtest86.entryFilename = "apple.conf";
+ nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
+ "memtest86-efi"
+ ];
+ };
+
+ testScript = ''
+ machine.fail("test -e /boot/loader/entries/memtest86.conf")
+ machine.succeed("test -e /boot/loader/entries/apple.conf")
+ machine.succeed("test -e /boot/efi/memtest86/BOOTX64.efi")
+ '';
+ };
+
+ extraEntries = makeTest {
+ name = "systemd-boot-extra-entries";
+ meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+
+ machine = { pkgs, lib, ... }: {
+ imports = [ common ];
+ boot.loader.systemd-boot.extraEntries = {
+ "banana.conf" = ''
+ title banana
+ '';
+ };
+ };
+
+ testScript = ''
+ machine.succeed("test -e /boot/loader/entries/banana.conf")
+ machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/banana.conf")
+ '';
+ };
+
+ extraFiles = makeTest {
+ name = "systemd-boot-extra-files";
+ meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+
+ machine = { pkgs, lib, ... }: {
+ imports = [ common ];
+ boot.loader.systemd-boot.extraFiles = {
+ "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
+ };
+ };
+
+ testScript = ''
+ machine.succeed("test -e /boot/efi/fruits/tomato.efi")
+ machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
+ '';
+ };
+
+ switch-test = makeTest {
+ name = "systemd-boot-switch-test";
+ meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+
+ nodes = {
+ inherit common;
+
+ machine = { pkgs, ... }: {
+ imports = [ common ];
+ boot.loader.systemd-boot.extraFiles = {
+ "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
+ };
+ };
+
+ with_netbootxyz = { pkgs, ... }: {
+ imports = [ common ];
+ boot.loader.systemd-boot.netbootxyz.enable = true;
+ };
+ };
+
+ testScript = { nodes, ... }: let
+ originalSystem = nodes.machine.config.system.build.toplevel;
+ baseSystem = nodes.common.config.system.build.toplevel;
+ finalSystem = nodes.with_netbootxyz.config.system.build.toplevel;
+ in ''
+ machine.succeed("test -e /boot/efi/fruits/tomato.efi")
+ machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
+
+ with subtest("remove files when no longer needed"):
+ machine.succeed("${baseSystem}/bin/switch-to-configuration boot")
+ machine.fail("test -e /boot/efi/fruits/tomato.efi")
+ machine.fail("test -d /boot/efi/fruits")
+ machine.succeed("test -d /boot/efi/nixos/.extra-files")
+ machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
+ machine.fail("test -d /boot/efi/nixos/.extra-files/efi/fruits")
+
+ with subtest("files are added back when needed again"):
+ machine.succeed("${originalSystem}/bin/switch-to-configuration boot")
+ machine.succeed("test -e /boot/efi/fruits/tomato.efi")
+ machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
+
+ with subtest("simultaneously removing and adding files works"):
+ machine.succeed("${finalSystem}/bin/switch-to-configuration boot")
+ machine.fail("test -e /boot/efi/fruits/tomato.efi")
+ machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
+ machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf")
+ machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
+ machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/o_netbootxyz.conf")
+ machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi")
+ '';
+ };
}
diff --git a/pkgs/tools/misc/netbootxyz-efi/default.nix b/pkgs/tools/misc/netbootxyz-efi/default.nix
new file mode 100644
index 000000000000..1394a8e745cb
--- /dev/null
+++ b/pkgs/tools/misc/netbootxyz-efi/default.nix
@@ -0,0 +1,21 @@
+{ lib
+, fetchurl
+}:
+
+let
+ pname = "netboot.xyz-efi";
+ version = "2.0.53";
+in fetchurl {
+ name = "${pname}-${version}";
+
+ url = "https://github.com/netbootxyz/netboot.xyz/releases/download/${version}/netboot.xyz.efi";
+ sha256 = "sha256-v7XqrhG94BLTpDHDazTiowQUXu/ITEcgVMmhlqgmSQE=";
+
+ meta = with lib; {
+ homepage = "https://netboot.xyz/";
+ description = "A tool to boot OS installers and utilities over the network, to be run from a bootloader";
+ license = licenses.asl20;
+ maintainers = with maintainers; [ Enzime ];
+ platforms = platforms.linux;
+ };
+}
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index e6355f8f2f1b..6e6b6586bfc3 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -7961,6 +7961,8 @@ with pkgs;
netboot = callPackage ../tools/networking/netboot {};
+ netbootxyz-efi = callPackage ../tools/misc/netbootxyz-efi { };
+
netcat = libressl.nc;
netcat-gnu = callPackage ../tools/networking/netcat { };