Read port forwards from per-VM forwards.conf files

A QEMU config file section cannot hold more than one hostfwd entry, so
the forwards move out of the ini files into qemu/<vm>/forwards.conf, one
"LABEL HOST_PORT GUEST_PORT" rule per line. The new vmforwards.py parses
them, skips rules whose host port is taken and builds the -netdev
argument, which every launcher now passes instead of relying on a
netdev section in the ini or on -nic.

The ini files move to qemu/<vm>/config.ini and qemu/<vm>/<os>-<arch>.ini.
The *_SSH_PORT / *_RDP_PORT variables and --rdp-port / --ssh-port still
replace the host port of the SSH and RDP rules.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GuofangandClaude Sonnet 5.5 committed 2026-10-29 14:55:30 +08:00
1 parent 07a2c6db3e
commit 78f6fd424b
29 files changed
+272 -131

No files matched your search

+21 -15
View File
@@ -3,13 +3,14 @@
import os
import platform
import re
import shutil
import string
import subprocess
import sys
from pathlib import Path
import vmforwards
script_dir = Path(__file__).resolve().parent
env = os.environ.get
@@ -69,11 +70,13 @@ native_arch = normalize_arch(platform.machine())
host_arch = normalize_arch(env("VM_IMAGE_ARCH") or native_arch)
qemu_binary = "qemu-system-aarch64" if host_arch == "arm64" else "qemu-system-x86_64"
qemu_config_template = Path(
env("UBUNTU_VM_QEMU_CONFIG") or script_dir / "qemu" / "ubuntu2604.ini")
env("UBUNTU_VM_QEMU_CONFIG") or script_dir / "qemu" / "ubuntu2604" / "config.ini")
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"ubuntu2604-{host_os}-{host_arch}.ini"
qemu_platform_config = script_dir / "qemu" / "ubuntu2604" / f"{host_os}-{host_arch}.ini"
forwards_file = Path(
env("UBUNTU_VM_FORWARDS") or script_dir / "qemu" / "ubuntu2604" / "forwards.conf")
def pick_firmware(override, *candidates):
@@ -126,11 +129,13 @@ Usage: {Path(sys.argv[0]).name} [--daemon] [--download-only] [--state-dir DIR] [
Start an Ubuntu 26.04 LTS cloud image virtual machine. A writable overlay is
created in the state directory (default: vm/ubuntu2604/), so the downloaded
base image is never modified. The VM hardware (memory, CPUs, SSH port forward,
...) is defined in qemu/ubuntu2604.ini, which is rendered to qemu.ini in the
state directory and loaded with QEMU's -readconfig, followed by
qemu/ubuntu2604-<os>-<arch>.ini (os is linux or darwin, arch is amd64 or arm64). Later QEMU arguments override them, e.g.
base image is never modified. The VM hardware (memory, CPUs, ...) is defined
in qemu/ubuntu2604/config.ini, which is rendered to qemu.ini in the state directory
and loaded with QEMU's -readconfig, followed by
qemu/ubuntu2604/<os>-<arch>.ini (os is linux or darwin, arch is amd64 or arm64). Later QEMU arguments override them, e.g.
`-- -m 4G -smp 4`.
Host port forwards, one "LABEL HOST_PORT GUEST_PORT" rule per line, are
read from qemu/ubuntu2604/forwards.conf.
Options:
--daemon Run in the background while keeping the graphical window
@@ -150,7 +155,8 @@ Environment variables:
UBUNTU_VM_UEFI_VARS UEFI variable-store template path (auto-detected for the selected architecture)
UBUNTU_VM_DISPLAY QEMU display backend (default: cocoa on macOS, gtk elsewhere)
UBUNTU_VM_QEMU_CONFIG QEMU config template rendered into the state directory and
passed to -readconfig (default: qemu/ubuntu2604.ini)
passed to -readconfig (default: qemu/ubuntu2604/config.ini)
UBUNTU_VM_FORWARDS Port forward file (default: qemu/ubuntu2604/forwards.conf)
""")
@@ -266,24 +272,24 @@ except (KeyError, ValueError) as error:
die(f"invalid placeholder in {qemu_config_template}: {error}")
qemu_config.write_text(qemu_config_text)
forwards = vmforwards.usable_forwards(vmforwards.load_forwards(forwards_file))
qemu_args = [
"-readconfig", str(qemu_config),
"-readconfig", str(qemu_platform_config),
# "max" is the host CPU under KVM/HVF and the fullest model under TCG.
"-cpu", "max",
# The config file's [netdev] does not stop QEMU from adding its default
# NIC, a second user network without the SSH port forward.
# 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", vmforwards.netdev_argument(forwards),
"-nic", "none",
"-display", display_backend,
]
print(f"Starting Ubuntu 26.04 ({host_arch}) from {qemu_config_template} "
f"and {qemu_platform_config}.", flush=True)
# The SSH port forward lives in the config file, e.g. hostfwd = "tcp::2225-:22".
ssh_forward = re.search(r'hostfwd\s*=\s*"tcp:[^:"]*:(\d+)-[^:"]*:22"', qemu_config_text)
if ssh_forward:
print(f"SSH is forwarded to localhost:{ssh_forward.group(1)}: "
f"ssh -p {ssh_forward.group(1)} guofang@localhost", flush=True)
vmforwards.print_forwards(forwards)
if daemonize:
log_file.write_text("")