Compare commits
10
Commits
fccc7986d9
...
2a8d5a1bed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a8d5a1bed | ||
|
|
a81f0f6e81 | ||
|
|
bea0dd4d70 | ||
|
|
83df64042c | ||
|
|
78f6fd424b | ||
|
|
07a2c6db3e | ||
|
|
887da87340 | ||
|
|
2b237bb8b3 | ||
|
|
d2fe47c911 | ||
|
|
72af276309 |
No files matched your search
+1
-1
@@ -1,3 +1,3 @@
|
||||
[submodule "vm-images"]
|
||||
path = vm-images
|
||||
url = https://wangguofang.net/git/vm-images.git
|
||||
url = https://wangguofang.net/gitea/guofang/vm-images.git
|
||||
@@ -26,12 +26,12 @@ original qcow2 image as its backing file. The base image therefore stays
|
||||
unchanged. Run `./run-debian13.py --help` for the supported environment
|
||||
variables.
|
||||
|
||||
The Debian VM hardware (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-<os>-<arch>.ini`
|
||||
The Debian VM hardware (8G memory, 8 CPUs, drives) is
|
||||
defined in the QEMU config file `qemu/debian13/config.ini`; the machine type and
|
||||
architecture-specific devices are in `qemu/debian13/<os>-<arch>.ini`
|
||||
(os `linux` or `darwin`, arch `amd64` or `arm64`), together with the
|
||||
accelerators. Edit these files to change the settings. The
|
||||
launcher fills in the `${...}` file path placeholders of `qemu/debian13.ini`,
|
||||
launcher fills in the `${...}` file path placeholders of `qemu/debian13/config.ini`,
|
||||
writes the result to `vm/debian13/qemu.ini` and starts QEMU with
|
||||
`-readconfig` for that file followed by the one for the host system and guest architecture.
|
||||
QEMU arguments given after `--` override the config files, for example
|
||||
@@ -122,9 +122,9 @@ 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.
|
||||
|
||||
Like the Debian VM, the Fedora VM hardware (8G memory, 8 CPUs, drives, SSH
|
||||
port `2224`) is defined in `qemu/fedora44.ini`, with the accelerators, machine type and
|
||||
platform-specific devices in `qemu/fedora44-<os>-<arch>.ini`. Other Fedora settings use the `FEDORA_VM_` prefix.
|
||||
Like the Debian VM, the Fedora VM hardware (8G memory, 8 CPUs, drives) is
|
||||
defined in `qemu/fedora44/config.ini`, with the accelerators, machine type and
|
||||
platform-specific devices in `qemu/fedora44/<os>-<arch>.ini`. Other Fedora settings use the `FEDORA_VM_` prefix.
|
||||
Run `./run-fedora44.py --help` for the complete list of overrides.
|
||||
|
||||
## Ubuntu 26.04
|
||||
@@ -150,9 +150,9 @@ On the first boot, cloud-init installs `ubuntu-desktop-minimal` (GNOME) and
|
||||
guest. Keep the VM running until this initial installation and reboot have
|
||||
completed.
|
||||
|
||||
The VM hardware (8G memory, 8 CPUs, drives, SSH port `2225`) is defined in
|
||||
`qemu/ubuntu2604.ini`, with the accelerators, machine type and platform-specific
|
||||
devices in `qemu/ubuntu2604-<os>-<arch>.ini`. Other Ubuntu
|
||||
The VM hardware (8G memory, 8 CPUs, drives) is defined in
|
||||
`qemu/ubuntu2604/config.ini`, with the accelerators, machine type and platform-specific
|
||||
devices in `qemu/ubuntu2604/<os>-<arch>.ini`. Other Ubuntu
|
||||
settings use the `UBUNTU_VM_` prefix. Run `./run-ubuntu2604.py --help` for the
|
||||
complete list of overrides.
|
||||
|
||||
@@ -168,7 +168,7 @@ from the shared Windows 11 QCOW2 base image:
|
||||
It creates a persistent 64G disk at `vm/windows11/disk.qcow2`, automatically
|
||||
partitions it, completes OOBE, creates the local administrator `guofang` with
|
||||
the initial password `wang`, and enables Remote Desktop on
|
||||
`localhost:3389`. During the first login it also silently installs the VirtIO
|
||||
`0.0.0.0:3389` (all host interfaces; connect to `localhost:3389` on the host itself). During the first login it also silently installs the VirtIO
|
||||
drivers (the guest tools installer on amd64, the Arm64 driver packages from the
|
||||
VirtIO ISO with `pnputil` on arm64), without which the guest has no network on
|
||||
arm64. The installer includes the VirtIO drivers, SPICE vdagent, and QEMU Guest
|
||||
@@ -182,10 +182,10 @@ seed ISO also carries the host's SSH public key (`$SSH_PUBLIC_KEY_FILE`, else th
|
||||
first of `~/.ssh/id_ed25519.pub`, `id_ecdsa.pub`, `id_rsa.pub`) for passwordless
|
||||
administrator login.
|
||||
The Windows VM hardware (8G memory, 8 CPUs, system disk, devices)
|
||||
is defined in `qemu/windows11.ini`, with the accelerators and machine type in
|
||||
`qemu/windows11-<os>-<arch>.ini`; the launcher renders the former to
|
||||
is defined in `qemu/windows11/config.ini`, with the accelerators and machine type in
|
||||
`qemu/windows11/<os>-<arch>.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
|
||||
(see Port forwards) 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`.
|
||||
@@ -202,3 +202,24 @@ is elsewhere:
|
||||
```bash
|
||||
WIN11_VM_TOOLS_ISO=/custom/path/virtio-win.iso ./run-windows11.py --install-from-iso
|
||||
```
|
||||
|
||||
## Port forwards
|
||||
|
||||
The host ports forwarded to the guest are not in the QEMU config files (a
|
||||
config file section cannot hold more than one `hostfwd`) but in one file per
|
||||
VM, `qemu/<vm>/forwards.conf`, which the launcher turns into the `-netdev`
|
||||
argument. Each line is `LABEL HOST_PORT GUEST_PORT`; `#` starts a comment:
|
||||
|
||||
```
|
||||
# qemu/windows11/forwards.conf
|
||||
RDP 3389 3389
|
||||
SSH 2222 22
|
||||
```
|
||||
|
||||
Add a line to forward another port. A rule whose host port is already in use
|
||||
is skipped with a warning. `ALMA_VM_FORWARDS`, `FEDORA_VM_FORWARDS`,
|
||||
`UBUNTU_VM_FORWARDS`, `VM_FORWARDS` (Debian) and `WIN11_VM_FORWARDS` select
|
||||
another file. `ALMA_VM_SSH_PORT`, `WIN11_VM_RDP_PORT`, `WIN11_VM_SSH_PORT`
|
||||
and `--rdp-port`/`--ssh-port` still replace the host port of the rule with the
|
||||
label `SSH` or `RDP`. The launchers import `vmforwards.py` from the repository
|
||||
root.
|
||||
@@ -0,0 +1,2 @@
|
||||
# Host ports forwarded to the guest: LABEL HOST_PORT GUEST_PORT
|
||||
SSH 2223 22
|
||||
@@ -1,6 +1,6 @@
|
||||
# QEMU configuration for the Debian 13 virtual machine, shared by all
|
||||
# architectures. The accelerators, machine type and
|
||||
# platform-specific devices are in debian13-<os>-<arch>.ini (os is linux or darwin),
|
||||
# platform-specific devices are in <os>-<arch>.ini (os is linux or darwin),
|
||||
# which QEMU reads after this file.
|
||||
#
|
||||
# run-debian13.py fills in the file path placeholders below, writes the result
|
||||
@@ -50,10 +50,9 @@
|
||||
driver = "virtio-blk-pci"
|
||||
drive = "seed0"
|
||||
|
||||
# User-mode networking with guest SSH forwarded to the host.
|
||||
[netdev "net0"]
|
||||
type = "user"
|
||||
hostfwd = "tcp::2222-:22"
|
||||
# The NIC is attached to the user-mode network "net0", which the launcher
|
||||
# defines with -netdev from qemu/debian13/forwards.conf because a config file
|
||||
# section cannot hold more than one hostfwd.
|
||||
|
||||
[device "nic0"]
|
||||
driver = "virtio-net-pci"
|
||||
@@ -1,4 +1,4 @@
|
||||
# macOS host, arm64 part of the Debian 13 QEMU configuration; read after debian13.ini.
|
||||
# macOS host, arm64 part of the Debian 13 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: HVF on macOS, else emulation.
|
||||
[accel]
|
||||
@@ -0,0 +1,2 @@
|
||||
# Host ports forwarded to the guest: LABEL HOST_PORT GUEST_PORT
|
||||
SSH 2222 22
|
||||
@@ -1,4 +1,4 @@
|
||||
# Linux host, amd64 part of the Debian 13 QEMU configuration; read after debian13.ini.
|
||||
# Linux host, amd64 part of the Debian 13 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: KVM on Linux, else emulation.
|
||||
[accel]
|
||||
@@ -1,4 +1,4 @@
|
||||
# Linux host, arm64 part of the Debian 13 QEMU configuration; read after debian13.ini.
|
||||
# Linux host, arm64 part of the Debian 13 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: KVM on Linux, else emulation.
|
||||
[accel]
|
||||
@@ -1,6 +1,6 @@
|
||||
# QEMU configuration for the Fedora 44 virtual machine, shared by all
|
||||
# architectures. The accelerators, machine type and
|
||||
# platform-specific devices are in fedora44-<os>-<arch>.ini (os is linux or darwin),
|
||||
# platform-specific devices are in <os>-<arch>.ini (os is linux or darwin),
|
||||
# which QEMU reads after this file.
|
||||
#
|
||||
# run-fedora44.py fills in the file path placeholders below, writes the result
|
||||
@@ -50,10 +50,9 @@
|
||||
driver = "virtio-blk-pci"
|
||||
drive = "seed0"
|
||||
|
||||
# User-mode networking with guest SSH forwarded to the host.
|
||||
[netdev "net0"]
|
||||
type = "user"
|
||||
hostfwd = "tcp::2224-:22"
|
||||
# The NIC is attached to the user-mode network "net0", which the launcher
|
||||
# defines with -netdev from qemu/fedora44/forwards.conf because a config file
|
||||
# section cannot hold more than one hostfwd.
|
||||
|
||||
[device "nic0"]
|
||||
driver = "virtio-net-pci"
|
||||
@@ -1,4 +1,4 @@
|
||||
# macOS host, arm64 part of the Fedora 44 QEMU configuration; read after fedora44.ini.
|
||||
# macOS host, arm64 part of the Fedora 44 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: HVF on macOS, else emulation.
|
||||
[accel]
|
||||
@@ -0,0 +1,2 @@
|
||||
# Host ports forwarded to the guest: LABEL HOST_PORT GUEST_PORT
|
||||
SSH 2224 22
|
||||
@@ -1,4 +1,4 @@
|
||||
# Linux host, amd64 part of the Fedora 44 QEMU configuration; read after fedora44.ini.
|
||||
# Linux host, amd64 part of the Fedora 44 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: KVM on Linux, else emulation.
|
||||
[accel]
|
||||
@@ -1,4 +1,4 @@
|
||||
# Linux host, arm64 part of the Fedora 44 QEMU configuration; read after fedora44.ini.
|
||||
# Linux host, arm64 part of the Fedora 44 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: KVM on Linux, else emulation.
|
||||
[accel]
|
||||
@@ -1,6 +1,6 @@
|
||||
# QEMU configuration for the Ubuntu 26.04 virtual machine, shared by all
|
||||
# architectures. The accelerators, machine type and
|
||||
# platform-specific devices are in ubuntu2604-<os>-<arch>.ini (os is linux or darwin),
|
||||
# platform-specific devices are in <os>-<arch>.ini (os is linux or darwin),
|
||||
# which QEMU reads after this file.
|
||||
#
|
||||
# run-ubuntu2604.py fills in the file path placeholders below, writes the result
|
||||
@@ -50,10 +50,9 @@
|
||||
driver = "virtio-blk-pci"
|
||||
drive = "seed0"
|
||||
|
||||
# User-mode networking with guest SSH forwarded to the host.
|
||||
[netdev "net0"]
|
||||
type = "user"
|
||||
hostfwd = "tcp::2225-:22"
|
||||
# The NIC is attached to the user-mode network "net0", which the launcher
|
||||
# defines with -netdev from qemu/ubuntu2604/forwards.conf because a config file
|
||||
# section cannot hold more than one hostfwd.
|
||||
|
||||
[device "nic0"]
|
||||
driver = "virtio-net-pci"
|
||||
@@ -1,4 +1,4 @@
|
||||
# macOS host, arm64 part of the Ubuntu 26.04 QEMU configuration; read after ubuntu2604.ini.
|
||||
# macOS host, arm64 part of the Ubuntu 26.04 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: HVF on macOS, else emulation.
|
||||
[accel]
|
||||
@@ -0,0 +1,2 @@
|
||||
# Host ports forwarded to the guest: LABEL HOST_PORT GUEST_PORT
|
||||
SSH 2225 22
|
||||
@@ -1,4 +1,4 @@
|
||||
# Linux host, amd64 part of the Ubuntu 26.04 QEMU configuration; read after ubuntu2604.ini.
|
||||
# Linux host, amd64 part of the Ubuntu 26.04 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: KVM on Linux, else emulation.
|
||||
[accel]
|
||||
@@ -1,4 +1,4 @@
|
||||
# Linux host, arm64 part of the Ubuntu 26.04 QEMU configuration; read after ubuntu2604.ini.
|
||||
# Linux host, arm64 part of the Ubuntu 26.04 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: KVM on Linux, else emulation.
|
||||
[accel]
|
||||
@@ -1,5 +1,5 @@
|
||||
# QEMU configuration for the Windows 11 virtual machine. The accelerators and machine type
|
||||
# are in windows11-<os>-<arch>.ini (os is linux or darwin), which QEMU reads after
|
||||
# are in <os>-<arch>.ini (os is linux or darwin), which QEMU reads after
|
||||
# this file.
|
||||
#
|
||||
# run-windows11.py fills in the file path placeholders below, writes the result
|
||||
@@ -30,8 +30,8 @@
|
||||
|
||||
# System disk image; the device that exposes it (and the NIC, which is
|
||||
# attached to the user-mode network "net0" that the launcher defines with
|
||||
# -netdev because a config file section cannot hold more than one hostfwd) is in
|
||||
# the architecture file.
|
||||
# -netdev from qemu/windows11/forwards.conf because a config file section cannot hold
|
||||
# more than one hostfwd) is in the architecture file.
|
||||
[drive "system_disk"]
|
||||
if = "none"
|
||||
format = "qcow2"
|
||||
@@ -1,4 +1,4 @@
|
||||
# macOS host, arm64 part of the Windows 11 QEMU configuration; read after windows11.ini.
|
||||
# macOS host, arm64 part of the Windows 11 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: HVF on macOS, else emulation.
|
||||
[accel]
|
||||
@@ -7,13 +7,10 @@
|
||||
[accel]
|
||||
accel = "tcg"
|
||||
|
||||
# With the HVF in-kernel GICv3, QEMU's usb-storage races on the status packet
|
||||
# and Windows Setup stalls 10 s after every burst of reads (utmapp/UTM#7970),
|
||||
# so use the emulated interrupt controller; upstream QEMU only gets the fix
|
||||
# from the UTM fork.
|
||||
# run-windows11.py adds kernel-irqchip=off unless installing with
|
||||
# --install-from-img (see there).
|
||||
[machine]
|
||||
type = "virt"
|
||||
kernel-irqchip = "off"
|
||||
|
||||
# The "virt" machine has no IDE. NVMe and USB mass storage (used by the
|
||||
# installation media of --install-from-iso) both have inbox Windows drivers.
|
||||
@@ -0,0 +1,6 @@
|
||||
# Host ports forwarded to the guest: LABEL HOST_PORT GUEST_PORT
|
||||
# --rdp-port and --ssh-port (or WIN11_VM_RDP_PORT and WIN11_VM_SSH_PORT)
|
||||
# replace the host port of the RDP and SSH rules.
|
||||
RDP 3389 3389
|
||||
SSH 2222 22
|
||||
DSH 3080 3080
|
||||
@@ -1,4 +1,4 @@
|
||||
# Linux host, amd64 part of the Windows 11 QEMU configuration; read after windows11.ini.
|
||||
# Linux host, amd64 part of the Windows 11 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: KVM on Linux, else emulation.
|
||||
[accel]
|
||||
@@ -1,4 +1,4 @@
|
||||
# Linux host, arm64 part of the Windows 11 QEMU configuration; read after windows11.ini.
|
||||
# Linux host, arm64 part of the Windows 11 QEMU configuration; read after config.ini.
|
||||
|
||||
# Accelerators are tried in order: KVM on Linux, else emulation.
|
||||
[accel]
|
||||
+17
-5
@@ -8,6 +8,8 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import vmforwards
|
||||
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
env = os.environ.get
|
||||
|
||||
@@ -55,7 +57,9 @@ state_dir = Path(env("ALMA_VM_STATE_DIR") or script_dir / "vm" / "almalinux10")
|
||||
disk_size = env("ALMA_VM_DISK_SIZE") or "20G"
|
||||
memory = env("ALMA_VM_MEMORY") or "8G"
|
||||
cpus = env("ALMA_VM_CPUS") or "8"
|
||||
ssh_port = env("ALMA_VM_SSH_PORT") or "2223"
|
||||
ssh_port = env("ALMA_VM_SSH_PORT")
|
||||
forwards_file = Path(
|
||||
env("ALMA_VM_FORWARDS") or script_dir / "qemu" / "almalinux10" / "forwards.conf")
|
||||
|
||||
|
||||
def normalize_arch(arch):
|
||||
@@ -127,6 +131,8 @@ Usage: {Path(sys.argv[0]).name} [--daemon] [--download-only] [--state-dir DIR] [
|
||||
Start an AlmaLinux 10 Generic Cloud virtual machine. The launcher obtains a
|
||||
missing base image through ../vm-images/download-images.sh and creates a
|
||||
writable overlay in the state directory, leaving the base image unchanged.
|
||||
Host port forwards, one "LABEL HOST_PORT GUEST_PORT" rule per line, are read
|
||||
from qemu/almalinux10/forwards.conf.
|
||||
|
||||
Options:
|
||||
--daemon Run in the background while keeping the graphical window
|
||||
@@ -138,7 +144,9 @@ Options:
|
||||
Environment variables:
|
||||
ALMA_VM_MEMORY Guest memory (default: 8G)
|
||||
ALMA_VM_CPUS Virtual CPU count (default: 8)
|
||||
ALMA_VM_SSH_PORT Host port forwarded to guest SSH (default: 2223)
|
||||
ALMA_VM_SSH_PORT Host port of the SSH rule in the port forward file
|
||||
(default: from the file, 2223)
|
||||
ALMA_VM_FORWARDS Port forward file (default: qemu/almalinux10/forwards.conf)
|
||||
ALMA_VM_BASE_IMAGE Base qcow2 image path (default: resolved by shared image helper)
|
||||
ALMA_VM_SEED_IMAGE cloud-init seed ISO path (default: alongside the VM disk)
|
||||
ALMA_VM_STATE_DIR Runtime state directory (default: vm/almalinux10)
|
||||
@@ -240,6 +248,9 @@ if not disk_image.is_file():
|
||||
is_macos = platform.system() == "Darwin"
|
||||
display_backend = env("ALMA_VM_DISPLAY") or ("cocoa" if is_macos else "gtk")
|
||||
|
||||
forwards = vmforwards.usable_forwards(vmforwards.load_forwards(
|
||||
forwards_file, {"SSH": ssh_port} if ssh_port else None))
|
||||
|
||||
qemu_args = [
|
||||
"-name", "almalinux10",
|
||||
"-machine", machine_type,
|
||||
@@ -252,7 +263,8 @@ qemu_args = [
|
||||
"-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",
|
||||
"-netdev", vmforwards.netdev_argument(forwards),
|
||||
"-device", "virtio-net-pci,netdev=net0",
|
||||
]
|
||||
|
||||
if machine_type == "virt":
|
||||
@@ -274,8 +286,8 @@ else:
|
||||
qemu_args += ["-accel", "tcg", "-cpu", "max"]
|
||||
accelerator = "TCG"
|
||||
|
||||
print(f"Starting AlmaLinux 10 ({host_arch}) with {accelerator} acceleration; "
|
||||
f"SSH is forwarded to localhost:{ssh_port}.", flush=True)
|
||||
print(f"Starting AlmaLinux 10 ({host_arch}) with {accelerator} acceleration.", flush=True)
|
||||
vmforwards.print_forwards(forwards)
|
||||
|
||||
if daemonize:
|
||||
log_file.write_text("")
|
||||
|
||||
+20
-14
@@ -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
|
||||
|
||||
@@ -68,11 +69,13 @@ def normalize_arch(arch):
|
||||
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("VM_QEMU_CONFIG") or script_dir / "qemu" / "debian13.ini")
|
||||
qemu_config_template = Path(env("VM_QEMU_CONFIG") or script_dir / "qemu" / "debian13" / "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"debian13-{host_os}-{host_arch}.ini"
|
||||
qemu_platform_config = script_dir / "qemu" / "debian13" / f"{host_os}-{host_arch}.ini"
|
||||
forwards_file = Path(
|
||||
env("VM_FORWARDS") or script_dir / "qemu" / "debian13" / "forwards.conf")
|
||||
|
||||
|
||||
def pick_firmware(override, *candidates):
|
||||
@@ -125,10 +128,12 @@ Usage: {Path(sys.argv[0]).name} [--daemon] [--download-only] [--state-dir DIR] [
|
||||
|
||||
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. 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-<os>-<arch>.ini (os is linux or darwin, arch is amd64 or arm64). Later
|
||||
modified. The VM hardware (memory, CPUs, ...) is defined in
|
||||
qemu/debian13/config.ini, which is rendered to qemu.ini in the state directory and
|
||||
loaded with QEMU's -readconfig, followed by qemu/debian13/<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/debian13/forwards.conf.
|
||||
|
||||
Options:
|
||||
--daemon Run in the background while keeping the graphical window
|
||||
@@ -148,7 +153,8 @@ Environment variables:
|
||||
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)
|
||||
passed to -readconfig (default: qemu/debian13/config.ini)
|
||||
VM_FORWARDS Port forward file (default: qemu/debian13/forwards.conf)
|
||||
""")
|
||||
|
||||
|
||||
@@ -264,24 +270,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 {host_arch} VM from {qemu_config_template} "
|
||||
f"and {qemu_platform_config}.", flush=True)
|
||||
# The SSH port forward lives in the config file, e.g. hostfwd = "tcp::2222-: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("")
|
||||
|
||||
+19
-13
@@ -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("FEDORA_VM_QEMU_CONFIG") or script_dir / "qemu" / "fedora44.ini")
|
||||
env("FEDORA_VM_QEMU_CONFIG") or script_dir / "qemu" / "fedora44" / "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"fedora44-{host_os}-{host_arch}.ini"
|
||||
qemu_platform_config = script_dir / "qemu" / "fedora44" / f"{host_os}-{host_arch}.ini"
|
||||
forwards_file = Path(
|
||||
env("FEDORA_VM_FORWARDS") or script_dir / "qemu" / "fedora44" / "forwards.conf")
|
||||
|
||||
|
||||
def pick_firmware(override, *candidates):
|
||||
@@ -127,10 +130,12 @@ Usage: {Path(sys.argv[0]).name} [--daemon] [--download-only] [--state-dir DIR] [
|
||||
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. The VM hardware
|
||||
(memory, CPUs, SSH port forward, ...) is defined in qemu/fedora44.ini, which is
|
||||
(memory, CPUs, ...) is defined in qemu/fedora44/config.ini, which is
|
||||
rendered to qemu.ini in the state directory and loaded with QEMU's -readconfig,
|
||||
followed by qemu/fedora44-<os>-<arch>.ini (os is linux or darwin, arch is amd64 or arm64). Later QEMU arguments override them, e.g.
|
||||
followed by qemu/fedora44/<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/fedora44/forwards.conf.
|
||||
|
||||
Options:
|
||||
--daemon Run in the background while keeping the graphical window
|
||||
@@ -150,7 +155,8 @@ Environment variables:
|
||||
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)
|
||||
passed to -readconfig (default: qemu/fedora44/config.ini)
|
||||
FEDORA_VM_FORWARDS Port forward file (default: qemu/fedora44/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 Fedora 44 ({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::2224-: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("")
|
||||
|
||||
+21
-15
@@ -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("")
|
||||
|
||||
+49
-43
@@ -4,12 +4,13 @@
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import socket
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import vmforwards
|
||||
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
env = os.environ.get
|
||||
|
||||
@@ -34,17 +35,6 @@ def capture(command):
|
||||
return result.stdout.rstrip("\n")
|
||||
|
||||
|
||||
def port_in_use(port):
|
||||
"""True if a host TCP port cannot be bound, as QEMU does for a hostfwd rule."""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
||||
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
probe.bind(("", int(port)))
|
||||
except OSError:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_executable(path):
|
||||
return path.is_file() and os.access(path, os.X_OK)
|
||||
|
||||
@@ -63,10 +53,12 @@ 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"
|
||||
rdp_port = env("WIN11_VM_RDP_PORT") or "3389"
|
||||
ssh_port = env("WIN11_VM_SSH_PORT") or "2222"
|
||||
rdp_port = env("WIN11_VM_RDP_PORT")
|
||||
ssh_port = env("WIN11_VM_SSH_PORT")
|
||||
forwards_file = Path(
|
||||
env("WIN11_VM_FORWARDS") or script_dir / "qemu" / "windows11" / "forwards.conf")
|
||||
qemu_config_template = Path(
|
||||
env("WIN11_VM_QEMU_CONFIG") or script_dir / "qemu" / "windows11.ini")
|
||||
env("WIN11_VM_QEMU_CONFIG") or script_dir / "qemu" / "windows11" / "config.ini")
|
||||
|
||||
|
||||
def normalize_arch(arch):
|
||||
@@ -84,18 +76,19 @@ download_only = False
|
||||
seed_only = False
|
||||
extra_args = []
|
||||
arch_option = None
|
||||
vnc_listen = None
|
||||
|
||||
|
||||
def usage(stream=sys.stdout):
|
||||
stream.write(f"""\
|
||||
Usage: {Path(sys.argv[0]).name} [--daemon] [--download-only] [--seed-only] [--install-from-iso | --install-from-img]
|
||||
Usage: {Path(sys.argv[0]).name} [--daemon] [--download-only] [--seed-only] [--vnc[=ADDR:N]] [--install-from-iso | --install-from-img]
|
||||
[--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-<os>-<arch>.ini (os is linux or darwin, arch is amd64 or arm64, chosen from the host system and
|
||||
qemu/windows11/config.ini, which is rendered to qemu.ini in the state directory and
|
||||
loaded with QEMU's -readconfig, followed by qemu/windows11/<os>-<arch>.ini (os is linux or darwin, arch is amd64 or arm64, chosen from the host system and
|
||||
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.
|
||||
@@ -115,15 +108,18 @@ Options:
|
||||
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
|
||||
--vnc[=ADDR:N] Serve the screen over VNC instead of opening a window, on
|
||||
ADDR (default: 127.0.0.1) at TCP port 5900+N (default N: 0).
|
||||
Overrides WIN11_VM_DISPLAY
|
||||
--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;
|
||||
overrides WIN11_VM_RDP_PORT)
|
||||
--ssh-port PORT Host port forwarded to guest SSH (default: 2222;
|
||||
overrides WIN11_VM_SSH_PORT)
|
||||
--rdp-port PORT Host port of the RDP rule in the port forward file
|
||||
(default: 3389; overrides WIN11_VM_RDP_PORT)
|
||||
--ssh-port PORT Host port of the SSH rule in the port forward file
|
||||
(default: 2222; overrides WIN11_VM_SSH_PORT)
|
||||
-h, --help Show this help
|
||||
|
||||
Environment variables:
|
||||
@@ -131,8 +127,9 @@ Environment variables:
|
||||
WIN11_VM_INSTALL_ISO Windows 11 installation ISO used with --install-from-iso
|
||||
(--install-from-img uses the same path ending in .img)
|
||||
WIN11_VM_TOOLS_ISO VirtIO Windows tools ISO (default: resolved by shared image helper)
|
||||
WIN11_VM_RDP_PORT Host RDP port (default: 3389)
|
||||
WIN11_VM_SSH_PORT Host SSH port (default: 2222)
|
||||
WIN11_VM_RDP_PORT Host RDP port (default: from the port forward file, 3389)
|
||||
WIN11_VM_SSH_PORT Host SSH port (default: from the port forward file, 2222)
|
||||
WIN11_VM_FORWARDS Port forward file (default: qemu/windows11/forwards.conf)
|
||||
WIN11_VM_STATE_DIR Runtime directory (default: vm/windows11)
|
||||
WIN11_VM_DISK_IMAGE System disk path
|
||||
WIN11_VM_DISK_SIZE New system disk size (default: 64G)
|
||||
@@ -140,7 +137,7 @@ Environment variables:
|
||||
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)
|
||||
(default: qemu/windows11/config.ini)
|
||||
WIN11_VM_DISPLAY QEMU display backend (default: cocoa on macOS,
|
||||
gtk elsewhere)
|
||||
""")
|
||||
@@ -155,6 +152,10 @@ while args:
|
||||
download_only = True
|
||||
elif arg == "--seed-only":
|
||||
seed_only = True
|
||||
elif arg == "--vnc" or arg.startswith("--vnc="):
|
||||
vnc_listen = arg.partition("=")[2] or "127.0.0.1:0"
|
||||
if ":" not in vnc_listen:
|
||||
vnc_listen += ":0"
|
||||
elif arg == "--install-from-iso":
|
||||
install_from_iso = True
|
||||
elif arg == "--install-from-img":
|
||||
@@ -201,7 +202,7 @@ qemu_binary = "qemu-system-aarch64" if host_arch == "arm64" else "qemu-system-x8
|
||||
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"
|
||||
qemu_platform_config = script_dir / "qemu" / "windows11" / f"{host_os}-{host_arch}.ini"
|
||||
|
||||
|
||||
def pick_firmware(override, *candidates):
|
||||
@@ -287,7 +288,8 @@ def build_seed_iso(image_downloader, force=False):
|
||||
"""Build the seed ISO, unless it is newer than all of its sources and not forced."""
|
||||
seed_dir = script_dir / "seediso" / "windows11"
|
||||
seed_sources = [seed_dir / "Autounattend.xml", seed_dir / "Configure-Windows.ps1",
|
||||
*sorted(seed_dir.glob("[0-9][0-9]-*.bat"))]
|
||||
*sorted(seed_dir.glob("[0-9][0-9]-*.bat")),
|
||||
*sorted(seed_dir.glob("[0-9][0-9]-*.ps1"))]
|
||||
|
||||
# build-seed.sh embeds the administrator SSH public key.
|
||||
ssh_key = os.environ.get("SSH_PUBLIC_KEY_FILE") or next(
|
||||
@@ -390,8 +392,13 @@ if installing:
|
||||
|
||||
# cocoa shows the guest at its own resolution. Append ,zoom-to-fit=on (via
|
||||
# WIN11_VM_DISPLAY) to stretch a low-resolution screen to the window.
|
||||
display_backend = env("WIN11_VM_DISPLAY") or (
|
||||
"cocoa" if platform.system() == "Darwin" else "gtk")
|
||||
display_backend = (f"vnc={vnc_listen}" if vnc_listen else env("WIN11_VM_DISPLAY") or (
|
||||
"cocoa" if platform.system() == "Darwin" else "gtk"))
|
||||
if vnc_listen:
|
||||
vnc_host, _, vnc_display = vnc_listen.rpartition(":")
|
||||
if not vnc_display.isdigit():
|
||||
die(f"--vnc display number must be an integer: {vnc_listen}", 2)
|
||||
print(f"VNC: connect to {vnc_host or '127.0.0.1'}:{5900 + int(vnc_display)}")
|
||||
|
||||
# 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.
|
||||
@@ -416,18 +423,9 @@ except (KeyError, ValueError) as error:
|
||||
die(f"invalid placeholder in {qemu_config_template}: {error}")
|
||||
qemu_config.write_text(qemu_config_text)
|
||||
|
||||
# QEMU refuses to start when a forwarded host port is taken, so warn and leave
|
||||
# that forward out instead.
|
||||
forwards = []
|
||||
forwarded_ports = []
|
||||
for label, host_port, guest_port in (("RDP", rdp_port, 3389), ("SSH", ssh_port, 22)):
|
||||
in_use = port_in_use(host_port)
|
||||
forwarded_ports.append((label, host_port, not in_use))
|
||||
if in_use:
|
||||
print(f"Warning: host port {host_port} is already in use; "
|
||||
f"{label} will not be forwarded to the guest.", file=sys.stderr)
|
||||
else:
|
||||
forwards.append(f"hostfwd=tcp::{host_port}-:{guest_port}")
|
||||
# Each rule of the port forward file is one hostfwd entry of the network.
|
||||
forwards = vmforwards.usable_forwards(vmforwards.load_forwards(
|
||||
forwards_file, {label: port for label, port in (("RDP", rdp_port), ("SSH", ssh_port)) if port}))
|
||||
|
||||
# Neither Windows Setup nor the base image has the viogpudo driver yet, and
|
||||
# without it the virtio-gpu in the config file shows "display output is not
|
||||
@@ -442,11 +440,19 @@ qemu_args += [
|
||||
# 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", ",".join(["user,id=net0", *forwards]),
|
||||
"-netdev", vmforwards.netdev_argument(forwards),
|
||||
"-nic", "none",
|
||||
"-display", display_backend,
|
||||
# "-display", "none", "-spice", "port=5900,addr=127.0.0.1,disable-ticketing=on,agent-mouse=off", "-device", "virtio-vga",
|
||||
]
|
||||
if host_os == "darwin" and host_arch == "arm64" and not install_from_img:
|
||||
# With the HVF in-kernel GICv3, QEMU's usb-storage races on the status
|
||||
# packet and Windows Setup stalls 10 s after every burst of reads
|
||||
# (utmapp/UTM#7970), so use the emulated interrupt controller; upstream
|
||||
# QEMU only gets the fix from the UTM fork. The NVMe install disk of
|
||||
# --install-from-img does not use usb-storage, and hangs with the emulated
|
||||
# controller.
|
||||
qemu_args += ["-machine", "kernel-irqchip=off"]
|
||||
if installing_from_iso:
|
||||
# q35 has IDE for the installation media; "virt" does not, so use USB.
|
||||
for media_id, media_file, boot_index, ide_bus in (
|
||||
@@ -477,7 +483,7 @@ elif installing:
|
||||
else:
|
||||
print("Starting Windows 11 normally.")
|
||||
print("Account: guofang / password; " + "; ".join(
|
||||
f"{label}: localhost:{port}" for label, port, forwarded in forwarded_ports if forwarded),
|
||||
f"{forward.label}: 0.0.0.0:{forward.host_port}" for forward in forwards),
|
||||
flush=True)
|
||||
|
||||
if daemonize:
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
This directory contains the cloud-init NoCloud configuration used by
|
||||
`run-almalinux10.py`.
|
||||
|
||||
`build-seed.sh` injects your SSH public key into `user-data` (the `@SSH_PUBKEY@`
|
||||
placeholder): `$SSH_PUBKEY_FILE`, else the first `~/.ssh/id_*.pub`, else the first
|
||||
key offered by `ssh-agent`.
|
||||
|
||||
After changing `user-data`, `meta-data`, or `network-config`, remove the old
|
||||
`seed.iso` and rebuild it:
|
||||
|
||||
|
||||
@@ -5,6 +5,33 @@ script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
output="${1:-${script_dir}/seed.iso}"
|
||||
files=(user-data meta-data)
|
||||
|
||||
# Public key injected into user-data: $SSH_PUBKEY_FILE, else the first
|
||||
# ~/.ssh/id_*.pub, else the first key offered by ssh-agent.
|
||||
pubkey=""
|
||||
if [[ -n "${SSH_PUBKEY_FILE:-}" ]]; then
|
||||
pubkey="$(head -n1 -- "${SSH_PUBKEY_FILE}")"
|
||||
else
|
||||
for f in "${HOME}"/.ssh/id_ed25519.pub "${HOME}"/.ssh/id_*.pub; do
|
||||
if [[ -f "$f" ]]; then pubkey="$(head -n1 -- "$f")"; break; fi
|
||||
done
|
||||
if [[ -z "$pubkey" ]] && command -v ssh-add >/dev/null 2>&1; then
|
||||
pubkey="$(ssh-add -L 2>/dev/null | head -n1 || true)"
|
||||
fi
|
||||
fi
|
||||
if [[ "$pubkey" != ssh-* && "$pubkey" != ecdsa-* && "$pubkey" != sk-* ]]; then
|
||||
printf 'Error: no SSH public key found; set SSH_PUBKEY_FILE.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf -- "${work}"' EXIT
|
||||
template="$(<"${script_dir}/user-data")"
|
||||
printf '%s\n' "${template//@SSH_PUBKEY@/${pubkey}}" > "${work}/user-data"
|
||||
cp -- "${script_dir}/meta-data" "${work}/meta-data"
|
||||
if [[ -f "${script_dir}/network-config" ]]; then
|
||||
cp -- "${script_dir}/network-config" "${work}/network-config"
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname -- "$output")"
|
||||
|
||||
if [[ -f "${script_dir}/network-config" ]]; then
|
||||
@@ -14,22 +41,22 @@ fi
|
||||
if command -v cloud-localds >/dev/null 2>&1; then
|
||||
args=()
|
||||
if [[ -f "${script_dir}/network-config" ]]; then
|
||||
args+=(--network-config="${script_dir}/network-config")
|
||||
args+=(--network-config="${work}/network-config")
|
||||
fi
|
||||
cloud-localds "${args[@]}" "$output" \
|
||||
"${script_dir}/user-data" "${script_dir}/meta-data"
|
||||
cloud-localds "${args[@]}" "${output}" \
|
||||
"${work}/user-data" "${work}/meta-data"
|
||||
elif command -v xorriso >/dev/null 2>&1; then
|
||||
xorriso -as mkisofs -quiet -volid cidata -joliet -rock \
|
||||
-output "$output" "${files[@]/#/${script_dir}/}"
|
||||
-output "${output}" "${files[@]/#/${work}/}"
|
||||
elif command -v genisoimage >/dev/null 2>&1; then
|
||||
genisoimage -quiet -output "$output" -volid cidata -joliet -rock \
|
||||
"${files[@]/#/${script_dir}/}"
|
||||
genisoimage -quiet -output "${output}" -volid cidata -joliet -rock \
|
||||
"${files[@]/#/${work}/}"
|
||||
elif command -v mkisofs >/dev/null 2>&1; then
|
||||
mkisofs -quiet -output "$output" -volid cidata -joliet -rock \
|
||||
"${files[@]/#/${script_dir}/}"
|
||||
mkisofs -quiet -output "${output}" -volid cidata -joliet -rock \
|
||||
"${files[@]/#/${work}/}"
|
||||
else
|
||||
printf 'Error: install cloud-image-utils, xorriso, genisoimage, or mkisofs.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'Created %s\n' "$output"
|
||||
printf 'Created %s\n' "${output}"
|
||||
@@ -10,6 +10,8 @@ users:
|
||||
shell: /bin/bash
|
||||
sudo: "ALL=(ALL) NOPASSWD: ALL"
|
||||
lock_passwd: false
|
||||
ssh_authorized_keys:
|
||||
- @SSH_PUBKEY@
|
||||
hashed_passwd: $6$0NtCERV3eO12t9oJ$nlFE2sSc66nFfuD5X0xMnOla1cuzOnHfeGNRY5X7dCDHg.WzWVmuRe7M17Puj44.eCWLnJAWt4WNjd9vP9X0D0
|
||||
|
||||
chpasswd:
|
||||
|
||||
@@ -4,6 +4,10 @@ This directory contains the cloud-init NoCloud configuration used by
|
||||
`run-fedora44.py`. On first boot, cloud-init's package module installs Fedora's
|
||||
GNOME package group and reboots the guest.
|
||||
|
||||
`build-seed.sh` injects your SSH public key into `user-data` (the `@SSH_PUBKEY@`
|
||||
placeholder): `$SSH_PUBKEY_FILE`, else the first `~/.ssh/id_*.pub`, else the first
|
||||
key offered by `ssh-agent`.
|
||||
|
||||
After changing `user-data`, `meta-data`, or `network-config`, remove the old
|
||||
`seed.iso` and rebuild it:
|
||||
|
||||
|
||||
@@ -5,24 +5,58 @@ script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
output="${1:-${script_dir}/seed.iso}"
|
||||
files=(user-data meta-data)
|
||||
|
||||
# Public key injected into user-data: $SSH_PUBKEY_FILE, else the first
|
||||
# ~/.ssh/id_*.pub, else the first key offered by ssh-agent.
|
||||
pubkey=""
|
||||
if [[ -n "${SSH_PUBKEY_FILE:-}" ]]; then
|
||||
pubkey="$(head -n1 -- "${SSH_PUBKEY_FILE}")"
|
||||
else
|
||||
for f in "${HOME}"/.ssh/id_ed25519.pub "${HOME}"/.ssh/id_*.pub; do
|
||||
if [[ -f "$f" ]]; then pubkey="$(head -n1 -- "$f")"; break; fi
|
||||
done
|
||||
if [[ -z "$pubkey" ]] && command -v ssh-add >/dev/null 2>&1; then
|
||||
pubkey="$(ssh-add -L 2>/dev/null | head -n1 || true)"
|
||||
fi
|
||||
fi
|
||||
if [[ "$pubkey" != ssh-* && "$pubkey" != ecdsa-* && "$pubkey" != sk-* ]]; then
|
||||
printf 'Error: no SSH public key found; set SSH_PUBKEY_FILE.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf -- "${work}"' EXIT
|
||||
template="$(<"${script_dir}/user-data")"
|
||||
printf '%s\n' "${template//@SSH_PUBKEY@/${pubkey}}" > "${work}/user-data"
|
||||
cp -- "${script_dir}/meta-data" "${work}/meta-data"
|
||||
if [[ -f "${script_dir}/network-config" ]]; then
|
||||
cp -- "${script_dir}/network-config" "${work}/network-config"
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname -- "$output")"
|
||||
|
||||
if [[ -f "${script_dir}/network-config" ]]; then
|
||||
files+=(network-config)
|
||||
fi
|
||||
|
||||
if command -v cloud-localds >/dev/null 2>&1; then
|
||||
args=()
|
||||
[[ -f "${script_dir}/network-config" ]] && args+=(--network-config="${script_dir}/network-config")
|
||||
cloud-localds "${args[@]}" "$output" "${script_dir}/user-data" "${script_dir}/meta-data"
|
||||
if [[ -f "${script_dir}/network-config" ]]; then
|
||||
args+=(--network-config="${work}/network-config")
|
||||
fi
|
||||
cloud-localds "${args[@]}" "${output}" \
|
||||
"${work}/user-data" "${work}/meta-data"
|
||||
elif command -v xorriso >/dev/null 2>&1; then
|
||||
xorriso -as mkisofs -quiet -volid cidata -joliet -rock -output "$output" "${files[@]/#/${script_dir}/}"
|
||||
xorriso -as mkisofs -quiet -volid cidata -joliet -rock \
|
||||
-output "${output}" "${files[@]/#/${work}/}"
|
||||
elif command -v genisoimage >/dev/null 2>&1; then
|
||||
genisoimage -quiet -output "$output" -volid cidata -joliet -rock "${files[@]/#/${script_dir}/}"
|
||||
genisoimage -quiet -output "${output}" -volid cidata -joliet -rock \
|
||||
"${files[@]/#/${work}/}"
|
||||
elif command -v mkisofs >/dev/null 2>&1; then
|
||||
mkisofs -quiet -output "$output" -volid cidata -joliet -rock "${files[@]/#/${script_dir}/}"
|
||||
mkisofs -quiet -output "${output}" -volid cidata -joliet -rock \
|
||||
"${files[@]/#/${work}/}"
|
||||
else
|
||||
printf 'Error: install cloud-image-utils, xorriso, genisoimage, or mkisofs.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'Created %s\n' "$output"
|
||||
printf 'Created %s\n' "${output}"
|
||||
@@ -10,6 +10,8 @@ users:
|
||||
shell: /bin/bash
|
||||
sudo: "ALL=(ALL) NOPASSWD: ALL"
|
||||
lock_passwd: false
|
||||
ssh_authorized_keys:
|
||||
- @SSH_PUBKEY@
|
||||
hashed_passwd: $6$0NtCERV3eO12t9oJ$nlFE2sSc66nFfuD5X0xMnOla1cuzOnHfeGNRY5X7dCDHg.WzWVmuRe7M17Puj44.eCWLnJAWt4WNjd9vP9X0D0
|
||||
|
||||
chpasswd:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
@echo off
|
||||
setlocal
|
||||
title Windows update status
|
||||
|
||||
fltmc >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo Requesting administrator rights...
|
||||
powershell.exe -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
|
||||
exit /b
|
||||
)
|
||||
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp080-Get-UpdateStatus-Admin.ps1"
|
||||
echo.
|
||||
pause
|
||||
@@ -0,0 +1,104 @@
|
||||
# Prints whether the guest is upgrading or downloading updates.
|
||||
# Read-only. Run as Administrator for complete results (BITS jobs of all users,
|
||||
# command lines of system processes); a standard user gets partial output.
|
||||
param(
|
||||
[int]$EventCount = 8
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
function Write-Section([string]$Title) {
|
||||
Write-Host ''
|
||||
Write-Host "== $Title =="
|
||||
}
|
||||
|
||||
function Out-Text($Value) {
|
||||
$text = ($Value | Out-String).TrimEnd()
|
||||
if ($text) { Write-Host $text } else { Write-Host '(none)' }
|
||||
}
|
||||
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$isAdmin = [Security.Principal.WindowsPrincipal]::new($identity).IsInRole(
|
||||
[Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
|
||||
Write-Section 'Host'
|
||||
Write-Host ('Time: {0}' -f (Get-Date))
|
||||
Write-Host ('Admin: {0}' -f $isAdmin)
|
||||
if (-not $isAdmin) {
|
||||
Write-Host 'Warning: not elevated, BITS jobs and process command lines may be incomplete.'
|
||||
}
|
||||
|
||||
Write-Section 'Operating system'
|
||||
Out-Text (Get-CimInstance Win32_OperatingSystem |
|
||||
Select-Object Caption, Version, BuildNumber, LastBootUpTime | Format-List)
|
||||
|
||||
Write-Section 'Update policy'
|
||||
$wuPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'
|
||||
$props = 'NoAutoUpdate', 'NoAutoRebootWithLoggedOnUsers', 'ExcludeWUDriversInQualityUpdate',
|
||||
'TargetReleaseVersion', 'TargetReleaseVersionInfo'
|
||||
foreach ($item in @(@{ Path = "$wuPath\AU" }, @{ Path = $wuPath })) {
|
||||
$values = Get-ItemProperty -Path $item.Path -ErrorAction SilentlyContinue
|
||||
foreach ($name in $props) {
|
||||
if ($values -and $values.PSObject.Properties[$name]) {
|
||||
Write-Host ('{0,-34} {1}' -f $name, $values.$name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Section 'Upgrade / pending-reboot markers'
|
||||
$markers = [ordered]@{
|
||||
'C:\$WINDOWS.~BT (upgrade staging)' = Test-Path -LiteralPath 'C:\$WINDOWS.~BT'
|
||||
'C:\Windows\WinSxS\pending.xml' = Test-Path -LiteralPath 'C:\Windows\WinSxS\pending.xml'
|
||||
'WindowsUpdate RebootRequired' = Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
|
||||
'Component Based Servicing RebootPending' = Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
|
||||
}
|
||||
foreach ($key in $markers.Keys) {
|
||||
Write-Host ('{0,-42} {1}' -f $key, $markers[$key])
|
||||
}
|
||||
|
||||
Write-Section 'Update services'
|
||||
Out-Text (Get-Service wuauserv, UsoSvc, TrustedInstaller, bits, WaaSMedicSvc, DoSvc `
|
||||
-ErrorAction SilentlyContinue |
|
||||
Select-Object Name, Status, StartType | Format-Table -AutoSize)
|
||||
|
||||
Write-Section 'Update scheduled tasks'
|
||||
Out-Text (Get-ScheduledTask `
|
||||
-TaskPath '\Microsoft\Windows\UpdateOrchestrator\*', '\Microsoft\Windows\WindowsUpdate\*' `
|
||||
-ErrorAction SilentlyContinue |
|
||||
Select-Object TaskName, State | Format-Table -AutoSize)
|
||||
|
||||
Write-Section 'Update-related processes'
|
||||
$pattern = 'setup|TiWorker|TrustedInstaller|UsoClient|MoSetup|SetupHost|SetupPrep|MusNotification|wusa|dism|EdgeUpdate|Installer'
|
||||
Out-Text (Get-CimInstance Win32_Process |
|
||||
Where-Object { $_.Name -match $pattern -or $_.CommandLine -match 'wuauserv|UsoSvc|DoSvc' } |
|
||||
Select-Object ProcessId, Name, CreationDate, CommandLine | Format-List)
|
||||
|
||||
Write-Section 'BITS download jobs'
|
||||
Out-Text (Get-BitsTransfer -AllUsers -ErrorAction SilentlyContinue |
|
||||
Select-Object DisplayName, JobState, BytesTransferred, BytesTotal | Format-Table -AutoSize)
|
||||
|
||||
Write-Section "Windows Update events (last $EventCount)"
|
||||
Out-Text (Get-WinEvent -LogName 'Microsoft-Windows-WindowsUpdateClient/Operational' `
|
||||
-MaxEvents $EventCount -ErrorAction SilentlyContinue |
|
||||
Select-Object TimeCreated, Id, Message | Format-List)
|
||||
|
||||
Write-Section 'Established HTTP/HTTPS connections by process'
|
||||
Out-Text (Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.RemotePort -in 80, 443 } |
|
||||
Group-Object OwningProcess |
|
||||
ForEach-Object {
|
||||
$process = Get-Process -Id $_.Name -ErrorAction SilentlyContinue
|
||||
[pscustomobject]@{ Pid = $_.Name; Process = $process.Name; Connections = $_.Count }
|
||||
} | Format-Table -AutoSize)
|
||||
|
||||
Write-Section 'Network receive rate (2 samples, 2 s apart)'
|
||||
$counter = Get-Counter '\Network Interface(*)\Bytes Received/sec' `
|
||||
-SampleInterval 2 -MaxSamples 2 -ErrorAction SilentlyContinue
|
||||
if ($counter) {
|
||||
Out-Text ($counter[-1].CounterSamples |
|
||||
Select-Object InstanceName, @{ n = 'KBps'; e = { [int]($_.CookedValue / 1024) } } |
|
||||
Format-Table -AutoSize)
|
||||
} else {
|
||||
Write-Host '(performance counters unavailable)'
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
@echo off
|
||||
setlocal
|
||||
title Toggle display scale
|
||||
|
||||
rem No argument: switch between 100% and 200%. Pass 100 or 200 to force a value.
|
||||
rem Runs as the current user (no elevation), because the scale is a per-user setting.
|
||||
rem The change takes effect after a restart; the script offers to restart at the end.
|
||||
|
||||
set "SCALE=%~1"
|
||||
if not defined SCALE set "SCALE=0"
|
||||
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp081-Toggle-Scale.ps1" -Scale %SCALE%
|
||||
if errorlevel 1 (
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
choice /c YN /m "Restart now"
|
||||
if errorlevel 2 exit /b 0
|
||||
shutdown.exe /r /t 5
|
||||
@@ -0,0 +1,34 @@
|
||||
# Switches the display scale between 100% and 200% for the current user.
|
||||
# With no argument it toggles; -Scale 100 or -Scale 200 forces a value.
|
||||
# The scale is stored in the registry (LogPixels), so the change takes effect only after
|
||||
# a restart. Use -Restart to reboot right away.
|
||||
param(
|
||||
[ValidateSet(0, 100, 200)]
|
||||
[int]$Scale = 0,
|
||||
[switch]$Restart
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$desktopKey = 'HKCU:\Control Panel\Desktop'
|
||||
$metricsKey = 'HKCU:\Control Panel\Desktop\WindowMetrics'
|
||||
|
||||
$logPixels = (Get-ItemProperty -Path $desktopKey -Name LogPixels -ErrorAction SilentlyContinue).LogPixels
|
||||
if (-not $logPixels) { $logPixels = 96 }
|
||||
$current = [int][math]::Round($logPixels * 100 / 96)
|
||||
|
||||
$target = if ($Scale -ne 0) { $Scale } elseif ($current -eq 100) { 200 } else { 100 }
|
||||
$dpi = [int]($target * 96 / 100)
|
||||
|
||||
Write-Host "Scale: $current% -> $target%"
|
||||
|
||||
# Win8DpiScaling=1 makes Windows use LogPixels instead of the per-monitor recommendation.
|
||||
Set-ItemProperty -Path $desktopKey -Name LogPixels -Type DWord -Value $dpi
|
||||
Set-ItemProperty -Path $desktopKey -Name Win8DpiScaling -Type DWord -Value 1
|
||||
Set-ItemProperty -Path $metricsKey -Name AppliedDPI -Type DWord -Value $dpi
|
||||
|
||||
Write-Host 'Restart Windows for the new scale to take effect.'
|
||||
if ($Restart) {
|
||||
Write-Host 'Restarting in 5 seconds...'
|
||||
shutdown.exe /r /t 5
|
||||
}
|
||||
@@ -144,12 +144,14 @@ function Disable-AutomaticUpdate {
|
||||
}
|
||||
|
||||
function Copy-InstallerScripts {
|
||||
$scripts = @(Get-ChildItem -Path $PSScriptRoot -Filter '*-Install-*.bat' -File)
|
||||
# Numbered scripts (NN-*.bat / NN-*.ps1: installers and tools) go to Desktop\Tools.
|
||||
$scripts = @(Get-ChildItem -Path $PSScriptRoot -File |
|
||||
Where-Object { $_.Name -match '^\d\d-.*\.(bat|ps1)$' })
|
||||
if ($scripts.Count -eq 0) {
|
||||
throw "No installer batch files were found next to $PSCommandPath."
|
||||
}
|
||||
|
||||
$target = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Installers'
|
||||
$target = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Tools'
|
||||
New-Item -ItemType Directory -Path $target -Force | Out-Null
|
||||
Copy-Item -Path $scripts.FullName -Destination $target -Force
|
||||
}
|
||||
|
||||
@@ -43,9 +43,10 @@ if [[ -n "$ssh_key" ]]; then
|
||||
cp -- "$ssh_key" "$work_dir/authorized_keys"
|
||||
seed_files+=("$work_dir/authorized_keys")
|
||||
fi
|
||||
# Numbered installer scripts (10-Install-Git-Admin.bat, 20-..., in install order).
|
||||
# Numbered scripts: installers (10-Install-Git-Admin.bat, 20-..., in install order)
|
||||
# and tools (80-Get-UpdateStatus-Admin.bat/.ps1); all land in Desktop\Tools.
|
||||
shopt -s nullglob
|
||||
seed_files+=("$script_dir"/[0-9][0-9]-*.bat)
|
||||
seed_files+=("$script_dir"/[0-9][0-9]-*.bat "$script_dir"/[0-9][0-9]-*.ps1)
|
||||
shopt -u nullglob
|
||||
|
||||
mkdir -p "$(dirname -- "$output")"
|
||||
|
||||
+1
-1
Submodule vm-images updated: 30342b4e1b...71c0c3d029.
@@ -0,0 +1,96 @@
|
||||
"""Host port forwards for the QEMU user-mode network, read from a .forwards file.
|
||||
|
||||
Each line of the file is "LABEL HOST_PORT GUEST_PORT"; "#" starts a comment.
|
||||
A QEMU config file section cannot hold more than one hostfwd entry, so the
|
||||
launchers build the -netdev argument from these rules instead.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
Forward = namedtuple("Forward", "label host_port guest_port")
|
||||
|
||||
|
||||
def _die(message):
|
||||
print(f"Error: {message}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _port(text, where):
|
||||
if not (text.isascii() and text.isdigit() and 1 <= int(text) <= 65535):
|
||||
_die(f"{where}: not a port number from 1 to 65535: {text}")
|
||||
return int(text)
|
||||
|
||||
|
||||
def port_in_use(port):
|
||||
"""True if a host TCP port cannot be bound, as QEMU does for a hostfwd rule."""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
||||
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
probe.bind(("", int(port)))
|
||||
except OSError:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def load_forwards(path, host_port_overrides=None):
|
||||
"""Read the rules in a .forwards file.
|
||||
|
||||
host_port_overrides maps a label to a host port that replaces the one in
|
||||
the file, e.g. {"SSH": "2200"}.
|
||||
"""
|
||||
if not path.is_file():
|
||||
_die(f"port forward file was not found: {path}")
|
||||
forwards = []
|
||||
for number, line in enumerate(path.read_text().splitlines(), 1):
|
||||
fields = line.partition("#")[0].split()
|
||||
if not fields:
|
||||
continue
|
||||
where = f"{path}:{number}"
|
||||
if len(fields) != 3:
|
||||
_die(f"{where}: expected LABEL HOST_PORT GUEST_PORT: {line.strip()}")
|
||||
label, host_port, guest_port = fields
|
||||
if any(forward.label == label for forward in forwards):
|
||||
_die(f"{where}: duplicate label: {label}")
|
||||
forwards.append(Forward(label, _port(host_port, where), _port(guest_port, where)))
|
||||
for label, host_port in (host_port_overrides or {}).items():
|
||||
if not any(forward.label == label for forward in forwards):
|
||||
_die(f"{path} has no {label} rule to override the port of")
|
||||
forwards = [forward._replace(host_port=_port(str(host_port), f"{label} port"))
|
||||
if forward.label == label else forward for forward in forwards]
|
||||
seen = set()
|
||||
for forward in forwards:
|
||||
if forward.host_port in seen:
|
||||
_die(f"{path}: host port {forward.host_port} is used by more than one rule")
|
||||
seen.add(forward.host_port)
|
||||
return forwards
|
||||
|
||||
|
||||
def usable_forwards(forwards):
|
||||
"""Drop (with a warning) the rules whose host port is taken.
|
||||
|
||||
QEMU refuses to start when a forwarded host port is taken.
|
||||
"""
|
||||
usable = []
|
||||
for forward in forwards:
|
||||
if port_in_use(forward.host_port):
|
||||
print(f"Warning: host port {forward.host_port} is already in use; "
|
||||
f"{forward.label} will not be forwarded to the guest.", file=sys.stderr)
|
||||
else:
|
||||
usable.append(forward)
|
||||
return usable
|
||||
|
||||
|
||||
def netdev_argument(forwards, netdev_id="net0"):
|
||||
"""The value of -netdev for a user-mode network with the given rules."""
|
||||
return ",".join([f"user,id={netdev_id}", *(
|
||||
f"hostfwd=tcp::{forward.host_port}-:{forward.guest_port}" for forward in forwards)])
|
||||
|
||||
|
||||
def print_forwards(forwards):
|
||||
for forward in forwards:
|
||||
print(f"{forward.label}: localhost:{forward.host_port} -> guest port "
|
||||
f"{forward.guest_port}", flush=True)
|
||||
if forward.guest_port == 22:
|
||||
print(f" ssh -p {forward.host_port} guofang@localhost", flush=True)
|
||||
@@ -6,7 +6,7 @@
|
||||
## 结论
|
||||
|
||||
- **根因(第五节)**:HVF 的内核态 GICv3 与 QEMU 的 `usb-storage` 有竞态,Windows Setup 每读一批数据就卡约 10 秒。
|
||||
在 `[machine]` 里加 `kernel-irqchip = "off"`(已写入 `qemu/windows11-darwin-arm64.ini`)后,
|
||||
由 `run-windows11.py` 加 `-machine kernel-irqchip=off`(`--install-from-img` 时不加)后,
|
||||
**同样走 USB 光驱的 `--install-from-iso` 也变快了**,和 UTM 的速度一致。
|
||||
- 下面第一到五节是更早的分析:当时把慢归因于"模拟 USB 光驱吞吐低"(平均约 4 MB/s),
|
||||
这个归因不准确,真实原因是上面的 10 秒停顿;那时测到的低吞吐就是停顿拉低的平均值。
|
||||
@@ -20,7 +20,7 @@
|
||||
|---|---|
|
||||
| 主机 | Apple M5 Max,arm64,macOS 26.4,18 核,48 GB 内存 |
|
||||
| QEMU | 11.1.2,`qemu-system-aarch64`,加速器 HVF |
|
||||
| 客户机配置 | `qemu/windows11.ini` + `qemu/windows11-darwin-arm64.ini`(`virt` 机型,8 vCPU,8G 内存,NVMe 系统盘,virtio-net,ramfb,usb-kbd/usb-tablet) |
|
||||
| 客户机配置 | `qemu/windows11/config.ini` + `qemu/windows11/darwin-arm64.ini`(`virt` 机型,8 vCPU,8G 内存,NVMe 系统盘,virtio-net,ramfb,usb-kbd/usb-tablet) |
|
||||
| 安装 ISO | `Windows11_Client_arm64_en-us_26300_9457.iso`(卷标 `CCCOMA_A64FRE_EN-US_DV9`,`install.wim` 7.6 GB,`boot.wim` 630 MB) |
|
||||
| 驱动 ISO | `virtio-win.iso`(含 ARM64 驱动目录,根目录只有 x64/x86 的 MSI) |
|
||||
|
||||
@@ -200,7 +200,7 @@ UTM 5.0.8 带有这个修复;Homebrew 的 QEMU 11.1.2 没有,所以上游版
|
||||
|
||||
### 处理
|
||||
|
||||
在 `qemu/windows11-darwin-arm64.ini` 的 `[machine]` 加 `kernel-irqchip = "off"`,改用模拟的中断控制器。
|
||||
在 `qemu/windows11/darwin-arm64.ini` 的 `[machine]` 加 `kernel-irqchip = "off"`,改用模拟的中断控制器。
|
||||
这一项改完后速度明显提升,已确认和 UTM 一致。
|
||||
|
||||
```ini
|
||||
|
||||
Reference in new issue
Block a user