From 169106255446c247bd893e39637e09b94565b18c Mon Sep 17 00:00:00 2001 From: Guofang Date: Thu, 15 Oct 2026 09:00:15 +0800 Subject: [PATCH] Add Ubuntu 26.04 virtual machine Add run-ubuntu2604.py with its QEMU config files and NoCloud seed. The launcher follows the Debian 13 one, with UBUNTU_VM_ settings and SSH forwarded to host port 2225. The seed injects the current user's SSH public key, uses the USTC mirror (ubuntu-ports on arm64) and installs the GNOME desktop on first boot. Update vm-images for the ubuntu2604 image target. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Kwxp2XWvKnuEBifnCbhyX5 --- README.md | 34 +++- qemu/ubuntu2604-amd64.ini | 4 + qemu/ubuntu2604-arm64.ini | 17 ++ qemu/ubuntu2604.ini | 69 +++++++ run-ubuntu2604.py | 296 ++++++++++++++++++++++++++++++ seediso/README.md | 3 +- seediso/ubuntu2604/.gitignore | 2 + seediso/ubuntu2604/README.md | 21 +++ seediso/ubuntu2604/build-seed.sh | 62 +++++++ seediso/ubuntu2604/meta-data | 2 + seediso/ubuntu2604/network-config | 10 + seediso/ubuntu2604/user-data | 46 +++++ vm-images | 2 +- 13 files changed, 564 insertions(+), 4 deletions(-) create mode 100644 qemu/ubuntu2604-amd64.ini create mode 100644 qemu/ubuntu2604-arm64.ini create mode 100644 qemu/ubuntu2604.ini create mode 100755 run-ubuntu2604.py create mode 100644 seediso/ubuntu2604/.gitignore create mode 100644 seediso/ubuntu2604/README.md create mode 100755 seediso/ubuntu2604/build-seed.sh create mode 100644 seediso/ubuntu2604/meta-data create mode 100644 seediso/ubuntu2604/network-config create mode 100644 seediso/ubuntu2604/user-data diff --git a/README.md b/README.md index 4d35ed3..8816440 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Start the required operating system with its distribution-specific launcher: ./run-debian13.py ./run-almalinux10.py ./run-fedora44.py +./run-ubuntu2604.py ./run-windows11.py ``` @@ -46,8 +47,8 @@ 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 and Fedora, and the VirtIO tools ISO for -Windows 11 (whose installation ISO and base QCOW2 are supplied manually): +base image for Debian, AlmaLinux, Fedora and Ubuntu, and the VirtIO tools ISO +for Windows 11 (whose installation ISO and base QCOW2 are supplied manually): ```bash ./run-fedora44.py --download-only @@ -126,6 +127,35 @@ architecture-specific devices in `qemu/fedora44-amd64.ini` and `qemu/fedora44-arm64.ini`. Other Fedora settings use the `FEDORA_VM_` prefix. Run `./run-fedora44.py --help` for the complete list of overrides. +## Ubuntu 26.04 + +Start an Ubuntu 26.04 LTS (Resolute Raccoon) cloud image VM with: + +```bash +./run-ubuntu2604.py +``` + +The launcher downloads the `ubuntu2604` image through the shared helper when +it is missing, creates `.vm/ubuntu2604/disk.qcow2`, and builds its NoCloud seed +from `seediso/ubuntu2604/`. Like the Debian seed, it injects the current user's +SSH public key and points APT at the USTC mirror (`ubuntu-ports` on arm64). +SSH is forwarded to host port `2225` by default: + +```bash +ssh -p 2225 guofang@localhost +``` + +On the first boot, cloud-init installs `ubuntu-desktop-minimal` (GNOME) and +`ibus-table-wubi`, makes the graphical target the default and reboots the +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 machine type and architecture-specific devices +in `qemu/ubuntu2604-amd64.ini` and `qemu/ubuntu2604-arm64.ini`. Other Ubuntu +settings use the `UBUNTU_VM_` prefix. Run `./run-ubuntu2604.py --help` for the +complete list of overrides. + ## Windows 11 23H2 On first boot, the Windows 11 launcher creates a writable system-disk overlay diff --git a/qemu/ubuntu2604-amd64.ini b/qemu/ubuntu2604-amd64.ini new file mode 100644 index 0000000..837e71a --- /dev/null +++ b/qemu/ubuntu2604-amd64.ini @@ -0,0 +1,4 @@ +# amd64 part of the Ubuntu 26.04 QEMU configuration; read after ubuntu2604.ini. + +[machine] + type = "q35" diff --git a/qemu/ubuntu2604-arm64.ini b/qemu/ubuntu2604-arm64.ini new file mode 100644 index 0000000..effacd3 --- /dev/null +++ b/qemu/ubuntu2604-arm64.ini @@ -0,0 +1,17 @@ +# arm64 part of the Ubuntu 26.04 QEMU configuration; read after ubuntu2604.ini. + +[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/ubuntu2604.ini b/qemu/ubuntu2604.ini new file mode 100644 index 0000000..4d641ef --- /dev/null +++ b/qemu/ubuntu2604.ini @@ -0,0 +1,69 @@ +# QEMU configuration for the Ubuntu 26.04 virtual machine, shared by all +# architectures. The machine type and architecture-specific devices are in +# ubuntu2604-.ini, which QEMU reads after this file. +# +# run-ubuntu2604.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 = "ubuntu2604" + +# Accelerators are tried in order: KVM on Linux, HVF on macOS, else emulation. +[accel] + accel = "kvm" + +[accel] + accel = "hvf" + +[accel] + accel = "tcg" + +[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" + +# cloud-init seed ISO. +[drive "seed0"] + if = "none" + format = "raw" + readonly = "on" + file = "${seed_image}" + +[device "seed0-dev"] + driver = "virtio-blk-pci" + drive = "seed0" + +# User-mode networking with guest SSH forwarded to the host. +[netdev "net0"] + type = "user" + hostfwd = "tcp::2225-:22" + +[device "nic0"] + driver = "virtio-net-pci" + netdev = "net0" diff --git a/run-ubuntu2604.py b/run-ubuntu2604.py new file mode 100755 index 0000000..67f5f93 --- /dev/null +++ b/run-ubuntu2604.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python3 +"""Start the Ubuntu 26.04 virtual machine.""" + +import os +import platform +import re +import shutil +import string +import subprocess +import sys +from pathlib import Path + +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("UBUNTU_VM_BASE_IMAGE"): + base_image = Path(env("UBUNTU_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", "ubuntu2604"], + 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("UBUNTU_VM_STATE_DIR") or script_dir / ".vm" / "ubuntu2604") +disk_size = env("UBUNTU_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("UBUNTU_VM_QEMU_CONFIG") or script_dir / "qemu" / "ubuntu2604.ini") +qemu_arch_config = script_dir / "qemu" / f"ubuntu2604-{host_arch}.ini" + + +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("UBUNTU_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("UBUNTU_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("UBUNTU_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("UBUNTU_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 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-.ini. Later QEMU arguments override them, e.g. +`-- -m 4G -smp 4`. + +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/ubuntu2604; overrides UBUNTU_VM_STATE_DIR) + -h, --help Show this help + +Environment variables: + UBUNTU_VM_BASE_IMAGE Base qcow2 image path (default: resolved by shared image helper) + UBUNTU_VM_SEED_IMAGE cloud-init seed ISO path (default: alongside the VM disk) + UBUNTU_VM_STATE_DIR Runtime state directory (default: .vm/ubuntu2604) + UBUNTU_VM_DISK_IMAGE Writable overlay path + UBUNTU_VM_DISK_SIZE New overlay virtual capacity (default: 20G) + VM_IMAGE_ARCH Guest/host architecture: arm64 or amd64 (default: auto-detected from the host) + UBUNTU_VM_UEFI_CODE UEFI read-only firmware path (auto-detected for the selected architecture) + 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) +""") + + +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("UBUNTU_VM_DISK_IMAGE") or state_dir / "disk.qcow2") +seed_image = Path(env("UBUNTU_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), "ubuntu2604"]) + else: + die(f"Ubuntu 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}") + +if not seed_image.is_file(): + seed_builder = script_dir / "seediso" / "ubuntu2604" / "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 Ubuntu virtual machine is already running (PID {old_pid}).") + pid_file.unlink(missing_ok=True) + +if not disk_image.is_file(): + 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("UBUNTU_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, + "seed_image": seed_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_arch_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) + +qemu_args = [ + "-readconfig", str(qemu_config), + "-readconfig", str(qemu_arch_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. + "-nic", "none", + "-display", display_backend, +] + +print(f"Starting Ubuntu 26.04 ({host_arch}) from {qemu_config_template} " + f"and {qemu_arch_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) + +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 7590060..d5209d8 100644 --- a/seediso/README.md +++ b/seediso/README.md @@ -7,7 +7,8 @@ Each operating system has an independent NoCloud configuration and generated seediso/ ├── debian13/ ├── almalinux10/ -└── fedora44/ +├── fedora44/ +└── ubuntu2604/ ``` Keeping per-OS metadata separate allows each VM to use a distinct hostname and diff --git a/seediso/ubuntu2604/.gitignore b/seediso/ubuntu2604/.gitignore new file mode 100644 index 0000000..0115591 --- /dev/null +++ b/seediso/ubuntu2604/.gitignore @@ -0,0 +1,2 @@ +seed.iso +*.iso diff --git a/seediso/ubuntu2604/README.md b/seediso/ubuntu2604/README.md new file mode 100644 index 0000000..f74472a --- /dev/null +++ b/seediso/ubuntu2604/README.md @@ -0,0 +1,21 @@ +# Ubuntu 26.04 seed.iso + +This directory contains the cloud-init NoCloud configuration used by +`run-ubuntu2604.py`. + +1. Edit `user-data`, `meta-data`, and (if needed) `network-config`. +2. Build the image: + + ```bash + ./seediso/ubuntu2604/build-seed.sh + ``` + + To choose another output path: + + ```bash + ./seediso/ubuntu2604/build-seed.sh /path/to/seed.iso + ``` + +The script uses `cloud-localds` when available, then falls back to `xorriso`, +`genisoimage`, or `mkisofs`. Attach the resulting ISO as a CD-ROM when booting +a cloud-init-enabled virtual machine. diff --git a/seediso/ubuntu2604/build-seed.sh b/seediso/ubuntu2604/build-seed.sh new file mode 100755 index 0000000..f54cb24 --- /dev/null +++ b/seediso/ubuntu2604/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/ubuntu2604/meta-data b/seediso/ubuntu2604/meta-data new file mode 100644 index 0000000..c3a44a0 --- /dev/null +++ b/seediso/ubuntu2604/meta-data @@ -0,0 +1,2 @@ +instance-id: ubuntu2604-host-001 +local-hostname: ubuntu2604-host diff --git a/seediso/ubuntu2604/network-config b/seediso/ubuntu2604/network-config new file mode 100644 index 0000000..fa9a6d7 --- /dev/null +++ b/seediso/ubuntu2604/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/ubuntu2604/user-data b/seediso/ubuntu2604/user-data new file mode 100644 index 0000000..4e35ef4 --- /dev/null +++ b/seediso/ubuntu2604/user-data @@ -0,0 +1,46 @@ +#cloud-config +preserve_hostname: false +manage_etc_hosts: true +ssh_pwauth: true + +users: + - name: guofang + gecos: Guofang + groups: [sudo] + 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 + +# amd64 packages are in the ubuntu archive, arm64 packages in ubuntu-ports. +apt: + primary: + - arches: [amd64, i386] + uri: "https://mirrors.ustc.edu.cn/ubuntu" + - arches: [default] + uri: "https://mirrors.ustc.edu.cn/ubuntu-ports" + security: + - arches: [amd64, i386] + uri: "https://mirrors.ustc.edu.cn/ubuntu" + - arches: [default] + uri: "https://mirrors.ustc.edu.cn/ubuntu-ports" + +package_update: true + +packages: + - ubuntu-desktop-minimal + - ibus-table-wubi + +runcmd: + - systemctl set-default graphical.target + +power_state: + delay: now + mode: reboot + message: "GNOME installation completed; rebooting into graphical mode." + condition: true diff --git a/vm-images b/vm-images index 5ba114f..6286495 160000 --- a/vm-images +++ b/vm-images @@ -1 +1 @@ -Subproject commit 5ba114f5ad9d98cf55421eb487d00938b5e5eca2 +Subproject commit 6286495d27836300a2cb5d621728108b7858c33a