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.
|
The derivation now installs "impl" headers selectively instead of by a wildcard.
|
||||||
Use `imgui.src` if you just want to access the unpacked sources.
|
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
|
- 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
|
- 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 {
|
sortKey = mkOption {
|
||||||
default = "nixos";
|
default = "nixos";
|
||||||
type = lib.types.str;
|
type = types.str;
|
||||||
description = ''
|
description = ''
|
||||||
The sort key used for the NixOS bootloader entries.
|
The sort key used for the NixOS bootloader entries.
|
||||||
This key determines sorting relative to non-NixOS entries.
|
This key determines sorting relative to non-NixOS entries.
|
||||||
@ -438,6 +438,87 @@ in
|
|||||||
Windows can unseal the encryption key.
|
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 {
|
config = mkIf cfg.enable {
|
||||||
@ -490,7 +571,13 @@ in
|
|||||||
assertion = !(hasInfix "nixos/.extra-files" (toLower filename));
|
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";
|
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;
|
boot.loader.grub.enable = mkDefault false;
|
||||||
|
|
||||||
@ -503,34 +590,44 @@ in
|
|||||||
(mkIf cfg.netbootxyz.enable {
|
(mkIf cfg.netbootxyz.enable {
|
||||||
"efi/netbootxyz/netboot.xyz.efi" = "${pkgs.netbootxyz-efi}";
|
"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";
|
${edk2ShellEspPath} = "${pkgs.edk2-uefi-shell}/shell.efi";
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
boot.loader.systemd-boot.extraEntries = mkMerge [
|
boot.loader.systemd-boot.extraEntries = mkMerge (
|
||||||
(mkIf cfg.memtest86.enable {
|
[
|
||||||
"memtest86.conf" = ''
|
(mkIf cfg.memtest86.enable {
|
||||||
title Memtest86+
|
"memtest86.conf" = ''
|
||||||
efi /efi/memtest86/memtest.efi
|
title Memtest86+
|
||||||
sort-key ${cfg.memtest86.sortKey}
|
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}
|
||||||
'';
|
'';
|
||||||
})
|
}) cfg.windows)
|
||||||
(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}
|
|
||||||
'';
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
boot.bootspec.extensions."org.nixos.systemd-boot" = {
|
boot.bootspec.extensions."org.nixos.systemd-boot" = {
|
||||||
inherit (config.boot.loader.systemd-boot) sortKey;
|
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 {
|
memtestSortKey = makeTest {
|
||||||
name = "systemd-boot-memtest-sortkey";
|
name = "systemd-boot-memtest-sortkey";
|
||||||
meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
|
meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
|
||||||
|
Loading…
Reference in New Issue
Block a user