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 + ''; }; }