Add --state-dir option to the Python VM launchers
Let each run-*.py launcher take the VM directory (disk, seed ISO, UEFI variables, PID file, console log) from the command line, taking precedence over the *_STATE_DIR environment variable. Paths derived from the state directory are now computed after argument parsing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1dybYsc36kf3gUPB7NuNB
This commit is contained in:
1 parent
a03fb23977
commit
8a7f974c11
5 files changed
+84
-37
No files matched your search
@@ -25,6 +25,15 @@ original qcow2 image as its backing file. The base image therefore stays
|
|||||||
unchanged. Resource settings can be overridden with environment variables;
|
unchanged. Resource settings can be overridden with environment variables;
|
||||||
run `./run-debian13.sh --help` for the full list.
|
run `./run-debian13.sh --help` for the full list.
|
||||||
|
|
||||||
|
The Python launchers (`run-*.py`) accept `--state-dir DIR` to keep
|
||||||
|
the VM disk, seed ISO, UEFI variables, PID file and console log in `DIR`
|
||||||
|
instead of the default `.vm/<name>/`. The option takes precedence over the
|
||||||
|
corresponding `*_STATE_DIR` environment variable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./run-debian13.py --state-dir /srv/vms/debian13-test --daemon
|
||||||
|
```
|
||||||
|
|
||||||
If `.vm/debian13/seed.iso` is missing, the launcher automatically builds it
|
If `.vm/debian13/seed.iso` is missing, the launcher automatically builds it
|
||||||
from `seediso/debian13/user-data`, `seediso/debian13/meta-data`, and
|
from `seediso/debian13/user-data`, `seediso/debian13/meta-data`, and
|
||||||
`seediso/debian13/network-config` before starting the VM.
|
`seediso/debian13/network-config` before starting the VM.
|
||||||
|
|||||||
+18
-9
@@ -52,8 +52,6 @@ else:
|
|||||||
uses_shared_base_image = True
|
uses_shared_base_image = True
|
||||||
|
|
||||||
state_dir = Path(env("ALMA_VM_STATE_DIR") or script_dir / ".vm" / "almalinux10")
|
state_dir = Path(env("ALMA_VM_STATE_DIR") or script_dir / ".vm" / "almalinux10")
|
||||||
disk_image = Path(env("ALMA_VM_DISK_IMAGE") or state_dir / "disk.qcow2")
|
|
||||||
seed_image = Path(env("ALMA_VM_SEED_IMAGE") or disk_image.parent / "seed.iso")
|
|
||||||
disk_size = env("ALMA_VM_DISK_SIZE") or "20G"
|
disk_size = env("ALMA_VM_DISK_SIZE") or "20G"
|
||||||
memory = env("ALMA_VM_MEMORY") or "8G"
|
memory = env("ALMA_VM_MEMORY") or "8G"
|
||||||
cpus = env("ALMA_VM_CPUS") or "8"
|
cpus = env("ALMA_VM_CPUS") or "8"
|
||||||
@@ -117,24 +115,23 @@ else:
|
|||||||
"/opt/homebrew/share/qemu/edk2-i386-vars.fd",
|
"/opt/homebrew/share/qemu/edk2-i386-vars.fd",
|
||||||
"/usr/local/share/qemu/edk2-i386-vars.fd",
|
"/usr/local/share/qemu/edk2-i386-vars.fd",
|
||||||
)
|
)
|
||||||
uefi_vars = state_dir / "OVMF_VARS_4M.fd"
|
|
||||||
pid_file = state_dir / "qemu.pid"
|
|
||||||
log_file = state_dir / "console.log"
|
|
||||||
daemonize = False
|
daemonize = False
|
||||||
extra_args = []
|
extra_args = []
|
||||||
|
|
||||||
|
|
||||||
def usage(stream=sys.stdout):
|
def usage(stream=sys.stdout):
|
||||||
stream.write(f"""\
|
stream.write(f"""\
|
||||||
Usage: {Path(sys.argv[0]).name} [--daemon] [-- QEMU_ARGUMENT ...]
|
Usage: {Path(sys.argv[0]).name} [--daemon] [--state-dir DIR] [-- QEMU_ARGUMENT ...]
|
||||||
|
|
||||||
Start an AlmaLinux 10 Generic Cloud virtual machine. The launcher obtains a
|
Start an AlmaLinux 10 Generic Cloud virtual machine. The launcher obtains a
|
||||||
missing base image through ../vm-images/download-images.sh and creates a
|
missing base image through ../vm-images/download-images.sh and creates a
|
||||||
writable overlay under .vm/almalinux10/, leaving the base image unchanged.
|
writable overlay in the state directory, leaving the base image unchanged.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--daemon Run in the background while keeping the graphical window
|
--daemon Run in the background while keeping the graphical window
|
||||||
-h, --help Show this help
|
--state-dir DIR Directory for the VM disk, seed ISO and runtime files
|
||||||
|
(default: .vm/almalinux10; overrides ALMA_VM_STATE_DIR)
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
ALMA_VM_MEMORY Guest memory (default: 8G)
|
ALMA_VM_MEMORY Guest memory (default: 8G)
|
||||||
@@ -157,6 +154,12 @@ while args:
|
|||||||
arg = args.pop(0)
|
arg = args.pop(0)
|
||||||
if arg == "--daemon":
|
if arg == "--daemon":
|
||||||
daemonize = True
|
daemonize = True
|
||||||
|
elif arg == "--state-dir":
|
||||||
|
if not args:
|
||||||
|
die(f"{arg} requires a directory argument.", 2)
|
||||||
|
state_dir = Path(args.pop(0))
|
||||||
|
elif arg.startswith("--state-dir="):
|
||||||
|
state_dir = Path(arg.split("=", 1)[1])
|
||||||
elif arg in ("-h", "--help"):
|
elif arg in ("-h", "--help"):
|
||||||
usage()
|
usage()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@@ -168,6 +171,12 @@ while args:
|
|||||||
usage(sys.stderr)
|
usage(sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
disk_image = Path(env("ALMA_VM_DISK_IMAGE") or state_dir / "disk.qcow2")
|
||||||
|
seed_image = Path(env("ALMA_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"
|
||||||
|
|
||||||
for command_name in (qemu_binary, "qemu-img"):
|
for command_name in (qemu_binary, "qemu-img"):
|
||||||
if shutil.which(command_name) is None:
|
if shutil.which(command_name) is None:
|
||||||
die(f"{command_name} is not installed.")
|
die(f"{command_name} is not installed.")
|
||||||
|
|||||||
+20
-10
@@ -52,8 +52,6 @@ else:
|
|||||||
uses_shared_base_image = True
|
uses_shared_base_image = True
|
||||||
|
|
||||||
state_dir = Path(env("VM_STATE_DIR") or script_dir / ".vm" / "debian13")
|
state_dir = Path(env("VM_STATE_DIR") or script_dir / ".vm" / "debian13")
|
||||||
disk_image = Path(env("VM_DISK_IMAGE") or state_dir / "disk.qcow2")
|
|
||||||
seed_image = Path(env("VM_SEED_IMAGE") or disk_image.parent / "seed.iso")
|
|
||||||
disk_size = env("VM_DISK_SIZE") or "20G"
|
disk_size = env("VM_DISK_SIZE") or "20G"
|
||||||
memory = env("VM_MEMORY") or "8G"
|
memory = env("VM_MEMORY") or "8G"
|
||||||
cpus = env("VM_CPUS") or "8"
|
cpus = env("VM_CPUS") or "8"
|
||||||
@@ -117,23 +115,23 @@ else:
|
|||||||
"/opt/homebrew/share/qemu/edk2-i386-vars.fd",
|
"/opt/homebrew/share/qemu/edk2-i386-vars.fd",
|
||||||
"/usr/local/share/qemu/edk2-i386-vars.fd",
|
"/usr/local/share/qemu/edk2-i386-vars.fd",
|
||||||
)
|
)
|
||||||
uefi_vars = state_dir / "OVMF_VARS_4M.fd"
|
|
||||||
pid_file = state_dir / "qemu.pid"
|
|
||||||
log_file = state_dir / "console.log"
|
|
||||||
daemonize = False
|
daemonize = False
|
||||||
extra_args = []
|
extra_args = []
|
||||||
|
|
||||||
|
|
||||||
def usage(stream=sys.stdout):
|
def usage(stream=sys.stdout):
|
||||||
stream.write(f"""\
|
stream.write(f"""\
|
||||||
Usage: {Path(sys.argv[0]).name} [--daemon] [-- QEMU_ARGUMENT ...]
|
Usage: {Path(sys.argv[0]).name} [--daemon] [--state-dir DIR] [-- QEMU_ARGUMENT ...]
|
||||||
|
|
||||||
Start the Debian 13 virtual machine. A writable overlay is created under
|
Start the Debian 13 virtual machine. A writable overlay is created in the state
|
||||||
.vm/debian13/, so the downloaded base image is never modified.
|
directory (default: .vm/debian13/), so the downloaded base image is never
|
||||||
|
modified.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--daemon Run in the background while keeping the graphical window
|
--daemon Run in the background while keeping the graphical window
|
||||||
-h, --help Show this help
|
--state-dir DIR Directory for the VM disk, seed ISO and runtime files
|
||||||
|
(default: .vm/debian13; overrides VM_STATE_DIR)
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
VM_MEMORY Guest memory (default: 8G)
|
VM_MEMORY Guest memory (default: 8G)
|
||||||
@@ -156,6 +154,12 @@ while args:
|
|||||||
arg = args.pop(0)
|
arg = args.pop(0)
|
||||||
if arg == "--daemon":
|
if arg == "--daemon":
|
||||||
daemonize = True
|
daemonize = True
|
||||||
|
elif arg == "--state-dir":
|
||||||
|
if not args:
|
||||||
|
die(f"{arg} requires a directory argument.", 2)
|
||||||
|
state_dir = Path(args.pop(0))
|
||||||
|
elif arg.startswith("--state-dir="):
|
||||||
|
state_dir = Path(arg.split("=", 1)[1])
|
||||||
elif arg in ("-h", "--help"):
|
elif arg in ("-h", "--help"):
|
||||||
usage()
|
usage()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@@ -167,6 +171,12 @@ while args:
|
|||||||
usage(sys.stderr)
|
usage(sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
disk_image = Path(env("VM_DISK_IMAGE") or state_dir / "disk.qcow2")
|
||||||
|
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"
|
||||||
|
|
||||||
for command_name in (qemu_binary, "qemu-img"):
|
for command_name in (qemu_binary, "qemu-img"):
|
||||||
if shutil.which(command_name) is None:
|
if shutil.which(command_name) is None:
|
||||||
die(f"{command_name} is not installed.")
|
die(f"{command_name} is not installed.")
|
||||||
|
|||||||
+18
-9
@@ -52,8 +52,6 @@ else:
|
|||||||
uses_shared_base_image = True
|
uses_shared_base_image = True
|
||||||
|
|
||||||
state_dir = Path(env("FEDORA_VM_STATE_DIR") or script_dir / ".vm" / "fedora44")
|
state_dir = Path(env("FEDORA_VM_STATE_DIR") or script_dir / ".vm" / "fedora44")
|
||||||
disk_image = Path(env("FEDORA_VM_DISK_IMAGE") or state_dir / "disk.qcow2")
|
|
||||||
seed_image = Path(env("FEDORA_VM_SEED_IMAGE") or disk_image.parent / "seed.iso")
|
|
||||||
disk_size = env("FEDORA_VM_DISK_SIZE") or "20G"
|
disk_size = env("FEDORA_VM_DISK_SIZE") or "20G"
|
||||||
memory = env("FEDORA_VM_MEMORY") or "8G"
|
memory = env("FEDORA_VM_MEMORY") or "8G"
|
||||||
cpus = env("FEDORA_VM_CPUS") or "8"
|
cpus = env("FEDORA_VM_CPUS") or "8"
|
||||||
@@ -117,24 +115,23 @@ else:
|
|||||||
"/opt/homebrew/share/qemu/edk2-i386-vars.fd",
|
"/opt/homebrew/share/qemu/edk2-i386-vars.fd",
|
||||||
"/usr/local/share/qemu/edk2-i386-vars.fd",
|
"/usr/local/share/qemu/edk2-i386-vars.fd",
|
||||||
)
|
)
|
||||||
uefi_vars = state_dir / "OVMF_VARS_4M.fd"
|
|
||||||
pid_file = state_dir / "qemu.pid"
|
|
||||||
log_file = state_dir / "console.log"
|
|
||||||
daemonize = False
|
daemonize = False
|
||||||
extra_args = []
|
extra_args = []
|
||||||
|
|
||||||
|
|
||||||
def usage(stream=sys.stdout):
|
def usage(stream=sys.stdout):
|
||||||
stream.write(f"""\
|
stream.write(f"""\
|
||||||
Usage: {Path(sys.argv[0]).name} [--daemon] [-- QEMU_ARGUMENT ...]
|
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
|
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
|
base image through the shared image helper and creates a writable overlay
|
||||||
under .vm/fedora44/, leaving the base image unchanged.
|
in the state directory, leaving the base image unchanged.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--daemon Run in the background while keeping the graphical window
|
--daemon Run in the background while keeping the graphical window
|
||||||
-h, --help Show this help
|
--state-dir DIR Directory for the VM disk, seed ISO and runtime files
|
||||||
|
(default: .vm/fedora44; overrides FEDORA_VM_STATE_DIR)
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
FEDORA_VM_MEMORY Guest memory (default: 8G)
|
FEDORA_VM_MEMORY Guest memory (default: 8G)
|
||||||
@@ -157,6 +154,12 @@ while args:
|
|||||||
arg = args.pop(0)
|
arg = args.pop(0)
|
||||||
if arg == "--daemon":
|
if arg == "--daemon":
|
||||||
daemonize = True
|
daemonize = True
|
||||||
|
elif arg == "--state-dir":
|
||||||
|
if not args:
|
||||||
|
die(f"{arg} requires a directory argument.", 2)
|
||||||
|
state_dir = Path(args.pop(0))
|
||||||
|
elif arg.startswith("--state-dir="):
|
||||||
|
state_dir = Path(arg.split("=", 1)[1])
|
||||||
elif arg in ("-h", "--help"):
|
elif arg in ("-h", "--help"):
|
||||||
usage()
|
usage()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@@ -168,6 +171,12 @@ while args:
|
|||||||
usage(sys.stderr)
|
usage(sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
disk_image = Path(env("FEDORA_VM_DISK_IMAGE") or state_dir / "disk.qcow2")
|
||||||
|
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"
|
||||||
|
|
||||||
for command_name in (qemu_binary, "qemu-img"):
|
for command_name in (qemu_binary, "qemu-img"):
|
||||||
if shutil.which(command_name) is None:
|
if shutil.which(command_name) is None:
|
||||||
die(f"{command_name} is not installed.")
|
die(f"{command_name} is not installed.")
|
||||||
|
|||||||
+19
-9
@@ -37,8 +37,6 @@ def is_executable(path):
|
|||||||
|
|
||||||
|
|
||||||
state_dir = Path(env("WIN11_VM_STATE_DIR") or script_dir / ".vm" / "windows11")
|
state_dir = Path(env("WIN11_VM_STATE_DIR") or script_dir / ".vm" / "windows11")
|
||||||
disk_image = Path(env("WIN11_VM_DISK_IMAGE") or state_dir / "disk.qcow2")
|
|
||||||
seed_iso = Path(env("WIN11_VM_SEED_ISO") or disk_image.parent / "seed.iso")
|
|
||||||
disk_size = env("WIN11_VM_DISK_SIZE") or "64G"
|
disk_size = env("WIN11_VM_DISK_SIZE") or "64G"
|
||||||
memory = env("WIN11_VM_MEMORY") or "8G"
|
memory = env("WIN11_VM_MEMORY") or "8G"
|
||||||
cpus = env("WIN11_VM_CPUS") or "8"
|
cpus = env("WIN11_VM_CPUS") or "8"
|
||||||
@@ -69,11 +67,6 @@ uefi_vars_template = pick_firmware(
|
|||||||
"/opt/homebrew/share/qemu/edk2-i386-vars.fd",
|
"/opt/homebrew/share/qemu/edk2-i386-vars.fd",
|
||||||
"/usr/local/share/qemu/edk2-i386-vars.fd",
|
"/usr/local/share/qemu/edk2-i386-vars.fd",
|
||||||
)
|
)
|
||||||
uefi_vars = state_dir / "OVMF_VARS_4M.fd"
|
|
||||||
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"
|
|
||||||
daemonize = False
|
daemonize = False
|
||||||
install_from_iso = False
|
install_from_iso = False
|
||||||
extra_args = []
|
extra_args = []
|
||||||
@@ -81,15 +74,18 @@ extra_args = []
|
|||||||
|
|
||||||
def usage(stream=sys.stdout):
|
def usage(stream=sys.stdout):
|
||||||
stream.write(f"""\
|
stream.write(f"""\
|
||||||
Usage: {Path(sys.argv[0]).name} [--daemon] [--install-from-iso] [-- QEMU_ARGUMENT ...]
|
Usage: {Path(sys.argv[0]).name} [--daemon] [--install-from-iso] [--state-dir DIR] [-- QEMU_ARGUMENT ...]
|
||||||
|
|
||||||
If {disk_image} is missing, create it from the Windows 11 QCOW2 base image.
|
If the system disk (DIR/disk.qcow2, see --state-dir) is missing, create it from
|
||||||
|
the Windows 11 QCOW2 base image.
|
||||||
Use --install-from-iso to perform an automatic ISO installation instead.
|
Use --install-from-iso to perform an automatic ISO installation instead.
|
||||||
If it exists, start Windows normally without attaching installation media.
|
If it exists, start Windows normally without attaching installation media.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--daemon Run QEMU in the background
|
--daemon Run QEMU in the background
|
||||||
--install-from-iso Install from ISO when the system disk is missing
|
--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)
|
||||||
-h, --help Show this help
|
-h, --help Show this help
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
@@ -117,6 +113,12 @@ while args:
|
|||||||
daemonize = True
|
daemonize = True
|
||||||
elif arg == "--install-from-iso":
|
elif arg == "--install-from-iso":
|
||||||
install_from_iso = True
|
install_from_iso = True
|
||||||
|
elif arg == "--state-dir":
|
||||||
|
if not args:
|
||||||
|
die(f"{arg} requires a directory argument.", 2)
|
||||||
|
state_dir = Path(args.pop(0))
|
||||||
|
elif arg.startswith("--state-dir="):
|
||||||
|
state_dir = Path(arg.split("=", 1)[1])
|
||||||
elif arg in ("-h", "--help"):
|
elif arg in ("-h", "--help"):
|
||||||
usage()
|
usage()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@@ -128,6 +130,14 @@ while args:
|
|||||||
usage(sys.stderr)
|
usage(sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
disk_image = Path(env("WIN11_VM_DISK_IMAGE") or state_dir / "disk.qcow2")
|
||||||
|
seed_iso = Path(env("WIN11_VM_SEED_ISO") 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"
|
||||||
|
monitor_socket = state_dir / "qemu-monitor.sock"
|
||||||
|
guest_agent_socket = state_dir / "qemu-guest-agent.sock"
|
||||||
|
|
||||||
installing = False
|
installing = False
|
||||||
installing_from_iso = False
|
installing_from_iso = False
|
||||||
if not disk_image.is_file():
|
if not disk_image.is_file():
|
||||||
|
|||||||
Reference in new issue
Block a user