The script no longer needs the parent repository or SSH: it builds an answer ISO from windows11/seed/Autounattend.xml, installs Windows from the ISO in QEMU (hardware in windows11/qemu uses only devices with inbox Windows drivers), and the answer file generalizes the image with sysprep at the end. The base holds only Windows: no account (the built-in Administrator is disabled), no SSH, no settings, no VirtIO drivers, no machine identity and no cached answer files. VMs are configured on first boot from an Unattend.xml on an attached CD-ROM. Add --window and --from-img; press a key at the ISO boot prompt with the z key, which neither UEFI nor Windows Setup ignores or acts on. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
140 lines
6.3 KiB
Markdown
140 lines
6.3 KiB
Markdown
# vm-images
|
|
|
|
Shared, locally cached virtual-machine installation media and base images.
|
|
Downloaded ISO, QCOW2, IMG, VHD, and VHDX files are intentionally excluded
|
|
from Git.
|
|
|
|
## Download images
|
|
|
|
Download every supported image:
|
|
|
|
```bash
|
|
./download-images.sh
|
|
```
|
|
|
|
Download or check selected targets:
|
|
|
|
```bash
|
|
./download-images.sh debian13
|
|
./download-images.sh almalinux10
|
|
./download-images.sh fedora44
|
|
./download-images.sh ubuntu2604
|
|
./download-images.sh virtio-win
|
|
```
|
|
|
|
The Windows installation ISO is not downloaded because Microsoft's temporary
|
|
download URLs expire. Place it manually in `install-iso/`; the script finds it
|
|
automatically by matching `Win`, `11`, the architecture (`x64`, or `arm64` on
|
|
arm64 hosts) and `.iso` in that order (`*Win*11*x64*.iso` or
|
|
`*Win*11*arm64*.iso`, case-insensitive). If several files match, the highest
|
|
version-sorted name is used. Check which file was picked with
|
|
`./download-images.sh --print-path windows11 --iso`.
|
|
|
|
To get a fresh temporary URL, `nodejs/get-windows-iso-url.js` drives Microsoft's
|
|
download page with headless Chromium (needs Node.js and an installed
|
|
Chromium, for example from Playwright):
|
|
|
|
```bash
|
|
cd nodejs
|
|
npm ci # once, installs playwright-core
|
|
node get-windows-iso-url.js # print the URL for the host architecture
|
|
node get-windows-iso-url.js --arch arm64 # choose the architecture explicitly
|
|
```
|
|
|
|
The script only prints the URL. `./download-images.sh windows11` downloads that
|
|
URL into `install-iso/` like the other images (aria2c, resumable) for the image
|
|
architecture (`VM_IMAGE_ARCH=arm64` for arm64), and skips it when a matching ISO
|
|
is already there. The `all` target does not include it.
|
|
|
|
The URL expires after about 24 hours and may be bound to the IP that requested
|
|
it, so download from the machine that ran the script. Run it with `--help` for
|
|
the language and path options.
|
|
|
|
## Query image paths
|
|
|
|
The path interface prints one absolute path without downloading anything:
|
|
|
|
```bash
|
|
./download-images.sh --print-path debian13
|
|
./download-images.sh --print-path almalinux10
|
|
./download-images.sh --print-path fedora44
|
|
./download-images.sh --print-path ubuntu2604
|
|
./download-images.sh --print-path virtio-win
|
|
./download-images.sh --print-path windows11
|
|
./download-images.sh --print-path windows11 --iso
|
|
```
|
|
|
|
The Windows target prints the QCOW2 path by default. Add `--iso` to print its
|
|
manually supplied installation ISO path. The QCOW2 is named after the detected
|
|
ISO (for example `Win11_25H2_English_x64_v2.iso` →
|
|
`base-qcow2/Win11_25H2_English_x64_v2.qcow2`); if no ISO is present, an
|
|
existing `*Win*11*x64*.qcow2` (`*Win*11*arm64*.qcow2` on arm64) in `base-qcow2/` is used instead.
|
|
|
|
Download URLs can be overridden with `DEBIAN_IMAGE_URL`, `ALMA_IMAGE_URL`,
|
|
`FEDORA_IMAGE_URL`, `UBUNTU_IMAGE_URL`, `VIRTIO_WIN_ISO_URL`, and
|
|
`OPENSSH_WIN_ZIP_URL`. Each may
|
|
list several mirrors of the same file, separated by whitespace; the Fedora
|
|
image defaults to the TUNA, BFSU and NJU mirrors, and the Ubuntu image to the
|
|
TUNA, NJU and official cloud-images.ubuntu.com mirrors.
|
|
|
|
When [aria2](https://aria2.github.io/) is installed (`aria2c`), an image is
|
|
downloaded from all of its mirrors at once, with a single connection to each
|
|
mirror. Without it, `curl` (or `wget`) tries the mirrors in order. A partial
|
|
download is only resumed with the same downloader and mirror list; otherwise
|
|
it starts over. The Fedora and Ubuntu checksums can be overridden with
|
|
`FEDORA_IMAGE_SHA256` and `UBUNTU_IMAGE_SHA256` when using a custom image.
|
|
|
|
The `openssh-win` target downloads the Win32-OpenSSH zip matching the
|
|
architecture into `tools-iso/`: `OpenSSH-Win64.zip` on amd64 hosts and
|
|
`OpenSSH-ARM64.zip` on arm64 hosts.
|
|
|
|
## Windows installation disk image
|
|
|
|
QEMU's emulated USB CD-ROM reads the Windows ISO at only a few MB/s on macOS.
|
|
`build-windows-install-disk.sh` builds a bootable FAT32 disk image from the ISO
|
|
(`install.wim` is split into `install.swm` parts because of the 4 GB FAT32 file
|
|
limit); attached as an NVMe disk it is read about 15 times faster.
|
|
|
|
```bash
|
|
# macOS (Homebrew), once
|
|
brew install wimlib dosfstools gptfdisk mtools
|
|
# Debian, once (p7zip-full or udisks2 only for non-root use)
|
|
sudo apt install wimtools dosfstools gdisk mtools p7zip-full
|
|
|
|
./build-windows-install-disk.sh [--force] install-iso/Windows11_..._arm64_....iso
|
|
```
|
|
|
|
The output defaults to the ISO path with `.img` in place of `.iso`. The
|
|
image is built without root. The ISO has to be read as UDF (its ISO 9660 side only
|
|
holds a README): macOS uses `hdiutil`, Linux uses `mount -t udf` as root,
|
|
otherwise `udisksctl`, otherwise `7z` extracts it into a temporary directory
|
|
(which needs the free space of the whole ISO). The `*.img` file is ignored by Git.
|
|
|
|
## Windows base image
|
|
|
|
A VM disk can be created as a QCOW2 overlay on a Windows base image, so a new VM
|
|
does not run Windows Setup again. `build-windows-base.sh` makes that base image
|
|
(by default at the path `./download-images.sh --print-path windows11` prints): it
|
|
installs Windows unattended from the ISO in `install-iso/` (it does everything
|
|
itself: answer file in `windows11/seed/`, QEMU hardware in `windows11/qemu/`; this
|
|
directory has no dependency on any other repository). The answer file logs on once
|
|
with the built-in Administrator, which it disables again, and runs
|
|
`sysprep /generalize` as its last step; the script waits for the guest to power
|
|
off and stores the disk read-only. The image is clean and generic, like a cloud
|
|
image: nothing but Windows, with no account, no SSH, no settings, no VirtIO or
|
|
other hardware-specific drivers and no machine identity. Each VM is configured
|
|
on its first boot: Windows reads `Unattend.xml` from the root of an attached
|
|
CD-ROM during its specialize and OOBE passes (a VM launcher can mount such a
|
|
seed ISO when it creates a VM disk).
|
|
It needs `qemu`, `qemu-img`, an ISO tool (`xorriso`, `genisoimage` or `mkisofs`)
|
|
and `socat` or `nc`; there is no SSH to the guest.
|
|
|
|
```bash
|
|
./build-windows-base.sh [--force] [--compress] [--from-img] [--window] [OUTPUT.qcow2]
|
|
```
|
|
|
|
It refuses to replace an existing base image without `--force`, because the
|
|
overlays of existing VMs still point at that file. Build to another `OUTPUT`
|
|
(for example with a version in its name) and point your VM launcher at it. The installation runs in a scratch directory
|
|
`/var/tmp/build-windows-base.*` (set `BASE_BUILD_TMPDIR` to use another parent; about 25 GB are needed).
|