From 04c0109baae421d59e79e6b5489aa209192f8686 Mon Sep 17 00:00:00 2001 From: Guofang Date: Tue, 18 Aug 2026 17:01:54 +0800 Subject: [PATCH] Initial commit --- .gitignore | 13 +++ README.md | 43 ++++++++ base-qcow2/.gitkeep | 1 + download-images.sh | 236 +++++++++++++++++++++++++++++++++++++++++++ install-iso/.gitkeep | 1 + tools-iso/.gitkeep | 1 + 6 files changed, 295 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 base-qcow2/.gitkeep create mode 100755 download-images.sh create mode 100644 install-iso/.gitkeep create mode 100644 tools-iso/.gitkeep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2276db1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Downloaded VM images are local assets and must never be committed. +*.iso +*.qcow2 +*.img +*.vhd +*.vhdx +*.part +*.part.url + +# Keep the intended directory layout in Git. +!base-qcow2/.gitkeep +!install-iso/.gitkeep +!tools-iso/.gitkeep diff --git a/README.md b/README.md new file mode 100644 index 0000000..534b8b1 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# vm-images + +Shared, locally cached virtual-machine installation media and base images. +Downloaded ISO, QCOW2, IMG, VHD, and VHDX files are intentionally excluded +from Git. + +## Download images + +Download every supported image: + +```bash +./download-images.sh +``` + +Download or check selected targets: + +```bash +./download-images.sh debian13 +./download-images.sh almalinux10 +./download-images.sh virtio-win +``` + +The Windows installation ISO is not downloaded because Microsoft's temporary +download URLs expire. Supply it manually at the path reported by +`./download-images.sh --print-path windows11 --iso`. + +## Query image paths + +The path interface prints one absolute path without downloading anything: + +```bash +./download-images.sh --print-path debian13 +./download-images.sh --print-path almalinux10 +./download-images.sh --print-path virtio-win +./download-images.sh --print-path windows11 +./download-images.sh --print-path windows11 --iso +``` + +The Windows target prints the QCOW2 path by default. Add `--iso` to print its +manually supplied installation ISO path. + +Download URLs can be overridden with `DEBIAN_IMAGE_URL`, `ALMA_IMAGE_URL`, +and `VIRTIO_WIN_ISO_URL`. diff --git a/base-qcow2/.gitkeep b/base-qcow2/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/base-qcow2/.gitkeep @@ -0,0 +1 @@ + diff --git a/download-images.sh b/download-images.sh new file mode 100755 index 0000000..857d4bf --- /dev/null +++ b/download-images.sh @@ -0,0 +1,236 @@ +#!/usr/bin/env bash +set -euo pipefail + +images_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +base_qcow2_dir="${images_dir}/base-qcow2" +install_iso_dir="${images_dir}/install-iso" +tools_iso_dir="${images_dir}/tools-iso" +debian_image="${base_qcow2_dir}/debian-13-generic-amd64-20260810-2566.qcow2" +alma_image="${base_qcow2_dir}/AlmaLinux-10-GenericCloud-10.2-20260526.0.x86_64.qcow2" +virtio_image="${tools_iso_dir}/virtio-win.iso" +windows_qcow2_image="${base_qcow2_dir}/Win11_25H2_English_x64_v2.qcow2" +windows_iso_image="${install_iso_dir}/Win11_25H2_English_x64_v2.iso" + +debian_url="${DEBIAN_IMAGE_URL:-https://cloud.debian.org/images/cloud/trixie/20260810-2566/debian-13-generic-amd64-20260810-2566.qcow2}" +alma_url="${ALMA_IMAGE_URL:-https://repo.almalinux.org/almalinux/10/cloud/x86_64/images/AlmaLinux-10-GenericCloud-10.2-20260526.0.x86_64.qcow2}" +virtio_url="${VIRTIO_WIN_ISO_URL:-https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso}" +debian_sha512="${DEBIAN_IMAGE_SHA512:-f6978100d8031c266d55d7815ceea7fcdeacf28e1e5834fdb9c94ac96880a054a6e6f8681c2d3b0584e0057eaf3ef7353856b85212d04134744faa9b3bb1f24f}" +alma_sha256="${ALMA_IMAGE_SHA256:-47f2218668dd4776be140dd92fa3bea700be1766e2c7d88bdfd6a4b50f477b4d}" +force_download=false + +usage() { + cat <&2 + usage >&2 + return 2 + ;; + esac +} + +if [[ "${1:-}" == --print-path ]]; then + if (($# < 2 || $# > 3)); then + printf 'Error: --print-path requires one target and optionally --iso.\n' >&2 + usage >&2 + exit 2 + fi + if (($# == 3)) && [[ "$2" != windows11 || "$3" != --iso ]]; then + printf 'Error: --iso is only valid after the windows11 target.\n' >&2 + usage >&2 + exit 2 + fi + media_option="${3:-}" + print_image_path "$2" "${media_option#--}" + exit +fi + +targets=() +for argument in "$@"; do + if [[ "$argument" == --force ]]; then + force_download=true + else + targets+=("$argument") + fi +done +if ((${#targets[@]} == 0)); then + targets=(all) +fi + +for target in "${targets[@]}"; do + case "$target" in + all|debian13|almalinux10|virtio-win) ;; + -h|--help) usage; exit 0 ;; + *) printf 'Error: unknown download target: %s\n' "$target" >&2; usage >&2; exit 2 ;; + esac +done + +wants_target() { + local requested="$1" + local target + for target in "${targets[@]}"; do + [[ "$target" == all || "$target" == "$requested" ]] && return 0 + done + return 1 +} + +mkdir -p "$base_qcow2_dir" "$install_iso_dir" "$tools_iso_dir" + +verify_file() { + local label="$1" + local file="$2" + local algorithm="$3" + local expected="$4" + local command_name="${algorithm}sum" + local actual + + if ! command -v "$command_name" >/dev/null 2>&1; then + printf 'Error: %s is required to verify %s.\n' "$command_name" "$label" >&2 + return 1 + fi + + actual="$("$command_name" "$file")" + actual="${actual%% *}" + if [[ "$actual" != "$expected" ]]; then + printf 'Error: %s checksum mismatch for %s.\nFile: %s\nExpected: %s\nActual: %s\n' \ + "${algorithm^^}" "$label" "$file" "$expected" "$actual" >&2 + return 1 + fi + + printf 'Verified %s checksum for %s.\n' "${algorithm^^}" "$label" +} + +download_file() { + local label="$1" + local url="$2" + local destination="$3" + local checksum_algorithm="${4:-}" + local expected_checksum="${5:-}" + local partial="${destination}.part" + local partial_url="${partial}.url" + local downloader + + if [[ "$force_download" == true ]]; then + printf 'Forcing a fresh download of %s.\n' "$label" + rm -f -- "$destination" "$partial" "$partial_url" + fi + + if [[ -s "$destination" ]]; then + if [[ -n "$expected_checksum" ]]; then + verify_file "$label" "$destination" "$checksum_algorithm" "$expected_checksum" || return 1 + fi + printf '%s already exists; skipping:\n%s\n' "$label" "$destination" + return + fi + + printf 'Downloading %s from:\n%s\n' "$label" "$url" + printf 'Download destination:\n%s\n' "$destination" + + if [[ -e "$partial" ]]; then + if [[ ! -f "$partial_url" ]] || [[ "$(<"$partial_url")" != "$url" ]]; then + printf 'Download URL changed; restarting partial download.\n' + rm -f -- "$partial" "$partial_url" + else + printf 'Resuming partial download:\n%s\n' "$partial" + fi + fi + printf '%s' "$url" >"$partial_url" + + if command -v curl >/dev/null 2>&1; then + downloader=curl + elif command -v wget >/dev/null 2>&1; then + downloader=wget + else + printf 'Error: curl or wget is required.\n' >&2 + exit 1 + fi + + if [[ "$downloader" == curl ]]; then + if ! curl --fail --location --retry 3 --continue-at - --output "$partial" "$url"; then + if [[ -s "$partial" ]]; then + printf 'Resume failed; restarting download from the beginning.\n' + rm -f -- "$partial" + curl --fail --location --retry 3 --output "$partial" "$url" + else + exit 1 + fi + fi + else + if ! wget --tries=3 --continue --output-document="$partial" "$url"; then + if [[ -s "$partial" ]]; then + printf 'Resume failed; restarting download from the beginning.\n' + rm -f -- "$partial" + wget --tries=3 --output-document="$partial" "$url" + else + exit 1 + fi + fi + fi + + if [[ ! -s "$partial" ]]; then + printf 'Error: downloaded file is empty: %s\n' "$partial" >&2 + exit 1 + fi + + if [[ -n "$expected_checksum" ]] && \ + ! verify_file "$label" "$partial" "$checksum_algorithm" "$expected_checksum"; then + rm -f -- "$partial" "$partial_url" + return 1 + fi + + mv -- "$partial" "$destination" + rm -f -- "$partial_url" + printf 'Downloaded %s:\n%s\n' "$label" "$destination" +} + +if wants_target debian13; then + download_file \ + 'Debian 13 cloud image' \ + "$debian_url" \ + "$debian_image" \ + sha512 \ + "$debian_sha512" +fi + +if wants_target almalinux10; then + download_file \ + 'AlmaLinux 10 cloud image' \ + "$alma_url" \ + "$alma_image" \ + sha256 \ + "$alma_sha256" +fi + +if wants_target virtio-win; then + download_file \ + 'VirtIO Windows tools ISO' \ + "$virtio_url" \ + "$virtio_image" +fi diff --git a/install-iso/.gitkeep b/install-iso/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/install-iso/.gitkeep @@ -0,0 +1 @@ + diff --git a/tools-iso/.gitkeep b/tools-iso/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tools-iso/.gitkeep @@ -0,0 +1 @@ +