Add essentials.windowsUpdate template that boots Audit Mode, uses the Windows Update COM API to search/download/install all available updates (cumulative, .NET, Defender), handles multi-round reboots with Audit Mode preservation, and compacts the image afterward. Known issues being worked: - Audit Mode preservation after update reboot needs verification - Install takes ~60-90 min with 4GB RAM on slow machines Includes full session notes in wip/ with detailed test log, build commands, issue analysis, timing data, and Claude memory files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
25 KiB
Windows Update Template - Full Development Session Log
Original Request
User wanted a reproducible way to update Windows images, inspired by https://massgrave.dev/update-windows-iso. The upstream Win10 LTSC 2021 ISO is stored in a git-lfs repo at git.sagar.ch.
Approach Evolution
Phase 1: ISO Update Approach (abandoned)
Initially explored creating an updated ISO by integrating cumulative updates. Considered:
-
UUP Dump approach: The massgrave page links to uupdump.net which provides download scripts for building fresh ISOs from Microsoft's UUP (Unified Update Platform) files. The user received a bash script from UUP dump that:
- Uses
aria2cto download UUP .cab files from Microsoft CDN - Uses
cabextract,wimlib-imagex,chntpwto build an ISO - Uses
genisoimage/mkisofsto create the final ISO - Problem: downloads
core;professional(Home/Pro), NOT LTSC - Problem: download URLs contain auth tokens that expire — not reproducible
- Uses
-
Offline DISM approach: Considered creating a Nix derivation that:
- Boots a Windows worker VM from the existing upstream image
- Mounts the original ISO inside the VM
- Uses
DISM /Imageto service install.wim offline (mount WIM, apply .cab updates, unmount) - Extracts the updated install.wim via guestfs
- Rebuilds the ISO with genisoimage on the Linux host
- Problem: very complex, requires managing WIM indexes, boot.wim, etc.
- Problem: needs a "worker" Windows VM just to run DISM
-
Offline .msu approach: Considered downloading specific .msu files from Microsoft Update Catalog:
- Search catalog.update.microsoft.com for
cumulative update for windows 10 version 21H2 x64 - Found latest: KB5087544 (May 2026), KB5088859 (.NET), KB2267602 (Defender)
- Problem: catalog uses JavaScript popups for downloads — can't scrape URLs
- Could use
curlPOST toDownloadDialog.aspxwith update GUIDs, but GUIDs are hard to extract - The download URLs from
catalog.s.download.windowsupdate.comare stable (don't expire) but finding them requires browser interaction
- Search catalog.update.microsoft.com for
Phase 2: customizeImage Template (adopted)
User said: "instead of iso, let's create a customizeImage template that basically updates the existing image and runs qcow2 compact on it"
This is much simpler — apply updates to the running Windows image during Audit Mode, not to the ISO.
Phase 3: Online vs Offline Updates
Initially built the template with two modes:
- Offline mode: user provides
.msu/.cabfiles asfetchurlderivations, applied viaDISM /Online /Add-Package - Online mode: triggers Windows Update service directly via COM API
User said "just do online updates" — removed offline mode entirely.
What was built
New files
lib/images/windows/templates/essentials/windows-update.nix— template that boots into Audit Mode, runs Windows Update via COM API, handles reboots automatically, compacts image
Modified files
lib/images/windows/helpers/customizeImage.nix— addedcompact,qemuTimeoutparameters (committed in3b454b7on master alongside other changes)lib/images/windows/templates/default.nix— wiredwindowsUpdateintoessentialslib/images/windows/win10/images.nix— addedupdatedstep betweenupstreamandbasiclib/images/windows/win11/images.nix— same
New customizeImage parameters
compact ? false
Added to customizeImage.nix. When true, runs qemu-img convert -O qcow2 after the audit boot to flatten the COW chain into a standalone qcow2 with no backing file dependency. This is important for the update template because the updates add several GB of data to the COW overlay.
Implementation in builderCommand:
${lib.optionalString compact ''
echo "=== vmix: compacting image ==="
qemu-img convert -O qcow2 ${resultImg} compact.qcow2
mv compact.qcow2 ${resultImg}
''}
mv ${resultImg} $out
qemuTimeout ? 1800
Added to customizeImage.nix. Replaces the hardcoded timeout 1800 in the QEMU boot command. The Windows Update template sets this to 7200 (2 hours) because update installation with reboots can take a long time.
Both timeout invocations in the builder (primary and SDL-fallback) now use ${toString qemuTimeout}.
How the template works
The template is a function that takes { maxRounds ? 3 } and returns a customizeImage-compatible attrset:
{
name = "windows-update";
compact = true;
memSize = 4096;
qemuTimeout = 7200;
auditScript = "...";
}
Audit script flow
-
Round tracking: Uses
C:\vmix-update-round.txtto track which round we're on across reboots. First run creates the file with "1", subsequent runs read and increment. -
Service initialization:
net start wuauserv 2>nul sc config wuauserv start= auto reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /f 2>nul reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /f 2>nulThe LTSC image may have update-blocking group policies. We delete them. We also wait 30 seconds on first round for the service to initialize and sync with Microsoft.
-
PowerShell COM API: The core update logic uses
Microsoft.Update.Session:$session = New-Object -ComObject Microsoft.Update.Session $searcher = $session.CreateUpdateSearcher() $result = $searcher.Search('IsInstalled=0') # ... accept EULAs, download, install ... if ($installResult.RebootRequired) { exit 3010 } else { exit 0 }- Exit code
3010= reboot required - Exit code
0= no reboot needed (or no updates found) - Exit code
1= error (caught by try/catch) - Per-update results are logged with HResult codes
- Exit code
-
Component cleanup: After updates, runs
dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase /Quietto reclaim space from superseded update components. -
Reboot handling (if exit code 3010 and round < maxRounds):
:: Copy script to survive wrapper deletion copy /y "%~f0" "C:\vmix-update-continue.cmd" >nul :: Register for next boot reg add "HKLM\...\RunOnce" /v vmixUpdate /d "cmd /c C:\vmix-update-continue.cmd" /f :: Preserve Audit Mode reg add "HKLM\SYSTEM\Setup\Status\AuditBoot" /v AuditBoot /t REG_DWORD /d 1 /f reg add "HKLM\SYSTEM\Setup" /v AuditInProgress /t REG_DWORD /d 1 /f :: Reboot shutdown /r /f /t 0 exit /b -
Final shutdown: When all rounds complete (or no more updates), the script shuts down:
del /q "%ROUND_FILE%" 2>nul del /q "C:\vmix-update-continue.cmd" 2>nul echo === vmix: Windows Update complete === shutdown /s /f /t 10 /c "vmix: windows-update complete"
Pipeline integration
win10/images.nix
ltsc = rec {
upstream = makeImage { ... };
updated = customizeImage upstream (templates.essentials.windowsUpdate {});
basic = customizeImageFold updated (with templates; [ ... ]);
# ... withApps, withAMDGPU ...
};
laptopUpstream = makeImage { ... useAHCI = true; };
laptopUpdated = customizeImage laptopUpstream (templates.essentials.windowsUpdate {});
laptopSlim = customizeImageFold laptopUpdated templates.bundles.laptopSlim;
laptop = customizeImageFold laptopUpdated templates.bundles.laptop;
win11/images.nix
Same pattern — updated step inserted between upstream and basic, laptopUpdated between laptopUpstream and laptopSlim/laptop.
Usage examples
# Online mode (default) - triggers Windows Update service
essentials.windowsUpdate {}
essentials.windowsUpdate { maxRounds = 5; }
# In image pipeline
updated = customizeImage upstream (templates.essentials.windowsUpdate {});
basic = customizeImageFold updated [ ... ];
# Override VNC display and disable compact for debugging
vmixLib.windows.customizeImage upstream (windowsUpdate // { vncDisplay = ":55"; compact = false; })
Detailed Test Log
Build 1: First attempt (no VNC, wrong attr path)
nix build --print-build-logs --impure --option sandbox relaxed --expr '
let vmixLib = (builtins.getFlake "path:/storage/gitrepos/vmix.nix").lib.x86_64-linux;
in vmixLib.images.win10.ltsc.updated
'
Result: Error — attribute 'images' missing. The correct path is vmixLib.windows.images.win10.ltsc.updated, not vmixLib.images.win10.ltsc.updated.
Build 2: Correct path, VNC :1, original script (no wuauserv start)
nix build ... --expr '
let vmixLib = ...;
upstream = vmixLib.windows.images.win10.ltsc.upstream;
windowsUpdate = vmixLib.windows.templates.essentials.windowsUpdate {};
in vmixLib.windows.customizeImage upstream (windowsUpdate // { vncDisplay = ":1"; })
'
Result: Build "succeeded" but image size identical to upstream (5.03 GB vs 5.02 GB). The COM API found 0 updates because:
- Windows Update service (
wuauserv) wasn't running in Audit Mode - No time given for service to initialize
- Possible update-blocking policies active
Build log showed: QEMU booted, script ran, shut down — no errors visible in nix log (VNC output isn't captured).
Build 3: Added wuauserv start, policy removal, 30s wait
Updated windows-update.nix to add:
net start wuauserv 2>nul
sc config wuauserv start= auto
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /f 2>nul
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /f 2>nul
timeout /t 30 /nobreak >nul
Also added EULA acceptance and try/catch error handling to the PowerShell block.
Build with VNC :55, chained virtio tools first:
nix build ... --expr '
let vmixLib = ...;
upstream = vmixLib.windows.images.win10.ltsc.upstream;
withVirtio = vmixLib.windows.customizeImage upstream vmixLib.windows.templates.essentials.virtioTools;
windowsUpdate = vmixLib.windows.templates.essentials.windowsUpdate {};
in vmixLib.windows.customizeImage withVirtio (windowsUpdate // { vncDisplay = ":55"; compact = false; })
'
VNC observations (screenshots taken with gvnccapture localhost:55 /tmp/screenshot.png):
- T+2min: Boot screen "Please wait"
- T+3min: CMD window showing:
=== vmix audit: windows-update === === vmix: Windows Update round 1 of 3 === The Windows Update service is starting. The Windows Update service was started successfully. [SC] ChangeServiceConfig SUCCESS Waiting for Windows Update service to initialize... Searching for updates... - T+7min: Found 6 updates:
Found 6 updates - 2022-02 Cumulative Update Preview for .NET Framework 3.5 and 4.8 for Windows 10 Version 21H2 for x64 (KB5010472) - Microsoft .NET Framework 4.8.1 for Windows 10 Version 21H2 for x64 (KB5011048) - Windows Malicious Software Removal Tool x64 - v5.141 (KB890830) - 2026-05 Cumulative Update for .NET Framework 3.5, 4.8 and 4.8.1 for Windows 10 Version 21H2 for x64 (KB5088859) - Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.451.326.0) - Current Channel (Broad) - 2026-05 Cumulative Update for Windows 10 Version 21H2 for x64-based Systems (KB5087544) Downloading... - T+12min: "Installing..."
- T+20min through T+55min: Still "Installing..." — KB5087544 is a massive cumulative update spanning Oct 2021 → May 2026
- T+58min: Sysprep dialog showing on desktop — the machine rebooted after update install and landed in Audit Mode desktop, but the round 2 script didn't run
Issue discovered: The wrapper script (vmix-audit-wrapper.cmd) deletes C:\vmix-audit-script.cmd after the audit script returns but before the reboot completes. The RunOnce entry points to the deleted file.
Build 4: Added script copy fix + shutdown fix
Updated script to:
- Copy itself to
C:\vmix-update-continue.cmdbefore rebooting - Register
cmd /c C:\vmix-update-continue.cmdin RunOnce (not the original path) - Always call
shutdown /s /f /t 10when done (handles both wrapper and RunOnce invocation)
Same build command as Build 3.
VNC observations:
- T+3min: Round 1 started, same 6 updates found
- T+7min: Downloading...
- T+12min: Installing...
- T+55min: TianoCore UEFI boot screen — machine rebooted!
- T+57min: "Working on updates — 90% complete" — Windows finalizing update installation after reboot
- T+62min: "Working on updates — 19% complete" (different counter — this is the post-reboot finalization)
- T+70min: Sysprep dialog... but NO round 2 script running
Issue discovered: After reboot, Windows exited Audit Mode and entered OOBE ("Choose your keyboard layout" screen) instead of staying in Audit Mode. The cumulative update reset the Audit Mode flags during its finalization pass.
Wait — actually on this build we saw the Sysprep dialog (which IS Audit Mode). The issue was the RunOnce not firing. On a subsequent build, we saw the keyboard layout screen (OOBE). The behavior is inconsistent.
Build 5: Added Audit Mode preservation
Added registry keys before reboot to preserve Audit Mode:
reg add "HKLM\SYSTEM\Setup\Status\AuditBoot" /v AuditBoot /t REG_DWORD /d 1 /f
reg add "HKLM\SYSTEM\Setup" /v AuditInProgress /t REG_DWORD /d 1 /f
Same build command. VNC observations:
- T+3min: Round 1 started, same 6 updates found
- T+70min: Still on "Installing..." — round 1 didn't finish in 70 min on this machine
- T+100min: "Working on updates — 19% complete" — rebooted, finalizing
- T+110min: "Choose your keyboard layout" — OOBE screen again!
Session ended here — the Audit Mode preservation fix hasn't been verified as working yet. The machine was moved to a faster machine for continued testing.
Detailed Issue Analysis
Issue 1: Windows Update service not running
Root cause: In Audit Mode, the Windows Update service (wuauserv) has Start=3 (manual) and isn't auto-started. The COM API requires the service to be running to search for updates.
Fix: Explicitly start the service and set it to auto-start:
net start wuauserv 2>nul
sc config wuauserv start= auto
Also remove any group policies that block updates (LTSC may have these pre-configured):
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /f 2>nul
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /f 2>nul
And wait 30 seconds for the service to initialize and sync with Microsoft servers on first round.
Issue 2: Wrapper deletes script before reboot completes
Root cause: The customizeImage.nix wrapper script flow:
call C:\vmix-audit-script.cmd ← our update script
del /q C:\vmix-audit-script.cmd ← DELETE happens here
shutdown /s /t 5 ← may override our shutdown /r
del /q C:\vmix-audit-wrapper.cmd
When our script calls shutdown /r /f /t 0 and then exit /b, control returns to the wrapper. The wrapper deletes the script file. Even though shutdown /r /t 0 was called, the batch file continues executing and the delete happens before the system actually reboots.
The RunOnce entry points to C:\vmix-audit-script.cmd which no longer exists after reboot.
Additionally, the wrapper's shutdown /s /t 5 may override our shutdown /r /t 0 (though in practice the /t 0 reboot usually wins).
Fix: Copy the script to a separate path before rebooting:
copy /y "%~f0" "C:\vmix-update-continue.cmd" >nul
reg add "HKLM\...\RunOnce" /v vmixUpdate /d "cmd /c C:\vmix-update-continue.cmd" /f
The wrapper only knows about C:\vmix-audit-script.cmd and C:\vmix-audit-wrapper.cmd. It doesn't know about C:\vmix-update-continue.cmd, so that file survives.
Issue 3: Audit Mode lost after update reboot
Root cause: Windows Audit Mode is maintained by registry keys:
HKLM\SYSTEM\Setup\Status\AuditBoot(AuditBoot = 1)HKLM\SYSTEM\Setup(AuditInProgress = 1)
Some cumulative updates include "specialize" or "generalize" passes during their finalization that can clear these keys, causing Windows to transition from Audit Mode to OOBE on the next boot.
Fix: Explicitly set the Audit Mode registry keys right before rebooting:
reg add "HKLM\SYSTEM\Setup\Status\AuditBoot" /v AuditBoot /t REG_DWORD /d 1 /f
reg add "HKLM\SYSTEM\Setup" /v AuditInProgress /t REG_DWORD /d 1 /f
Status: NOT YET VERIFIED. The last build with this fix was still in the install phase when the session ended. The fix may or may not be sufficient — some updates may clear these keys AFTER the reboot during the "Working on updates X% complete" phase, which would happen after our registry writes.
Alternative approaches if the fix doesn't work:
- Use
C:\Windows\System32\Sysprep\sysprep.exe /audit /reboot /quietinstead ofshutdown /r— this explicitly tells Windows to re-enter Audit Mode - Set up a
SetupComplete.cmdorSpecializeunattend pass that forces Audit Mode - Use a scheduled task instead of RunOnce (scheduled tasks persist across mode transitions)
- Accept single-round updates only (
maxRounds = 1) — no reboot needed if updates don't require it
Issue 4: No shutdown after round 2
Root cause: When the script is invoked via RunOnce (round 2+), it runs directly — not through the wrapper. The wrapper is what normally calls shutdown /s /t 5 after the script completes. Without the wrapper, the script finishes and the machine just sits at the Audit Mode desktop indefinitely (until the 2-hour QEMU timeout).
Fix: The script itself calls shutdown /s /f /t 10 when all rounds are complete. This is harmless when called from the wrapper (two shutdown commands — the second one either fails silently or is a no-op since shutdown is already pending).
All Build Commands Used
Quick evaluation (check attribute paths)
nix eval --impure --expr '
let vmixLib = (builtins.getFlake "path:/storage/gitrepos/vmix.nix").lib.x86_64-linux;
in builtins.attrNames vmixLib.windows.images.win10.ltsc
'
Build updated image directly (no VNC, with compact)
nix build --print-build-logs --impure --option sandbox relaxed --expr '
let
vmixLib = (builtins.getFlake "path:/storage/gitrepos/vmix.nix").lib.x86_64-linux;
in vmixLib.windows.images.win10.ltsc.updated
'
Build with VNC monitoring on port 5901 (display :1)
nix build --print-build-logs --impure --option sandbox relaxed --expr '
let
vmixLib = (builtins.getFlake "path:/storage/gitrepos/vmix.nix").lib.x86_64-linux;
upstream = vmixLib.windows.images.win10.ltsc.upstream;
windowsUpdate = vmixLib.windows.templates.essentials.windowsUpdate {};
in vmixLib.windows.customizeImage upstream (windowsUpdate // { vncDisplay = ":1"; })
'
Build with virtio tools + VNC :55 + no compact (debugging)
nix build --print-build-logs --impure --option sandbox relaxed --expr '
let
vmixLib = (builtins.getFlake "path:/storage/gitrepos/vmix.nix").lib.x86_64-linux;
upstream = vmixLib.windows.images.win10.ltsc.upstream;
withVirtio = vmixLib.windows.customizeImage upstream vmixLib.windows.templates.essentials.virtioTools;
windowsUpdate = vmixLib.windows.templates.essentials.windowsUpdate {};
in vmixLib.windows.customizeImage withVirtio (windowsUpdate // { vncDisplay = ":55"; compact = false; })
'
Build with more RAM (8GB) for faster installs
nix build --print-build-logs --impure --option sandbox relaxed --expr '
let
vmixLib = (builtins.getFlake "path:/storage/gitrepos/vmix.nix").lib.x86_64-linux;
upstream = vmixLib.windows.images.win10.ltsc.upstream;
windowsUpdate = vmixLib.windows.templates.essentials.windowsUpdate {};
in vmixLib.windows.customizeImage upstream (windowsUpdate // { vncDisplay = ":55"; memSize = 8192; })
'
Build with more rounds
nix build --print-build-logs --impure --option sandbox relaxed --expr '
let
vmixLib = (builtins.getFlake "path:/storage/gitrepos/vmix.nix").lib.x86_64-linux;
upstream = vmixLib.windows.images.win10.ltsc.upstream;
windowsUpdate = vmixLib.windows.templates.essentials.windowsUpdate { maxRounds = 5; };
in vmixLib.windows.customizeImage upstream (windowsUpdate // { vncDisplay = ":55"; })
'
Full pipeline test (upstream → updated → basic → generalize)
nix build --print-build-logs --impure --option sandbox relaxed --expr '
let
vmixLib = (builtins.getFlake "path:/storage/gitrepos/vmix.nix").lib.x86_64-linux;
in vmixLib.windows.images.win10.ltsc.basic.generalize {
username = "User"; password = ""; hostname = "WIN-VM";
vncDisplay = ":55";
}
'
VNC monitoring commands
# Take a screenshot
nix-shell -p gtk-vnc --run 'gvnccapture localhost:55 /tmp/screenshot.png'
# Connect with a VNC viewer
vncviewer localhost:5955
# Check if QEMU is running
ps aux | grep 'qemu-system' | grep -v grep
# Check nix build log for a store path
nix log /nix/store/HASH-windows-update-....qcow2
# Check image info
nix-shell -p qemu --run 'qemu-img info /nix/store/HASH-....qcow2'
Image sizes observed
| Image | disk size | virtual size |
|---|---|---|
| upstream (win10-ltsc-2021) | 5.02 GB | 64 GB |
| updated (killed build, round 1 only) | 8.33 GB | 64 GB |
| upstream (compacted, no updates) | 5.03 GB | 64 GB |
Updates found by Windows Update (Win10 LTSC 2021 → May 2026)
- 2022-02 Cumulative Update Preview for .NET Framework 3.5 and 4.8 (KB5010472)
- Microsoft .NET Framework 4.8.1 for Windows 10 21H2 x64 (KB5011048)
- Windows Malicious Software Removal Tool x64 v5.141 (KB890830)
- 2026-05 Cumulative Update for .NET Framework 3.5, 4.8 and 4.8.1 (KB5088859)
- Security Intelligence Update for Microsoft Defender Antivirus (KB2267602, Version 1.451.326.0+)
- 2026-05 Cumulative Update for Windows 10 Version 21H2 (KB5087544) — the big one, ~870 MB
Timing observations (4 vCPU, 4GB RAM machine)
- Nix evaluation: ~30 seconds
- VirtIO tools step (cached): instant
- Windows boot to Audit Mode desktop: ~60 seconds
- Windows Update search: ~2-3 minutes
- Download all 6 updates: ~3-5 minutes
- Install all updates (especially KB5087544): 50-70 minutes
- Reboot + "Working on updates" finalization: ~10-15 minutes
- Total for round 1: ~70-90 minutes
- DISM cleanup: ~2-5 minutes
- qemu-img convert (compact): ~2-3 minutes
Remaining TODO (for next session on faster machine)
- CRITICAL: Verify the Audit Mode preservation fix works (AuditBoot + AuditInProgress registry keys)
- If Audit Mode fix doesn't work, try
sysprep /audit /reboot /quietinstead ofshutdown /r - Test round 2 → round 3 flow (find additional updates after cumulative? likely Defender definition updates)
- Test with
compact = trueto verify final standalone image - Test full pipeline:
upstream → updated → basic → generalize - Test win11 images
- Consider using 8GB RAM (
memSize = 8192) for faster update installs - Consider: should
windowsUpdatego before or aftervirtioTools? Currently before in the pipeline, but the explicit build command chains virtio first for better I/O - The template is impure (downloads from Microsoft during build) — this is intentional but should be documented
- Consider
maxRounds = 1mode for builds where you only want non-reboot updates - The
compactstep doesn't compress — consider adding-cflag toqemu-img convertfor compressed qcow2 - The wrapper's
shutdown /s /t 5may still race with the script'sshutdown /r /t 0— consider usingshutdown /a(abort) beforeshutdown /rto cancel any pending shutdown
Architecture notes
How customizeImage works (for context)
- Creates a COW overlay on the original image:
qemu-img create -f qcow2 -b ${originalImage} -F qcow2 disk.qcow2 - Optionally resizes:
qemu-img resize disk.qcow2 ${diskSize} - Merges offline registry entries via
virt-win-reg --merge - Injects audit script + wrapper via
virt-customize --upload - Adds RunOnce registry entry for the wrapper
- Boots QEMU with the image (user networking, UEFI, VirtIO or AHCI)
- The wrapper runs the audit script, then shuts down
- Optionally compacts:
qemu-img convert -O qcow2 - Moves result to
$out
The wrapper problem
The wrapper (vmix-audit-wrapper.cmd) is designed for simple, single-boot templates:
call C:\vmix-audit-script.cmd
del /q C:\vmix-audit-script.cmd
shutdown /s /t 5
del /q C:\vmix-audit-wrapper.cmd
This is fine for templates that don't reboot. But the Windows Update template needs multiple reboots, which conflicts with the wrapper's assumptions. The workarounds (copy script, self-shutdown) are necessary because modifying the wrapper would affect all templates.
A future improvement might be to add a multiboot flag to customizeImage that changes the wrapper behavior for templates that need reboots.
Why QEMU user networking works for Windows Update
QEMU's -nic user (SLIRP) provides NAT networking. The guest gets DHCP and can reach the internet via the host. Windows Update uses HTTPS to Microsoft's servers, which works through NAT. No special firewall rules or port forwarding needed.
Why VirtIO tools are chained before updates (in test builds)
The upstream image uses VirtIO storage (if=virtio) but doesn't have VirtIO guest tools installed. The update template doesn't need guest tools to work, but having them improves:
- Disk I/O performance (VirtIO balloon, better driver)
- Memory management
- Guest agent for monitoring
In the pipeline (win10/images.nix), updates are applied directly to upstream without virtio tools, because virtio tools installation is part of the basic step. For testing, we chain them explicitly.