[multipass] add cloud-init feed available (enable rpios repo)

This commit is contained in:
Luis Guzmán 2026-01-12 12:41:02 -06:00
parent 293e35a4bf
commit 2049944eaf
1 changed files with 92 additions and 14 deletions

View File

@ -7,7 +7,6 @@
# --clean Stop+delete+purge target VMs (by BASE-<number>, regardless of COUNT)
# Distro selection:
# --debian-13 Debian 13 only (sets IMAGE=$DEBIAN13_IMAGE_URL and BASE=deb13)
# --debian-13 Debian 13 only (sets IMAGE=$DEBIAN13_IMAGE_URL and BASE=deb13)
# --both-distros Run Ubuntu + Debian 13 in parallel: COUNT=N => 2N VMs (default order: interleaved)
# --first-ubuntu (with --both-distros) order: all Ubuntu first, then all Debian
# --first-debian (with --both-distros) order: all Debian first, then all Ubuntu
@ -16,6 +15,11 @@
# --pr 4122 (repeatable) add PR numbers passed to install.txt
# --run-pr 4122 same as --pr but also forces --run (alias: --test-pr)
#
# Cloud-init:
# --cloud-init FILE Apply cloud-init FILE to all VMs (Ubuntu + Debian)
# --cloud-init-rpios Apply RaspberryPiOS-style cloud-init only to Debian side
# (alias of --debian-13, but sets Debian BASE=rpios-d13)
#
# Env vars:
# IIAB_PR="4122 4191" Space-separated PRs
# (compat) IIAB_INSTALL_ARGS If set, used as fallback for IIAB_PR
@ -34,12 +38,21 @@
set -euo pipefail
DPKG_ARCH="$(dpkg --print-architecture)"
# Resolve paths relative to this script (so cloud-init files work from any cwd)
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# Debian 13 (Trixie) official cloud image (qcow2). Multipass can launch from URL/file:// on Linux.
# Source: Debian cloud images live under cloud.debian.org/images/cloud/ ('genericcloud' includes cloud-init).
DEBIAN13_IMAGE_URL="${DEBIAN13_IMAGE_URL:-https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-${DPKG_ARCH}.qcow2}"
IMAGE="${IMAGE:-24.04}"
BASE="${BASE:-ubu2404}"
# cloud-init controls
CLOUD_INIT_FILE="${CLOUD_INIT_FILE:-}"
RPIOS_CLOUD_INIT_FILE="${RPIOS_CLOUD_INIT_FILE:-$SCRIPT_DIR/cloud-init-rpios-like-arm64.yaml}"
DEB_CLOUD_INIT_FILE="${DEB_CLOUD_INIT_FILE:-}"
RPIOS_MODE=0
COUNT="${COUNT:-1}"
[ "$DPKG_ARCH" = "arm64" ] && CPUS="${CPUS:-2}" # SBC don't have spare CPUs.
[ "$DPKG_ARCH" = "amd64" ] && CPUS="${CPUS:-3}"
@ -91,6 +104,10 @@ Image shortcuts:
--first-ubuntu With --both-distros: run all Ubuntu first, then Debian
--first-debian With --both-distros: run all Debian first, then Ubuntu
Cloud-init:
--cloud-init FILE Apply FILE to all VMs
--cloud-init-rpios Apply rpios cloud-init only to Debian side; BASE=rpios-d13 (Debian)
PR options:
--pr N Add PR number (repeatable)
--run-pr N Add PR number and force --run
@ -111,12 +128,25 @@ while [[ $# -gt 0 ]]; do
--first-ubuntu) FIRST_UBUNTU=1; shift ;;
--first-debian) FIRST_DEBIAN=1; shift ;;
--cloud-init)
[[ $# -lt 2 ]] && { echo "[ERROR] --cloud-init needs a file path"; exit 2; }
CLOUD_INIT_FILE="$2"; shift 2 ;;
--debian-13)
DEBIAN13_ONLY=1
IMAGE="$DEBIAN13_IMAGE_URL"
BASE="deb13"
shift ;;
--cloud-init-rpios)
# Debian-only rpios flavor: also implies Debian 13 selection
DEBIAN13_ONLY=1
RPIOS_MODE=1
IMAGE="$DEBIAN13_IMAGE_URL"
BASE="rpios-d13"
DEB_CLOUD_INIT_FILE="$RPIOS_CLOUD_INIT_FILE"
shift ;;
--module)
[[ $# -lt 2 ]] && { echo "[ERROR] --module needs a value"; exit 2; }
modules+=("$2"); shift 2 ;;
@ -139,7 +169,7 @@ while [[ $# -gt 0 ]]; do
esac
done
# ---- Incoherency checks (fail fast) ----
# Incoherency checks (fail fast)
if [[ "$FIRST_UBUNTU" == "1" && "$FIRST_DEBIAN" == "1" ]]; then
echo "[ERROR] Incoherent options: --first-ubuntu and --first-debian cannot be used together."
exit 2
@ -149,7 +179,31 @@ if [[ ( "$FIRST_UBUNTU" == "1" || "$FIRST_DEBIAN" == "1" ) && "$BOTH_DISTROS" !=
echo "[ERROR] --first-ubuntu/--first-debian requires --both-distros."
exit 2
fi
# ----
if (( COUNT < 1 )); then
echo "[ERROR] COUNT must be >= 1" >&2
exit 2
fi
# ---
# Fail-fast validation for cloud-init files
if [[ -n "${CLOUD_INIT_FILE:-}" && ! -f "$CLOUD_INIT_FILE" ]]; then
echo "[ERROR] --cloud-init file not found on host: $CLOUD_INIT_FILE" >&2
exit 2
fi
if [[ -n "${DEB_CLOUD_INIT_FILE:-}" && ! -f "$DEB_CLOUD_INIT_FILE" ]]; then
echo "[ERROR] Debian cloud-init file not found on host: $DEB_CLOUD_INIT_FILE" >&2
exit 2
fi
# RPiOS flavor is meant for arm64 (rpios-like). Fail fast to avoid surprising runs.
if [[ "$RPIOS_MODE" == "1" && "$DPKG_ARCH" != "arm64" ]]; then
echo "[ERROR] --cloud-init-rpios is intended for arm64 hosts (detected: $DPKG_ARCH)." >&2
echo " If you really want to run it on non-arm64, pass a normal --cloud-init FILE instead." >&2
exit 2
fi
# ---
# Default module
if [[ "${#modules[@]}" -eq 0 ]]; then
@ -169,6 +223,7 @@ if [[ "$BOTH_DISTROS" == "1" ]]; then
UBU_BASE="$UBU_BASE_ORIG"
DEB_IMAGE="$DEBIAN13_IMAGE_URL"
DEB_BASE="deb13"
[[ "$RPIOS_MODE" == "1" ]] && DEB_BASE="rpios-d13"
else
UBU_IMAGE="$IMAGE"
UBU_BASE="$BASE"
@ -214,12 +269,13 @@ wait_all() {
# Escape BASE for regex usage
re_escape() { printf '%s' "$1" | sed -e 's/[].[^$*+?(){}|\\]/\\&/g'; }
declare -A VM_IMAGE
declare -A VM_IMAGE VM_CLOUD_INIT
names=()
build_vm_lists() {
names=()
VM_IMAGE=()
VM_CLOUD_INIT=()
if [[ "$BOTH_DISTROS" == "1" ]]; then
if [[ "$FIRST_UBUNTU" == "1" ]]; then
@ -228,11 +284,13 @@ build_vm_lists() {
local u="${UBU_BASE}-${n}"
names+=("$u")
VM_IMAGE["$u"]="$UBU_IMAGE"
VM_CLOUD_INIT["$u"]="$CLOUD_INIT_FILE"
done
for n in $(seq 0 $((COUNT-1))); do
local d="${DEB_BASE}-${n}"
names+=("$d")
VM_IMAGE["$d"]="$DEB_IMAGE"
VM_CLOUD_INIT["$d"]="${DEB_CLOUD_INIT_FILE:-$CLOUD_INIT_FILE}"
done
elif [[ "$FIRST_DEBIAN" == "1" ]]; then
@ -241,11 +299,13 @@ build_vm_lists() {
local d="${DEB_BASE}-${n}"
names+=("$d")
VM_IMAGE["$d"]="$DEB_IMAGE"
VM_CLOUD_INIT["$d"]="${DEB_CLOUD_INIT_FILE:-$CLOUD_INIT_FILE}"
done
for n in $(seq 0 $((COUNT-1))); do
local u="${UBU_BASE}-${n}"
names+=("$u")
VM_IMAGE["$u"]="$UBU_IMAGE"
VM_CLOUD_INIT["$u"]="$CLOUD_INIT_FILE"
done
else
@ -256,6 +316,8 @@ build_vm_lists() {
names+=("$u" "$d")
VM_IMAGE["$u"]="$UBU_IMAGE"
VM_IMAGE["$d"]="$DEB_IMAGE"
VM_CLOUD_INIT["$u"]="$CLOUD_INIT_FILE"
VM_CLOUD_INIT["$d"]="${DEB_CLOUD_INIT_FILE:-$CLOUD_INIT_FILE}"
done
fi
else
@ -263,6 +325,11 @@ build_vm_lists() {
local vm="${BASE}-${n}"
names+=("$vm")
VM_IMAGE["$vm"]="$IMAGE"
if [[ "$DEBIAN13_ONLY" == "1" ]]; then
VM_CLOUD_INIT["$vm"]="${DEB_CLOUD_INIT_FILE:-$CLOUD_INIT_FILE}"
else
VM_CLOUD_INIT["$vm"]="$CLOUD_INIT_FILE"
fi
done
fi
}
@ -320,14 +387,20 @@ cleanup_vms() {
launch_one() {
local vm="$1"
local img="${VM_IMAGE[$vm]:-}"
[[ -z "$img" ]] && { echo "[ERROR] No image mapping for VM '$vm'"; return 2; }
[[ -n "$img" ]] || { echo "[ERROR] No image mapping for VM '$vm'"; return 2; }
# Cloud-init file to apply (already validated earlier in fail-fast checks)
local ci="${VM_CLOUD_INIT[$vm]:-}"
local -a ci_args=()
[[ -n "${ci:-}" ]] && ci_args=(--cloud-init "$ci")
if vm_exists "$vm"; then
echo "[INFO] VM already exists: $vm"
return 0
fi
echo "[INFO] Launching $vm ..."
multipass launch "$img" -n "$vm" -c "$CPUS" -m "$MEM" -d "$DISK" >/dev/null
multipass launch "$img" -n "$vm" -c "$CPUS" -m "$MEM" -d "$DISK" "${ci_args[@]}" >/dev/null
}
run_install_txt() {
@ -337,6 +410,14 @@ run_install_txt() {
log="$LOGROOT/${vm}.install.${t}.log"
rc="$LOGROOT/${vm}.install.${t}.rc"
echo "[INFO] Waiting for VM readiness before install: $vm"
if ! wait_for_vm "$vm"; then
echo "[ERROR] VM did not become ready in time (install phase): $vm"
echo "88" >"$rc"
set_latest_links "$vm" "install" "$log" "$rc"
return 88
fi
echo "[INFO] install.txt in $vm (log $(basename "$log")) ..."
local modules_str pr_str
@ -423,7 +504,7 @@ run_install_txt() {
resume_iiab() {
local vm="$1"
local do_long_wait="$2" # 1 => wait_for_vm (only for first auto-resume), 0 => no long wait
local do_long_wait="$2"
local t log rc
t="$(stamp)"
log="$LOGROOT/${vm}.resume.${t}.log"
@ -432,7 +513,7 @@ resume_iiab() {
echo "[INFO] resume (iiab -f) in $vm (log $(basename "$log")) ..."
set +e
{
(
multipass start "$vm" >/dev/null 2>&1 || true
if [[ "$do_long_wait" == "1" ]]; then
@ -445,7 +526,6 @@ resume_iiab() {
# Retry on rc=255 (SSH connection dropped; often reboot/network restart during apt/upgrade)
# Also neutralize apt-listchanges/pagers to avoid "Waiting for data..." stalls.
local attempt r
r=1
for attempt in $(seq 1 "$RESUME_TRIES"); do
echo "[INFO] Resume attempt ${attempt}/${RESUME_TRIES} on $vm"
@ -493,7 +573,7 @@ resume_iiab() {
done
exit "$r"
} >"$log" 2>&1
) >"$log" 2>&1
echo "$?" >"$rc"
set -e
@ -510,9 +590,7 @@ summary() {
[[ -f "$LOGROOT/latest.${vm}.install.rc" ]] && ir="$(cat "$LOGROOT/latest.${vm}.install.rc" 2>/dev/null || echo n/a)"
[[ -f "$LOGROOT/latest.${vm}.resume.rc" ]] && rr="$(cat "$LOGROOT/latest.${vm}.resume.rc" 2>/dev/null || echo n/a)"
printf "%-12s %-8s %-8s %s\n" \
"$vm" "$ir" "$rr" \
"latest.${vm}.install.log / latest.${vm}.resume.log"
printf "%-12s %-8s %-8s %s\n" "$vm" "$ir" "$rr" "latest.${vm}.install.log / latest.${vm}.resume.log"
done
echo
@ -561,7 +639,7 @@ pipeline_parallel_stagger() {
run_install_txt "$vm"
# Only Ubuntu tends to reboot during install; Debian often doesn't.
local waitflag=1
if [[ "${VM_IMAGE[$vm]}" == "$DEBIAN13_IMAGE_URL" ]]; then
if [[ "$vm" == deb13-* || "$vm" == rpios-d13-* ]]; then
waitflag=0
fi
resume_iiab "$vm" "$waitflag"