diff --git a/README.md b/README.md index 62dfd67..ce932ef 100644 --- a/README.md +++ b/README.md @@ -111,8 +111,11 @@ On the first boot, cloud-init updates package metadata and installs Fedora's GNOME package group through its native package module, then reboots the guest. Keep the VM running until this initial installation and reboot have completed. -Fedora settings use the `FEDORA_VM_` prefix. Run `./run-fedora44.py --help` -for the complete list of overrides. +Like the Debian VM, the Fedora VM hardware (8G memory, 8 CPUs, drives, SSH +port `2224`) is defined in `qemu/fedora44.ini`, with the machine type and +architecture-specific devices in `qemu/fedora44-amd64.ini` and +`qemu/fedora44-arm64.ini`. Other Fedora settings use the `FEDORA_VM_` prefix. +Run `./run-fedora44.py --help` for the complete list of overrides. ## Windows 11 23H2 diff --git a/qemu/fedora44-amd64.ini b/qemu/fedora44-amd64.ini new file mode 100644 index 0000000..82c356b --- /dev/null +++ b/qemu/fedora44-amd64.ini @@ -0,0 +1,4 @@ +# amd64 part of the Fedora 44 QEMU configuration; read after fedora44.ini. + +[machine] + type = "q35" diff --git a/qemu/fedora44-arm64.ini b/qemu/fedora44-arm64.ini new file mode 100644 index 0000000..d54c4b9 --- /dev/null +++ b/qemu/fedora44-arm64.ini @@ -0,0 +1,17 @@ +# arm64 part of the Fedora 44 QEMU configuration; read after fedora44.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/fedora44.ini b/qemu/fedora44.ini new file mode 100644 index 0000000..e2a7b2b --- /dev/null +++ b/qemu/fedora44.ini @@ -0,0 +1,69 @@ +# QEMU configuration for the Fedora 44 virtual machine, shared by all +# architectures. The machine type and architecture-specific devices are in +# fedora44-.ini, which QEMU reads after this file. +# +# run-fedora44.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 = "fedora44" + +# 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::2224-:22" + +[device "nic0"] + driver = "virtio-net-pci" + netdev = "net0" diff --git a/run-fedora44.py b/run-fedora44.py index a2ad359..1dced1f 100755 --- a/run-fedora44.py +++ b/run-fedora44.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("FEDORA_VM_STATE_DIR") or script_dir / ".vm" / "fedora44") disk_size = env("FEDORA_VM_DISK_SIZE") or "20G" -memory = env("FEDORA_VM_MEMORY") or "8G" -cpus = env("FEDORA_VM_CPUS") or "8" -ssh_port = env("FEDORA_VM_SSH_PORT") or "2224" def normalize_arch(arch): @@ -68,12 +66,10 @@ 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("FEDORA_VM_QEMU_CONFIG") or script_dir / "qemu" / "fedora44.ini") +qemu_arch_config = script_dir / "qemu" / f"fedora44-{host_arch}.ini" def pick_firmware(override, *candidates): @@ -125,7 +121,11 @@ Usage: {Path(sys.argv[0]).name} [--daemon] [--state-dir DIR] [-- QEMU_ARGUMENT . Start a Fedora 44 Cloud Base virtual machine. The launcher obtains a missing base image through the shared image helper and creates a writable overlay -in the state directory, leaving the base image unchanged. +in the state directory, leaving the base image unchanged. The VM hardware +(memory, CPUs, SSH port forward, ...) is defined in qemu/fedora44.ini, which is +rendered to qemu.ini in the state directory and loaded with QEMU's -readconfig, +followed by qemu/fedora44-.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 +134,6 @@ Options: -h, --help Show this help Environment variables: - FEDORA_VM_MEMORY Guest memory (default: 8G) - FEDORA_VM_CPUS Virtual CPU count (default: 8) - FEDORA_VM_SSH_PORT Host port forwarded to guest SSH (default: 2224) FEDORA_VM_BASE_IMAGE Base qcow2 image path (default: resolved by shared image helper) FEDORA_VM_SEED_IMAGE cloud-init seed ISO path (default: alongside the VM disk) FEDORA_VM_STATE_DIR Runtime state directory (default: .vm/fedora44) @@ -146,6 +143,8 @@ Environment variables: FEDORA_VM_UEFI_CODE UEFI read-only firmware path (auto-detected for the selected architecture) FEDORA_VM_UEFI_VARS UEFI variable-store template path (auto-detected for the selected architecture) FEDORA_VM_DISPLAY QEMU display backend (default: cocoa on macOS, gtk elsewhere) + FEDORA_VM_QEMU_CONFIG QEMU config template rendered into the state directory and + passed to -readconfig (default: qemu/fedora44.ini) """) @@ -176,6 +175,7 @@ seed_image = Path(env("FEDORA_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 +232,38 @@ if not disk_image.is_file(): is_macos = platform.system() == "Darwin" display_backend = env("FEDORA_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", "fedora44", - "-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 Fedora 44 ({host_arch}) with {accelerator} acceleration; " - f"SSH is forwarded to localhost:{ssh_port}.", flush=True) +print(f"Starting Fedora 44 ({host_arch}) from {qemu_config_template} " + f"and {qemu_arch_config}.", flush=True) if daemonize: log_file.write_text("")