From fd79eaa407bd52a4e9ace28982fe9e0d019417a3 Mon Sep 17 00:00:00 2001 From: Guofang Date: Fri, 13 Nov 2026 11:02:14 +0800 Subject: [PATCH] Add a Rocky Linux 9 virtual machine run-rocky9.py with its QEMU config, port forwards (SSH on 2226) and cloud-init seed, modelled on the Fedora 44 launcher. Update vm-images for the rocky9 download target. Co-Authored-By: Claude Sonnet 5.5 --- README.md | 26 ++- qemu/rocky9/config.ini | 48 ++++++ qemu/rocky9/darwin-arm64.ini | 24 +++ qemu/rocky9/forwards.conf | 2 + qemu/rocky9/linux-amd64.ini | 11 ++ qemu/rocky9/linux-arm64.ini | 24 +++ run-rocky9.py | 312 ++++++++++++++++++++++++++++++++++ seediso/README.md | 1 + seediso/rocky9/.gitignore | 2 + seediso/rocky9/README.md | 17 ++ seediso/rocky9/build-seed.sh | 62 +++++++ seediso/rocky9/meta-data | 2 + seediso/rocky9/network-config | 10 ++ seediso/rocky9/user-data | 18 ++ vm-images | 2 +- 15 files changed, 558 insertions(+), 3 deletions(-) create mode 100644 qemu/rocky9/config.ini create mode 100644 qemu/rocky9/darwin-arm64.ini create mode 100644 qemu/rocky9/forwards.conf create mode 100644 qemu/rocky9/linux-amd64.ini create mode 100644 qemu/rocky9/linux-arm64.ini create mode 100755 run-rocky9.py create mode 100644 seediso/rocky9/.gitignore create mode 100644 seediso/rocky9/README.md create mode 100755 seediso/rocky9/build-seed.sh create mode 100644 seediso/rocky9/meta-data create mode 100644 seediso/rocky9/network-config create mode 100644 seediso/rocky9/user-data diff --git a/README.md b/README.md index 97acc6a..00afbd3 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Start the required operating system with its distribution-specific launcher: ```bash ./run-debian13.py ./run-almalinux10.py +./run-rocky9.py ./run-fedora44.py ./run-ubuntu2604.py ./run-windows11.py @@ -48,7 +49,7 @@ corresponding `*_STATE_DIR` environment variable: With `--download-only`, a launcher only downloads the media it would fetch automatically and then exits without creating a VM disk or starting QEMU: the -base image for Debian, AlmaLinux, Fedora and Ubuntu: +base image for Debian, AlmaLinux, Rocky Linux, Fedora and Ubuntu: ```bash ./run-fedora44.py --download-only @@ -101,6 +102,27 @@ stored alongside the VM disk at `vm/almalinux10/seed.iso`. If it already exists after editing its source files, rebuild it explicitly with `./seediso/almalinux10/build-seed.sh vm/almalinux10/seed.iso`. +## Rocky Linux 9 + +Start a Rocky Linux 9 GenericCloud Base VM with: + +```bash +./run-rocky9.py +``` + +The launcher downloads the `rocky9` image through the shared helper when it +is missing, creates `vm/rocky9/disk.qcow2`, and builds its NoCloud seed from +`seediso/rocky9/`. SSH is forwarded to host port `2226` by default: + +```bash +ssh -p 2226 guofang@localhost +``` + +Like the Debian and Fedora VMs, the Rocky Linux VM hardware (8G memory, 8 CPUs, drives) is +defined in `qemu/rocky9/config.ini`, with the accelerators, machine type and +platform-specific devices in `qemu/rocky9/-.ini`. Other Rocky Linux settings use the `ROCKY_VM_` prefix. +Run `./run-rocky9.py --help` for the complete list of overrides. + ## Fedora 44 Start a Fedora 44 Cloud Base VM with: @@ -222,7 +244,7 @@ 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`, +is skipped with a warning. `ALMA_VM_FORWARDS`, `ROCKY_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 diff --git a/qemu/rocky9/config.ini b/qemu/rocky9/config.ini new file mode 100644 index 0000000..831cb3d --- /dev/null +++ b/qemu/rocky9/config.ini @@ -0,0 +1,48 @@ +# QEMU configuration for the Rocky Linux 9 virtual machine, shared by all +# architectures. The accelerators, machine type and +# platform-specific devices are in -.ini (os is linux or darwin), +# which QEMU reads after this file. +# +# run-rocky9.py fills in the file path placeholders below, writes the result +# to /qemu.ini and passes it to QEMU with -readconfig. Options that +# have no -readconfig equivalent (-cpu, -display, daemon mode) are still added +# by the launcher. + +[name] + guest = "rocky9" + +[memory] + size = "8G" + +[smp-opts] + cpus = "8" + +# UEFI firmware: read-only code and the per-VM variable store. +[drive "pflash0"] + if = "pflash" + format = "raw" + readonly = "on" + file = "${uefi_code}" + +[drive "pflash1"] + if = "pflash" + format = "raw" + file = "${uefi_vars}" + +# Writable qcow2 overlay on top of the shared base image. +[drive "disk0"] + if = "none" + format = "qcow2" + file = "${disk_image}" + +[device "disk0-dev"] + driver = "virtio-blk-pci" + drive = "disk0" + +# The NIC is attached to the user-mode network "net0", which the launcher +# defines with -netdev from qemu/rocky9/forwards.conf because a config file +# section cannot hold more than one hostfwd. + +[device "nic0"] + driver = "virtio-net-pci" + netdev = "net0" diff --git a/qemu/rocky9/darwin-arm64.ini b/qemu/rocky9/darwin-arm64.ini new file mode 100644 index 0000000..d693811 --- /dev/null +++ b/qemu/rocky9/darwin-arm64.ini @@ -0,0 +1,24 @@ +# macOS host, arm64 part of the Rocky Linux 9 QEMU configuration; read after config.ini. + +# Accelerators are tried in order: HVF on macOS, else emulation. +[accel] + accel = "hvf" + +[accel] + accel = "tcg" + +[machine] + type = "virt" + +# Unlike q35, the "virt" machine has no default GPU or USB input devices. +[device "gpu0"] + driver = "virtio-gpu-pci" + +[device "usb0"] + driver = "qemu-xhci" + +[device "kbd0"] + driver = "usb-kbd" + +[device "tablet0"] + driver = "usb-tablet" diff --git a/qemu/rocky9/forwards.conf b/qemu/rocky9/forwards.conf new file mode 100644 index 0000000..0fae38f --- /dev/null +++ b/qemu/rocky9/forwards.conf @@ -0,0 +1,2 @@ +# Host ports forwarded to the guest: LABEL HOST_PORT GUEST_PORT +SSH 2226 22 diff --git a/qemu/rocky9/linux-amd64.ini b/qemu/rocky9/linux-amd64.ini new file mode 100644 index 0000000..b4a9695 --- /dev/null +++ b/qemu/rocky9/linux-amd64.ini @@ -0,0 +1,11 @@ +# Linux host, amd64 part of the Rocky Linux 9 QEMU configuration; read after config.ini. + +# Accelerators are tried in order: KVM on Linux, else emulation. +[accel] + accel = "kvm" + +[accel] + accel = "tcg" + +[machine] + type = "q35" diff --git a/qemu/rocky9/linux-arm64.ini b/qemu/rocky9/linux-arm64.ini new file mode 100644 index 0000000..eb7a051 --- /dev/null +++ b/qemu/rocky9/linux-arm64.ini @@ -0,0 +1,24 @@ +# Linux host, arm64 part of the Rocky Linux 9 QEMU configuration; read after config.ini. + +# Accelerators are tried in order: KVM on Linux, else emulation. +[accel] + accel = "kvm" + +[accel] + accel = "tcg" + +[machine] + type = "virt" + +# Unlike q35, the "virt" machine has no default GPU or USB input devices. +[device "gpu0"] + driver = "virtio-gpu-pci" + +[device "usb0"] + driver = "qemu-xhci" + +[device "kbd0"] + driver = "usb-kbd" + +[device "tablet0"] + driver = "usb-tablet" diff --git a/run-rocky9.py b/run-rocky9.py new file mode 100755 index 0000000..07810ce --- /dev/null +++ b/run-rocky9.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 +"""Start the Rocky Linux 9 virtual machine.""" + +import os +import platform +import shutil +import string +import subprocess +import sys +from pathlib import Path + +import vmforwards + +script_dir = Path(__file__).resolve().parent +env = os.environ.get + + +def die(message, code=1): + print(f"Error: {message}", file=sys.stderr) + sys.exit(code) + + +def run(command): + """Run a command, exiting with its status on failure (like `set -e`).""" + result = subprocess.run(command) + if result.returncode != 0: + sys.exit(result.returncode) + + +def is_executable(path): + return path.is_file() and os.access(path, os.X_OK) + + +if env("VM_IMAGE_HELPER"): + image_downloader = Path(env("VM_IMAGE_HELPER")) +elif is_executable(script_dir / "vm-images" / "download-images.sh"): + image_downloader = script_dir / "vm-images" / "download-images.sh" +else: + image_downloader = script_dir / ".." / "vm-images" / "download-images.sh" + +uses_shared_base_image = False +if env("ROCKY_VM_BASE_IMAGE"): + base_image = Path(env("ROCKY_VM_BASE_IMAGE")) +else: + if not is_executable(image_downloader): + die(f"shared image helper is unavailable: {image_downloader}") + result = subprocess.run( + [str(image_downloader), "--print-path", "rocky9"], + stdout=subprocess.PIPE, + text=True, + ) + if result.returncode != 0: + sys.exit(result.returncode) + base_image = Path(result.stdout.rstrip("\n")) + uses_shared_base_image = True + +state_dir = Path(env("ROCKY_VM_STATE_DIR") or script_dir / "vm" / "rocky9") +disk_size = env("ROCKY_VM_DISK_SIZE") or "20G" + + +def normalize_arch(arch): + if arch in ("arm64", "aarch64"): + return "arm64" + if arch in ("x86_64", "amd64"): + return "amd64" + die(f"unsupported architecture: {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("ROCKY_VM_QEMU_CONFIG") or script_dir / "qemu" / "rocky9" / "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" / "rocky9" / f"{host_os}-{host_arch}.ini" +forwards_file = Path( + env("ROCKY_VM_FORWARDS") or script_dir / "qemu" / "rocky9" / "forwards.conf") + + +def pick_firmware(override, *candidates): + if override: + return Path(override) + for candidate in candidates: + if Path(candidate).is_file(): + return Path(candidate) + return Path(candidates[0]) + + +if host_arch == "arm64": + uefi_code = pick_firmware( + env("ROCKY_VM_UEFI_CODE"), + "/usr/share/AAVMF/AAVMF_CODE.fd", + "/usr/share/edk2/aarch64/QEMU_EFI.fd", + "/opt/homebrew/share/qemu/edk2-aarch64-code.fd", + "/usr/local/share/qemu/edk2-aarch64-code.fd", + ) + uefi_vars_template = pick_firmware( + env("ROCKY_VM_UEFI_VARS"), + "/usr/share/AAVMF/AAVMF_VARS.fd", + "/usr/share/edk2/aarch64/vars-template-pflash.raw", + "/opt/homebrew/share/qemu/edk2-arm-vars.fd", + "/usr/local/share/qemu/edk2-arm-vars.fd", + ) +else: + uefi_code = pick_firmware( + env("ROCKY_VM_UEFI_CODE"), + "/usr/share/OVMF/OVMF_CODE_4M.fd", + "/usr/share/edk2/ovmf/OVMF_CODE.fd", + "/opt/homebrew/share/qemu/edk2-x86_64-code.fd", + "/usr/local/share/qemu/edk2-x86_64-code.fd", + ) + uefi_vars_template = pick_firmware( + env("ROCKY_VM_UEFI_VARS"), + "/usr/share/OVMF/OVMF_VARS_4M.fd", + "/usr/share/edk2/ovmf/OVMF_VARS.fd", + "/opt/homebrew/share/qemu/edk2-i386-vars.fd", + "/usr/local/share/qemu/edk2-i386-vars.fd", + ) +daemonize = False +download_only = False +extra_args = [] + + +def usage(stream=sys.stdout): + stream.write(f"""\ +Usage: {Path(sys.argv[0]).name} [--daemon] [--download-only] [--state-dir DIR] [-- QEMU_ARGUMENT ...] + +Start a Rocky Linux 9 GenericCloud 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, ...) is defined in qemu/rocky9/config.ini, which is +rendered to qemu.ini in the state directory and loaded with QEMU's -readconfig, +followed by qemu/rocky9/-.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/rocky9/forwards.conf. + +Options: + --daemon Run in the background while keeping the graphical window + --download-only Download the base image if it is missing, then exit + --state-dir DIR Directory for the VM disk, seed ISO and runtime files + (default: vm/rocky9; overrides ROCKY_VM_STATE_DIR) + -h, --help Show this help + +Environment variables: + ROCKY_VM_BASE_IMAGE Base qcow2 image path (default: resolved by shared image helper) + ROCKY_VM_SEED_IMAGE cloud-init seed ISO path (default: alongside the VM disk) + ROCKY_VM_STATE_DIR Runtime state directory (default: vm/rocky9) + ROCKY_VM_DISK_IMAGE Writable overlay path + ROCKY_VM_DISK_SIZE New overlay virtual capacity (default: 20G) + VM_IMAGE_ARCH Guest/host architecture: arm64 or amd64 (default: auto-detected from the host) + ROCKY_VM_UEFI_CODE UEFI read-only firmware path (auto-detected for the selected architecture) + ROCKY_VM_UEFI_VARS UEFI variable-store template path (auto-detected for the selected architecture) + ROCKY_VM_DISPLAY QEMU display backend (default: cocoa on macOS, gtk elsewhere) + ROCKY_VM_QEMU_CONFIG QEMU config template rendered into the state directory and + passed to -readconfig (default: qemu/rocky9/config.ini) + ROCKY_VM_FORWARDS Port forward file (default: qemu/rocky9/forwards.conf) +""") + + +args = sys.argv[1:] +while args: + arg = args.pop(0) + if arg == "--daemon": + daemonize = True + elif arg == "--download-only": + download_only = 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) + elif arg == "--": + extra_args.extend(args) + break + else: + print(f"Error: unknown option: {arg}", file=sys.stderr) + usage(sys.stderr) + sys.exit(2) + +disk_image = Path(env("ROCKY_VM_DISK_IMAGE") or state_dir / "disk.qcow2") +seed_image = Path(env("ROCKY_VM_SEED_IMAGE") or disk_image.parent / "seed.iso") +uefi_vars = state_dir / "OVMF_VARS_4M.fd" +pid_file = state_dir / "qemu.pid" +log_file = state_dir / "console.log" +qemu_config = state_dir / "qemu.ini" + +if not download_only: + for command_name in (qemu_binary, "qemu-img"): + if shutil.which(command_name) is None: + die(f"{command_name} is not installed.") + +if not base_image.is_file(): + if uses_shared_base_image: + run([str(image_downloader), "rocky9"]) + else: + die(f"Rocky Linux base image was not found: {base_image}") +if not base_image.is_file(): + die(f"downloader did not create: {base_image}") +if download_only: + print(f"Base image is ready: {base_image}") + sys.exit(0) +for firmware_file in (uefi_code, uefi_vars_template): + if not firmware_file.is_file(): + die(f"required UEFI firmware was not found: {firmware_file}") + +creating = not disk_image.is_file() +if creating and not seed_image.is_file(): + seed_builder = script_dir / "seediso" / "rocky9" / "build-seed.sh" + if not is_executable(seed_builder): + die(f"seed image is missing and builder is unavailable: {seed_builder}") + print(f"Seed image not found; building {seed_image}...", flush=True) + run([str(seed_builder), str(seed_image)]) + +state_dir.mkdir(parents=True, exist_ok=True) + +# QEMU requires the code and vars pflash images to be exactly the same size. +# Homebrew's edk2 firmware ships code/vars pairs at mismatched sizes (true for +# both the aarch64 and x86_64 pairs), so pad per-VM copies of whichever one is +# smaller up to match the other. +uefi_code_flash = state_dir / "OVMF_CODE_4M.fd" +if not uefi_code_flash.is_file() or not uefi_vars.is_file(): + flash_size = max(uefi_code.stat().st_size, uefi_vars_template.stat().st_size) + for source, target in ((uefi_code, uefi_code_flash), (uefi_vars_template, uefi_vars)): + if not target.is_file(): + shutil.copyfile(source, target) + os.truncate(target, flash_size) + +if pid_file.is_file(): + old_pid = pid_file.read_text().strip() + if old_pid.isdigit(): + try: + os.kill(int(old_pid), 0) + except OSError: + pass + else: + die(f"the Rocky Linux virtual machine is already running (PID {old_pid}).") + pid_file.unlink(missing_ok=True) + +if creating: + run(["qemu-img", "create", "-q", "-f", "qcow2", "-F", "qcow2", + "-b", str(base_image), str(disk_image), disk_size]) + print(f"Created writable disk overlay: {disk_image}") + +is_macos = platform.system() == "Darwin" +display_backend = env("ROCKY_VM_DISPLAY") or ("cocoa" if is_macos else "gtk") + +# Most of the VM definition lives in the QEMU config template; fill in the +# file paths and hand the rendered file to QEMU with -readconfig. +config_values = { + "uefi_code": uefi_code_flash, + "uefi_vars": uefi_vars, + "disk_image": disk_image, +} +for key, value in config_values.items(): + # QEMU's config file parser has no escape syntax for quoted values. + if any(char in str(value) for char in '"\n'): + die(f"{key} must not contain double quotes or newlines: {value}") +for config_file in (qemu_config_template, qemu_platform_config): + if not config_file.is_file(): + die(f"QEMU config file was not found: {config_file}") +try: + qemu_config_text = string.Template(qemu_config_template.read_text()).substitute( + {key: str(value) for key, value in config_values.items()}) +except (KeyError, ValueError) as error: + die(f"invalid placeholder in {qemu_config_template}: {error}") +qemu_config.write_text(qemu_config_text) + +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", + # 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, +] + +if creating: + # cloud-init reads the seed ISO on the first boot only (the users, keys, + # network and packages it sets up stay on the disk), so it is attached only + # when the disk is created. + qemu_args += ["-drive", f"file={seed_image},if=none,id=seed0,format=raw,readonly=on", + "-device", "virtio-blk-pci,drive=seed0"] + +print(f"Starting Rocky Linux 9 ({host_arch}) from {qemu_config_template} " + f"and {qemu_platform_config}.", flush=True) +vmforwards.print_forwards(forwards) + +if daemonize: + log_file.write_text("") + qemu_args += [ + "-serial", f"file:{log_file}", + "-monitor", "none", + "-daemonize", + "-pidfile", str(pid_file), + ] + run([qemu_binary, *qemu_args, *extra_args]) + print(f"VM started (PID {pid_file.read_text().strip()}); console log: {log_file}") +else: + os.execvp(qemu_binary, [qemu_binary, *qemu_args, *extra_args]) diff --git a/seediso/README.md b/seediso/README.md index d5209d8..28be280 100644 --- a/seediso/README.md +++ b/seediso/README.md @@ -7,6 +7,7 @@ Each operating system has an independent NoCloud configuration and generated seediso/ ├── debian13/ ├── almalinux10/ +├── rocky9/ ├── fedora44/ └── ubuntu2604/ ``` diff --git a/seediso/rocky9/.gitignore b/seediso/rocky9/.gitignore new file mode 100644 index 0000000..0115591 --- /dev/null +++ b/seediso/rocky9/.gitignore @@ -0,0 +1,2 @@ +seed.iso +*.iso diff --git a/seediso/rocky9/README.md b/seediso/rocky9/README.md new file mode 100644 index 0000000..98e018e --- /dev/null +++ b/seediso/rocky9/README.md @@ -0,0 +1,17 @@ +# Rocky Linux 9 seed.iso + +This directory contains the cloud-init NoCloud configuration used by +`run-rocky9.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: + +```bash +./seediso/rocky9/build-seed.sh +``` + +Use a unique `instance-id` when cloning this configuration for another VM. diff --git a/seediso/rocky9/build-seed.sh b/seediso/rocky9/build-seed.sh new file mode 100755 index 0000000..f54cb24 --- /dev/null +++ b/seediso/rocky9/build-seed.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +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=() + 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[@]/#/${work}/}" +elif command -v genisoimage >/dev/null 2>&1; then + 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[@]/#/${work}/}" +else + printf 'Error: install cloud-image-utils, xorriso, genisoimage, or mkisofs.\n' >&2 + exit 1 +fi + +printf 'Created %s\n' "${output}" diff --git a/seediso/rocky9/meta-data b/seediso/rocky9/meta-data new file mode 100644 index 0000000..3c674f3 --- /dev/null +++ b/seediso/rocky9/meta-data @@ -0,0 +1,2 @@ +instance-id: rocky9-host-001 +local-hostname: rocky9-host diff --git a/seediso/rocky9/network-config b/seediso/rocky9/network-config new file mode 100644 index 0000000..fa9a6d7 --- /dev/null +++ b/seediso/rocky9/network-config @@ -0,0 +1,10 @@ +version: 2 +ethernets: + all-en: + match: + name: "en*" + dhcp4: true + all-eth: + match: + name: "eth*" + dhcp4: true diff --git a/seediso/rocky9/user-data b/seediso/rocky9/user-data new file mode 100644 index 0000000..48625c9 --- /dev/null +++ b/seediso/rocky9/user-data @@ -0,0 +1,18 @@ +#cloud-config +preserve_hostname: false +manage_etc_hosts: true +ssh_pwauth: true + +users: + - name: guofang + gecos: Guofang + groups: [wheel] + shell: /bin/bash + sudo: "ALL=(ALL) NOPASSWD: ALL" + lock_passwd: false + ssh_authorized_keys: + - @SSH_PUBKEY@ + hashed_passwd: $6$0NtCERV3eO12t9oJ$nlFE2sSc66nFfuD5X0xMnOla1cuzOnHfeGNRY5X7dCDHg.WzWVmuRe7M17Puj44.eCWLnJAWt4WNjd9vP9X0D0 + +chpasswd: + expire: false diff --git a/vm-images b/vm-images index 6064bd6..384a4ad 160000 --- a/vm-images +++ b/vm-images @@ -1 +1 @@ -Subproject commit 6064bd650d0c565f2dd5d81bcbcbc3abde61fdd4 +Subproject commit 384a4ade9f6d066db4615008e1937031c200e7ff