diff --git a/README.md b/README.md index dbe9428..a9b7825 100644 --- a/README.md +++ b/README.md @@ -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; 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//`. 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 from `seediso/debian13/user-data`, `seediso/debian13/meta-data`, and `seediso/debian13/network-config` before starting the VM. diff --git a/run-almalinux10.py b/run-almalinux10.py index 275f949..7c48969 100755 --- a/run-almalinux10.py +++ b/run-almalinux10.py @@ -52,8 +52,6 @@ else: uses_shared_base_image = True 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" memory = env("ALMA_VM_MEMORY") or "8G" cpus = env("ALMA_VM_CPUS") or "8" @@ -117,24 +115,23 @@ else: "/opt/homebrew/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 extra_args = [] def usage(stream=sys.stdout): 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 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: - --daemon Run in the background while keeping the graphical window - -h, --help Show this help + --daemon Run in the background while keeping the graphical window + --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: ALMA_VM_MEMORY Guest memory (default: 8G) @@ -157,6 +154,12 @@ while args: arg = args.pop(0) if arg == "--daemon": 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"): usage() sys.exit(0) @@ -168,6 +171,12 @@ while args: usage(sys.stderr) 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"): if shutil.which(command_name) is None: die(f"{command_name} is not installed.") diff --git a/run-debian13.py b/run-debian13.py index 017b5f3..b336b0b 100755 --- a/run-debian13.py +++ b/run-debian13.py @@ -52,8 +52,6 @@ else: uses_shared_base_image = True 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" memory = env("VM_MEMORY") or "8G" cpus = env("VM_CPUS") or "8" @@ -117,23 +115,23 @@ else: "/opt/homebrew/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 extra_args = [] def usage(stream=sys.stdout): 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 -.vm/debian13/, so the downloaded base image is never modified. +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. Options: - --daemon Run in the background while keeping the graphical window - -h, --help Show this help + --daemon Run in the background while keeping the graphical window + --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: VM_MEMORY Guest memory (default: 8G) @@ -156,6 +154,12 @@ while args: arg = args.pop(0) if arg == "--daemon": 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"): usage() sys.exit(0) @@ -167,6 +171,12 @@ while args: usage(sys.stderr) 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"): if shutil.which(command_name) is None: die(f"{command_name} is not installed.") diff --git a/run-fedora44.py b/run-fedora44.py index 0cf4a7c..0fb385a 100755 --- a/run-fedora44.py +++ b/run-fedora44.py @@ -52,8 +52,6 @@ else: uses_shared_base_image = True 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" memory = env("FEDORA_VM_MEMORY") or "8G" cpus = env("FEDORA_VM_CPUS") or "8" @@ -117,24 +115,23 @@ else: "/opt/homebrew/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 extra_args = [] def usage(stream=sys.stdout): 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 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: - --daemon Run in the background while keeping the graphical window - -h, --help Show this help + --daemon Run in the background while keeping the graphical window + --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: FEDORA_VM_MEMORY Guest memory (default: 8G) @@ -157,6 +154,12 @@ while args: arg = args.pop(0) if arg == "--daemon": 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"): usage() sys.exit(0) @@ -168,6 +171,12 @@ while args: usage(sys.stderr) 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"): if shutil.which(command_name) is None: die(f"{command_name} is not installed.") diff --git a/run-windows11.py b/run-windows11.py index 0f429d2..156acec 100755 --- a/run-windows11.py +++ b/run-windows11.py @@ -37,8 +37,6 @@ def is_executable(path): 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" memory = env("WIN11_VM_MEMORY") or "8G" cpus = env("WIN11_VM_CPUS") or "8" @@ -69,11 +67,6 @@ uefi_vars_template = pick_firmware( "/opt/homebrew/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 install_from_iso = False extra_args = [] @@ -81,15 +74,18 @@ extra_args = [] def usage(stream=sys.stdout): 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. If it exists, start Windows normally without attaching installation media. Options: --daemon Run QEMU in the background --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 Environment variables: @@ -117,6 +113,12 @@ while args: daemonize = True elif arg == "--install-from-iso": 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"): usage() sys.exit(0) @@ -128,6 +130,14 @@ while args: usage(sys.stderr) 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_from_iso = False if not disk_image.is_file():