From b7e6418fa6cf1b0a5b9a1659e4e81586601075fb Mon Sep 17 00:00:00 2001 From: ahmza Date: Thu, 6 Aug 2026 17:08:30 +0100 Subject: [PATCH] chainload the windows boot manager from grub on shadow windows sits on the 476g nvme - the disk with three partitions: a 16m "microsoft reserved" msr with no filesystem, the 476g ntfs c: volume, and a 832m winre. it has no efi system partition of its own, because it was installed against the esp that nixos now owns (fs-uuid 5873-AD05). both oses therefore boot from one shared esp; there is no separate windows boot drive. the nixos install reformatted that esp, which wiped EFI/Microsoft/Boot/{bootmgfw.efi,BCD} and left windows unbootable. that has since been repaired from winre, so the entry is live. the bcdboot command is kept in the comment for the next time the esp is reformatted - it has to run from windows media or winre because the bcd store cannot be generated from linux, and copying bootmgfw.efi alone fails 0xc000000f. chainloaded explicitly rather than via grub.useOSProber: deterministic, pulls in no extra tooling, and avoids probing every disk on each bootloader install. the -f guard makes the entry self-disabling, so a future esp reformat degrades to a missing menu item rather than one that boots into an error. because grub evaluates it at boot rather than at nix eval time, repairing windows needed no rebuild. searching by fs-uuid rather than (hdX,gptN) is deliberate: nvme enumeration is not stable on this board, and /boot has been seen as both nvme0n1p1 and nvme1n1p1. the -n "$esp" test is a separate nested if rather than `[ -n "$esp" -a -f ... ]` because grub's test builtin evaluates both operands of -a instead of short-circuiting, so the combined form would still expand the path test against an unset device. --- modules/hosts/shadow/shadowConfiguration.nix | 87 ++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/modules/hosts/shadow/shadowConfiguration.nix b/modules/hosts/shadow/shadowConfiguration.nix index f976942..f57c85c 100644 --- a/modules/hosts/shadow/shadowConfiguration.nix +++ b/modules/hosts/shadow/shadowConfiguration.nix @@ -7,6 +7,93 @@ networking = { hostName = "shadow"; }; + + # Windows lives on the 476G NVMe -- the one with three partitions: 16M + # "Microsoft reserved" MSR, 476G NTFS (fs-uuid 70E4E841E4E80AE4, the C: + # volume), 832M WinRE. Identified by layout rather than by nvmeXnY on + # purpose; see the enumeration note below. + # + # That disk has NO EFI System Partition of its own -- its 16M partition is a + # "Microsoft reserved" MSR with no filesystem. Windows was installed against + # the ESP that NixOS now owns: the 1G vfat partition fs-uuid 5873-AD05 on the + # 1.9T NVMe (that disk holds only two partitions, the ESP and the ext4 root + # deff63fc-1738-4cfc-a79c-06e58c78471a). So both OSes boot from one shared + # ESP; there is no separate Windows boot drive. + # + # The NixOS install reformatted that ESP, which wiped + # EFI/Microsoft/Boot/{bootmgfw.efi,BCD} and left Windows unbootable. That has + # since been repaired from WinRE and both files are back in place, so the + # menuentry below is live. Keep the recovery command to hand in case the ESP + # is ever reformatted again -- it has to be run from Windows install media or + # WinRE, because the BCD store cannot be generated from Linux: + # + # bcdboot C:\Windows /s S: /f UEFI (S: = the ESP, fs-uuid 5873-AD05) + # + # Copying bootmgfw.efi alone is not enough; without BCD it fails 0xc000000f. + # Note bcdboot also drops a copy of the Windows boot manager at + # EFI/BOOT/bootx64.efi -- see the fallback-path note in core/boot.nix. + # + # Chainloaded explicitly rather than via grub.useOSProber: deterministic, + # pulls in no extra tooling, and avoids probing every disk on each bootloader + # install. The `-f` guard makes the entry self-disabling: it renders only + # while bootmgfw.efi is actually present, so a future ESP reformat degrades + # to a missing menu item rather than one that boots into an error. + # + # fs-uuid is shadow's ESP, matching the "/boot" entry in disks.nix. Searching + # by UUID rather than (hdX,gptN) matters here: NVMe enumeration is not stable + # on this board -- /boot has been seen as both nvme0n1p1 and nvme1n1p1. For + # the same reason, re-derive device names before running anything + # destructive: + # + # lsblk -o NAME,SIZE,FSTYPE,PARTTYPENAME,UUID,MOUNTPOINT + # + # Windows' boot files now occupy ~30-60MiB of that shared 1G ESP and Windows + # Update rewrites them periodically, which is part of why + # grub.configurationLimit is held at 10 in core/boot.nix. + # + # Before dual-booting, turn off Windows Fast Startup. hiberfil.sys is present + # on the 476G NTFS volume, and Fast Startup plus this host's hibernation + # setup will corrupt any NTFS volume mounted read-write from both sides. + # + # On the GRUB script itself: + # + # The outer `search` + `-f` test run at *boot*, while GRUB parses grub.cfg — + # not at Nix eval time. That is why repairing Windows needed no rebuild: the + # entry started rendering as soon as bcdboot put bootmgfw.efi back, and it + # will stop again by itself if the file disappears. + # + # The `-n "$esp"` test is a *separate, + # nested* if rather than `[ -n "$esp" -a -f ... ]`: a failed search leaves + # $esp unset, and GRUB's `test` builtin evaluates both operands of `-a` + # rather than short-circuiting, so the combined form would still expand the + # `-f` to `()/...` and could complain about a bad device on every boot. + # Nesting guarantees the path test is not reached at all. + # + # No `insmod` is needed for the outer search: GRUB's EFI core image already + # embeds part_gpt/fat/search_fs_uuid, since it must find its own $prefix on + # the FAT ESP before reading this file. NixOS' own install-grub.pl relies on + # exactly that for the search it emits. The insmods inside the menuentry are + # kept only because `chain` genuinely is a separate module. + # + # The inner `search` deliberately repeats the outer one rather than reusing + # `set root=$esp`. It costs one extra disk scan, and only when the entry is + # actually selected, in exchange for a menuentry that is self-contained and + # cannot be broken by anything that clobbers $esp in between. This also + # matches the per-entry search install-grub.pl generates. + boot.loader.grub.extraEntries = '' + search --no-floppy --set=esp --fs-uuid 5873-AD05 + if [ -n "$esp" ]; then + if [ -f ($esp)/EFI/Microsoft/Boot/bootmgfw.efi ]; then + menuentry "Windows Boot Manager" --class windows --class os { + insmod part_gpt + insmod fat + insmod chain + search --no-floppy --set=root --fs-uuid 5873-AD05 + chainloader /EFI/Microsoft/Boot/bootmgfw.efi + } + fi + fi + ''; }; }