From fe8b6f93ff87896c93fa6f9363916705fdb9d476 Mon Sep 17 00:00:00 2001 From: Guofang Date: Wed, 30 Sep 2026 10:24:45 +0800 Subject: [PATCH] Load the Debian 13 VM hardware from QEMU config files Move the Debian launcher's QEMU arguments into qemu/debian13.ini, which the launcher renders with the per-run file paths and passes to QEMU with -readconfig, followed by qemu/debian13-.ini for the machine type and arm64-only devices. Memory, CPU count, SSH port and accelerator are now set in the config files, replacing VM_MEMORY, VM_CPUS and VM_SSH_PORT; VM_QEMU_CONFIG selects a different shared config file. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01YKxXLyiL7XJ3a9e8HmR2my --- README.md | 14 ++++++- qemu/debian13-amd64.ini | 4 ++ qemu/debian13-arm64.ini | 17 +++++++++ qemu/debian13.ini | 69 +++++++++++++++++++++++++++++++++ run-debian13.py | 84 +++++++++++++++++++---------------------- 5 files changed, 141 insertions(+), 47 deletions(-) create mode 100644 qemu/debian13-amd64.ini create mode 100644 qemu/debian13-arm64.ini create mode 100644 qemu/debian13.ini diff --git a/README.md b/README.md index 5692b80..62dfd67 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,18 @@ background while still showing the graphical window: The launcher uses KVM when `/dev/kvm` is accessible, forwards host port `2222` to guest port `22`, and creates `.vm/debian13/disk.qcow2` as a 20G writable overlay with the original qcow2 image as its backing file. The base image therefore stays -unchanged. Resource settings can be overridden with environment variables; -run `./run-debian13.py --help` for the full list. +unchanged. Run `./run-debian13.py --help` for the supported environment +variables. + +The Debian VM hardware (accelerator, 8G memory, 8 CPUs, drives, network) is +defined in the QEMU config file `qemu/debian13.ini`; the machine type and +architecture-specific devices are in `qemu/debian13-amd64.ini` and +`qemu/debian13-arm64.ini`. Edit these files to change the settings. The +launcher fills in the `${...}` file path placeholders of `qemu/debian13.ini`, +writes the result to `.vm/debian13/qemu.ini` and starts QEMU with +`-readconfig` for that file followed by the one for the guest architecture. +QEMU arguments given after `--` override the config files, for example +`./run-debian13.py -- -m 4G -smp 4`. The launchers accept `--state-dir DIR` to keep the VM disk, seed ISO, UEFI variables, PID file and console log in `DIR` diff --git a/qemu/debian13-amd64.ini b/qemu/debian13-amd64.ini new file mode 100644 index 0000000..17272d3 --- /dev/null +++ b/qemu/debian13-amd64.ini @@ -0,0 +1,4 @@ +# amd64 part of the Debian 13 QEMU configuration; read after debian13.ini. + +[machine] + type = "q35" diff --git a/qemu/debian13-arm64.ini b/qemu/debian13-arm64.ini new file mode 100644 index 0000000..68865a7 --- /dev/null +++ b/qemu/debian13-arm64.ini @@ -0,0 +1,17 @@ +# arm64 part of the Debian 13 QEMU configuration; read after debian13.ini. + +[machine] + type = "virt" + +# Unlike q35, the "virt" machine has no default GPU or USB input devices. +[device "gpu0"] + driver = "virtio-gpu-pci" + +[device "usb0"] + driver = "qemu-xhci" + +[device "kbd0"] + driver = "usb-kbd" + +[device "tablet0"] + driver = "usb-tablet" diff --git a/qemu/debian13.ini b/qemu/debian13.ini new file mode 100644 index 0000000..bec8b8b --- /dev/null +++ b/qemu/debian13.ini @@ -0,0 +1,69 @@ +# QEMU configuration for the Debian 13 virtual machine, shared by all +# architectures. The machine type and architecture-specific devices are in +# debian13-.ini, which QEMU reads after this file. +# +# run-debian13.py fills in the file path placeholders below, writes the result +# to /qemu.ini and passes it to QEMU with -readconfig. Options that +# have no -readconfig equivalent (-cpu, -display, daemon mode) are still added +# by the launcher. + +[name] + guest = "debian13" + +# 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}" + +# Writable qcow2 overlay on top of the shared base image. +[drive "disk0"] + if = "none" + format = "qcow2" + file = "${disk_image}" + +[device "disk0-dev"] + driver = "virtio-blk-pci" + drive = "disk0" + +# cloud-init seed ISO. +[drive "seed0"] + if = "none" + format = "raw" + readonly = "on" + file = "${seed_image}" + +[device "seed0-dev"] + driver = "virtio-blk-pci" + drive = "seed0" + +# User-mode networking with guest SSH forwarded to the host. +[netdev "net0"] + type = "user" + hostfwd = "tcp::2222-:22" + +[device "nic0"] + driver = "virtio-net-pci" + netdev = "net0" diff --git a/run-debian13.py b/run-debian13.py index 7caede1..a190757 100755 --- a/run-debian13.py +++ b/run-debian13.py @@ -4,6 +4,7 @@ import os import platform import shutil +import string import subprocess import sys from pathlib import Path @@ -53,9 +54,6 @@ else: state_dir = Path(env("VM_STATE_DIR") or script_dir / ".vm" / "debian13") disk_size = env("VM_DISK_SIZE") or "20G" -memory = env("VM_MEMORY") or "8G" -cpus = env("VM_CPUS") or "8" -ssh_port = env("VM_SSH_PORT") or "2222" def normalize_arch(arch): @@ -68,12 +66,9 @@ def normalize_arch(arch): native_arch = normalize_arch(platform.machine()) host_arch = normalize_arch(env("VM_IMAGE_ARCH") or native_arch) -if host_arch == "arm64": - qemu_binary = "qemu-system-aarch64" - machine_type = "virt" -else: - qemu_binary = "qemu-system-x86_64" - machine_type = "q35" +qemu_binary = "qemu-system-aarch64" if host_arch == "arm64" else "qemu-system-x86_64" +qemu_config_template = Path(env("VM_QEMU_CONFIG") or script_dir / "qemu" / "debian13.ini") +qemu_arch_config = script_dir / "qemu" / f"debian13-{host_arch}.ini" def pick_firmware(override, *candidates): @@ -125,7 +120,10 @@ Usage: {Path(sys.argv[0]).name} [--daemon] [--state-dir DIR] [-- QEMU_ARGUMENT . Start the Debian 13 virtual machine. A writable overlay is created in the state directory (default: .vm/debian13/), so the downloaded base image is never -modified. +modified. The VM hardware (memory, CPUs, SSH port forward, ...) is defined in +qemu/debian13.ini, which is rendered to qemu.ini in the state directory and +loaded with QEMU's -readconfig, followed by qemu/debian13-.ini. Later +QEMU arguments override them, e.g. `-- -m 4G -smp 4`. Options: --daemon Run in the background while keeping the graphical window @@ -134,9 +132,6 @@ Options: -h, --help Show this help Environment variables: - VM_MEMORY Guest memory (default: 8G) - VM_CPUS Virtual CPU count (default: 8) - VM_SSH_PORT Host port forwarded to guest SSH (default: 2222) VM_BASE_IMAGE Base qcow2 image path (default: resolved by shared image helper) VM_SEED_IMAGE cloud-init seed ISO path (default: alongside the VM disk) VM_STATE_DIR Runtime state directory (default: .vm/debian13) @@ -146,6 +141,8 @@ Environment variables: VM_UEFI_CODE UEFI read-only firmware path (auto-detected for the selected architecture) VM_UEFI_VARS UEFI variable-store template path (auto-detected for the selected architecture) VM_DISPLAY QEMU display backend (default: cocoa on macOS, gtk elsewhere) + VM_QEMU_CONFIG QEMU config template rendered into the state directory and + passed to -readconfig (default: qemu/debian13.ini) """) @@ -176,6 +173,7 @@ seed_image = Path(env("VM_SEED_IMAGE") or disk_image.parent / "seed.iso") uefi_vars = state_dir / "OVMF_VARS_4M.fd" pid_file = state_dir / "qemu.pid" log_file = state_dir / "console.log" +qemu_config = state_dir / "qemu.ini" for command_name in (qemu_binary, "qemu-img"): if shutil.which(command_name) is None: @@ -232,42 +230,38 @@ if not disk_image.is_file(): is_macos = platform.system() == "Darwin" display_backend = env("VM_DISPLAY") or ("cocoa" if is_macos 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_flash, + "uefi_vars": uefi_vars, + "disk_image": disk_image, + "seed_image": seed_image, +} +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", "debian13", - "-machine", machine_type, + "-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", "-display", display_backend, - "-m", memory, - "-smp", cpus, - "-drive", f"if=pflash,format=raw,readonly=on,file={uefi_code_flash}", - "-drive", f"if=pflash,format=raw,file={uefi_vars}", - "-drive", f"file={disk_image},if=none,id=disk0,format=qcow2", - "-device", "virtio-blk-pci,drive=disk0", - "-drive", f"file={seed_image},if=none,id=seed0,format=raw,readonly=on", - "-device", "virtio-blk-pci,drive=seed0", - "-nic", f"user,model=virtio-net-pci,hostfwd=tcp::{ssh_port}-:22", ] -if machine_type == "virt": - # Unlike q35, the "virt" machine has no default GPU or USB input devices. - qemu_args += [ - "-device", "virtio-gpu-pci", - "-device", "qemu-xhci", - "-device", "usb-kbd", - "-device", "usb-tablet", - ] - -if is_macos and host_arch == native_arch: - qemu_args += ["-accel", "hvf", "-cpu", "host"] - accelerator = "HVF" -elif host_arch == native_arch and 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" - -print(f"Starting {host_arch} VM with {accelerator} acceleration; " - f"SSH is forwarded to localhost:{ssh_port}.", flush=True) +print(f"Starting {host_arch} VM from {qemu_config_template} " + f"and {qemu_arch_config}.", flush=True) if daemonize: log_file.write_text("")