1# w7 — Windows 7 lab VMs on this Mac
2
3Every native check runs OneNote 2010 inside disposable QEMU clones of a sealed
4Windows 7 base image. A model drives a clone by writing AutoHotkey v2 scripts:
5each call runs on the clone's desktop and comes back with stdout and a
6screenshot.
7
8Two halves:
9
10- `payload/` — installed into the base image as `C:\win7-agent`. It starts an
11 HTTP listener that executes AHK and screenshots; QEMU forwards it to a host
12 loopback port per clone. See [payload/README.txt](payload/README.txt).
13- `mcp_win7.py` — stays on the Mac. An MCP server (stdio) for desktop control,
14 file transfer, commands, detached processes, and VM lifecycle. System python3,
15 stdlib only.
16
17The original physical-box version of the server is kept unchanged in
18[wayback/](wayback/README.md); nothing uses it.
19
20## Settings
21
22Copy `.env.example` at the repository root to `.env` and fill in `ONE_VM_HOME`
23(the directory holding the base image, `targets.json`, clone overlays and the
24Linux appliance) and `ONE_WIN7_ISO` (licensed installation media, used only by
25`vm.py install`). Both stay outside the repository. Environment variables of the
26same names override the file.
27
28## Wire up an MCP client
29
30Paste [codex-config-snippet.toml](codex-config-snippet.toml) into your client's
31MCP configuration with the repository path filled in, then restart it; it should
32list `win7` and `one-linux`.
33
34## Smoke test
35
36```sh
37./vm.py up alpha --wait
38./mcp_win7.py --target alpha health # listener version info
39./mcp_win7.py --target alpha shot # -> ./screenshot.png
40./mcp_win7.py --target alpha exec 'Run("notepad.exe")
41WinWait("Untitled - Notepad",, 10)
42SendText("hello")' # prints stdout/exit, -> ./screenshot.png
43./mcp_win7.py --target alpha cmd 'ipconfig'
44./vm.py down alpha
45```
46
47## Local QEMU VM
48
49`vm.py` runs the Windows 7 build VM on Apple silicon. VM state stays under
50`ONE_VM_HOME`; the Windows ISO is opened read-only.
51
52```sh
53./vm.py install # create the disk and boot the Windows installer
54./vm.py status win7-build
55./vm.py screenshot
56./vm.py poweroff # request a clean Windows shutdown
57./vm.py run # boot the installed system
58./vm.py media # rebuild win7-agent.iso from payload/
59./vm.py seal # validate and publish the stopped build disk
60```
61
62Normal boots attach `win7-agent.iso` as a read-only CD. Copy its contents to
63`C:\win7-agent`, run `install-autostart.cmd` as administrator, restart, then
64verify the forwarded listener:
65
66```sh
67./mcp_win7.py --target local health
68```
69
70Do not put the build disk or licensed installation media in this repository.
71Create the immutable base image only after Windows, Office, and the agent pass
72their checks and the VM has shut down cleanly.
73
74## Test VMs
75
76Each clone is a sparse overlay on the read-only base. Its name derives a unique
77Windows hostname (`alpha` becomes `ONE-ALPHA`), MAC address, forwarded control
78port, authentication token, and MCP target. Two vCPUs and 4 GiB are the defaults.
79
80```sh
81./vm.py up alpha --wait # creates when absent; waits through hostname restart
82./vm.py up alpha --display # visible Cocoa window instead of headless
83./mcp_win7.py --target alpha health
84./vm.py screenshot alpha
85./vm.py status # lists every clone
86./vm.py down alpha # clean shutdown, then delete the clone
87./vm.py down alpha --preserve-machine # keep a stopped clone for reproduction
88```
89
90The MCP server exposes `win7_vm_up`, `win7_vm_down`, and `win7_vm_status`.
91`up` accepts creation settings and a `wait` flag; `down` deletes by default and
92accepts `preserve_machine`. Use `win7_spawn` for a long-lived process such as
93Procmon so it cannot inherit and hold the command channel.
94
95The control agent starts minimized after login. Its taskbar button remains
96available for diagnosis without covering OneNote or changing screenshot-based
97test coordinates.
98
99## Windows 10 and 11
100
101`win10` (22H2 x64, emulated like Windows 7, so slow) and `win11` (24H2 arm64,
102hardware-virtualized) are further bases for clones. They install unattended and
103headless from Microsoft's Media Creation Tool catalog: Pro from the
104volume-license image, which installs without a product key and stays
105unactivated, with local administrator `one` / `one`. Setup's first logon runs
106[unattend/lab-setup.cmd](unattend/lab-setup.cmd), which installs the agent like
107the Windows 7 build and restarts into it.
108
109```sh
110./windows_media.py win11 # -> $ONE_VM_HOME/media/win11.iso (brew: wimlib xorriso)
111./vm.py install --base win11 # headless; add --display to watch
112./mcp_win7.py --target win11-build health # answers once the build reaches its desktop
113./vm.py poweroff win11-build
114./vm.py seal --base win11
115./vm.py up w11 --base win11 --wait # clones work as above; --base is kept per clone
116./mcp_win7.py --target w11 shot
117```
118
119A Windows 11 install reaches its agent in about 10 minutes and a new clone in
120one; Windows 10 takes about 55 minutes and 7, so pass `--timeout 900` to its
121`up --wait`.
122
123Screens are 1024x768, the largest mode edk2 offers the arm64 guest's framebuffer.
124The arm64 guest runs x64 programs, including the agent, through Windows' own
125emulation. Windows on Arm has no inbox driver for any NIC QEMU emulates, so
126`windows_media.py win11` also keeps Red Hat's signed NetKVM driver from the
127virtio-win disc. Transparency is on and DWM's `ForceEffectMode` is set, so Mica
128and Acrylic render on the emulated display adapter.
129
130## Abrupt-stop recovery tests
131
132`python3 tools/w7/crash.py windows NAME` and `python3 tools/w7/crash.py linux NAME`
133(from the repository root) stop a registered disposable QEMU process without
134requesting guest shutdown. They verify its VM/socket identity and preserve its
135disk and configuration. Restart with the corresponding `up NAME --wait`, and
136finish with `down NAME` to delete the owned machine. The existing MCP lifecycle
137continues to perform clean shutdowns.
138
139These stops discard guest memory while host storage remains powered. Pair them
140with the library's persisted-image tests when evaluating crash recovery; neither
141operation certifies a physical disk's behavior during host power loss.
142
143## Linux Samba VM
144
145`linux_vm.py` creates an SSH-only Debian 13 arm64 appliance from Debian's
146official generic-cloud image. The downloaded base is SHA-512 verified and kept
147under `ONE_VM_HOME/linux`; instances are sparse copy-on-write
148overlays. Cloud-init installs Samba, dnsmasq, CIFS tools, smbclient, and fio.
149
150```sh
151./linux_vm.py fetch
152./linux_vm.py up samba --wait
153./linux_vm.py ssh samba -- 'systemctl is-active smbd dnsmasq'
154./linux_vm.py status
155./linux_vm.py down samba
156```
157
158While it runs, Windows VMs obtain an address on their second NIC and reach the
159guest-writable share at `\\192.168.77.1\agent`. The Mac controls and inspects
160the appliance over its forwarded SSH port. One appliance runs at a time because
161that stable share address is the synchronization point for concurrent Windows
162clients. A local VDE switch joins up to 63 guests without host privileges. The
163`one-linux` MCP exposes fetch, up, SSH, down, and status; its `up` and `down`
164follow the same auto-create, wait-flag, and preserve conventions as Windows.
165
166## Linux desktop VM
167
168`up --desktop` adds an X display for driving GUI apps: Xvfb `:0` at 1280x800
169under openbox, run as lingering systemd user units, with xdotool, xclip, maim,
170AT-SPI and Mesa's software Vulkan (lavapipe) for wgpu. There is no guest agent;
171each MCP call pipes [linux_desktop.py](linux_desktop.py) into `python3 -` over
172SSH.
173
174```sh
175./linux_vm.py up desk --desktop --cpus 8 --memory 8192 --disk 40 --wait
176./linux_vm.py ssh desk -- 'xdotool getmouselocation'
177```
178
179`linux_exec` runs a bash script and returns output plus a screenshot, the
180counterpart of `win7_exec` with xdotool in place of AutoHotkey. `linux_shot`,
181`linux_ui` (AT-SPI tree), `linux_spawn` (transient user unit), `linux_put` and
182`linux_get` mirror the Windows tools. A desktop clone is still a Samba
183appliance, so it shares the one-at-a-time lab address rule.
184
185To restore a base from private object storage, upload the sealed qcow2 beside
186its JSON manifest and pass the manifest URL. A presigned URL needs no secret;
187otherwise set `ONE_VM_AUTHORIZATION` to the complete HTTP Authorization value.
188
189```sh
190./vm.py fetch 'https://private.example/one/win7-office-base.json'
191```
192
193`install-autostart.cmd` disables UAC so the isolated test guest can rename
194itself, start the control agent, and run Procmon without human prompts. QEMU
195publishes each guest agent only on host loopback. Do not run that installer on
196a physical or network-reachable Windows machine.
197
198## Gotchas
199
200- `-cpu qemu64` lacks SSSE3, which Mesa's llvmpipe (software OpenGL for Snowbound on a clone)
201 needs: `ONE_VM_CPU=Nehalem ./vm.py up NAME --wait` picks another model.
202- Keep Windows display scaling at 100%. Anything else and the screenshot
203 coordinates no longer match where clicks land.
204- The listener must run in the interactive logged-in session — as a scheduled
205 task or service it gets session 0 and sees a black screen.