diff --git a/multipass/cloud-init-rpios-like-arm64.yaml b/multipass/cloud-init-rpios-like-arm64.yaml new file mode 100644 index 0000000..0e06770 --- /dev/null +++ b/multipass/cloud-init-rpios-like-arm64.yaml @@ -0,0 +1,62 @@ +#cloud-config +hostname: rpios-like-arm64 +manage_etc_hosts: true + +write_files: + - path: /usr/local/sbin/setup-raspi-repo-and-sys-mods-arm64.sh + permissions: "0755" + content: | + #!/usr/bin/env bash + set -euo pipefail + + arch="$(dpkg --print-architecture)" + if [[ "$arch" != "arm64" ]]; then + echo "arm64-only; detected '$arch' -> skipping" >&2 + exit 0 + fi + + export DEBIAN_FRONTEND=noninteractive + + apt-get update + apt-get install -y --no-install-recommends ca-certificates curl gnupg util-linux + + install -d -m 0755 /usr/share/keyrings /etc/apt/preferences.d + + if [[ ! -s /usr/share/keyrings/raspberrypi-archive-keyring.gpg ]]; then + curl -fsSL https://archive.raspberrypi.org/debian/raspberrypi.gpg.key \ + | gpg --dearmor -o /usr/share/keyrings/raspberrypi-archive-keyring.gpg + chmod 0644 /usr/share/keyrings/raspberrypi-archive-keyring.gpg + fi + + #cat >/etc/apt/preferences.d/raspi.pref <<'EOF' + #Package: * + #Pin: origin "archive.raspberrypi.org" + #Pin-Priority: 100 + #EOF + + if [[ ! -f /etc/apt/sources.list.d/raspi.sources ]]; then + cat >/etc/apt/sources.list.d/raspi.sources <<'EOF' + Types: deb + URIs: https://archive.raspberrypi.org/debian/ + Suites: trixie + Components: main + Signed-By: /usr/share/keyrings/raspberrypi-archive-keyring.gpg + EOF + fi + + apt-get update + + # Add custom /etc/rpi-issue for "rpios-like" + if [[ ! -s /etc/rpi-issue ]]; then + { + echo "Raspberry Pi reference trixie-latest $(date -R)" + echo "Emulated using debian-13-genericcloud" + } >/etc/rpi-issue + fi + + echo "---- /etc/rpi-issue ----" + cat /etc/rpi-issue || true + echo "------------------------" + +runcmd: + - [ bash, -lc, "/usr/local/sbin/setup-raspi-repo-and-sys-mods-arm64.sh" ] diff --git a/multipass/run_parallel_iiab_test.sh b/multipass/run_parallel_iiab_test.sh index 4d6c26a..8a39dd4 100644 --- a/multipass/run_parallel_iiab_test.sh +++ b/multipass/run_parallel_iiab_test.sh @@ -7,7 +7,6 @@ # --clean Stop+delete+purge target VMs (by BASE-, 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}" @@ -54,7 +67,7 @@ LOCAL_VARS_URL="${LOCAL_VARS_URL:-https://raw.githubusercontent.com/iiab/iiab/re WAIT_TRIES="${WAIT_TRIES:-60}" # used ONLY for the first auto-resume WAIT_SLEEP="${WAIT_SLEEP:-5}" -STAGGER="${STAGGER:-20}" +STAGGER="${STAGGER:-30}" RESUME_TRIES="${RESUME_TRIES:-3}" RESUME_RETRY_SLEEP="${RESUME_RETRY_SLEEP:-8}" @@ -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}" =~ ^[0-9]+$ ]] || (( COUNT < 1 )); then + echo "[ERROR] COUNT must be an integer >= 1 (got: '${COUNT}')" >&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,17 @@ 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 "[INFO] Waiting for VM readiness before install: $vm" + echo "[ERROR] VM did not become ready in time (install phase): $vm" + } >"$log" + 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 +507,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 +516,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 +529,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 +576,7 @@ resume_iiab() { done exit "$r" - } >"$log" 2>&1 + ) >"$log" 2>&1 echo "$?" >"$rc" set -e @@ -510,9 +593,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 +642,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-* ]]; then waitflag=0 fi resume_iiab "$vm" "$waitflag"