Files
owui-tools/vm/images/windows/build.md
T
2026-09-15 15:24:03 -04:00

6.6 KiB

The Windows base image

Unlike Ubuntu, this one is installed by hand. There is no unattended path here and that is deliberate: Windows licensing, edition choice and the out-of-box experience are all decisions for the operator, not for a script.

What is already prepared

/var/lib/owuivm/iso/Win11_25H2_English_x64_v2.iso   the media you supplied
/var/lib/owuivm/iso/virtio-win.iso                  the driver disc

Both are recorded in windows.manifest and virtio.manifest with their SHA-256. Recorded, not verified - unlike Ubuntu, there is no published checksum list to compare a Windows ISO against, so the manifest says what the file hashed to and nothing stronger. That difference is real and is not papered over.

Three things Windows needs that Ubuntu does not

All three are already in the launcher, applied only when the image is named windows:

Requirement What is provided What happens without it
TPM 2.0 swtpm on a unix socket, tpm-tis device Setup stops at "This PC can't run Windows 11"
Secure Boot OVMF_CODE_4M.secboot.fd + a variable store seeded from OVMF_VARS_4M.ms.fd, with property=secure,value=on so the guest cannot rewrite it Same message. Microsoft's keys have to be enrolled, not just the firmware present
virtio drivers virtio-win.iso attached as a second CD during the install only Setup reports no drives to install to, which looks like a broken VM
Hyper-V enlightenments fourteen hv-* flags on -cpu, Windows only The machine is simply slow, and the host looks busy while the guest looks idle - measured at 347% CPU on an idle installer with the cgroup neither short of memory nor throttled
-rtc base=localtime Windows only The clock is hours out, so TLS verification fails and every download breaks in a way that reads as a network fault

swtpm also needed an AppArmor exception: Ubuntu's shipped profile permits libvirt's paths and nothing else, so the socket was refused with Could not open UnixIO socket: Permission denied - a policy denial wearing a permission error's clothes, with nothing in dmesg. See vm/host/apparmor-local-swtpm.

Installing it

  1. Finish the Ubuntu image first, or close it. Only one maintenance machine runs at a time and the prepare step refuses the second. Starting a frozen image is always safe, at any time: it boots a throwaway copy and cannot change what is on disk. That is the way to check that an image still comes up before handing it to the agent.
  2. Tool Server VM Maintenance on the desktop -> the Windows row -> Install from ISO. A blank 60 GB disk is created, the media is attached, and the console opens.
  3. Two prompts eat keystrokes before Windows does, in this order. OVMF shows its own "press a key for boot options" window for about five seconds - any key there drops you into the firmware menu, not the installer (if that happens: Boot Manager -> UEFI QEMU DVD-ROM QM00001, which is the Windows ISO; QM00003 is virtio-win). Then the ISO itself shows "Press any key to boot from CD or DVD" for about five seconds, and that one has to be answered or the machine falls through to an EFI shell. So: let the firmware window pass untouched, then press a key.
  4. When setup asks where to install and shows no drives, choose Load driver, browse the second CD, and take amd64\w11 (or amd64\2k25). The disk appears immediately afterwards.
  5. Install normally. A local account is simplest - the machine has no way to reach a Microsoft account sign-in that needs a phone, and nothing about this image should depend on one.

Before you shut down

The equivalents of what the Ubuntu image needed, and for the same reasons:

  • qemu-guest-agent from the driver disc (virtio-win-guest-tools.exe, or guest-agent\qemu-ga-x86_64.msi). This is the entire control channel; without it the machine boots and is unreachable, which looks identical to a machine that failed to boot.
  • Static networking: address 10.0.2.15/24, gateway 10.0.2.2, DNS 1.1.1.1. DHCP hands out SLIRP's own resolver at 10.0.2.3, which relays to this host's resolver and is correctly blocked by the sandbox filter - giving a machine with working TCP and no name resolution.
  • Auto-logon for the admin account, so a GUI session has a desktop from boot with no human. netplwiz, or Set-LocalUser plus the AutoAdminLogon registry values.
  • The rest of virtio-win, not just the storage driver setup needed: NetKVM for the network, and the display driver in particular. Until it is installed virtio-vga is a plain VGA framebuffer to Windows and every redraw is a full repaint - which is most of what "the VM feels slow" turns out to be once the enlightenments are on.
  • Turn off Windows Update's automatic reboots and anything else that changes the machine between sessions. Every session starts from this frozen disk, so background change here is change in every future run.

Then

Press Shut down. The guest is asked to power off over ACPI and systemd waits for it, so this is the machine shutting itself down rather than the plug coming out. The variable store is copied back on the way - a UEFI boot entry added by setup is otherwise lost and the disk will not boot.

Then press Freeze, which is the operator's own button and is not done for you: nothing re-freezes an image behind your back any more. Freezing sets 0444 and records the checksum, and until it is done no agent session will start - vmd refuses to open one on a writable base and says so.

Then press Installed — remove disc, which appears on the row while an install is in progress and wakes up once the machine is stopped. That is what says the install is finished: until it is pressed the installer media is still attached at every boot, deliberately, because a machine shut down halfway through setup needs its disc to be finished and nothing here can tell the two cases apart. The disc first, the disk second - the boot order tries the disk and falls through to the media, so an installed system boots itself either way and the only cost of leaving it attached is the ISO staying open.

Not yet done

Nothing on this page has been run. The Ubuntu path was measured end to end; this one is written from the same code paths plus the documented Windows requirements, and the first real install will find something. The argv it will be given is asserted, by tests/test_vm_launch_argv.sh, which checks that the Windows machine gets secure-boot firmware, a TPM and a protected variable store while the Ubuntu one gets none of them.