[multipass] add custom rpios cloud init to emulate rpios64 #6

Merged
Ark74 merged 3 commits from improve_arm64_mp into main 2026-01-12 23:39:09 +00:00
2 changed files with 158 additions and 15 deletions

View File

@ -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" ]

View File

@ -7,7 +7,6 @@
# --clean Stop+delete+purge target VMs (by BASE-<number>, regardless of COUNT) # --clean Stop+delete+purge target VMs (by BASE-<number>, regardless of COUNT)
# Distro selection: # 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)
# --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) # --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-ubuntu (with --both-distros) order: all Ubuntu first, then all Debian
# --first-debian (with --both-distros) order: all Debian first, then all Ubuntu # --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 # --pr 4122 (repeatable) add PR numbers passed to install.txt
# --run-pr 4122 same as --pr but also forces --run (alias: --test-pr) # --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: # Env vars:
# IIAB_PR="4122 4191" Space-separated PRs # IIAB_PR="4122 4191" Space-separated PRs
# (compat) IIAB_INSTALL_ARGS If set, used as fallback for IIAB_PR # (compat) IIAB_INSTALL_ARGS If set, used as fallback for IIAB_PR
@ -34,12 +38,21 @@
set -euo pipefail set -euo pipefail
DPKG_ARCH="$(dpkg --print-architecture)" 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. # 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). # 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}" DEBIAN13_IMAGE_URL="${DEBIAN13_IMAGE_URL:-https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-${DPKG_ARCH}.qcow2}"
IMAGE="${IMAGE:-24.04}" IMAGE="${IMAGE:-24.04}"
BASE="${BASE:-ubu2404}" 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}" COUNT="${COUNT:-1}"
[ "$DPKG_ARCH" = "arm64" ] && CPUS="${CPUS:-2}" # SBC don't have spare CPUs. [ "$DPKG_ARCH" = "arm64" ] && CPUS="${CPUS:-2}" # SBC don't have spare CPUs.
[ "$DPKG_ARCH" = "amd64" ] && CPUS="${CPUS:-3}" [ "$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_TRIES="${WAIT_TRIES:-60}" # used ONLY for the first auto-resume
WAIT_SLEEP="${WAIT_SLEEP:-5}" WAIT_SLEEP="${WAIT_SLEEP:-5}"
STAGGER="${STAGGER:-20}" STAGGER="${STAGGER:-30}"
RESUME_TRIES="${RESUME_TRIES:-3}" RESUME_TRIES="${RESUME_TRIES:-3}"
RESUME_RETRY_SLEEP="${RESUME_RETRY_SLEEP:-8}" 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-ubuntu With --both-distros: run all Ubuntu first, then Debian
--first-debian With --both-distros: run all Debian first, then Ubuntu --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 options:
--pr N Add PR number (repeatable) --pr N Add PR number (repeatable)
--run-pr N Add PR number and force --run --run-pr N Add PR number and force --run
@ -111,12 +128,25 @@ while [[ $# -gt 0 ]]; do
--first-ubuntu) FIRST_UBUNTU=1; shift ;; --first-ubuntu) FIRST_UBUNTU=1; shift ;;
--first-debian) FIRST_DEBIAN=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) --debian-13)
DEBIAN13_ONLY=1 DEBIAN13_ONLY=1
IMAGE="$DEBIAN13_IMAGE_URL" IMAGE="$DEBIAN13_IMAGE_URL"
BASE="deb13" BASE="deb13"
shift ;; 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) --module)
[[ $# -lt 2 ]] && { echo "[ERROR] --module needs a value"; exit 2; } [[ $# -lt 2 ]] && { echo "[ERROR] --module needs a value"; exit 2; }
modules+=("$2"); shift 2 ;; modules+=("$2"); shift 2 ;;
@ -139,7 +169,7 @@ while [[ $# -gt 0 ]]; do
esac esac
done done
# ---- Incoherency checks (fail fast) ---- # Incoherency checks (fail fast)
if [[ "$FIRST_UBUNTU" == "1" && "$FIRST_DEBIAN" == "1" ]]; then if [[ "$FIRST_UBUNTU" == "1" && "$FIRST_DEBIAN" == "1" ]]; then
echo "[ERROR] Incoherent options: --first-ubuntu and --first-debian cannot be used together." echo "[ERROR] Incoherent options: --first-ubuntu and --first-debian cannot be used together."
exit 2 exit 2
@ -149,7 +179,31 @@ if [[ ( "$FIRST_UBUNTU" == "1" || "$FIRST_DEBIAN" == "1" ) && "$BOTH_DISTROS" !=
echo "[ERROR] --first-ubuntu/--first-debian requires --both-distros." echo "[ERROR] --first-ubuntu/--first-debian requires --both-distros."
exit 2 exit 2
fi 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 # Default module
if [[ "${#modules[@]}" -eq 0 ]]; then if [[ "${#modules[@]}" -eq 0 ]]; then
@ -169,6 +223,7 @@ if [[ "$BOTH_DISTROS" == "1" ]]; then
UBU_BASE="$UBU_BASE_ORIG" UBU_BASE="$UBU_BASE_ORIG"
DEB_IMAGE="$DEBIAN13_IMAGE_URL" DEB_IMAGE="$DEBIAN13_IMAGE_URL"
DEB_BASE="deb13" DEB_BASE="deb13"
[[ "$RPIOS_MODE" == "1" ]] && DEB_BASE="rpios-d13"
else else
UBU_IMAGE="$IMAGE" UBU_IMAGE="$IMAGE"
UBU_BASE="$BASE" UBU_BASE="$BASE"
@ -214,12 +269,13 @@ wait_all() {
# Escape BASE for regex usage # Escape BASE for regex usage
re_escape() { printf '%s' "$1" | sed -e 's/[].[^$*+?(){}|\\]/\\&/g'; } re_escape() { printf '%s' "$1" | sed -e 's/[].[^$*+?(){}|\\]/\\&/g'; }
declare -A VM_IMAGE declare -A VM_IMAGE VM_CLOUD_INIT
names=() names=()
build_vm_lists() { build_vm_lists() {
names=() names=()
VM_IMAGE=() VM_IMAGE=()
VM_CLOUD_INIT=()
if [[ "$BOTH_DISTROS" == "1" ]]; then if [[ "$BOTH_DISTROS" == "1" ]]; then
if [[ "$FIRST_UBUNTU" == "1" ]]; then if [[ "$FIRST_UBUNTU" == "1" ]]; then
@ -228,11 +284,13 @@ build_vm_lists() {
local u="${UBU_BASE}-${n}" local u="${UBU_BASE}-${n}"
names+=("$u") names+=("$u")
VM_IMAGE["$u"]="$UBU_IMAGE" VM_IMAGE["$u"]="$UBU_IMAGE"
VM_CLOUD_INIT["$u"]="$CLOUD_INIT_FILE"
done done
for n in $(seq 0 $((COUNT-1))); do for n in $(seq 0 $((COUNT-1))); do
local d="${DEB_BASE}-${n}" local d="${DEB_BASE}-${n}"
names+=("$d") names+=("$d")
VM_IMAGE["$d"]="$DEB_IMAGE" VM_IMAGE["$d"]="$DEB_IMAGE"
VM_CLOUD_INIT["$d"]="${DEB_CLOUD_INIT_FILE:-$CLOUD_INIT_FILE}"
done done
elif [[ "$FIRST_DEBIAN" == "1" ]]; then elif [[ "$FIRST_DEBIAN" == "1" ]]; then
@ -241,11 +299,13 @@ build_vm_lists() {
local d="${DEB_BASE}-${n}" local d="${DEB_BASE}-${n}"
names+=("$d") names+=("$d")
VM_IMAGE["$d"]="$DEB_IMAGE" VM_IMAGE["$d"]="$DEB_IMAGE"
VM_CLOUD_INIT["$d"]="${DEB_CLOUD_INIT_FILE:-$CLOUD_INIT_FILE}"
done done
for n in $(seq 0 $((COUNT-1))); do for n in $(seq 0 $((COUNT-1))); do
local u="${UBU_BASE}-${n}" local u="${UBU_BASE}-${n}"
names+=("$u") names+=("$u")
VM_IMAGE["$u"]="$UBU_IMAGE" VM_IMAGE["$u"]="$UBU_IMAGE"
VM_CLOUD_INIT["$u"]="$CLOUD_INIT_FILE"
done done
else else
@ -256,6 +316,8 @@ build_vm_lists() {
names+=("$u" "$d") names+=("$u" "$d")
VM_IMAGE["$u"]="$UBU_IMAGE" VM_IMAGE["$u"]="$UBU_IMAGE"
VM_IMAGE["$d"]="$DEB_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 done
fi fi
else else
@ -263,6 +325,11 @@ build_vm_lists() {
local vm="${BASE}-${n}" local vm="${BASE}-${n}"
names+=("$vm") names+=("$vm")
VM_IMAGE["$vm"]="$IMAGE" 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 done
fi fi
} }
@ -320,14 +387,20 @@ cleanup_vms() {
launch_one() { launch_one() {
local vm="$1" local vm="$1"
local img="${VM_IMAGE[$vm]:-}" 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 if vm_exists "$vm"; then
echo "[INFO] VM already exists: $vm" echo "[INFO] VM already exists: $vm"
return 0 return 0
fi fi
echo "[INFO] Launching $vm ..." 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() { run_install_txt() {
@ -337,6 +410,17 @@ run_install_txt() {
log="$LOGROOT/${vm}.install.${t}.log" log="$LOGROOT/${vm}.install.${t}.log"
rc="$LOGROOT/${vm}.install.${t}.rc" 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")) ..." echo "[INFO] install.txt in $vm (log $(basename "$log")) ..."
local modules_str pr_str local modules_str pr_str
@ -423,7 +507,7 @@ run_install_txt() {
resume_iiab() { resume_iiab() {
local vm="$1" 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 local t log rc
t="$(stamp)" t="$(stamp)"
log="$LOGROOT/${vm}.resume.${t}.log" log="$LOGROOT/${vm}.resume.${t}.log"
@ -432,7 +516,7 @@ resume_iiab() {
echo "[INFO] resume (iiab -f) in $vm (log $(basename "$log")) ..." echo "[INFO] resume (iiab -f) in $vm (log $(basename "$log")) ..."
set +e set +e
{ (
multipass start "$vm" >/dev/null 2>&1 || true multipass start "$vm" >/dev/null 2>&1 || true
if [[ "$do_long_wait" == "1" ]]; then 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) # 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. # Also neutralize apt-listchanges/pagers to avoid "Waiting for data..." stalls.
local attempt r
r=1 r=1
for attempt in $(seq 1 "$RESUME_TRIES"); do for attempt in $(seq 1 "$RESUME_TRIES"); do
echo "[INFO] Resume attempt ${attempt}/${RESUME_TRIES} on $vm" echo "[INFO] Resume attempt ${attempt}/${RESUME_TRIES} on $vm"
@ -493,7 +576,7 @@ resume_iiab() {
done done
exit "$r" exit "$r"
} >"$log" 2>&1 ) >"$log" 2>&1
echo "$?" >"$rc" echo "$?" >"$rc"
set -e 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}.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)" [[ -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" \ printf "%-12s %-8s %-8s %s\n" "$vm" "$ir" "$rr" "latest.${vm}.install.log / latest.${vm}.resume.log"
"$vm" "$ir" "$rr" \
"latest.${vm}.install.log / latest.${vm}.resume.log"
done done
echo echo
@ -561,7 +642,7 @@ pipeline_parallel_stagger() {
run_install_txt "$vm" run_install_txt "$vm"
# Only Ubuntu tends to reboot during install; Debian often doesn't. # Only Ubuntu tends to reboot during install; Debian often doesn't.
local waitflag=1 local waitflag=1
if [[ "${VM_IMAGE[$vm]}" == "$DEBIAN13_IMAGE_URL" ]]; then if [[ "$vm" == deb13-* ]]; then
waitflag=0 waitflag=0
fi fi
resume_iiab "$vm" "$waitflag" resume_iiab "$vm" "$waitflag"