A Windows 11 VM under QEMU/KVM for building and testing Cygwin programs
(such as mintty) from Linux. Everything runs as your user. You need x86-64
Linux with hardware virtualization and read-write access to /dev/kvm (on
NixOS, membership in the kvm group), Nix with flakes, an internet
connection for the install, and about 25 GB of disk space. QEMU, OVMF,
swtpm, and the virtio-win drivers come from the nixpkgs pinned in
flake.lock.
nix build -o ~/code/system/winvm/result ~/code/system/winvm
alias winvm=~/code/system/winvm/result/bin/winvm # or: nix profile add ~/code/system/winvm
winvm install # about 15 minutes, unattended (plus the 8.2 GB ISO download, if needed)
winvm start
winvm sshwinvm install installs Windows into a base image that it then never
changes. The VM runs on a copy-on-write overlay; winvm reset throws the
overlay away, which gives a clean Windows (with Cygwin) again in seconds.
| Command | What it does |
|---|---|
install [--replace] |
Install Windows and Cygwin into a new base image. --replace deletes the existing VM, once the files for the new one are ready. |
start |
Start the VM in the background (unless it runs already), and wait until SSH works (about 20 seconds after a cold start). |
stop [--force] |
Shut Windows down, closing programs without asking them (if SSH fails, it presses the ACPI power button instead). --force powers off at once. |
reset |
Put the stopped VM back to the clean install. |
status |
Tell whether the VM runs and whether SSH into it works. |
ssh [ARGS...] |
Run ssh to the guest; the arguments go to ssh. |
gui COMMAND [ARGS...] |
Start a program on the guest's desktop, detached from SSH. |
view |
Show the screen in a VNC viewer (TigerVNC). |
screenshot [FILE] |
Save the screen as a PNG (default: ~/vm/winvm/screen.png). |
type TEXT... |
Type the arguments, joined with spaces, on the VM's keyboard (US layout, ASCII only). Text that starts with - works too. |
key COMBO... |
Press keys, e.g. ret, esc, ctrl-c, alt-f4, ctrl-alt-delete (QEMU key codes joined by -). |
click X Y [--button left|right|middle] [--double] |
Click at pixel (X, Y) of the screen, as in the screenshot. |
type, key, and click go through the VM's keyboard and mouse, so the
program in the foreground gets real Windows input events.
rsync -a --delete --exclude=/.git -e "ssh -F $HOME/vm/winvm/ssh_config" ~/code/system/mintty/ winvm:mintty/
winvm ssh 'cd mintty/src && make -j8'
winvm gui mintty/bin/mintty.exe
winvm type 'echo hello'; winvm key ret
winvm screenshot /tmp/mintty.pngThe build does not need .git (without it, the version stamp has the
build time instead of the commit), and in a linked worktree, .git is a
file that points to a path on the host. With Include ~/vm/winvm/ssh_config in ~/.ssh/config, plain ssh winvm and
rsync ... winvm: work too.
- Windows 11 Enterprise Evaluation, version 26H2. The evaluation ends
90 days after
winvm install; after that, Windows shuts down every hour.winvm resetdoes not help then, butwinvm install --replacedoes (the ISO stays in~/vm/iso). When Microsoft replaces the ISO on the Evaluation Center, the download fails; then updateISO_URLandISO_SHA256inwinvm.py. - Account: the same name as your Linux login, password
winvm, an administrator, logged on automatically. UAC still runs programs without elevation, but elevates without a prompt (e.g.powershell Start-Process -Verb RunAs ...). - Cygwin in
C:\cygwin64, with gcc-core, make, git, openssh, rsync, run, procps-ng, psmisc, and python3. Defender does not scanC:\cygwin64. - sshd is Cygwin's, started at logon from the
HKCU\...\Runkey, in the desktop session and without elevation. That is why programs started over SSH appear on the screen. It only accepts the key~/vm/winvm/id_ed25519. - No automatic Windows updates, no sleep, no lock screen. Time zone: UTC.
- 8 CPUs, 8 GB of memory, a 128 GB disk (allocated as it fills), and a
1920x1080 screen; the constants at the top of
winvm.pychange these.
QEMU forwards only 127.0.0.1:10022 on the host to the guest's SSH port.
The VNC and QMP sockets have no password; they are in ~/vm/winvm, which
only you can enter. Note that the guest can reach the host's localhost
services through 10.0.2.2, as with any QEMU user-mode network.
- During
winvm install,~/vm/winvm/install-screen.pngshows the screen, updated about every 30 seconds;winvm viewshows it live. - In the guest,
C:\winvm\setup.logis the log of the provisioning script (guest/setup.ps1), and~/.winvm-sshd/sshd.logis sshd's log.
winvm.py: the command; its docstring lists the main files in~/vm/winvm.guest/autounattend.xml: answers for Windows Setup.guest/setup.ps1: provisioning at the first logon (Cygwin, sshd, settings).guest/sshd.sh: runs sshd in the desktop session, at each logon.