diff --git a/misc/api.func b/misc/api.func index 4f7bd624c..899c6dfda 100644 --- a/misc/api.func +++ b/misc/api.func @@ -196,7 +196,7 @@ explain_exit_code() { 103) echo "Validation: Shell is not Bash" ;; 104) echo "Validation: Not running as root (or invoked via sudo)" ;; 105) echo "Validation: Proxmox VE version not supported" ;; - 106) echo "Validation: Architecture not supported (ARM / PiMox)" ;; + 106) echo "Validation: Unsupported architecture (requires amd64 or arm64)" ;; 107) echo "Validation: Kernel key parameters unreadable" ;; 108) echo "Validation: Kernel key limits exceeded" ;; 109) echo "Proxmox: No available container ID after max attempts" ;; diff --git a/misc/build.func b/misc/build.func index 78233a28f..6d595f88c 100644 --- a/misc/build.func +++ b/misc/build.func @@ -52,6 +52,11 @@ variables() { # as "/tmp/${NSAPP}-${CTID}-${SESSION_ID}.log" (requires CTID, not available here) CTTYPE="${CTTYPE:-${CT_TYPE:-1}}" + # ARM64 Template default variables + DEBIAN_DEFAULT_CODENAME="trixie" + UBUNTU_DEFAULT_CODENAME="noble" + ALPINE_DEFAULT_VERSION="3.23" + # Parse dev_mode early parse_dev_mode @@ -1936,7 +1941,7 @@ advanced_settings() { # ═══════════════════════════════════════════════════════════════════════════ # STEP 2: Root Password - # ════════════════════════════════════════���═══════════════════════════════���══ + # ═══════════════════════════════════════════════════════════════════════════ 2) if PW1=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \ --title "ROOT PASSWORD" \ @@ -3046,6 +3051,9 @@ echo_default() { echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}" echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}" + if [[ "$(dpkg --print-architecture)" == "arm64" ]]; then + echo -e "${INFO}${BOLD}${DGN}Architecture: ${BGN}arm64${CL}" + fi if [[ -n "${var_gpu:-}" && "${var_gpu}" == "yes" ]]; then echo -e "${GPU}${BOLD}${DGN}GPU Passthrough: ${BGN}Enabled${CL}" fi @@ -3082,7 +3090,9 @@ install_script() { pve_check shell_check root_check + ensure_whiptail arch_check + arm64_notice ssh_check maxkeys_check diagnostics_check @@ -4355,19 +4365,23 @@ EOF msg_warn "Skipping timezone setup – zone '$tz' not found in container" fi + local _base_pkgs="sudo curl mc gnupg2 jq" + if [[ "${ARCH:-amd64}" == "arm64" ]]; then + _base_pkgs+=" openssh-server wget gcc" + fi + # Detect broken DNS resolver (e.g. Tailscale MagicDNS) and inject public DNS if ! pct exec "$CTID" -- bash -c "getent hosts deb.debian.org >/dev/null 2>&1 && getent hosts archive.ubuntu.com >/dev/null 2>&1"; then msg_warn "APT repository DNS resolution failed in container, injecting public DNS servers" pct exec "$CTID" -- bash -c "echo -e 'nameserver 8.8.8.8\nnameserver 1.1.1.1' >/etc/resolv.conf" fi - pct exec "$CTID" -- bash -c "apt-get update 2>&1 && apt-get install -y sudo curl mc gnupg2 jq 2>&1" >>"$BUILD_LOG" 2>&1 || { + pct exec "$CTID" -- bash -c "apt-get update 2>&1 && apt-get install -y ${_base_pkgs} 2>&1" >>"$BUILD_LOG" 2>&1 || { local failed_mirror failed_mirror=$(pct exec "$CTID" -- bash -c "grep -m1 -oP '(?<=URIs: https?://)[^/]+' /etc/apt/sources.list.d/debian.sources 2>/dev/null || grep -m1 -oP '(?<=deb https?://)[^/]+' /etc/apt/sources.list 2>/dev/null" 2>/dev/null || echo "unknown") msg_warn "apt-get update failed (${failed_mirror}), trying alternate mirrors..." local mirror_exit=0 - pct exec "$CTID" -- bash -c ' - APT_BASE="sudo curl mc gnupg2 jq" + pct exec "$CTID" -- env APT_BASE="$_base_pkgs" bash -c ' DISTRO=$(. /etc/os-release 2>/dev/null && echo "$ID" || echo "debian") if [ "$DISTRO" = "ubuntu" ]; then @@ -4477,7 +4491,7 @@ EOF [ -f \"\$src\" ] && sed -i \"s|URIs: http[s]*://[^/]*/|URIs: http://${custom_mirror}/|g; s|deb http[s]*://[^/]*/|deb http://${custom_mirror}/|g\" \"\$src\" done rm -rf /var/lib/apt/lists/* - apt-get update >/dev/null 2>&1 && apt-get install -y sudo curl mc gnupg2 jq >/dev/null 2>&1 + apt-get update >/dev/null 2>&1 && apt-get install -y ${_base_pkgs} >/dev/null 2>&1 " && break msg_warn "Mirror '${custom_mirror}' also failed. Try another or type 'skip'." done @@ -4517,7 +4531,9 @@ EOF # that sends "configuring" status AFTER the host already reported "failed" export CONTAINER_INSTALLING=true - lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)" + local _install_script + _install_script="$(curl -fsSL "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh")" + lxc-attach -n "$CTID" -- bash -c "$_install_script" local lxc_exit=$? unset CONTAINER_INSTALLING @@ -4912,7 +4928,9 @@ EOF # Re-run install script in existing container (don't destroy/recreate) set +Eeuo pipefail trap - ERR - lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)" + local _install_script + _install_script="$(curl -fsSL "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh")" + lxc-attach -n "$CTID" -- bash -c "$_install_script" local apt_retry_exit=$? set -Eeuo pipefail trap 'error_handler' ERR @@ -5670,6 +5688,72 @@ create_lxc_container() { esac } + 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 + } + + # 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. + download_arm64_template() { + local dest="$1" url + + mkdir -p "$(dirname "$dest")" || { + msg_error "Cannot create template dir." + exit 207 + } + + 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 207 + } + else + url="https://jenkins.linuxcontainers.org/job/image-${PCT_OSTYPE}/architecture=arm64,release=${CUSTOM_TEMPLATE_VARIANT},variant=default/lastStableBuild/artifact/rootfs.tar.xz" + fi + + msg_info "Downloading ${PCT_OSTYPE^} ${CUSTOM_TEMPLATE_VARIANT} ARM64 template" + if ! curl -fsSL -o "$dest" "$url"; then + msg_error "Failed to download ARM64 template from: $url" + exit 208 + fi + msg_ok "Downloaded ARM64 LXC template" + } + + download_template() { + local dest="${1:-$TEMPLATE_PATH}" + if [[ "$ARCH" == "arm64" ]]; then + download_arm64_template "$dest" + else + pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 || { + msg_error "Failed to download template '$TEMPLATE' to storage '$TEMPLATE_STORAGE'" + exit 222 + } + fi + } + # ------------------------------------------------------------------------------ # Required input variables # ------------------------------------------------------------------------------ @@ -5836,153 +5920,120 @@ create_lxc_container() { # ------------------------------------------------------------------------------ # Template discovery & validation # ------------------------------------------------------------------------------ - TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}" - case "$PCT_OSTYPE" in - debian | ubuntu) TEMPLATE_PATTERN="-standard_" ;; - alpine | fedora | rocky | centos) TEMPLATE_PATTERN="-default_" ;; - *) TEMPLATE_PATTERN="" ;; - esac + CUSTOM_TEMPLATE_VARIANT="" - msg_info "Searching for template '$TEMPLATE_SEARCH'" + if [[ "$ARCH" == "arm64" ]]; then + # ARM64: use custom template download from linuxcontainers.org / GitHub + msg_info "Preparing ARM64 template" - # Initialize variables - ONLINE_TEMPLATE="" - ONLINE_TEMPLATES=() + CUSTOM_TEMPLATE_VARIANT=$(arm64_template_variant "$PCT_OSTYPE" "${PCT_OSVERSION:-}") || { + msg_error "No ARM64 template mapping for ${PCT_OSTYPE} ${PCT_OSVERSION:-latest}" + exit 207 + } - # 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}" '$1 ~ search && $1 ~ pattern {print $1}' | - sed 's|.*/||' | sort -t - -k 2 -V - ) + TEMPLATE="${PCT_OSTYPE}-${CUSTOM_TEMPLATE_VARIANT}-rootfs.tar.xz" + TEMPLATE_SOURCE="custom-arm64" - # Step 2: If local template found, use it immediately (skip pveam update) - if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then - TEMPLATE="${LOCAL_TEMPLATES[-1]}" - TEMPLATE_SOURCE="local" - msg_ok "Template search completed" - else - # Step 3: No local template - need to check online (this may be slow) - msg_info "No local template found, checking online catalog..." - - # Update catalog with timeout to prevent long hangs - if command -v timeout &>/dev/null; then - if ! timeout 30 pveam update >/dev/null 2>&1; then - msg_warn "Template catalog update timed out (possible network/DNS issue). Run 'pveam update' manually to diagnose." - fi - else - pveam update >/dev/null 2>&1 || msg_warn "Could not update template catalog (pveam update failed)" + # Resolve template path + TEMPLATE_PATH="$(pvesm path "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" 2>/dev/null || true)" + if [[ -z "$TEMPLATE_PATH" ]]; then + local _tpl_base + _tpl_base=$(awk -v s="$TEMPLATE_STORAGE" ' + $0 ~ "^[^:]+:[[:space:]]*" s "$" {f=1; next} + f && /^[^[:space:]]/ {f=0} + f && $1 == "path" {print $2; exit} + ' /etc/pve/storage.cfg) + TEMPLATE_PATH="${_tpl_base:-/var/lib/vz}/template/cache/$TEMPLATE" fi + # Download if missing, too small, or corrupt + if [[ ! -f "$TEMPLATE_PATH" ]]; then + download_arm64_template "$TEMPLATE_PATH" + elif [[ "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]] || ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then + msg_warn "Local template invalid - re-downloading." + rm -f "$TEMPLATE_PATH" + download_arm64_template "$TEMPLATE_PATH" + else + msg_ok "Template ${BL}$TEMPLATE${CL} found locally." + fi + + else + TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}" + case "$PCT_OSTYPE" in + debian | ubuntu) TEMPLATE_PATTERN="-standard_" ;; + alpine | fedora | rocky | centos) TEMPLATE_PATTERN="-default_" ;; + *) TEMPLATE_PATTERN="" ;; + esac + + msg_info "Searching for template '$TEMPLATE_SEARCH'" + + # Initialize variables + ONLINE_TEMPLATE="" ONLINE_TEMPLATES=() - mapfile -t ONLINE_TEMPLATES < <(pveam available -section system 2>/dev/null | grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | awk '{print $2}' | grep -E "^${TEMPLATE_SEARCH}.*${TEMPLATE_PATTERN}" | sort -t - -k 2 -V 2>/dev/null || true) - [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE="${ONLINE_TEMPLATES[-1]}" - TEMPLATE="$ONLINE_TEMPLATE" - TEMPLATE_SOURCE="online" - msg_ok "Template search completed" - fi - - # If still no template, try to find alternatives - if [[ -z "$TEMPLATE" ]]; then - msg_warn "No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}, searching for alternatives..." - - # Get all available versions for this OS type - AVAILABLE_VERSIONS=() - mapfile -t AVAILABLE_VERSIONS < <( - pveam available -section system 2>/dev/null | - 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 + # 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}" '$1 ~ search && $1 ~ pattern {print $1}' | + sed 's|.*/||' | sort -t - -k 2 -V ) - if [[ ${#AVAILABLE_VERSIONS[@]} -gt 0 ]]; then - echo "" - echo "${BL}Available ${PCT_OSTYPE} versions:${CL}" - for i in "${!AVAILABLE_VERSIONS[@]}"; do - echo " [$((i + 1))] ${AVAILABLE_VERSIONS[$i]}" - done - echo "" - read -p "Select version [1-${#AVAILABLE_VERSIONS[@]}] or press Enter to cancel: " choice /dev/null | - grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | - awk '{print $2}' | - grep -E "^${TEMPLATE_SEARCH}-.*${TEMPLATE_PATTERN}" | - sort -t - -k 2 -V 2>/dev/null || true - ) - - if [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]]; then - TEMPLATE="${ONLINE_TEMPLATES[-1]}" - TEMPLATE_SOURCE="online" - else - msg_error "No templates available for ${PCT_OSTYPE} ${PCT_OSVERSION}" - exit 225 + # Update catalog with timeout to prevent long hangs + if command -v timeout &>/dev/null; then + if ! timeout 30 pveam update >/dev/null 2>&1; then + msg_warn "Template catalog update timed out (possible network/DNS issue). Run 'pveam update' manually to diagnose." fi else - msg_custom "🚫" "${YW}" "Installation cancelled" - exit 0 + pveam update >/dev/null 2>&1 || msg_warn "Could not update template catalog (pveam update failed)" fi - else - msg_error "No ${PCT_OSTYPE} templates available at all" - exit 225 + + ONLINE_TEMPLATES=() + mapfile -t ONLINE_TEMPLATES < <(pveam available -section system 2>/dev/null | grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | awk '{print $2}' | grep -E "^${TEMPLATE_SEARCH}.*${TEMPLATE_PATTERN}" | sort -t - -k 2 -V 2>/dev/null || true) + [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE="${ONLINE_TEMPLATES[-1]}" + + TEMPLATE="$ONLINE_TEMPLATE" + TEMPLATE_SOURCE="online" + msg_ok "Template search completed" fi - fi - TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)" - if [[ -z "$TEMPLATE_PATH" ]]; then - TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg) - [[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE" - fi - - # If we still don't have a path but have a valid template name, construct it - if [[ -z "$TEMPLATE_PATH" && -n "$TEMPLATE" ]]; then - TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE" - fi - - [[ -n "$TEMPLATE_PATH" ]] || { + # If still no template, try to find alternatives if [[ -z "$TEMPLATE" ]]; then - msg_error "Template ${PCT_OSTYPE} ${PCT_OSVERSION} not available" + msg_warn "No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}, searching for alternatives..." - # Get available versions + # Get all available versions for this OS type + AVAILABLE_VERSIONS=() mapfile -t AVAILABLE_VERSIONS < <( pveam available -section system 2>/dev/null | + 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/' | - grep -E '^[0-9]+\.[0-9]+$' | - sort -u -V 2>/dev/null || sort -u + sed -E "s/.*${PCT_OSTYPE}-([0-9]+(\.[0-9]+)?).*/\1/" | + sort -u -V 2>/dev/null ) if [[ ${#AVAILABLE_VERSIONS[@]} -gt 0 ]]; then - echo -e "\n${BL}Available versions:${CL}" + echo "" + echo "${BL}Available ${PCT_OSTYPE} versions:${CL}" for i in "${!AVAILABLE_VERSIONS[@]}"; do echo " [$((i + 1))] ${AVAILABLE_VERSIONS[$i]}" done - echo "" - read -p "Select version [1-${#AVAILABLE_VERSIONS[@]}] or Enter to exit: " choice /dev/null | - awk -v search="${TEMPLATE_SEARCH}-" -v pattern="${TEMPLATE_PATTERN}" '$1 ~ search && $1 ~ pattern {print $1}' | - sed 's|.*/||' | sort -t - -k 2 -V - ) + ONLINE_TEMPLATES=() mapfile -t ONLINE_TEMPLATES < <( pveam available -section system 2>/dev/null | grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | @@ -5990,109 +6041,181 @@ create_lxc_container() { grep -E "^${TEMPLATE_SEARCH}-.*${TEMPLATE_PATTERN}" | sort -t - -k 2 -V 2>/dev/null || true ) - ONLINE_TEMPLATE="" - [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE="${ONLINE_TEMPLATES[-1]}" - if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then - TEMPLATE="${LOCAL_TEMPLATES[-1]}" - TEMPLATE_SOURCE="local" - else - TEMPLATE="$ONLINE_TEMPLATE" + if [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]]; then + TEMPLATE="${ONLINE_TEMPLATES[-1]}" TEMPLATE_SOURCE="online" + else + msg_error "No templates available for ${PCT_OSTYPE} ${PCT_OSVERSION}" + exit 225 fi - - TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)" - if [[ -z "$TEMPLATE_PATH" ]]; then - TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg) - [[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE" - fi - - # If we still don't have a path but have a valid template name, construct it - if [[ -z "$TEMPLATE_PATH" && -n "$TEMPLATE" ]]; then - TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE" - fi - - [[ -n "$TEMPLATE_PATH" ]] || { - msg_error "Template still not found after version change" - exit 220 - } else msg_custom "🚫" "${YW}" "Installation cancelled" exit 0 fi else msg_error "No ${PCT_OSTYPE} templates available" - exit 220 + exit 225 fi fi - } - # Validate that we found a template - if [[ -z "$TEMPLATE" ]]; then - msg_error "No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}" - msg_custom "ℹ️" "${YW}" "Please check:" - msg_custom " •" "${YW}" "Is pveam catalog available? (run: pveam available -section system)" - msg_custom " •" "${YW}" "Does the template exist for your OS version?" - exit 225 - fi - - msg_ok "Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]" - msg_debug "Resolved TEMPLATE_PATH=$TEMPLATE_PATH" - - NEED_DOWNLOAD=0 - if [[ ! -f "$TEMPLATE_PATH" ]]; then - msg_info "Template not present locally – will download." - NEED_DOWNLOAD=1 - elif [[ ! -r "$TEMPLATE_PATH" ]]; then - msg_error "Template file exists but is not readable – check permissions." - exit 221 - elif [[ "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then - if [[ -n "$ONLINE_TEMPLATE" ]]; then - msg_warn "Template file too small (<1MB) – re-downloading." - NEED_DOWNLOAD=1 - else - msg_warn "Template looks too small, but no online version exists. Keeping local file." + TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)" + if [[ -z "$TEMPLATE_PATH" ]]; then + TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg) + [[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE" fi - elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then - if [[ -n "$ONLINE_TEMPLATE" ]]; then - msg_warn "Template appears corrupted – re-downloading." - NEED_DOWNLOAD=1 - else - msg_warn "Template appears corrupted, but no online version exists. Keeping local file." - fi - else - $STD msg_ok "Template $TEMPLATE is present and valid." - fi - if [[ "$TEMPLATE_SOURCE" == "local" && -n "$ONLINE_TEMPLATE" && "$TEMPLATE" != "$ONLINE_TEMPLATE" ]]; then - msg_warn "Local template is outdated: $TEMPLATE (latest available: $ONLINE_TEMPLATE)" - if whiptail --yesno "A newer template is available:\n$ONLINE_TEMPLATE\n\nDo you want to download and use it instead?" 12 70; then - TEMPLATE="$ONLINE_TEMPLATE" - NEED_DOWNLOAD=1 - else - msg_custom "ℹ️" "${BL}" "Continuing with local template $TEMPLATE" + # If we still don't have a path but have a valid template name, construct it + if [[ -z "$TEMPLATE_PATH" && -n "$TEMPLATE" ]]; then + TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE" fi - fi - if [[ "$NEED_DOWNLOAD" -eq 1 ]]; then - [[ -f "$TEMPLATE_PATH" ]] && rm -f "$TEMPLATE_PATH" - for attempt in {1..3}; do - msg_info "Attempt $attempt: Downloading template $TEMPLATE to $TEMPLATE_STORAGE" - if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1; then - msg_ok "Template download successful." - break + [[ -n "$TEMPLATE_PATH" ]] || { + if [[ -z "$TEMPLATE" ]]; then + msg_error "Template ${PCT_OSTYPE} ${PCT_OSVERSION} not available" + + # Get available versions + mapfile -t AVAILABLE_VERSIONS < <( + pveam available -section system 2>/dev/null | + grep "^${PCT_OSTYPE}-" | + sed -E 's/.*'"${PCT_OSTYPE}"'-([0-9]+\.[0-9]+).*/\1/' | + grep -E '^[0-9]+\.[0-9]+$' | + sort -u -V 2>/dev/null || sort -u + ) + + if [[ ${#AVAILABLE_VERSIONS[@]} -gt 0 ]]; then + echo -e "\n${BL}Available versions:${CL}" + for i in "${!AVAILABLE_VERSIONS[@]}"; do + echo " [$((i + 1))] ${AVAILABLE_VERSIONS[$i]}" + done + + echo "" + read -p "Select version [1-${#AVAILABLE_VERSIONS[@]}] or press Enter to exit: " choice /dev/null | + awk -v search="${TEMPLATE_SEARCH}-" -v pattern="${TEMPLATE_PATTERN}" '$1 ~ search && $1 ~ pattern {print $1}' | + sed 's|.*/||' | sort -t - -k 2 -V + ) + mapfile -t ONLINE_TEMPLATES < <( + pveam available -section system 2>/dev/null | + grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | + awk '{print $2}' | + grep -E "^${TEMPLATE_SEARCH}-.*${TEMPLATE_PATTERN}" | + sort -t - -k 2 -V 2>/dev/null || true + ) + ONLINE_TEMPLATE="" + [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE="${ONLINE_TEMPLATES[-1]}" + + if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then + TEMPLATE="${LOCAL_TEMPLATES[-1]}" + TEMPLATE_SOURCE="local" + else + TEMPLATE="$ONLINE_TEMPLATE" + TEMPLATE_SOURCE="online" + fi + + TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)" + if [[ -z "$TEMPLATE_PATH" ]]; then + TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg) + [[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE" + fi + + # If we still don't have a path but have a valid template name, construct it + if [[ -z "$TEMPLATE_PATH" && -n "$TEMPLATE" ]]; then + TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE" + fi + + [[ -n "$TEMPLATE_PATH" ]] || { + msg_error "Template still not found after version change" + exit 220 + } + else + msg_custom "🚫" "${YW}" "Installation cancelled" + exit 0 + fi + else + msg_error "No ${PCT_OSTYPE} templates available" + exit 220 + fi fi - if [[ $attempt -eq 3 ]]; then - msg_error "Failed after 3 attempts. Please check network access, permissions, or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE" - exit 222 - fi - sleep $((attempt * 5)) - done - fi + } - if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE"; then - msg_error "Template $TEMPLATE not available in storage $TEMPLATE_STORAGE after download." - exit 223 + # Validate that we found a template + if [[ -z "$TEMPLATE" ]]; then + msg_error "No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}" + msg_custom "ℹ️" "${YW}" "Please check:" + msg_custom " •" "${YW}" "Is pveam catalog available? (run: pveam available -section system)" + msg_custom " •" "${YW}" "Does the template exist for your OS version?" + exit 225 + fi + + msg_ok "Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]" + msg_debug "Resolved TEMPLATE_PATH=$TEMPLATE_PATH" + + NEED_DOWNLOAD=0 + if [[ ! -f "$TEMPLATE_PATH" ]]; then + msg_info "Template not present locally, will download it." + NEED_DOWNLOAD=1 + elif [[ ! -r "$TEMPLATE_PATH" ]]; then + msg_error "Template file exists but is not readable, check permissions." + exit 221 + elif [[ "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then + if [[ -n "$ONLINE_TEMPLATE" ]]; then + msg_warn "Template file too small (<1MB), re-downloading." + NEED_DOWNLOAD=1 + else + msg_warn "Template looks too small, but no online version exists. Keeping local file." + fi + elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then + if [[ -n "$ONLINE_TEMPLATE" ]]; then + msg_warn "Template appears corrupted, re-downloading." + NEED_DOWNLOAD=1 + else + msg_warn "Template appears corrupted, but no online version exists. Keeping local file." + fi + else + $STD msg_ok "Template $TEMPLATE is present and valid." + fi + + if [[ "$TEMPLATE_SOURCE" == "local" && -n "$ONLINE_TEMPLATE" && "$TEMPLATE" != "$ONLINE_TEMPLATE" ]]; then + msg_warn "Local template is outdated: $TEMPLATE (latest available: $ONLINE_TEMPLATE)" + if whiptail --yesno "A newer template is available:\n$ONLINE_TEMPLATE\n\nDo you want to download and use it instead?" 12 70; then + TEMPLATE="$ONLINE_TEMPLATE" + NEED_DOWNLOAD=1 + else + msg_custom "ℹ️" "${BL}" "Continuing with local template $TEMPLATE" + fi + fi + + if [[ "$NEED_DOWNLOAD" -eq 1 ]]; then + [[ -f "$TEMPLATE_PATH" ]] && rm -f "$TEMPLATE_PATH" + for attempt in {1..3}; do + msg_info "Attempt $attempt: Downloading template $TEMPLATE to $TEMPLATE_STORAGE" + if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1; then + msg_ok "Template download successful." + break + fi + if [[ $attempt -eq 3 ]]; then + msg_error "Failed after 3 attempts. Please check network access, permissions, or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE" + exit 222 + fi + sleep $((attempt * 5)) + done + fi + + if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE"; then + msg_error "Template $TEMPLATE not available in storage $TEMPLATE_STORAGE after download." + exit 223 + fi fi # ------------------------------------------------------------------------------ @@ -6169,21 +6292,15 @@ create_lxc_container() { # Validate template before pct create (while holding lock) if [[ ! -s "$TEMPLATE_PATH" || "$(stat -c%s "$TEMPLATE_PATH" 2>/dev/null || echo 0)" -lt 1000000 ]]; then - msg_info "Template file missing or too small – downloading" + msg_info "Template file missing or too small - downloading" rm -f "$TEMPLATE_PATH" - pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 || { - msg_error "Failed to download template '$TEMPLATE' to storage '$TEMPLATE_STORAGE'" - exit 222 - } + download_template msg_ok "Template downloaded" elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then - if [[ -n "$ONLINE_TEMPLATE" ]]; then - msg_info "Template appears corrupted – re-downloading" + if [[ "$ARCH" == "arm64" || -n "$ONLINE_TEMPLATE" ]]; then + msg_info "Template appears corrupted - re-downloading" rm -f "$TEMPLATE_PATH" - pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 || { - msg_error "Failed to re-download template '$TEMPLATE'" - exit 222 - } + download_template msg_ok "Template re-downloaded" else msg_warn "Template appears corrupted, but no online version exists. Skipping re-download." @@ -6231,9 +6348,9 @@ create_lxc_container() { else # Not a CTID collision - check if template issue and retry with fresh download if grep -qiE 'unable to open|corrupt|invalid' "$LOGFILE"; then - msg_info "Template may be corrupted – re-downloading" + msg_info "Template may be corrupted - re-downloading" rm -f "$TEMPLATE_PATH" - pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 + download_template msg_ok "Template re-downloaded" fi @@ -6246,7 +6363,11 @@ create_lxc_container() { if [[ ! -f "$LOCAL_TEMPLATE_PATH" ]]; then msg_ok "Trying local storage fallback" msg_info "Downloading template to local" - pveam download local "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 + if [[ "$ARCH" == "arm64" ]]; then + download_arm64_template "$LOCAL_TEMPLATE_PATH" + else + pveam download local "$TEMPLATE" >>"${BUILD_LOG:-/dev/null}" 2>&1 + fi msg_ok "Template downloaded to local" else msg_ok "Trying local storage fallback" diff --git a/misc/core.func b/misc/core.func index cef38028b..00cbf2e89 100644 --- a/misc/core.func +++ b/misc/core.func @@ -346,9 +346,15 @@ pve_check() { # - Provides link to ARM64-compatible scripts # ------------------------------------------------------------------------------ arch_check() { - if [ "$(dpkg --print-architecture)" != "amd64" ]; then - msg_error "This script will not work with PiMox (ARM architecture detected)." - msg_warn "Visit https://github.com/asylumexp/Proxmox for ARM64 support." + local arch + arch="$(dpkg --print-architecture)" + if [[ "$arch" != "amd64" && "$arch" != "arm64" ]]; then + msg_error "This script requires amd64 or arm64 (detected: $arch)." + sleep 2 + exit 106 + fi + if [[ "$arch" == "arm64" && "${var_arm64:-}" != "yes" ]]; then + msg_error "This script does not yet support arm64." sleep 2 exit 106 fi @@ -1714,6 +1720,38 @@ function get_lxc_ip() { export LOCAL_IP } +# ------------------------------------------------------------------------------ +# ensure_whiptail() +# +# - Ensures whiptail is installed +# - Some ARM64 systems will not have whiptail installed +# - Exits with error message if installation fails +# ------------------------------------------------------------------------------ +ensure_whiptail() { + command -v whiptail >/dev/null 2>&1 && return 0 + + msg_info "Installing whiptail" + apt_update_safe + $STD apt-get install -y whiptail || { + msg_error "Failed to install whiptail" + exit 100 + } + msg_ok "Installed whiptail" +} + +# ------------------------------------------------------------------------------ +# arm64_notice() +# +# - Shows a short warning when running scripts on ARM64 systems +# ------------------------------------------------------------------------------ +arm64_notice() { + [[ "$(dpkg --print-architecture)" == "arm64" ]] || return 0 + + whiptail --backtitle "Proxmox VE Helper Scripts" \ + --title "ARM64 SUPPORT" \ + --ok-button "Continue" \ + --msgbox "ARM64 support is in active development.\n\nSome scripts, packages, or application releases may not be fully tested or working yet." 10 68 +} # ============================================================================== # SIGNAL TRAPS # ============================================================================== diff --git a/misc/error_handler.func b/misc/error_handler.func index 7ec573306..cb1f012ed 100644 --- a/misc/error_handler.func +++ b/misc/error_handler.func @@ -99,7 +99,7 @@ if ! declare -f explain_exit_code &>/dev/null; then 103) echo "Validation: Shell is not Bash" ;; 104) echo "Validation: Not running as root (or invoked via sudo)" ;; 105) echo "Validation: Proxmox VE version not supported" ;; - 106) echo "Validation: Architecture not supported (ARM / PiMox)" ;; + 106) echo "Validation: Unsupported architecture (requires amd64 or arm64)" ;; 107) echo "Validation: Kernel key parameters unreadable" ;; 108) echo "Validation: Kernel key limits exceeded" ;; 109) echo "Proxmox: No available container ID after max attempts" ;; diff --git a/misc/tools.func b/misc/tools.func index 0ca215102..4b9617a9e 100644 --- a/misc/tools.func +++ b/misc/tools.func @@ -3157,10 +3157,14 @@ fetch_and_deploy_codeberg_release() { # Fall back to architecture heuristic if [[ -z "$url_match" ]]; then for u in $assets; do - if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then - url_match="$u" - break + [[ "$u" =~ \.deb$ ]] || continue + if [[ "${arch,,}" =~ ^(amd64|x86_64)$ ]]; then + [[ "$u" =~ (amd64|x86_64).*\.deb$ ]] || continue + elif [[ "${arch,,}" =~ ^(arm64|aarch64)$ ]]; then + [[ "$u" =~ (arm64|aarch64).*\.deb$ ]] || continue fi + url_match="$u" + break done fi @@ -3457,7 +3461,11 @@ _gh_scan_older_releases() { done) fi if [[ "$has_match" != "true" ]]; then - has_match=$(echo "$releases_list" | jq -r ".[$i].assets[].browser_download_url" | grep -qE "($arch|amd64|x86_64|aarch64|arm64).*\.deb$" && echo true) + if [[ "${arch,,}" =~ ^(amd64|x86_64)$ ]]; then + has_match=$(echo "$releases_list" | jq -r ".[$i].assets[].browser_download_url" | grep -qE '(amd64|x86_64).*\.deb$' && echo true) + elif [[ "${arch,,}" =~ ^(arm64|aarch64)$ ]]; then + has_match=$(echo "$releases_list" | jq -r ".[$i].assets[].browser_download_url" | grep -qE '(arm64|aarch64).*\.deb$' && echo true) + fi fi if [[ "$has_match" != "true" ]]; then has_match=$(echo "$releases_list" | jq -r ".[$i].assets[].browser_download_url" | grep -qE '\.deb$' && echo true) @@ -3663,10 +3671,14 @@ fetch_and_deploy_gh_release() { # If no match via explicit pattern, fall back to architecture heuristic if [[ -z "$url_match" ]]; then for u in $assets; do - if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then - url_match="$u" - break + [[ "$u" =~ \.deb$ ]] || continue + if [[ "${arch,,}" =~ ^(amd64|x86_64)$ ]]; then + [[ "$u" =~ (amd64|x86_64).*\.deb$ ]] || continue + elif [[ "${arch,,}" =~ ^(arm64|aarch64)$ ]]; then + [[ "$u" =~ (arm64|aarch64).*\.deb$ ]] || continue fi + url_match="$u" + break done fi @@ -3697,10 +3709,14 @@ fetch_and_deploy_gh_release() { fi if [[ -z "$url_match" ]]; then for u in $assets; do - if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then - url_match="$u" - break + [[ "$u" =~ \.deb$ ]] || continue + if [[ "${arch,,}" =~ ^(amd64|x86_64)$ ]]; then + [[ "$u" =~ (amd64|x86_64).*\.deb$ ]] || continue + elif [[ "${arch,,}" =~ ^(arm64|aarch64)$ ]]; then + [[ "$u" =~ (arm64|aarch64).*\.deb$ ]] || continue fi + url_match="$u" + break done fi if [[ -z "$url_match" ]]; then @@ -4458,7 +4474,12 @@ setup_ffmpeg() { # Binary fallback mode if [[ "$TYPE" == "binary" ]]; then - if ! CURL_TIMEOUT=300 curl_with_retry "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz" "$TMP_DIR/ffmpeg.tar.xz"; then + local ffmpeg_arch + case "$(dpkg --print-architecture 2>/dev/null || echo amd64)" in + arm64) ffmpeg_arch="arm64" ;; + *) ffmpeg_arch="amd64" ;; + esac + if ! CURL_TIMEOUT=300 curl_with_retry "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-${ffmpeg_arch}-static.tar.xz" "$TMP_DIR/ffmpeg.tar.xz"; then msg_error "Failed to download FFmpeg binary" msg_error "Hint: Check connectivity to johnvansickle.com/ffmpeg (static builds, may be slow — large file)" rm -rf "$TMP_DIR" @@ -4546,7 +4567,17 @@ setup_ffmpeg() { # If no source download (either VERSION empty or download failed), use binary if [[ -z "$VERSION" ]]; then msg_info "Setup FFmpeg from pre-built binary" - if ! CURL_TIMEOUT=300 curl_with_retry "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz" "$TMP_DIR/ffmpeg.tar.xz"; then + local ffmpeg_arch detected_arch + detected_arch="$(dpkg --print-architecture 2>/dev/null || true)" + if [[ -z "$detected_arch" ]]; then + detected_arch="$(uname -m 2>/dev/null || true)" + fi + case "$detected_arch" in + arm64 | aarch64) ffmpeg_arch="arm64" ;; + amd64 | x86_64) ffmpeg_arch="amd64" ;; + *) ffmpeg_arch="amd64" ;; + esac + if ! CURL_TIMEOUT=300 curl_with_retry "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-${ffmpeg_arch}-static.tar.xz" "$TMP_DIR/ffmpeg.tar.xz"; then msg_error "Failed to download FFmpeg pre-built binary" msg_error "Hint: Check connectivity to johnvansickle.com/ffmpeg (large file, may timeout on slow connections)" rm -rf "$TMP_DIR" @@ -8576,7 +8607,19 @@ setup_yq() { msg_info "Setup yq $LATEST_VERSION" fi - if ! curl_with_retry "https://github.com/${GITHUB_REPO}/releases/download/v${LATEST_VERSION}/yq_linux_amd64" "$TMP_DIR/yq"; then + local yq_arch detected_arch + if command -v dpkg &>/dev/null; then + detected_arch="$(dpkg --print-architecture 2>/dev/null)" + else + detected_arch="$(uname -m 2>/dev/null)" + fi + + case "$detected_arch" in + arm64 | aarch64) yq_arch="arm64" ;; + amd64 | x86_64) yq_arch="amd64" ;; + *) yq_arch="amd64" ;; + esac + if ! curl_with_retry "https://github.com/${GITHUB_REPO}/releases/download/v${LATEST_VERSION}/yq_linux_${yq_arch}" "$TMP_DIR/yq"; then msg_error "Failed to download yq" msg_error "Hint: Check connectivity to github.com/${GITHUB_REPO} — set GITHUB_TOKEN to avoid rate-limiting" rm -rf "$TMP_DIR"