﻿---
solution: src/NETSetup.sln
project: NETSetup
testProject: NETSetup.Tests
language: csharp
testCommand: dotnet test src/NETSetup.Tests --nologo --verbosity minimal
---

## In Development

### Plan D — 3-Partition USB with Proxmox ISO on LINUX Partition

**GOAL:** Replace the fragile 2-partition USB flow (which formats the target disk, copies VoidPE there, then boots VoidPE from target disk) with a 3-partition USB where WinPE dd's the Proxmox ISO to a dedicated LINUX partition on the USB stick, writes answer.toml to SYSTEM, activates GRUB, and reboots directly into the Proxmox installer — leaving the target disk untouched until the Proxmox installer itself formats it. This eliminates the VoidPE intermediary for Proxmox installs, reduces reboots from 3 to 2, and makes the USB stick reusable.

Decisions:
- **Requires TWO USB sticks for Proxmox installs** (each ≥ 16 GB):
  - **USB #1 (NETSetup/WinPE)**: Contains WinPE + NETSetup binary. Flashed once with the NETSetup ISO. Reusable for all installs.
  - **USB #2 (Proxmox installer)**: Empty USB, plugged in when prompted. WinPE prepares it at runtime with a 3-partition layout:
    - PROXMOX-EFI (FAT32, 128 MB): Proxmox's own EFI bootloader (`BOOTX64.EFI`, `GRUBX64.EFI`) + custom `GRUB.CFG` that directly invokes `proxmox-start-auto-installer`
    - PROXMOX-AIS (FAT32, 128 MB): `answer.toml` — proxmox-fetch-answer scans for a partition with this label
    - LINUX (raw iso9660, ~2 GB): Proxmox ISO dd'd onto the partition so GRUB can load kernel/initrd
- **Why two USBs?** USB #1's ESP is tiny (1 GB) and mostly full with `sources/boot.wim` (~975 MB). Not enough room for Proxmox EFI files. A fresh USB #2 has a dedicated ESP with plenty of space.
- Windows Boot Manager stays at EFI/BOOT/BOOTX64.EFI for Secure Boot. GRUB at EFI/grub/grubx64.efi, activated by swapping EFI files.
- WinPE raw-writes Proxmox ISO to LINUX partition via Win32 FileStream on \\.\PhysicalDriveN at partition offset
- answer.toml on SYSTEM partition (label=SYSTEM) — proxmox-fetch-answer finds it by label
- rdinit wrapper (proven, tested on hardware) tells /init: netsetup.isodev=LABEL=LINUX
- First-boot script restores bootmgfw.efi on USB ESP after Proxmox install
- Non-Proxmox Linux paths keep VoidPE on LINUX partition unchanged
- create-iso produces a flat UDF ISO via oscdimg (unchanged). Rufus/user flashes it to USB → 2-partition layout. WinPE creates the LINUX partition at runtime by shrinking NTFS and adding partition 3. No changes to create-iso or UsbBootDisk needed.

- [ ] **RawPartitionWriter**: Create `RawPartitionWriter` static class in `NETSetup/Helpers/RawPartitionWriter.cs` that writes a file's raw bytes to a specified disk partition. Method `WriteFileToPartition(ISystemHost host, string sourceFilePath, int diskNumber, int partitionNumber)` opens `\\.\PhysicalDriveN` via `FileStream` with `FileAccess.Write`, seeks to the partition's byte offset (obtained from GPT table or WMI `MSFT_Partition`), and writes the source file in 4MB chunks. Works in WinPE (runs as SYSTEM). Uses `ISystemHost` for logging only.
  Assertions:
  - Given a mock disk: result should write exactly sourceFile.Length bytes
  - Given invalid diskNumber: act.Should().Throw<IOException>()
  - Given sourceFile that doesn't exist: act.Should().Throw<FileNotFoundException>()
  - Chunk size should be 4MB (4 * 1024 * 1024)
  - Should call FileStream.Write in a loop for files > 4MB
  - Should log progress at each chunk via host.Logger

- [ ] **GrubActivator**: Create `GrubActivator` static class in `NETSetup/Helpers/GrubActivator.cs` with two methods: `ActivateGrub(ISystemHost host, AbsolutePath espRoot)` renames `EFI/BOOT/BOOTX64.EFI` to `EFI/BOOT/bootmgfw.efi.bak` and copies `EFI/grub/grubx64.efi` to `EFI/BOOT/BOOTX64.EFI`. `RestoreWindowsBootManager(ISystemHost host, AbsolutePath espRoot)` reverses the swap. Both use `AbsolutePath` / operator and `FileExists()` checks. Throws `NETSetupException` if expected files don't exist.
  Assertions:
  - After ActivateGrub: espRoot / "EFI/BOOT/BOOTX64.EFI" should be a copy of grubx64.efi
  - After ActivateGrub: espRoot / "EFI/BOOT/bootmgfw.efi.bak" should exist
  - After RestoreWindowsBootManager: espRoot / "EFI/BOOT/BOOTX64.EFI" should be the original bootmgfw.efi
  - After RestoreWindowsBootManager: espRoot / "EFI/BOOT/bootmgfw.efi.bak" should not exist
  - ActivateGrub when grubx64.efi missing: act.Should().Throw<NETSetupException>()
  - RestoreWindowsBootManager when .bak missing: act.Should().Throw<NETSetupException>()

- [ ] **GrubConfigWriter**: Create `GrubConfigWriter` static class in `NETSetup/Helpers/GrubConfigWriter.cs` with method `WriteProxmoxGrubConfig(ISystemHost host, AbsolutePath espRoot, string osName)` that writes a GRUB config file to `espRoot / "EFI/grub/grub.cfg"`. The config contains: `set timeout=5`, `set default="NETSetup Proxmox"`, a "NETSetup Proxmox" menuentry that uses `search --label LINUX` and loads `/boot/linux26` with kernel params `ro ramdisk_size=16777216 rw quiet splash=silent proxmox-start-auto-installer netsetup.isodev=LABEL=LINUX netsetup.os=Proxmox rdinit=/netsetup-init`, initrd `/boot/initrd.img` + `($efipart)/netsetup-overlay.img`, and a "NETSetup VoidPE" fallback entry. Also writes `WriteVoidPEGrubConfig(...)` variant with VoidPE as default.
  Assertions:
  - result file at espRoot / "EFI/grub/grub.cfg" should exist
  - content.Should().Contain("netsetup.isodev=LABEL=LINUX")
  - content.Should().Contain("rdinit=/netsetup-init")
  - content.Should().Contain("proxmox-start-auto-installer")
  - content.Should().Contain("set default=\"NETSetup Proxmox\"")
  - content.Should().Contain("menuentry \"NETSetup VoidPE\"")
  - VoidPE variant: content.Should().Contain("set default=\"NETSetup VoidPE\"")

- [ ] **MainWinPE Proxmox branch rewrite**: Modify the `else` (Linux) branch in `MainWinPE.InstallOperatingSystem` (`src/NETSetup/Stages/2-WinPE/MainWinPE.cs`, line ~174). When `computer.OperatingSystem == OS.Proxmox`: (1) Find USB LINUX partition number via WMI/PowerShell (`Get-Partition` where DiskNumber = USB disk, PartitionNumber = 3), (2) call `RawPartitionWriter.WriteFileToPartition` to dd the Proxmox ISO from `SYSTEM:\NETSetup\Images\Proxmox\proxmox.iso` to USB LINUX partition, (3) generate answer.toml and write to SYSTEM partition root (existing `ToProxmoxAutoInstall` + `WriteTomlFile`), (4) call `GrubActivator.ActivateGrub` on ESP, (5) reboot. Do NOT format or touch the target disk. Non-Proxmox Linux path remains unchanged (formats target disk, copies VoidPE).
  Assertions:
  - For Proxmox: installationDisk.Clear should NOT be called
  - For Proxmox: RawPartitionWriter.WriteFileToPartition should be called with ISO path
  - For Proxmox: answer.toml should exist on SYSTEM root
  - For Proxmox: GrubActivator.ActivateGrub should be called
  - For Proxmox: host.ExitReboot should be called
  - For non-Proxmox Linux: existing behavior (format target disk) unchanged

- [ ] **S1NETSetupProject.CopyGrubFiles**: Add method `CopyGrubFiles(IFileOperator netSetupDevFolder, IFileProvider source, ISystemHost host)` to `S1NETSetupProject` that copies GRUB bootloader files from the source project's `grub/` folder to the staging area `BOOT/EFI/grub/` (grubx64.efi, grub.cfg). Also copies `netsetup-overlay.img` (rdinit wrapper cpio) to `BOOT/EFI/grub/`. Call this from `MainISO.CreateRelease` after `CopyWinPEFiles`. The grub.cfg is the VoidPE-default variant (Proxmox entry exists but VoidPE is default — WinPE switches to Proxmox default at deploy time).
  Assertions:
  - devFolder.GetFileInfo("BOOT/EFI/grub/grubx64.efi").Exists.Should().BeTrue()
  - devFolder.GetFileInfo("BOOT/EFI/grub/grub.cfg").Exists.Should().BeTrue()
  - devFolder.GetFileInfo("BOOT/EFI/grub/netsetup-overlay.img").Exists.Should().BeTrue()

- [ ] **UsbLinuxPartitionManager**: Create `UsbLinuxPartitionManager` static class in `NETSetup/Helpers/UsbLinuxPartitionManager.cs` that ensures a LINUX partition exists on the USB boot disk at runtime. Method `EnsureLinuxPartition(ISystemHost host, int usbDiskNumber, long requiredSizeMb)` checks if a 3rd partition with label LINUX already exists on the USB disk. If not: (1) queries current NTFS partition size via PowerShell `Get-Partition -DiskNumber N -PartitionNumber 2`, (2) shrinks NTFS via `Resize-Partition -DiskNumber N -PartitionNumber 2 -Size $newSize` (current size minus requiredSizeMb), (3) creates new partition in freed space via `New-Partition -DiskNumber N -UseMaximumSize` with GPT type basic data, (4) sets label to LINUX. Returns the partition number of the LINUX partition. Idempotent — if partition 3 labeled LINUX already exists, returns it immediately. Uses PowerShell commands via `ProcessStartInfo` (same pattern as existing WinPE code in MainWinPE.cs).
  Assertions:
  - When LINUX partition already exists: should return partition number without modifying disk
  - When no LINUX partition: should shrink NTFS and create new partition
  - requiredSizeMb should be subtracted from NTFS partition size
  - New partition should have label LINUX
  - Should throw NETSetupException if NTFS partition is too small to shrink
  - Should throw NETSetupException if disk number is invalid
  - Should be idempotent (calling twice returns same partition number)

- [ ] **netsetup-first-boot.sh restore bootmgfw.efi**: Add a step to `src/NETSetup/Resources/netsetup-first-boot.sh` that restores the Windows Boot Manager on the USB stick's ESP after Proxmox installation. Mount the USB ESP partition (find by GRUB presence or known device), check for `EFI/BOOT/bootmgfw.efi.bak`, if found: `mv bootmgfw.efi.bak BOOTX64.EFI` (restore original). This makes the USB stick reusable for the next deployment. Only runs if the backup file exists (idempotent).
  Assertions:
  - After running: EFI/BOOT/BOOTX64.EFI should be the original Windows Boot Manager
  - After running: EFI/BOOT/bootmgfw.efi.bak should not exist
  - If no .bak file exists: script should skip silently (idempotent)
  - If USB is not plugged in: script should skip silently
