diff --git a/run-windows11.py b/run-windows11.py index 4fddd6d..5471bfc 100755 --- a/run-windows11.py +++ b/run-windows11.py @@ -77,85 +77,44 @@ def normalize_arch(arch): 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" -host_os = platform.system().lower() -if host_os == "darwin" and host_arch == "amd64": - die("amd64 guests are not supported on macOS hosts; use an arm64 image") -qemu_platform_config = script_dir / "qemu" / f"windows11-{host_os}-{host_arch}.ini" - - -def pick_firmware(override, *candidates): - if override: - return Path(override) - for candidate in candidates: - if Path(candidate).is_file(): - return Path(candidate) - return Path(candidates[0]) - - -if host_arch == "arm64": - uefi_code = pick_firmware( - env("WIN11_VM_UEFI_CODE"), - "/usr/share/AAVMF/AAVMF_CODE.fd", - "/usr/share/edk2/aarch64/QEMU_EFI.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"), - "/usr/share/AAVMF/AAVMF_VARS.fd", - "/usr/share/edk2/aarch64/vars-template-pflash.raw", - "/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 install_from_iso = False install_from_img = False download_only = False extra_args = [] +arch_option = None def usage(stream=sys.stdout): stream.write(f"""\ Usage: {Path(sys.argv[0]).name} [--daemon] [--download-only] [--install-from-iso | --install-from-img] - [--state-dir DIR] [--rdp-port PORT] [--ssh-port PORT] [-- QEMU_ARGUMENT ...] + [--arch ARCH] [--state-dir DIR] [--rdp-port PORT] [--ssh-port PORT] [-- QEMU_ARGUMENT ...] If the system disk (DIR/disk.qcow2, see --state-dir) is missing, create it from the Windows 11 QCOW2 base image. 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 loaded with QEMU's -readconfig, followed by qemu/windows11--.ini (os is linux or darwin, arch is amd64 or arm64, chosen from the host system and -architecture or VM_IMAGE_ARCH). Later QEMU arguments override them, e.g. -- -m 4G -smp 4. +architecture, --arch 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, or --install-from-img to install from a disk image built from the ISO. If it exists, start Windows normally without attaching installation media. Options: --daemon Run QEMU in the background - --download-only Download the VirtIO tools ISO if it is missing, then exit - (the Windows ISO and base QCOW2 are supplied manually) + --download-only Download the VirtIO tools ISO and the Windows 11 ISO + (for --arch) if they are missing, then exit; needs + Node.js and Chromium for the latter, see vm-images/README.md + (the base QCOW2 is supplied manually) --install-from-iso Install from ISO when the system disk is missing --install-from-img Like --install-from-iso, but install from the disk image made by vm-images/build-windows-install-disk.sh, which is the ISO path ending in .img instead of .iso. It is attached as an NVMe disk, much faster than the emulated USB CD-ROM, and is never modified + --arch ARCH Guest architecture: arm64 or amd64 (default: the host's; + overrides VM_IMAGE_ARCH). A guest that differs from the + host runs under slow TCG emulation --state-dir DIR Directory for the VM disk, seed ISO and runtime files (default: .vm/windows11; overrides WIN11_VM_STATE_DIR) --rdp-port PORT Host port forwarded to guest RDP (default: 3389; @@ -212,6 +171,12 @@ while args: rdp_port = value else: ssh_port = value + elif arg == "--arch": + if not args: + die(f"{arg} requires an architecture argument.", 2) + arch_option = args.pop(0) + elif arg.startswith("--arch="): + arch_option = arg.split("=", 1)[1] elif arg in ("-h", "--help"): usage() sys.exit(0) @@ -223,11 +188,62 @@ while args: usage(sys.stderr) sys.exit(2) +host_arch = normalize_arch(arch_option or env("VM_IMAGE_ARCH") or platform.machine()) +# The shared image helper and build-seed.sh pick the architecture from here. +os.environ["VM_IMAGE_ARCH"] = host_arch +qemu_binary = "qemu-system-aarch64" if host_arch == "arm64" else "qemu-system-x86_64" +host_os = platform.system().lower() +if host_os == "darwin" and host_arch == "amd64": + die("amd64 guests are not supported on macOS hosts; use an arm64 image") +qemu_platform_config = script_dir / "qemu" / f"windows11-{host_os}-{host_arch}.ini" + + +def pick_firmware(override, *candidates): + if override: + return Path(override) + for candidate in candidates: + if Path(candidate).is_file(): + return Path(candidate) + return Path(candidates[0]) + + +if host_arch == "arm64": + uefi_code = pick_firmware( + env("WIN11_VM_UEFI_CODE"), + "/usr/share/AAVMF/AAVMF_CODE.fd", + "/usr/share/edk2/aarch64/QEMU_EFI.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"), + "/usr/share/AAVMF/AAVMF_VARS.fd", + "/usr/share/edk2/aarch64/vars-template-pflash.raw", + "/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", + ) if install_from_img: install_from_iso = True if download_only: - # The VirtIO tools ISO is the only medium the launcher downloads itself. + # The VirtIO tools ISO and the Windows installation ISO are the media the + # launcher downloads itself; the base QCOW2 is supplied manually. + image_downloader = None if env("WIN11_VM_TOOLS_ISO"): tools_iso = Path(env("WIN11_VM_TOOLS_ISO")) if not tools_iso.is_file(): @@ -240,6 +256,15 @@ if download_only: if not tools_iso.is_file(): die(f"downloader did not create: {tools_iso}") print(f"VirtIO Windows tools ISO is ready: {tools_iso}") + if env("WIN11_VM_INSTALL_ISO"): + install_iso = Path(env("WIN11_VM_INSTALL_ISO")) + if not install_iso.is_file(): + die(f"Windows 11 installation ISO was not found: {install_iso}") + else: + image_downloader = image_downloader or find_image_downloader() + run([str(image_downloader), "windows11"]) + install_iso = Path(capture([str(image_downloader), "--print-path", "windows11", "--iso"])) + print(f"Windows 11 installation ISO is ready: {install_iso}") sys.exit(0) disk_image = Path(env("WIN11_VM_DISK_IMAGE") or state_dir / "disk.qcow2") diff --git a/vm-images b/vm-images index 5e58722..592c729 160000 --- a/vm-images +++ b/vm-images @@ -1 +1 @@ -Subproject commit 5e58722aca71e30feab903cd2c36ba827ef2bad6 +Subproject commit 592c7294c3c90a31ad56e3561ee20f7f446e9c03