From 6e8fa7877bd050268c46f3782fe0d550273f7810 Mon Sep 17 00:00:00 2001 From: I-am-PUID-0 <36779668+I-am-PUID-0@users.noreply.github.com> Date: Thu, 20 Aug 2026 00:02:08 +0000 Subject: [PATCH] fix(pve): use pveam for Debian ARM64 templates Route Debian ARM64 container creation through Proxmox's official, architecture-filtered pveam catalog instead of the missing community-scripts/debian-arm64-lxc release source. Preserve the Jenkins custom-template fallback for other ARM64 distributions. Apply exact architecture filtering to local, online, fallback, previous-version, and interactive template selection paths. Add a regression test covering PVE and non-PVE routing and run it from the split-file verification workflow. --- .github/workflows/verify-split-files.yml | 5 ++ pve/backend.func | 77 ++++++++-------- tests/arm64-template-routing.sh | 107 +++++++++++++++++++++++ ui/defaults.func | 14 ++- 4 files changed, 163 insertions(+), 40 deletions(-) create mode 100755 tests/arm64-template-routing.sh diff --git a/.github/workflows/verify-split-files.yml b/.github/workflows/verify-split-files.yml index 318ed02..7a5bc00 100644 --- a/.github/workflows/verify-split-files.yml +++ b/.github/workflows/verify-split-files.yml @@ -22,6 +22,7 @@ on: - "vm/**" - "pve/**" - "incus/**" + - "tests/**" push: branches: [main] paths: @@ -34,6 +35,7 @@ on: - "vm/**" - "pve/**" - "incus/**" + - "tests/**" workflow_dispatch: env: @@ -54,6 +56,9 @@ jobs: done < <(find $ENGINE_DIRS -name '*.func' -type f) exit $fail + - name: ARM64 template routing + run: bash tests/arm64-template-routing.sh + - name: Prefetch lists cover every engine file run: | set -euo pipefail diff --git a/pve/backend.func b/pve/backend.func index b86a122..a29c2b6 100644 --- a/pve/backend.func +++ b/pve/backend.func @@ -16,6 +16,16 @@ _PVE_BACKEND_FUNC_LOADED=1 # SECTION 8: CONTAINER CREATION & DEPLOYMENT # ============================================================================== +_use_custom_arm64_template() { + [[ "${1:-}" == "arm64" && "${2:-}" != "debian" ]] +} + +_filter_templates_by_arch() { + local arch="${1:-}" + [[ -n "$arch" ]] || return 1 + grep -E "_${arch}\\.(tar\\.zst|tar\\.xz|tar\\.gz)([[:space:]]|$)" +} + # ------------------------------------------------------------------------------ # _apply_http_proxy_in_container() # ------------------------------------------------------------------------------ @@ -2037,6 +2047,7 @@ create_lxc_container() { pveam list "$TEMPLATE_STORAGE" 2>/dev/null | awk -v os="$os_type" -v pat="$tpl_pattern" '$1 ~ ("^"os"|/"os) && $1 ~ pat {print $1}' | sed 's|.*/||' | + _filter_templates_by_arch "$ARCH" | sed -E "s/^${os_type}-([0-9]+(\.[0-9]+)?).*/\1/" | sort -u -V || true ) @@ -2048,6 +2059,7 @@ create_lxc_container() { awk '{print $2}' | grep -E "^${os_type}-[0-9]" | { [[ -n "$tpl_pattern" ]] && grep "$tpl_pattern" || cat; } | + _filter_templates_by_arch "$ARCH" | sed -E "s/^${os_type}-([0-9]+(\.[0-9]+)?).*/\1/" | sort -u -V 2>/dev/null || true ) @@ -2072,7 +2084,9 @@ create_lxc_container() { mapfile -t _fb_local < <( pveam list "$TEMPLATE_STORAGE" 2>/dev/null | awk -v search="$fallback_search" -v pat="$tpl_pattern" '$1 ~ search && $1 ~ pat {print $1}' | - sed 's|.*/||' | sort -t - -k 2 -V || true + sed 's|.*/||' | + _filter_templates_by_arch "$ARCH" | + sort -t - -k 2 -V || true ) if [[ ${#_fb_local[@]} -gt 0 ]]; then fallback_template="${_fb_local[-1]}" @@ -2081,6 +2095,7 @@ create_lxc_container() { _pveam_available | awk '{print $2}' | grep -E "^${fallback_search}.*${tpl_pattern}" | + _filter_templates_by_arch "$ARCH" | sort -t - -k 2 -V 2>/dev/null || true ) [[ ${#_fb_online[@]} -gt 0 ]] && fallback_template="${_fb_online[-1]}" @@ -2147,6 +2162,7 @@ create_lxc_container() { sed 's|.*/||' | grep -E "^${os_type}-[0-9]" | { [[ -n "$tpl_pattern" ]] && grep "$tpl_pattern" || cat; } | + _filter_templates_by_arch "$ARCH" | sed -E "s/^${os_type}-([0-9]+(\.[0-9]+)?).*/\1/" | sort -u -V || true ) @@ -2158,6 +2174,7 @@ create_lxc_container() { awk '{print $2}' | grep -E "^${os_type}-[0-9]" | { [[ -n "$tpl_pattern" ]] && grep "$tpl_pattern" || cat; } | + _filter_templates_by_arch "$ARCH" | sed -E "s/^${os_type}-([0-9]+(\.[0-9]+)?).*/\1/" | sort -u -V 2>/dev/null || true ) @@ -2277,7 +2294,8 @@ create_lxc_container() { ARCH="$(dpkg --print-architecture)" - # Maps OS type + version to the release variant name used by ARM64 template sources. + # Maps OS type + version to the release variant name used by custom ARM64 + # template sources. Debian uses the official pveam catalog below. arm64_template_variant() { case "$1:$2" in debian:12) echo "bookworm" ;; @@ -2294,9 +2312,8 @@ create_lxc_container() { esac } - # Downloads an ARM64 LXC rootfs template to $1. - # Debian: fetches latest release from community-scripts/debian-arm64-lxc on GitHub. - # Others: fetches from jenkins.linuxcontainers.org. + # Downloads a custom ARM64 LXC rootfs template to $1. Debian uses the + # official pveam catalog; this fallback is retained for other distributions. download_arm64_template() { local dest="$1" url @@ -2305,21 +2322,7 @@ create_lxc_container() { exit 217 } - if [[ "$PCT_OSTYPE" == "debian" ]]; then - url=$( - curl -fsSL "https://api.github.com/repos/community-scripts/debian-arm64-lxc/releases/latest" | - jq -r --arg v "$CUSTOM_TEMPLATE_VARIANT" \ - '.assets[].browser_download_url | select(test("debian-" + $v + "-arm64-rootfs\\.tar\\.xz$"))' | - head -n1 - ) - - [[ -n "$url" ]] || { - msg_error "Could not find Debian ${CUSTOM_TEMPLATE_VARIANT} ARM64 template URL." - exit 225 - } - else - url="https://jenkins.linuxcontainers.org/job/image-${PCT_OSTYPE}/architecture=arm64,release=${CUSTOM_TEMPLATE_VARIANT},variant=default/lastStableBuild/artifact/rootfs.tar.xz" - fi + url="https://jenkins.linuxcontainers.org/job/image-${PCT_OSTYPE}/architecture=arm64,release=${CUSTOM_TEMPLATE_VARIANT},variant=default/lastStableBuild/artifact/rootfs.tar.xz" msg_info "Downloading ${PCT_OSTYPE^} ${CUSTOM_TEMPLATE_VARIANT} ARM64 template" local patched="${dest%/*}/.${dest##*/}.patched" @@ -2356,7 +2359,7 @@ create_lxc_container() { download_template() { local dest="${1:-$TEMPLATE_PATH}" - if [[ "$ARCH" == "arm64" ]]; then + if [[ "$TEMPLATE_SOURCE" == "custom-arm64" ]]; then download_arm64_template "$dest" else pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 || { @@ -2531,8 +2534,10 @@ create_lxc_container() { # ------------------------------------------------------------------------------ CUSTOM_TEMPLATE_VARIANT="" - if [[ "$ARCH" == "arm64" ]]; then - # ARM64: use custom template download from linuxcontainers.org / GitHub + if _use_custom_arm64_template "$ARCH" "$PCT_OSTYPE"; then + # Use a custom ARM64 template only when the Proxmox catalog does not supply + # the distribution. Debian ARM64 is available through pveam and follows the + # same architecture-filtered discovery path as Debian AMD64. msg_info "Preparing ARM64 template" CUSTOM_TEMPLATE_VARIANT=$(arm64_template_variant "$PCT_OSTYPE" "${PCT_OSVERSION:-}") || { @@ -2583,9 +2588,11 @@ create_lxc_container() { # Step 1: Check local templates first (instant) mapfile -t LOCAL_TEMPLATES < <( pveam list "$TEMPLATE_STORAGE" 2>/dev/null | - awk -v search="${TEMPLATE_SEARCH}" -v pattern="${TEMPLATE_PATTERN}" -v arch="${ARCH}" \ - '$1 ~ search && $1 ~ pattern && $1 ~ ("_" arch "\\.(tar\\.zst|tar\\.xz|tar\\.gz)$") {print $1}' | - sed 's|.*/||' | sort -t - -k 2 -V || true + awk -v search="${TEMPLATE_SEARCH}" -v pattern="${TEMPLATE_PATTERN}" \ + '$1 ~ search && $1 ~ pattern {print $1}' | + sed 's|.*/||' | + _filter_templates_by_arch "$ARCH" | + sort -t - -k 2 -V || true ) # Step 2: If local template found, use it immediately (skip pveam update) @@ -2630,14 +2637,7 @@ create_lxc_container() { msg_custom "ℹ️" "${YW}" "Select a different available version or press Enter to cancel." AVAILABLE_VERSIONS=() - mapfile -t AVAILABLE_VERSIONS < <( - _pveam_available | - grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | - awk -F'\t' '{print $1}' | - grep "^${PCT_OSTYPE}-" | - sed -E "s/.*${PCT_OSTYPE}-([0-9]+(\.[0-9]+)?).*/\1/" | - sort -u -V 2>/dev/null || true - ) + mapfile -t AVAILABLE_VERSIONS < <(_list_os_versions "$PCT_OSTYPE") if [[ ${#AVAILABLE_VERSIONS[@]} -gt 0 ]]; then echo "" @@ -2659,13 +2659,16 @@ create_lxc_container() { mapfile -t LOCAL_TEMPLATES < <( pveam list "$TEMPLATE_STORAGE" 2>/dev/null | awk -v search="${TEMPLATE_SEARCH}-" -v pattern="${TEMPLATE_PATTERN}" '$1 ~ search && $1 ~ pattern {print $1}' | - sed 's|.*/||' | sort -t - -k 2 -V || true + sed 's|.*/||' | + _filter_templates_by_arch "$ARCH" | + sort -t - -k 2 -V || true ) mapfile -t ONLINE_TEMPLATES < <( _pveam_available | grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | awk '{print $2}' | grep -E "^${TEMPLATE_SEARCH}-.*${TEMPLATE_PATTERN}" | + _filter_templates_by_arch "$ARCH" | sort -t - -k 2 -V 2>/dev/null || true ) ONLINE_TEMPLATE="" @@ -2868,7 +2871,7 @@ create_lxc_container() { download_template msg_ok "Template downloaded" elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then - if [[ "$ARCH" == "arm64" || -n "$ONLINE_TEMPLATE" ]]; then + if [[ "$TEMPLATE_SOURCE" == "custom-arm64" || -n "$ONLINE_TEMPLATE" ]]; then msg_info "Template appears corrupted – re-downloading" rm -f "$TEMPLATE_PATH" download_template @@ -2954,7 +2957,7 @@ create_lxc_container() { if [[ ! -f "$LOCAL_TEMPLATE_PATH" ]]; then msg_ok "Trying local storage fallback" msg_info "Downloading template to local" - if [[ "$ARCH" == "arm64" ]]; then + if [[ "$TEMPLATE_SOURCE" == "custom-arm64" ]]; then download_arm64_template "$LOCAL_TEMPLATE_PATH" else pveam download local "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 diff --git a/tests/arm64-template-routing.sh b/tests/arm64-template-routing.sh new file mode 100755 index 0000000..9bf7cfb --- /dev/null +++ b/tests/arm64-template-routing.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +# shellcheck source=../ui/defaults.func +source "$repo_root/ui/defaults.func" + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + exit 1 +} + +msg_warn() { :; } +msg_ok() { :; } + +MOCK_ARCH="arm64" +dpkg() { + [[ "${1:-}" == "--print-architecture" ]] || return 1 + printf '%s\n' "$MOCK_ARCH" +} + +_pveam_available() { + cat <<'EOF' +system debian-13-standard_13.6-1_amd64.tar.zst +system debian-13-standard_13.6-1_arm64.tar.zst +system ubuntu-24.04-standard_24.04-1_arm64.tar.zst +EOF +} + +pveam() { + if [[ "${1:-}" == "list" ]]; then + printf '%s\n' 'local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst' + return 0 + fi + return 1 +} + +# Without a PVE backend hook, ARM64 version resolution remains unchanged for +# Incus and any other platform-specific template source. +var_os="debian" +var_version="12" +export var_os var_version +resolve_os_version +[[ "$var_version" == "12" ]] || fail "Non-PVE ARM64 version resolution changed" + +# shellcheck source=../pve/backend.func +source "$repo_root/pve/backend.func" + +if _use_custom_arm64_template arm64 debian; then + fail "Debian ARM64 must use the official pveam catalog" +fi +if ! _use_custom_arm64_template arm64 ubuntu; then + fail "Ubuntu ARM64 must retain the custom-template fallback" +fi +if ! _use_custom_arm64_template arm64 alpine; then + fail "Alpine ARM64 must retain the custom-template fallback" +fi +if _use_custom_arm64_template amd64 debian; then + fail "AMD64 must not use the custom ARM64 path" +fi + +mapfile -t templates < <(_list_templates "debian-13" "-standard_") +[[ "${#templates[@]}" -eq 1 ]] || fail "Expected exactly one Debian 13 ARM64 template" +[[ "${templates[0]}" == "debian-13-standard_13.6-1_arm64.tar.zst" ]] || + fail "Template discovery selected the wrong architecture" + +mapfile -t filtered < <( + printf '%s\n' \ + 'debian-13-standard_13.6-1_amd64.tar.zst' \ + 'debian-13-standard_13.6-1_arm64.tar.zst' \ + 'debian-12-standard_12.12-1_amd64.tar.gz' \ + 'debian-12-standard_12.12-1_arm64.tar.xz' | + _filter_templates_by_arch arm64 +) +[[ "${#filtered[@]}" -eq 2 ]] || fail "ARM64 fallback filtering returned the wrong number of templates" +[[ "${filtered[0]}" == *'_arm64.tar.zst' && "${filtered[1]}" == *'_arm64.tar.xz' ]] || + fail "ARM64 fallback filtering selected another architecture" + +# Debian ARM64 now resolves through pveam. An amd64-only local Debian 12 +# template must not prevent selecting the available ARM64 Debian 13 template. +export PHS_SILENT=1 +export PVEVERSION="test" +export TEMPLATE_STORAGE="local" +var_os="debian" +var_version="12" +resolve_os_version +[[ "$var_version" == "13" ]] || fail "Debian ARM64 did not resolve through the architecture-filtered catalog" + +# Non-Debian ARM64 distributions continue to use their custom template source. +var_os="ubuntu" +var_version="22.04" +resolve_os_version +[[ "$var_version" == "22.04" ]] || fail "Ubuntu ARM64 unexpectedly used the pveam catalog" + +# AMD64 keeps the existing pveam resolution behavior. +MOCK_ARCH="amd64" +var_os="debian" +var_version="12" +resolve_os_version +[[ "$var_version" == "12" ]] || fail "AMD64 local template resolution changed" + +if grep -qF 'community-scripts/debian-arm64-lxc' "$repo_root/pve/backend.func"; then + fail "The removed Debian ARM64 repository reference returned" +fi + +printf 'ARM64 template routing checks passed\n' diff --git a/ui/defaults.func b/ui/defaults.func index ab234ab..00676f3 100644 --- a/ui/defaults.func +++ b/ui/defaults.func @@ -127,8 +127,15 @@ resolve_os_version() { local ostype="${var_os:-}" want="${var_version:-}" [[ -n "$ostype" && -n "$want" ]] || return 0 - # ARM64 pulls from linuxcontainers.org, the pveam catalog says nothing there - [[ "$(dpkg --print-architecture 2>/dev/null)" == "arm64" ]] && return 0 + local arch + arch="$(dpkg --print-architecture 2>/dev/null || echo amd64)" + + # ARM64 normally pulls from a platform-specific source, so the pveam catalog + # says nothing useful there. The PVE backend can opt an OS into its catalog. + if [[ "$arch" == "arm64" ]] && + { ! declare -F _use_custom_arm64_template >/dev/null || _use_custom_arm64_template "$arch" "$ostype"; }; then + return 0 + fi local -a versions=() mapfile -t versions < <(_list_os_versions "$ostype") @@ -141,7 +148,8 @@ resolve_os_version() { # Already downloaded counts as available even if the catalog dropped it if [[ -n "${TEMPLATE_STORAGE:-}" ]] && - pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "/${ostype}-${want}-"; then + pveam list "$TEMPLATE_STORAGE" 2>/dev/null | + grep -Eq "/${ostype}-${want}-.*_${arch}\\.(tar\\.zst|tar\\.xz|tar\\.gz)$"; then return 0 fi