Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions pve/vm-core.func
Original file line number Diff line number Diff line change
Expand Up @@ -1832,6 +1832,32 @@ vm_prompt_start_vm() {
echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}${START_VM}${CL}"
}

# cloud.debian.org points latest/ at its newest build even when that build lacks
# an image (20261111-2659 has no nocloud-amd64) or a mirror has not synced it.
# Echoes the newest of the last five dated builds that has the file, else the URL.
_vm_debian_build_url() {
local url="$1" base file stem ext dir cand
[[ "$url" =~ ^(https://cloud\.debian\.org/images/cloud/[a-z]+(/daily)?)/latest/([^/]+)$ ]] || { echo "$url"; return 0; }
base="${BASH_REMATCH[1]}" file="${BASH_REMATCH[3]}"
stem="${file%.*}" ext="${file##*.}"
curl -fsIL --max-time 15 -o /dev/null "$url" 2>/dev/null && { echo "$url"; return 0; }
# daily/latest/ names its files <image>-daily.<ext>; a caller may have left the suffix off.
local -a names=("$stem")
[[ "$base" == */daily && "$stem" != *-daily ]] && names=("$stem-daily" "$stem")
for cand in "${names[@]}"; do
curl -fsIL --max-time 15 -o /dev/null "$base/latest/$cand.$ext" 2>/dev/null && { echo "$base/latest/$cand.$ext"; return 0; }
done
for dir in $(curl -fsSL --max-time 15 "$base/" 2>/dev/null | grep -oE 'href="[0-9]{8}-[0-9]+/"' | grep -oE '[0-9]{8}-[0-9]+' | sort -ru | head -n 5); do
for cand in "${names[@]}"; do
if curl -fsIL --max-time 15 -o /dev/null "$base/$dir/$cand-$dir.$ext" 2>/dev/null; then
echo "$base/$dir/$cand-$dir.$ext"
return 0
fi
done
done
echo "$url"
}

# Downloads an image and proves it is the image. A bad mirror serves HTML with
# status 200, so size decides rather than curl's exit code. Vendors also respin
# images without updating the published checksum at the same byte count, so
Expand Down Expand Up @@ -1939,6 +1965,13 @@ vm_fetch_image() {
esac
done

local resolved
resolved="$(_vm_debian_build_url "$url")"
if [[ "$resolved" != "$url" ]]; then
msg_warn "cloud.debian.org has no $(basename "$url") in latest/ right now; using build $(basename "$(dirname "$resolved")")"
url="$resolved"
fi

if [[ -n "$sha256" ]]; then
sha_tool=sha256sum sha_want="${sha256,,}"
elif [[ -n "$sha1" ]]; then
Expand Down
34 changes: 34 additions & 0 deletions tests/vm-helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,40 @@ done
if vm_image_checksum "https://sourceforge.net/projects/x/files/a.iso/download" >/dev/null; then fail "unknown hosts have no checksum"; fi
ok "vendor checksum files are derived from the download URL"

# ── Debian latest/ without the image ────────────────────────────────────────
# 20261111-2659 shipped no nocloud-amd64, and latest/ pointed at it: docker-vm
# and debian-vm failed with 404 until the next build (VE #18702).
DEB=https://cloud.debian.org/images/cloud/trixie
fixture "$DEB/" <<'EOF'
<a href="20261026-2643/">20261026-2643/</a> <a href="20261103-2651/">20261103-2651/</a>
<a href="20261111-2659/">20261111-2659/</a> <a href="daily/">daily/</a> <a href="latest/">latest/</a>
EOF
fixture "$DEB/daily/" <<<'<a href="20261111-2659/">x</a> <a href="20261112-2660/">x</a>'
HEAD_OK["$DEB/latest/debian-13-generic-amd64.qcow2"]=1
HEAD_OK["$DEB/20261103-2651/debian-13-nocloud-amd64-20261103-2651.qcow2"]=1
HEAD_OK["$DEB/20261026-2643/debian-13-nocloud-amd64-20261026-2643.qcow2"]=1
HEAD_OK["$DEB/daily/20261112-2660/debian-13-generic-amd64-daily-20261112-2660.qcow2"]=1
assert_eq "$(_vm_debian_build_url "$DEB/latest/debian-13-generic-amd64.qcow2")" "$DEB/latest/debian-13-generic-amd64.qcow2" "latest/ that has the image"
assert_eq "$(_vm_debian_build_url "$DEB/latest/debian-13-nocloud-amd64.qcow2")" \
"$DEB/20261103-2651/debian-13-nocloud-amd64-20261103-2651.qcow2" "the newest build that has it"
assert_eq "$(_vm_debian_build_url "$DEB/daily/latest/debian-13-generic-amd64-daily.qcow2")" \
"$DEB/daily/20261112-2660/debian-13-generic-amd64-daily-20261112-2660.qcow2" "daily builds"
HEAD_OK["$DEB/daily/latest/debian-13-generic-amd64-daily.qcow2"]=1
assert_eq "$(_vm_debian_build_url "$DEB/daily/latest/debian-13-generic-amd64.qcow2")" "$DEB/daily/latest/debian-13-generic-amd64-daily.qcow2" "daily name without its suffix (waydroid-vm)"
assert_eq "$(_vm_debian_build_url "$DEB/latest/debian-13-nocloud-riscv64.qcow2")" "$DEB/latest/debian-13-nocloud-riscv64.qcow2" "no build has it"
assert_eq "$(_vm_debian_build_url https://dl.test/openwrt.img.gz)" https://dl.test/openwrt.img.gz "other hosts"
ok "a Debian latest/ without the image falls back to the newest build that has it"

reset_logs
fixture "$DEB/20261103-2651/debian-13-nocloud-amd64-20261103-2651.qcow2" <"$IMAGE"
fixture "$DEB/20261103-2651/SHA512SUMS" <<<"${IMAGE_SHA512} debian-13-nocloud-amd64-20261103-2651.qcow2"
vm_fetch_image "$DEB/latest/debian-13-nocloud-amd64.qcow2" "$TEST_DIR/cache/debian-13-nocloud-amd64.qcow2" ||
fail "a missing latest/ image must come from the build that has it"
cmp -s "$IMAGE" "$TEST_DIR/cache/debian-13-nocloud-amd64.qcow2" || fail "the image kept its cache name"
assert_contains "$LOG" "$DEB/20261103-2651/SHA512SUMS"
assert_contains "$WARNINGS" "using build 20261103-2651"
ok "vm_fetch_image downloads and verifies it from that build"

# ── vm_fetch_image checksum options ─────────────────────────────────────────
fixture "https://dl.test/openwrt.img.gz" <"$IMAGE"
reset_logs
Expand Down
Loading