Support arm64 hosts in the Windows 11 launcher
Pick qemu-system-aarch64, the AArch64 UEFI firmware and a per-architecture QEMU config (NVMe disk, virtio-net, ramfb, USB installation media) on arm64, render the answer file for the target architecture, and move the q35-only disk and NIC devices out of the shared config. Update vm-images to match arm64 Windows ISO names. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5feed51c61
commit
c13ebc7d11
6 files changed
+124
-49
No files matched your search
@@ -2,3 +2,16 @@
|
|||||||
|
|
||||||
[machine]
|
[machine]
|
||||||
type = "q35"
|
type = "q35"
|
||||||
|
|
||||||
|
# The system disk is attached through IDE so that Windows can boot before the
|
||||||
|
# VirtIO storage driver is installed; the installation media of
|
||||||
|
# --install-from-iso use ide.1 to ide.3.
|
||||||
|
[device "system_disk-dev"]
|
||||||
|
driver = "ide-hd"
|
||||||
|
drive = "system_disk"
|
||||||
|
bus = "ide.0"
|
||||||
|
bootindex = "1"
|
||||||
|
|
||||||
|
[device "nic0"]
|
||||||
|
driver = "e1000e"
|
||||||
|
netdev = "net0"
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# arm64 part of the Windows 11 QEMU configuration; read after windows11.ini.
|
||||||
|
|
||||||
|
[machine]
|
||||||
|
type = "virt"
|
||||||
|
|
||||||
|
# The "virt" machine has no IDE. NVMe and USB mass storage (used by the
|
||||||
|
# installation media of --install-from-iso) both have inbox Windows drivers.
|
||||||
|
[device "system_disk-dev"]
|
||||||
|
driver = "nvme"
|
||||||
|
drive = "system_disk"
|
||||||
|
serial = "win11disk"
|
||||||
|
bootindex = "1"
|
||||||
|
|
||||||
|
# Windows has no inbox driver for virtio-net; the guest tools installed by
|
||||||
|
# Configure-Windows.ps1 provide it.
|
||||||
|
[device "nic0"]
|
||||||
|
driver = "virtio-net-pci"
|
||||||
|
netdev = "net0"
|
||||||
|
|
||||||
|
# Unlike q35, "virt" has no default GPU or keyboard. ramfb is used rather than
|
||||||
|
# virtio-gpu because the firmware's virtio-gpu output freezes at the Windows
|
||||||
|
# boot manager; Windows drives either one through its basic display driver.
|
||||||
|
[device "gpu0"]
|
||||||
|
driver = "ramfb"
|
||||||
|
|
||||||
|
[device "kbd0"]
|
||||||
|
driver = "usb-kbd"
|
||||||
+5
-17
@@ -1,5 +1,5 @@
|
|||||||
# QEMU configuration for the Windows 11 virtual machine. The machine type is in
|
# QEMU configuration for the Windows 11 virtual machine. The machine type is in
|
||||||
# windows11-amd64.ini, which QEMU reads after this file.
|
# windows11-<arch>.ini, which QEMU reads after this file.
|
||||||
#
|
#
|
||||||
# run-windows11.py fills in the file path placeholders below, writes the result
|
# run-windows11.py fills in the file path placeholders below, writes the result
|
||||||
# to <state dir>/qemu.ini and passes it to QEMU with -readconfig. Options that
|
# to <state dir>/qemu.ini and passes it to QEMU with -readconfig. Options that
|
||||||
@@ -37,27 +37,15 @@
|
|||||||
format = "raw"
|
format = "raw"
|
||||||
file = "${uefi_vars}"
|
file = "${uefi_vars}"
|
||||||
|
|
||||||
# System disk. It is attached through IDE so that Windows can boot before the
|
# System disk image; the device that exposes it (and the NIC, which is
|
||||||
# VirtIO storage driver is installed; the installation media of
|
# attached to the user-mode network "net0" that the launcher defines with
|
||||||
# --install-from-iso use ide.1 to ide.3.
|
# -netdev because a config file section cannot hold more than one hostfwd) is in
|
||||||
|
# the architecture file.
|
||||||
[drive "system_disk"]
|
[drive "system_disk"]
|
||||||
if = "none"
|
if = "none"
|
||||||
format = "qcow2"
|
format = "qcow2"
|
||||||
file = "${disk_image}"
|
file = "${disk_image}"
|
||||||
|
|
||||||
[device "system_disk-dev"]
|
|
||||||
driver = "ide-hd"
|
|
||||||
drive = "system_disk"
|
|
||||||
bus = "ide.0"
|
|
||||||
bootindex = "1"
|
|
||||||
|
|
||||||
# The NIC is attached to the user-mode network "net0", which the launcher
|
|
||||||
# defines with -netdev because a config file section cannot hold more than one
|
|
||||||
# hostfwd (Remote Desktop and SSH are both forwarded to the host).
|
|
||||||
[device "nic0"]
|
|
||||||
driver = "e1000e"
|
|
||||||
netdev = "net0"
|
|
||||||
|
|
||||||
# Host/guest clipboard integration through the SPICE vdagent channel.
|
# Host/guest clipboard integration through the SPICE vdagent channel.
|
||||||
[device "virtio_serial"]
|
[device "virtio_serial"]
|
||||||
driver = "virtio-serial-pci"
|
driver = "virtio-serial-pci"
|
||||||
|
|||||||
+63
-30
@@ -67,7 +67,19 @@ rdp_port = env("WIN11_VM_RDP_PORT") or "3389"
|
|||||||
ssh_port = env("WIN11_VM_SSH_PORT") or "2222"
|
ssh_port = env("WIN11_VM_SSH_PORT") or "2222"
|
||||||
qemu_config_template = Path(
|
qemu_config_template = Path(
|
||||||
env("WIN11_VM_QEMU_CONFIG") or script_dir / "qemu" / "windows11.ini")
|
env("WIN11_VM_QEMU_CONFIG") or script_dir / "qemu" / "windows11.ini")
|
||||||
qemu_arch_config = script_dir / "qemu" / "windows11-amd64.ini"
|
|
||||||
|
|
||||||
|
def normalize_arch(arch):
|
||||||
|
if arch in ("arm64", "aarch64"):
|
||||||
|
return "arm64"
|
||||||
|
if arch in ("x86_64", "amd64"):
|
||||||
|
return "amd64"
|
||||||
|
die(f"unsupported architecture: {arch}")
|
||||||
|
|
||||||
|
|
||||||
|
host_arch = normalize_arch(env("VM_IMAGE_ARCH") or platform.machine())
|
||||||
|
qemu_binary = "qemu-system-aarch64" if host_arch == "arm64" else "qemu-system-x86_64"
|
||||||
|
qemu_arch_config = script_dir / "qemu" / f"windows11-{host_arch}.ini"
|
||||||
|
|
||||||
|
|
||||||
def pick_firmware(override, *candidates):
|
def pick_firmware(override, *candidates):
|
||||||
@@ -79,20 +91,36 @@ def pick_firmware(override, *candidates):
|
|||||||
return Path(candidates[0])
|
return Path(candidates[0])
|
||||||
|
|
||||||
|
|
||||||
uefi_code = pick_firmware(
|
if host_arch == "arm64":
|
||||||
env("WIN11_VM_UEFI_CODE"),
|
uefi_code = pick_firmware(
|
||||||
"/usr/share/OVMF/OVMF_CODE_4M.fd",
|
env("WIN11_VM_UEFI_CODE"),
|
||||||
"/usr/share/edk2/ovmf/OVMF_CODE.fd",
|
"/usr/share/AAVMF/AAVMF_CODE.fd",
|
||||||
"/opt/homebrew/share/qemu/edk2-x86_64-code.fd",
|
"/usr/share/edk2/aarch64/QEMU_EFI.fd",
|
||||||
"/usr/local/share/qemu/edk2-x86_64-code.fd",
|
"/opt/homebrew/share/qemu/edk2-aarch64-code.fd",
|
||||||
)
|
"/usr/local/share/qemu/edk2-aarch64-code.fd",
|
||||||
uefi_vars_template = pick_firmware(
|
)
|
||||||
env("WIN11_VM_UEFI_VARS"),
|
uefi_vars_template = pick_firmware(
|
||||||
"/usr/share/OVMF/OVMF_VARS_4M.fd",
|
env("WIN11_VM_UEFI_VARS"),
|
||||||
"/usr/share/edk2/ovmf/OVMF_VARS.fd",
|
"/usr/share/AAVMF/AAVMF_VARS.fd",
|
||||||
"/opt/homebrew/share/qemu/edk2-i386-vars.fd",
|
"/usr/share/edk2/aarch64/vars-template-pflash.raw",
|
||||||
"/usr/local/share/qemu/edk2-i386-vars.fd",
|
"/opt/homebrew/share/qemu/edk2-arm-vars.fd",
|
||||||
)
|
"/usr/local/share/qemu/edk2-arm-vars.fd",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
uefi_code = pick_firmware(
|
||||||
|
env("WIN11_VM_UEFI_CODE"),
|
||||||
|
"/usr/share/OVMF/OVMF_CODE_4M.fd",
|
||||||
|
"/usr/share/edk2/ovmf/OVMF_CODE.fd",
|
||||||
|
"/opt/homebrew/share/qemu/edk2-x86_64-code.fd",
|
||||||
|
"/usr/local/share/qemu/edk2-x86_64-code.fd",
|
||||||
|
)
|
||||||
|
uefi_vars_template = pick_firmware(
|
||||||
|
env("WIN11_VM_UEFI_VARS"),
|
||||||
|
"/usr/share/OVMF/OVMF_VARS_4M.fd",
|
||||||
|
"/usr/share/edk2/ovmf/OVMF_VARS.fd",
|
||||||
|
"/opt/homebrew/share/qemu/edk2-i386-vars.fd",
|
||||||
|
"/usr/local/share/qemu/edk2-i386-vars.fd",
|
||||||
|
)
|
||||||
daemonize = False
|
daemonize = False
|
||||||
install_from_iso = False
|
install_from_iso = False
|
||||||
download_only = False
|
download_only = False
|
||||||
@@ -108,8 +136,8 @@ If the system disk (DIR/disk.qcow2, see --state-dir) is missing, create it from
|
|||||||
the Windows 11 QCOW2 base image.
|
the Windows 11 QCOW2 base image.
|
||||||
The VM hardware (accelerator, memory, CPUs, drives, devices) is defined in
|
The VM hardware (accelerator, memory, CPUs, drives, devices) is defined in
|
||||||
qemu/windows11.ini, which is rendered to qemu.ini in the state directory and
|
qemu/windows11.ini, which is rendered to qemu.ini in the state directory and
|
||||||
loaded with QEMU's -readconfig, followed by qemu/windows11-amd64.ini. Later QEMU
|
loaded with QEMU's -readconfig, followed by qemu/windows11-<arch>.ini (amd64 or arm64, chosen from the host
|
||||||
arguments override them, e.g. -- -m 4G -smp 4.
|
architecture or VM_IMAGE_ARCH). Later QEMU arguments override them, e.g. -- -m 4G -smp 4.
|
||||||
Use --install-from-iso to perform an automatic ISO installation instead.
|
Use --install-from-iso to perform an automatic ISO installation instead.
|
||||||
If it exists, start Windows normally without attaching installation media.
|
If it exists, start Windows normally without attaching installation media.
|
||||||
|
|
||||||
@@ -226,8 +254,8 @@ if not disk_image.is_file():
|
|||||||
base_image = Path(env("WIN11_VM_BASE_IMAGE")
|
base_image = Path(env("WIN11_VM_BASE_IMAGE")
|
||||||
or capture([str(image_downloader), "--print-path", "windows11"]))
|
or capture([str(image_downloader), "--print-path", "windows11"]))
|
||||||
|
|
||||||
if shutil.which("qemu-system-x86_64") is None:
|
if shutil.which(qemu_binary) is None:
|
||||||
die("qemu-system-x86_64 is not installed.")
|
die(f"{qemu_binary} is not installed.")
|
||||||
if installing:
|
if installing:
|
||||||
if shutil.which("qemu-img") is None:
|
if shutil.which("qemu-img") is None:
|
||||||
die("qemu-img is not installed.")
|
die("qemu-img is not installed.")
|
||||||
@@ -259,7 +287,7 @@ if installing_from_iso:
|
|||||||
return source.exists() and source.stat().st_mtime > seed_iso.stat().st_mtime
|
return source.exists() and source.stat().st_mtime > seed_iso.stat().st_mtime
|
||||||
|
|
||||||
if not seed_iso.is_file() or any(newer_than_seed(s) for s in seed_sources):
|
if not seed_iso.is_file() or any(newer_than_seed(s) for s in seed_sources):
|
||||||
run([str(script_dir / "seediso" / "windows11" / "build-seed.sh"), str(seed_iso)])
|
run([str(script_dir / "seediso" / "windows11" / "build-seed.sh"), str(seed_iso), host_arch])
|
||||||
|
|
||||||
if pid_file.is_file():
|
if pid_file.is_file():
|
||||||
old_pid = pid_file.read_text().strip()
|
old_pid = pid_file.read_text().strip()
|
||||||
@@ -336,14 +364,19 @@ qemu_args = [
|
|||||||
# "-display", "none", "-spice", "port=5900,addr=127.0.0.1,disable-ticketing=on,agent-mouse=off", "-device", "virtio-vga",
|
# "-display", "none", "-spice", "port=5900,addr=127.0.0.1,disable-ticketing=on,agent-mouse=off", "-device", "virtio-vga",
|
||||||
]
|
]
|
||||||
if installing_from_iso:
|
if installing_from_iso:
|
||||||
qemu_args += [
|
# q35 has IDE for the installation media; "virt" does not, so use USB.
|
||||||
"-drive", f"file={install_iso},if=none,id=install_media,media=cdrom,readonly=on",
|
for media_id, media_file, boot_index, ide_bus in (
|
||||||
"-device", "ide-cd,drive=install_media,bus=ide.1,bootindex=2",
|
("install_media", install_iso, 2, "ide.1"),
|
||||||
"-drive", f"file={seed_iso},if=none,id=answer_media,media=cdrom,readonly=on",
|
("answer_media", seed_iso, 3, "ide.2"),
|
||||||
"-device", "ide-cd,drive=answer_media,bus=ide.2,bootindex=3",
|
("tools_media", tools_iso, 4, "ide.3")):
|
||||||
"-drive", f"file={tools_iso},if=none,id=tools_media,media=cdrom,readonly=on",
|
device = (f"ide-cd,drive={media_id},bus={ide_bus},bootindex={boot_index}"
|
||||||
"-device", "ide-cd,drive=tools_media,bus=ide.3,bootindex=4",
|
if host_arch == "amd64"
|
||||||
]
|
else f"usb-storage,drive={media_id},bootindex={boot_index}")
|
||||||
|
qemu_args += [
|
||||||
|
"-drive", f"file={media_file},if=none,id={media_id},media=cdrom,readonly=on",
|
||||||
|
"-device", device,
|
||||||
|
]
|
||||||
|
|
||||||
if installing_from_iso:
|
if installing_from_iso:
|
||||||
print("Starting Windows 11 automatic installation.")
|
print("Starting Windows 11 automatic installation.")
|
||||||
elif installing:
|
elif installing:
|
||||||
@@ -357,7 +390,7 @@ print("Account: guofang / password; " + "; ".join(
|
|||||||
if daemonize:
|
if daemonize:
|
||||||
log_file.write_text("")
|
log_file.write_text("")
|
||||||
qemu_args += ["-serial", f"file:{log_file}", "-daemonize", "-pidfile", str(pid_file)]
|
qemu_args += ["-serial", f"file:{log_file}", "-daemonize", "-pidfile", str(pid_file)]
|
||||||
run(["qemu-system-x86_64", *qemu_args, *extra_args])
|
run([qemu_binary, *qemu_args, *extra_args])
|
||||||
print(f"VM started (PID {pid_file.read_text().strip()}); console log: {log_file}")
|
print(f"VM started (PID {pid_file.read_text().strip()}); console log: {log_file}")
|
||||||
else:
|
else:
|
||||||
os.execvp("qemu-system-x86_64", ["qemu-system-x86_64", *qemu_args, *extra_args])
|
os.execvp(qemu_binary, [qemu_binary, *qemu_args, *extra_args])
|
||||||
@@ -3,8 +3,22 @@ set -euo pipefail
|
|||||||
|
|
||||||
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
output="${1:-${script_dir}/seed.iso}"
|
output="${1:-${script_dir}/seed.iso}"
|
||||||
|
arch="${2:-${VM_IMAGE_ARCH:-$(uname -m)}}"
|
||||||
|
case "$arch" in
|
||||||
|
arm64|aarch64) answer_arch=arm64 ;;
|
||||||
|
x86_64|amd64) answer_arch=amd64 ;;
|
||||||
|
*) printf 'Error: unsupported architecture: %s\n' "$arch" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# The answer file's components name the processor architecture, so render a
|
||||||
|
# copy for the target architecture.
|
||||||
|
work_dir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$work_dir"' EXIT
|
||||||
|
sed "s/processorArchitecture=\"amd64\"/processorArchitecture=\"${answer_arch}\"/" \
|
||||||
|
"$script_dir/Autounattend.xml" >"$work_dir/Autounattend.xml"
|
||||||
|
|
||||||
seed_files=(
|
seed_files=(
|
||||||
"$script_dir/Autounattend.xml"
|
"$work_dir/Autounattend.xml"
|
||||||
"$script_dir/Configure-Windows.ps1"
|
"$script_dir/Configure-Windows.ps1"
|
||||||
)
|
)
|
||||||
# Numbered installer scripts (10-Install-Git.bat, 20-..., in install order).
|
# Numbered installer scripts (10-Install-Git.bat, 20-..., in install order).
|
||||||
|
|||||||
+1
-1
Submodule vm-images updated: 6286495d27...4d9e1b43d9.
Reference in new issue
Block a user