Replace --window with --headless (VNC); the window is the default. Fill the QEMU config placeholders without nested quotes (bash 3.2 kept them literally), and run QEMU as a background job instead of -daemonize when it shows a window (Cocoa crashes in fork()). 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 boots
|
|
into audit mode (no account and no credentials are needed) and runs
|
|
`sysprep /generalize` there 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] [--headless] [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).
|