From 400905e533c211dd0e8286acc7a803abe3773de3 Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:19:53 +0200 Subject: [PATCH 1/4] Ask Proxmox for a template before reaching for linuxcontainers.org Every ARM64 container took the custom-template path, so pveam was never consulted on that architecture. For Debian that path resolved its URL from community-scripts/debian-arm64-lxc, a repository that does not exist: the releases endpoint answers 404, so ARM64 Debian could not be created at all through the split core. Template lookup now runs the same way on both architectures. Local templates first, then the catalog, both matched on OS, version and exact architecture. Only when Proxmox has nothing does the linuxcontainers.org build step in, which is still the only source for Ubuntu, Fedora and the rest on ARM64. Against a live catalog this routes debian 13 and alpine 3.24 through pveam on ARM64 -- Proxmox ships both -- while debian 12, ubuntu and fedora keep the old path. A local template short-circuits before any catalog read. The interactive "pick another version" fallback filtered by name only. On a host whose catalog carries both architectures, which is now the normal case, it could offer an amd64 template to an ARM64 host and fail later at creation. Supersedes the Debian-only routing in #5. --- pve/backend.func | 53 ++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/pve/backend.func b/pve/backend.func index ce3718a..d64d9f7 100644 --- a/pve/backend.func +++ b/pve/backend.func @@ -2367,8 +2367,31 @@ create_lxc_container() { } # 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. + # Does Proxmox offer this OS/version/arch? Local first, then the catalog. + _pveam_offers_template() { + local search pattern + search="${PCT_OSTYPE}-${PCT_OSVERSION:-}" + case "$PCT_OSTYPE" in + debian | ubuntu) pattern="-standard_" ;; + alpine | fedora | rocky | centos) pattern="-default_" ;; + *) pattern="" ;; + esac + + if pveam list "$TEMPLATE_STORAGE" 2>/dev/null | + awk -v s="$search" -v p="$pattern" -v a="$ARCH" \ + '$1 ~ s && $1 ~ p && $1 ~ ("_" a "\\.(tar\\.zst|tar\\.xz|tar\\.gz)$") {found=1} + END {exit !found}'; then + return 0 + fi + + _pveam_update >/dev/null 2>&1 || true + _pveam_available | + grep -E "_${ARCH}\.(tar\.zst|tar\.xz|tar\.gz)([[:space:]]|$)" | + awk '{print $2}' | + grep -qE "^${search}.*${pattern}" + } + + # Only reached when pveam has nothing for this arch. download_arm64_template() { local dest="$1" url @@ -2377,21 +2400,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" @@ -2603,9 +2612,8 @@ create_lxc_container() { # ------------------------------------------------------------------------------ CUSTOM_TEMPLATE_VARIANT="" - if [[ "$ARCH" == "arm64" ]]; then - # ARM64: use custom template download from linuxcontainers.org / GitHub - msg_info "Preparing ARM64 template" + if [[ "$ARCH" == "arm64" ]] && ! _pveam_offers_template; then + msg_info "No Proxmox template for ${PCT_OSTYPE} on ${ARCH}, using linuxcontainers.org" CUSTOM_TEMPLATE_VARIANT=$(arm64_template_variant "$PCT_OSTYPE" "${PCT_OSVERSION:-}") || { msg_error "No ARM64 template mapping for ${PCT_OSTYPE} ${PCT_OSVERSION:-latest}" @@ -2730,12 +2738,13 @@ 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}' | + 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 ) mapfile -t ONLINE_TEMPLATES < <( _pveam_available | - grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | + grep -E "_${ARCH}\.(tar\.zst|tar\.xz|tar\.gz)([[:space:]]|$)" | awk '{print $2}' | grep -E "^${TEMPLATE_SEARCH}-.*${TEMPLATE_PATTERN}" | sort -t - -k 2 -V 2>/dev/null || true From 5df7aa7d00c0f18983f7984265963ee8dbf2e5e2 Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:28:01 +0200 Subject: [PATCH 2/4] incus: check the local image cache before asking the remote Image resolution tried up to six images: candidates over the network before looking at what was already copied locally, so an image on disk still cost round-trips and resolution failed outright on a host without internet. The local match was also a substring search over the whole CSV row: asking for debian/12 matched a debian/13 row uploaded on 2026/12/01, because the row contains "debian" and the date contains "12". That launched the wrong release with no error. It now compares the alias field whole. --- incus/backend.func | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/incus/backend.func b/incus/backend.func index 17b220a..10b5651 100644 --- a/incus/backend.func +++ b/incus/backend.func @@ -90,7 +90,22 @@ _incus_resolve_launch_image() { candidates+=("images:ubuntu/${ver}.04" "images:ubuntu/${ver}.04/cloud") fi - local cand local_match + local cand local_aliases + + # Local first: a copied image needs no round-trip, and this keeps working on a + # host without internet. + local_aliases=$(incus image list -f csv,noheader -c l 2>/dev/null || true) + for cand in "${candidates[@]}"; do + # Match the alias field whole. The previous substring search ran over the + # entire row, so a version matched any digits in a date or size. + if [[ -n "$local_aliases" ]] && + grep -qxF "${cand#images:}" <<<"$local_aliases" && + incus image info "${cand#images:}" &>/dev/null; then + INCUS_LAUNCH_IMAGE="${cand#images:}" + return 0 + fi + done + for cand in "${candidates[@]}"; do if incus image info "$cand" &>/dev/null; then INCUS_LAUNCH_IMAGE="$cand" @@ -98,16 +113,6 @@ _incus_resolve_launch_image() { fi done - # Local image fingerprint/alias match (already copied) - local_match=$(incus image list -f csv,noheader 2>/dev/null | - awk -F, -v os="$os" -v ver="$ver" ' - tolower($0) ~ os && $0 ~ ver { print $1; exit } - ' || true) - if [[ -n "$local_match" ]] && incus image info "$local_match" &>/dev/null; then - INCUS_LAUNCH_IMAGE="$local_match" - return 0 - fi - # Last resort: try remote launch of primary (incus may pull on demand) INCUS_LAUNCH_IMAGE="images:${os}/${ver}" return 1 From 314726bd1ca9caad2d79e553ca9137f2a2d89cbd Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:30:16 +0200 Subject: [PATCH 3/4] incus: trim comment --- incus/backend.func | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/incus/backend.func b/incus/backend.func index 10b5651..55868cb 100644 --- a/incus/backend.func +++ b/incus/backend.func @@ -92,12 +92,9 @@ _incus_resolve_launch_image() { local cand local_aliases - # Local first: a copied image needs no round-trip, and this keeps working on a - # host without internet. + # Local first: no round-trip, and works without internet. local_aliases=$(incus image list -f csv,noheader -c l 2>/dev/null || true) for cand in "${candidates[@]}"; do - # Match the alias field whole. The previous substring search ran over the - # entire row, so a version matched any digits in a date or size. if [[ -n "$local_aliases" ]] && grep -qxF "${cand#images:}" <<<"$local_aliases" && incus image info "${cand#images:}" &>/dev/null; then From d6c516548765df22ad800ac380d5d7f3b71a8c22 Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:40:47 +0200 Subject: [PATCH 4/4] test: resolve fallback templates from the linuxcontainers catalog The fallback only knew what arm64_template_variant() hardcoded: debian 12/13, ubuntu 24.04/26.04, alpine. Everything else exited 225, and the Jenkins URL it built is a legacy endpoint. Resolves against the simplestreams index instead, which is what Incus already uses. The index carries aliases (debian/13 -> trixie), so the mapping table is gone rather than extended. Opens up 22 distributions and 42 arm64 images against 2 arm64 templates in a live pveam catalog: almalinux, alt, amazonlinux, archlinux, busybox, centos, devuan, kali, mint, nixos, openeuler, opensuse, openwrt, oracle, plamo, rockylinux, slackware, voidlinux and the four already covered. Only reached when pveam has nothing, so amd64 behaviour is unchanged for anything Proxmox ships. Needs jq and installs it when missing. Verified against the live catalog: lookup returns correct paths for debian, ubuntu, alpine, rockylinux, fedora, archlinux and voidlinux, refuses unknown releases, and the resulting URLs are real tarballs (debian trixie arm64 86 MB, rockylinux 9 arm64 99 MB). Not run on a Proxmox host. --- pve/backend.func | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/pve/backend.func b/pve/backend.func index d64d9f7..2d417fb 100644 --- a/pve/backend.func +++ b/pve/backend.func @@ -2350,20 +2350,21 @@ create_lxc_container() { ARCH="$(dpkg --print-architecture)" # Maps OS type + version to the release variant name used by ARM64 template sources. - arm64_template_variant() { - case "$1:$2" in - debian:12) echo "bookworm" ;; - debian:13) echo "trixie" ;; - debian:) echo "$DEBIAN_DEFAULT_CODENAME" ;; - - ubuntu:24.04) echo "noble" ;; - ubuntu:26.04) echo "questing" ;; - ubuntu:) echo "$UBUNTU_DEFAULT_CODENAME" ;; - - alpine:*) echo "${2:-$ALPINE_DEFAULT_VERSION}" ;; - - *) return 1 ;; - esac + _lxc_catalog_path() { + local os="${1,,}" ver="$2" arch="$3" + command -v jq >/dev/null 2>&1 || { + $STD apt-get update + $STD apt-get install -y jq || return 1 + } + curl -fsSL --max-time 30 "https://images.linuxcontainers.org/streams/v1/images.json" 2>/dev/null | + jq -r --arg os "$os" --arg ver "$ver" --arg arch "$arch" ' + .products | to_entries[] + | select(.value.arch == $arch and .value.variant == "default") + | select((.value.os | ascii_downcase) == $os) + | select(.value.release == $ver or ((.value.aliases // "") | split(",") | index($os + "/" + $ver))) + | .value.versions | to_entries | sort_by(.key) | last + | .value.items["root.tar.xz"].path // empty + ' 2>/dev/null | head -1 } # Downloads an ARM64 LXC rootfs template to $1. @@ -2400,7 +2401,7 @@ create_lxc_container() { exit 217 } - url="https://jenkins.linuxcontainers.org/job/image-${PCT_OSTYPE}/architecture=arm64,release=${CUSTOM_TEMPLATE_VARIANT},variant=default/lastStableBuild/artifact/rootfs.tar.xz" + url="https://images.linuxcontainers.org/${CUSTOM_TEMPLATE_PATH}" msg_info "Downloading ${PCT_OSTYPE^} ${CUSTOM_TEMPLATE_VARIANT} ARM64 template" local patched="${dest%/*}/.${dest##*/}.patched" @@ -2611,16 +2612,19 @@ create_lxc_container() { # Template discovery & validation # ------------------------------------------------------------------------------ CUSTOM_TEMPLATE_VARIANT="" + CUSTOM_TEMPLATE_PATH="" if [[ "$ARCH" == "arm64" ]] && ! _pveam_offers_template; then msg_info "No Proxmox template for ${PCT_OSTYPE} on ${ARCH}, using linuxcontainers.org" - CUSTOM_TEMPLATE_VARIANT=$(arm64_template_variant "$PCT_OSTYPE" "${PCT_OSVERSION:-}") || { - msg_error "No ARM64 template mapping for ${PCT_OSTYPE} ${PCT_OSVERSION:-latest}" + CUSTOM_TEMPLATE_PATH=$(_lxc_catalog_path "$PCT_OSTYPE" "${PCT_OSVERSION:-}" "$ARCH") + [[ -n "$CUSTOM_TEMPLATE_PATH" ]] || { + msg_error "linuxcontainers.org has no ${PCT_OSTYPE} ${PCT_OSVERSION:-latest} for ${ARCH}" exit 225 } - TEMPLATE="${PCT_OSTYPE}-${CUSTOM_TEMPLATE_VARIANT}-rootfs.tar.xz" + CUSTOM_TEMPLATE_VARIANT=$(awk -F/ '{print $3}' <<<"$CUSTOM_TEMPLATE_PATH") + TEMPLATE="${PCT_OSTYPE}-${CUSTOM_TEMPLATE_VARIANT}-${ARCH}-rootfs.tar.xz" TEMPLATE_SOURCE="custom-arm64" # Resolve template path