Load the Windows 11 VM hardware from QEMU config files and add port options
Move the QEMU hardware definition of the Windows 11 VM into qemu/windows11.ini and qemu/windows11-amd64.ini, loaded with -readconfig like the Linux launchers. The network stays in the launcher because a config file section cannot hold two hostfwd entries. Add --rdp-port and --ssh-port, which override the WIN11_VM_*_PORT environment variables. WIN11_VM_MEMORY and WIN11_VM_CPUS are replaced by the config file. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
26baf25172
commit
e1e5a2149c
4 files changed
+179
-29
No files matched your search
@@ -175,6 +175,12 @@ passwordless administrator login. The installer includes the VirtIO drivers, SPI
|
||||
and QEMU Guest Agent. Connect with `ssh guofang@localhost -p 2222`. The launcher
|
||||
exposes the vdagent channel for host/guest clipboard integration and the QEMU Guest Agent at
|
||||
`.vm/windows11/qemu-guest-agent.sock`. Change the password after first login.
|
||||
The Windows VM hardware (accelerator, 8G memory, 8 CPUs, system disk, devices)
|
||||
is defined in `qemu/windows11.ini`, with the machine type in
|
||||
`qemu/windows11-amd64.ini`; the launcher renders the former to
|
||||
`.vm/windows11/qemu.ini` and loads both with `-readconfig`. Only the network
|
||||
and the installation media are still set by the launcher. QEMU arguments after
|
||||
`--` override the config files, for example `./run-windows11.py -- -m 4G -smp 4`.
|
||||
The generated answer-file ISO is stored beside the disk as
|
||||
`.vm/windows11/seed.iso`.
|
||||
|
||||
@@ -183,7 +189,7 @@ To install from the Windows ISO instead, use `./run-windows11.py
|
||||
disk; subsequent starts always boot the existing system disk normally. During
|
||||
ISO installation, the launcher resolves its tools ISO through the shared image
|
||||
helper and downloads it when missing. Override the base QCOW2, source ISO,
|
||||
tools ISO, resources, RDP port, or SSH port with the `WIN11_VM_` environment
|
||||
tools ISO, RDP port, or SSH port with the `WIN11_VM_` environment
|
||||
variables shown by `./run-windows11.py --help`. For example, when the VirtIO ISO
|
||||
is elsewhere:
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# amd64 part of the Windows 11 QEMU configuration; read after windows11.ini.
|
||||
|
||||
[machine]
|
||||
type = "q35"
|
||||
@@ -0,0 +1,104 @@
|
||||
# QEMU configuration for the Windows 11 virtual machine. The machine type is in
|
||||
# windows11-amd64.ini, which QEMU reads after this file.
|
||||
#
|
||||
# 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
|
||||
# have no -readconfig equivalent (-cpu, -display) and the installation media
|
||||
# attached by --install-from-iso are still added by the launcher.
|
||||
|
||||
[name]
|
||||
guest = "windows11"
|
||||
|
||||
# Accelerators are tried in order: KVM on Linux, HVF on macOS, else emulation.
|
||||
[accel]
|
||||
accel = "kvm"
|
||||
|
||||
[accel]
|
||||
accel = "hvf"
|
||||
|
||||
[accel]
|
||||
accel = "tcg"
|
||||
|
||||
[memory]
|
||||
size = "8G"
|
||||
|
||||
[smp-opts]
|
||||
cpus = "8"
|
||||
|
||||
# UEFI firmware: read-only code and the per-VM variable store.
|
||||
[drive "pflash0"]
|
||||
if = "pflash"
|
||||
format = "raw"
|
||||
readonly = "on"
|
||||
file = "${uefi_code}"
|
||||
|
||||
[drive "pflash1"]
|
||||
if = "pflash"
|
||||
format = "raw"
|
||||
file = "${uefi_vars}"
|
||||
|
||||
# System disk. It 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.
|
||||
[drive "system_disk"]
|
||||
if = "none"
|
||||
format = "qcow2"
|
||||
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.
|
||||
[device "virtio_serial"]
|
||||
driver = "virtio-serial-pci"
|
||||
|
||||
[chardev "vdagent"]
|
||||
backend = "qemu-vdagent"
|
||||
name = "vdagent"
|
||||
clipboard = "on"
|
||||
mouse = "off"
|
||||
|
||||
[device "vdagent-port"]
|
||||
driver = "virtserialport"
|
||||
chardev = "vdagent"
|
||||
name = "com.redhat.spice.0"
|
||||
|
||||
# Register the USB tablet after vdagent so QEMU selects it as the active pointer.
|
||||
[device "usb_controller"]
|
||||
driver = "qemu-xhci"
|
||||
|
||||
[device "tablet0"]
|
||||
driver = "usb-tablet"
|
||||
bus = "usb_controller.0"
|
||||
|
||||
# QEMU Guest Agent and the QEMU monitor, both on sockets in the state directory.
|
||||
[chardev "qga"]
|
||||
backend = "socket"
|
||||
path = "${guest_agent_socket}"
|
||||
server = "on"
|
||||
wait = "off"
|
||||
|
||||
[device "qga-port"]
|
||||
driver = "virtserialport"
|
||||
chardev = "qga"
|
||||
name = "org.qemu.guest_agent.0"
|
||||
|
||||
[chardev "monitor"]
|
||||
backend = "socket"
|
||||
path = "${monitor_socket}"
|
||||
server = "on"
|
||||
wait = "off"
|
||||
|
||||
[mon "monitor0"]
|
||||
chardev = "monitor"
|
||||
mode = "readline"
|
||||
+64
-28
@@ -4,6 +4,7 @@
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -50,10 +51,11 @@ def find_image_downloader():
|
||||
|
||||
state_dir = Path(env("WIN11_VM_STATE_DIR") or script_dir / ".vm" / "windows11")
|
||||
disk_size = env("WIN11_VM_DISK_SIZE") or "64G"
|
||||
memory = env("WIN11_VM_MEMORY") or "8G"
|
||||
cpus = env("WIN11_VM_CPUS") or "8"
|
||||
rdp_port = env("WIN11_VM_RDP_PORT") or "3389"
|
||||
ssh_port = env("WIN11_VM_SSH_PORT") or "2222"
|
||||
qemu_config_template = Path(
|
||||
env("WIN11_VM_QEMU_CONFIG") or script_dir / "qemu" / "windows11.ini")
|
||||
qemu_arch_config = script_dir / "qemu" / "windows11-amd64.ini"
|
||||
|
||||
|
||||
def pick_firmware(override, *candidates):
|
||||
@@ -87,10 +89,15 @@ extra_args = []
|
||||
|
||||
def usage(stream=sys.stdout):
|
||||
stream.write(f"""\
|
||||
Usage: {Path(sys.argv[0]).name} [--daemon] [--download-only] [--install-from-iso] [--state-dir DIR] [-- QEMU_ARGUMENT ...]
|
||||
Usage: {Path(sys.argv[0]).name} [--daemon] [--download-only] [--install-from-iso] [--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-amd64.ini. Later QEMU
|
||||
arguments override them, e.g. -- -m 4G -smp 4.
|
||||
Use --install-from-iso to perform an automatic ISO installation instead.
|
||||
If it exists, start Windows normally without attaching installation media.
|
||||
|
||||
@@ -101,14 +108,16 @@ Options:
|
||||
--install-from-iso Install from ISO when the system disk is missing
|
||||
--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;
|
||||
overrides WIN11_VM_RDP_PORT)
|
||||
--ssh-port PORT Host port forwarded to guest SSH (default: 2222;
|
||||
overrides WIN11_VM_SSH_PORT)
|
||||
-h, --help Show this help
|
||||
|
||||
Environment variables:
|
||||
WIN11_VM_BASE_IMAGE Windows 11 base QCOW2 image (default: beside shared images)
|
||||
WIN11_VM_INSTALL_ISO Windows 11 installation ISO used with --install-from-iso
|
||||
WIN11_VM_TOOLS_ISO VirtIO Windows tools ISO (default: resolved by shared image helper)
|
||||
WIN11_VM_MEMORY Guest memory (default: 8G)
|
||||
WIN11_VM_CPUS Virtual CPU count (default: 8)
|
||||
WIN11_VM_RDP_PORT Host RDP port (default: 3389)
|
||||
WIN11_VM_SSH_PORT Host SSH port (default: 2222)
|
||||
WIN11_VM_STATE_DIR Runtime directory (default: .vm/windows11)
|
||||
@@ -117,6 +126,8 @@ Environment variables:
|
||||
WIN11_VM_SEED_ISO Answer-file ISO path (default: alongside the VM disk)
|
||||
WIN11_VM_UEFI_CODE OVMF read-only firmware path (auto-detected if unset)
|
||||
WIN11_VM_UEFI_VARS OVMF variable-store template path (auto-detected if unset)
|
||||
WIN11_VM_QEMU_CONFIG QEMU config template passed to -readconfig
|
||||
(default: qemu/windows11.ini)
|
||||
WIN11_VM_DISPLAY QEMU display backend (default: cocoa on macOS, gtk elsewhere)
|
||||
""")
|
||||
|
||||
@@ -136,6 +147,18 @@ while args:
|
||||
state_dir = Path(args.pop(0))
|
||||
elif arg.startswith("--state-dir="):
|
||||
state_dir = Path(arg.split("=", 1)[1])
|
||||
elif arg in ("--rdp-port", "--ssh-port") or arg.startswith(("--rdp-port=", "--ssh-port=")):
|
||||
name, sep, value = arg.partition("=")
|
||||
if not sep:
|
||||
if not args:
|
||||
die(f"{name} requires a port argument.", 2)
|
||||
value = args.pop(0)
|
||||
if not (value.isascii() and value.isdigit() and 1 <= int(value) <= 65535):
|
||||
die(f"{name} must be a port number from 1 to 65535: {value}", 2)
|
||||
if name == "--rdp-port":
|
||||
rdp_port = value
|
||||
else:
|
||||
ssh_port = value
|
||||
elif arg in ("-h", "--help"):
|
||||
usage()
|
||||
sys.exit(0)
|
||||
@@ -170,6 +193,7 @@ pid_file = state_dir / "qemu.pid"
|
||||
log_file = state_dir / "console.log"
|
||||
monitor_socket = state_dir / "qemu-monitor.sock"
|
||||
guest_agent_socket = state_dir / "qemu-guest-agent.sock"
|
||||
qemu_config = state_dir / "qemu.ini"
|
||||
|
||||
installing = False
|
||||
installing_from_iso = False
|
||||
@@ -249,23 +273,41 @@ if installing:
|
||||
display_backend = env("WIN11_VM_DISPLAY") or (
|
||||
"cocoa" if platform.system() == "Darwin" else "gtk")
|
||||
|
||||
# Most of the VM definition lives in the QEMU config template; fill in the
|
||||
# file paths and hand the rendered file to QEMU with -readconfig.
|
||||
config_values = {
|
||||
"uefi_code": uefi_code,
|
||||
"uefi_vars": uefi_vars,
|
||||
"disk_image": disk_image,
|
||||
"guest_agent_socket": guest_agent_socket,
|
||||
"monitor_socket": monitor_socket,
|
||||
}
|
||||
for key, value in config_values.items():
|
||||
# QEMU's config file parser has no escape syntax for quoted values.
|
||||
if any(char in str(value) for char in '"\n'):
|
||||
die(f"{key} must not contain double quotes or newlines: {value}")
|
||||
for config_file in (qemu_config_template, qemu_arch_config):
|
||||
if not config_file.is_file():
|
||||
die(f"QEMU config file was not found: {config_file}")
|
||||
try:
|
||||
qemu_config_text = string.Template(qemu_config_template.read_text()).substitute(
|
||||
{key: str(value) for key, value in config_values.items()})
|
||||
except (KeyError, ValueError) as error:
|
||||
die(f"invalid placeholder in {qemu_config_template}: {error}")
|
||||
qemu_config.write_text(qemu_config_text)
|
||||
|
||||
qemu_args = [
|
||||
"-name", "windows11", "-machine", "q35", "-m", memory, "-smp", cpus,
|
||||
"-readconfig", str(qemu_config),
|
||||
"-readconfig", str(qemu_arch_config),
|
||||
# "max" is the host CPU under KVM/HVF and the fullest model under TCG.
|
||||
"-cpu", "max",
|
||||
# A config file section cannot hold two hostfwd entries, so the network
|
||||
# that the NIC in the config file uses is defined here. -nic none stops
|
||||
# QEMU from adding its default NIC as well.
|
||||
"-netdev", f"user,id=net0,hostfwd=tcp::{rdp_port}-:3389,hostfwd=tcp::{ssh_port}-:22",
|
||||
"-nic", "none",
|
||||
"-display", display_backend,
|
||||
# "-display", "none", "-spice", "port=5900,addr=127.0.0.1,disable-ticketing=on,agent-mouse=off", "-device", "virtio-vga",
|
||||
"-drive", f"if=pflash,format=raw,readonly=on,file={uefi_code}",
|
||||
"-drive", f"if=pflash,format=raw,file={uefi_vars}",
|
||||
"-drive", f"file={disk_image},if=none,id=system_disk,format=qcow2",
|
||||
"-device", "ide-hd,drive=system_disk,bus=ide.0,bootindex=1",
|
||||
"-nic", f"user,model=e1000e,hostfwd=tcp::{rdp_port}-:3389,hostfwd=tcp::{ssh_port}-:22",
|
||||
"-device", "virtio-serial-pci,id=virtio_serial",
|
||||
"-chardev", "qemu-vdagent,id=vdagent,name=vdagent,clipboard=on,mouse=off",
|
||||
"-device", "virtserialport,chardev=vdagent,name=com.redhat.spice.0",
|
||||
# Register the USB tablet after vdagent so QEMU selects it as the active pointer.
|
||||
"-device", "qemu-xhci,id=usb_controller", "-device", "usb-tablet,bus=usb_controller.0",
|
||||
"-chardev", f"socket,path={guest_agent_socket},server=on,wait=off,id=qga",
|
||||
"-device", "virtserialport,chardev=qga,name=org.qemu.guest_agent.0",
|
||||
"-monitor", f"unix:{monitor_socket},server=on,wait=off",
|
||||
]
|
||||
if installing_from_iso:
|
||||
qemu_args += [
|
||||
@@ -276,18 +318,12 @@ if installing_from_iso:
|
||||
"-drive", f"file={tools_iso},if=none,id=tools_media,media=cdrom,readonly=on",
|
||||
"-device", "ide-cd,drive=tools_media,bus=ide.3,bootindex=4",
|
||||
]
|
||||
if os.access("/dev/kvm", os.R_OK | os.W_OK):
|
||||
qemu_args += ["-enable-kvm", "-cpu", "host"]
|
||||
accelerator = "KVM"
|
||||
else:
|
||||
qemu_args += ["-accel", "tcg", "-cpu", "max"]
|
||||
accelerator = "TCG"
|
||||
if installing_from_iso:
|
||||
print(f"Starting Windows 11 automatic installation with {accelerator} acceleration.")
|
||||
print("Starting Windows 11 automatic installation.")
|
||||
elif installing:
|
||||
print(f"Starting Windows 11 from the QCOW2 base image with {accelerator} acceleration.")
|
||||
print("Starting Windows 11 from the QCOW2 base image.")
|
||||
else:
|
||||
print(f"Starting Windows 11 normally with {accelerator} acceleration.")
|
||||
print("Starting Windows 11 normally.")
|
||||
print(f"Account: guofang / password; RDP: localhost:{rdp_port}; SSH: localhost:{ssh_port}",
|
||||
flush=True)
|
||||
|
||||
|
||||
Reference in new issue
Block a user