nixos/systemd-boot: add windows option for easy dual-booting
When installing NixOS on a machine with Windows, the "easiest" solution to dual-boot is re-using the existing EFI System Partition (ESP), which allows systemd-boot to detect Windows automatically. However, if there are multiple ESPs, maybe even on multiple disks, systemd-boot is unable to detect the other OSes, and you either have to use Grub and os-prober, or do a tedious manual configuration as described in the wiki: https://wiki.nixos.org/w/index.php?title=Dual_Booting_NixOS_and_Windows&redirect=no#EFI_with_multiple_disks This commit automates and documents this properly so only a single line like boot.loader.systemd-boot.windows."10".efiDeviceHandle = "HD0c2"; is required. In the future, we might want to try automatically detecting this during installation, but finding the correct device handle while the kernel is running is tricky.
This commit is contained in:
parent
f2e5b04c4e
commit
73011ba96f
@ -522,6 +522,8 @@
|
||||
The derivation now installs "impl" headers selectively instead of by a wildcard.
|
||||
Use `imgui.src` if you just want to access the unpacked sources.
|
||||
|
||||
- The new `boot.loader.systemd-boot.windows` option makes setting up dual-booting with Windows on a different drive easier
|
||||
|
||||
- Linux 4.19 has been removed because it will reach its end of life within the lifespan of 24.11
|
||||
|
||||
- Unprivileged access to the kernel syslog via `dmesg` is now restricted by default. Users wanting to keep an
|
||||
|
@ -182,7 +182,7 @@ in
|
||||
|
||||
sortKey = mkOption {
|
||||
default = "nixos";
|
||||
type = lib.types.str;
|
||||
type = types.str;
|
||||
description = ''
|
||||
The sort key used for the NixOS bootloader entries.
|
||||
This key determines sorting relative to non-NixOS entries.
|
||||
@ -438,6 +438,87 @@ in
|
||||
Windows can unseal the encryption key.
|
||||
'';
|
||||
};
|
||||
|
||||
windows = mkOption {
|
||||
default = { };
|
||||
description = ''
|
||||
Make Windows bootable from systemd-boot. This option is not necessary when Windows and
|
||||
NixOS use the same EFI System Partition (ESP). In that case, Windows will automatically be
|
||||
detected by systemd-boot.
|
||||
|
||||
However, if Windows is installed on a separate drive or ESP, you can use this option to add
|
||||
a menu entry for each installation manually.
|
||||
|
||||
The attribute name is used for the title of the menu entry and internal file names.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
"10".efiDeviceHandle = "HD0c3";
|
||||
"11-ame" = {
|
||||
title = "Windows 11 Ameliorated Edition";
|
||||
efiDeviceHandle = "HD0b1";
|
||||
};
|
||||
"11-home" = {
|
||||
title = "Windows 11 Home";
|
||||
efiDeviceHandle = "FS1";
|
||||
sortKey = "z_windows";
|
||||
};
|
||||
}
|
||||
'';
|
||||
type = types.attrsOf (
|
||||
types.submodule (
|
||||
{ name, ... }:
|
||||
{
|
||||
options = {
|
||||
efiDeviceHandle = mkOption {
|
||||
type = types.str;
|
||||
example = "HD1b3";
|
||||
description = ''
|
||||
The device handle of the EFI System Partition (ESP) where the Windows bootloader is
|
||||
located. This is the device handle that the EDK2 UEFI Shell uses to load the
|
||||
bootloader.
|
||||
|
||||
To find this handle, follow these steps:
|
||||
1. Set {option}`boot.loader.systemd-boot.edk2-uefi-shell.enable` to `true`
|
||||
2. Run `nixos-rebuild boot`
|
||||
3. Reboot and select "EDK2 UEFI Shell" from the systemd-boot menu
|
||||
4. Run `map -c` to list all consistent device handles
|
||||
5. For each device handle (for example, `HD0c1`), run `ls HD0c1:\EFI`
|
||||
6. If the output contains the directory `Microsoft`, you might have found the correct device handle
|
||||
7. Run `HD0c1:\EFI\Microsoft\Boot\Bootmgfw.efi` to check if Windows boots correctly
|
||||
8. If it does, this device handle is the one you need (in this example, `HD0c1`)
|
||||
|
||||
This option is required, there is no useful default.
|
||||
'';
|
||||
};
|
||||
|
||||
title = mkOption {
|
||||
type = types.str;
|
||||
example = "Michaelsoft Binbows";
|
||||
default = "Windows ${name}";
|
||||
defaultText = ''attribute name of this entry, prefixed with "Windows "'';
|
||||
description = ''
|
||||
The title of the boot menu entry.
|
||||
'';
|
||||
};
|
||||
|
||||
sortKey = mkOption {
|
||||
type = types.str;
|
||||
default = "o_windows_${name}";
|
||||
defaultText = ''attribute name of this entry, prefixed with "o_windows_"'';
|
||||
description = ''
|
||||
`systemd-boot` orders the menu entries by their sort keys,
|
||||
so if you want something to appear after all the NixOS entries,
|
||||
it should start with {file}`o` or onwards.
|
||||
|
||||
See also {option}`boot.loader.systemd-boot.sortKey`..
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
@ -490,7 +571,13 @@ in
|
||||
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);
|
||||
]) (builtins.attrNames cfg.extraFiles)
|
||||
++ concatMap (winVersion: [
|
||||
{
|
||||
assertion = lib.match "^[-_0-9A-Za-z]+$" winVersion != null;
|
||||
message = "boot.loader.systemd-boot.windows.${winVersion} is invalid: key must only contain alphanumeric characters, hyphens, and underscores";
|
||||
}
|
||||
]) (builtins.attrNames cfg.windows);
|
||||
|
||||
boot.loader.grub.enable = mkDefault false;
|
||||
|
||||
@ -503,34 +590,44 @@ in
|
||||
(mkIf cfg.netbootxyz.enable {
|
||||
"efi/netbootxyz/netboot.xyz.efi" = "${pkgs.netbootxyz-efi}";
|
||||
})
|
||||
(mkIf cfg.edk2-uefi-shell.enable {
|
||||
(mkIf (cfg.edk2-uefi-shell.enable || cfg.windows != { }) {
|
||||
${edk2ShellEspPath} = "${pkgs.edk2-uefi-shell}/shell.efi";
|
||||
})
|
||||
];
|
||||
|
||||
boot.loader.systemd-boot.extraEntries = mkMerge [
|
||||
(mkIf cfg.memtest86.enable {
|
||||
"memtest86.conf" = ''
|
||||
title Memtest86+
|
||||
efi /efi/memtest86/memtest.efi
|
||||
sort-key ${cfg.memtest86.sortKey}
|
||||
boot.loader.systemd-boot.extraEntries = mkMerge (
|
||||
[
|
||||
(mkIf cfg.memtest86.enable {
|
||||
"memtest86.conf" = ''
|
||||
title Memtest86+
|
||||
efi /efi/memtest86/memtest.efi
|
||||
sort-key ${cfg.memtest86.sortKey}
|
||||
'';
|
||||
})
|
||||
(mkIf cfg.netbootxyz.enable {
|
||||
"netbootxyz.conf" = ''
|
||||
title netboot.xyz
|
||||
efi /efi/netbootxyz/netboot.xyz.efi
|
||||
sort-key ${cfg.netbootxyz.sortKey}
|
||||
'';
|
||||
})
|
||||
(mkIf cfg.edk2-uefi-shell.enable {
|
||||
"edk2-uefi-shell.conf" = ''
|
||||
title EDK2 UEFI Shell
|
||||
efi /${edk2ShellEspPath}
|
||||
sort-key ${cfg.edk2-uefi-shell.sortKey}
|
||||
'';
|
||||
})
|
||||
]
|
||||
++ (mapAttrsToList (winVersion: cfg: {
|
||||
"windows_${winVersion}.conf" = ''
|
||||
title ${cfg.title}
|
||||
efi /${edk2ShellEspPath}
|
||||
options -nointerrupt -nomap -noversion ${cfg.efiDeviceHandle}:EFI\Microsoft\Boot\Bootmgfw.efi
|
||||
sort-key ${cfg.sortKey}
|
||||
'';
|
||||
})
|
||||
(mkIf cfg.netbootxyz.enable {
|
||||
"netbootxyz.conf" = ''
|
||||
title netboot.xyz
|
||||
efi /efi/netbootxyz/netboot.xyz.efi
|
||||
sort-key ${cfg.netbootxyz.sortKey}
|
||||
'';
|
||||
})
|
||||
(mkIf cfg.edk2-uefi-shell.enable {
|
||||
"edk2-uefi-shell.conf" = ''
|
||||
title EDK2 UEFI Shell
|
||||
efi /${edk2ShellEspPath}
|
||||
sort-key ${cfg.edk2-uefi-shell.sortKey}
|
||||
'';
|
||||
})
|
||||
];
|
||||
}) cfg.windows)
|
||||
);
|
||||
|
||||
boot.bootspec.extensions."org.nixos.systemd-boot" = {
|
||||
inherit (config.boot.loader.systemd-boot) sortKey;
|
||||
|
@ -354,6 +354,51 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
windows = makeTest {
|
||||
name = "systemd-boot-windows";
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ iFreilicht ];
|
||||
|
||||
nodes.machine = { ... }: {
|
||||
imports = [ common ];
|
||||
boot.loader.systemd-boot.windows = {
|
||||
"7" = {
|
||||
efiDeviceHandle = "HD0c1";
|
||||
sortKey = "before_all_others";
|
||||
};
|
||||
"Ten".efiDeviceHandle = "FS0";
|
||||
"11" = {
|
||||
title = "Title with-_-punctuation ...?!";
|
||||
efiDeviceHandle = "HD0d4";
|
||||
sortKey = "zzz";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.succeed("test -e /boot/efi/edk2-uefi-shell/shell.efi")
|
||||
|
||||
machine.succeed("test -e /boot/loader/entries/windows_7.conf")
|
||||
machine.succeed("test -e /boot/loader/entries/windows_Ten.conf")
|
||||
machine.succeed("test -e /boot/loader/entries/windows_11.conf")
|
||||
|
||||
machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_7.conf")
|
||||
machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_Ten.conf")
|
||||
machine.succeed("grep 'efi /efi/edk2-uefi-shell/shell.efi' /boot/loader/entries/windows_11.conf")
|
||||
|
||||
machine.succeed("grep 'HD0c1:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_7.conf")
|
||||
machine.succeed("grep 'FS0:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_Ten.conf")
|
||||
machine.succeed("grep 'HD0d4:EFI\\\\Microsoft\\\\Boot\\\\Bootmgfw.efi' /boot/loader/entries/windows_11.conf")
|
||||
|
||||
machine.succeed("grep 'sort-key before_all_others' /boot/loader/entries/windows_7.conf")
|
||||
machine.succeed("grep 'sort-key o_windows_Ten' /boot/loader/entries/windows_Ten.conf")
|
||||
machine.succeed("grep 'sort-key zzz' /boot/loader/entries/windows_11.conf")
|
||||
|
||||
machine.succeed("grep 'title Windows 7' /boot/loader/entries/windows_7.conf")
|
||||
machine.succeed("grep 'title Windows Ten' /boot/loader/entries/windows_Ten.conf")
|
||||
machine.succeed('grep "title Title with-_-punctuation ...?!" /boot/loader/entries/windows_11.conf')
|
||||
'';
|
||||
};
|
||||
|
||||
memtestSortKey = makeTest {
|
||||
name = "systemd-boot-memtest-sortkey";
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
|
||||
|
Loading…
Reference in New Issue
Block a user