diff --git a/misc/alpine-install.func b/misc/alpine-install.func index 0b7d9708b..598d257ef 100644 --- a/misc/alpine-install.func +++ b/misc/alpine-install.func @@ -135,10 +135,34 @@ network_check() { trap 'error_handler $LINENO "$BASH_COMMAND"' ERR } -# This function updates the Container OS by running apt-get update and upgrade +# This function updates the Container OS by running apk upgrade with mirror fallback update_os() { msg_info "Updating Container OS" - $STD apk -U upgrade + if ! $STD apk -U upgrade; then + msg_warn "apk update failed (dl-cdn.alpinelinux.org), trying alternate mirrors..." + local alpine_mirrors="mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org" + local apk_ok=false + for m in $(printf '%s\n' $alpine_mirrors | shuf); do + if timeout 2 bash -c "echo >/dev/tcp/$m/80" 2>/dev/null; then + msg_info "Attempting mirror: ${m}" + cat </etc/apk/repositories +http://$m/alpine/latest-stable/main +http://$m/alpine/latest-stable/community +EOF + if $STD apk -U upgrade; then + msg_ok "CDN set to ${m}: tests passed" + apk_ok=true + break + else + msg_warn "Mirror ${m} failed" + fi + fi + done + if [[ "$apk_ok" != true ]]; then + msg_error "All Alpine mirrors failed. Check network or try again later." + exit 1 + fi + fi local tools_content tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || { msg_error "Failed to download tools.func" diff --git a/misc/build.func b/misc/build.func index ca147f42b..1ac4ee41c 100644 --- a/misc/build.func +++ b/misc/build.func @@ -4088,8 +4088,31 @@ https://dl-cdn.alpinelinux.org/alpine/latest-stable/main https://dl-cdn.alpinelinux.org/alpine/latest-stable/community EOF' pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses jq" >>"$BUILD_LOG" 2>&1 || { - msg_error "Failed to install base packages in Alpine container" - install_exit_code=1 + msg_warn "apk install failed (dl-cdn.alpinelinux.org), trying alternate mirrors..." + local alpine_exit=0 + pct exec "$CTID" -- ash -c ' + ALPINE_MIRRORS="mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org" + for m in $(printf "%s\n" $ALPINE_MIRRORS | shuf); do + if wget -q --spider --timeout=2 "http://$m/alpine/latest-stable/main/" 2>/dev/null; then + echo " Attempting mirror: $m" + cat </etc/apk/repositories +http://$m/alpine/latest-stable/main +http://$m/alpine/latest-stable/community +EOF + if apk update >/dev/null 2>&1 && apk add bash newt curl openssh nano mc ncurses jq >/dev/null 2>&1; then + echo " CDN set to $m: tests passed" + exit 0 + else + echo " Mirror $m failed" + fi + fi + done + exit 2 + ' && alpine_exit=0 || alpine_exit=$? + if [[ $alpine_exit -ne 0 ]]; then + msg_error "Failed to install base packages in Alpine container" + install_exit_code=1 + fi } else sleep 3 @@ -4115,9 +4138,140 @@ EOF' msg_warn "Skipping timezone setup – zone '$tz' not found in container" 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 || { - msg_error "apt-get base packages installation failed" - install_exit_code=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" + DISTRO=$(. /etc/os-release 2>/dev/null && echo "$ID" || echo "debian") + + if [ "$DISTRO" = "ubuntu" ]; then + EU_MIRRORS="de.archive.ubuntu.com fr.archive.ubuntu.com se.archive.ubuntu.com nl.archive.ubuntu.com it.archive.ubuntu.com ch.archive.ubuntu.com mirrors.xtom.de" + US_MIRRORS="us.archive.ubuntu.com archive.ubuntu.com mirrors.edge.kernel.org mirror.csclub.uwaterloo.ca mirrors.ocf.berkeley.edu mirror.math.princeton.edu" + AP_MIRRORS="au.archive.ubuntu.com jp.archive.ubuntu.com kr.archive.ubuntu.com tw.archive.ubuntu.com mirror.aarnet.edu.au" + else + EU_MIRRORS="ftp.de.debian.org ftp.fr.debian.org ftp.nl.debian.org ftp.uk.debian.org ftp.ch.debian.org ftp.se.debian.org ftp.it.debian.org ftp.fau.de ftp.halifax.rwth-aachen.de debian.mirror.lrz.de mirror.init7.net debian.ethz.ch mirrors.dotsrc.org debian.mirrors.ovh.net" + US_MIRRORS="ftp.us.debian.org ftp.ca.debian.org debian.csail.mit.edu mirrors.ocf.berkeley.edu mirrors.wikimedia.org debian.osuosl.org mirror.cogentco.com" + AP_MIRRORS="ftp.au.debian.org ftp.jp.debian.org ftp.tw.debian.org ftp.kr.debian.org ftp.hk.debian.org ftp.sg.debian.org mirror.aarnet.edu.au mirror.nitc.ac.in" + fi + + TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC") + case "$TZ" in + Europe/*|Arctic/*) REGIONAL="$EU_MIRRORS"; OTHERS="$US_MIRRORS $AP_MIRRORS" ;; + America/*) REGIONAL="$US_MIRRORS"; OTHERS="$EU_MIRRORS $AP_MIRRORS" ;; + Asia/*|Australia/*|Pacific/*) REGIONAL="$AP_MIRRORS"; OTHERS="$EU_MIRRORS $US_MIRRORS" ;; + *) REGIONAL=""; OTHERS="$EU_MIRRORS $US_MIRRORS $AP_MIRRORS" ;; + esac + + echo "Acquire::By-Hash \"no\";" >/etc/apt/apt.conf.d/99no-by-hash + + try_mirrors() { + for src in /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list; do + [ -f "$src" ] && sed -i "s|URIs: http[s]*://[^/]*/|URIs: http://${1}/|g; s|deb http[s]*://[^/]*/|deb http://${1}/|g" "$src" + done + rm -rf /var/lib/apt/lists/* + APT_OUT=$(apt-get update 2>&1) + APT_RC=$? + if echo "$APT_OUT" | grep -qi "hashsum\|hash sum"; then + echo " Mirror $1 failed (hash mismatch)" + return 1 + elif echo "$APT_OUT" | grep -qi "SSL\|certificate"; then + echo " Mirror $1 failed (SSL/certificate error)" + return 1 + elif [ $APT_RC -ne 0 ]; then + echo " Mirror $1 failed (apt-get update error)" + return 1 + elif apt-get install -y $APT_BASE >/dev/null 2>&1; then + echo " CDN set to $1: tests passed" + return 0 + else + echo " Mirror $1 failed (package install error)" + return 1 + fi + } + + scan_reachable() { + local result="" + for m in $1; do + if timeout 2 bash -c "echo >/dev/tcp/$m/80" 2>/dev/null; then + result="$result $m" + fi + done + echo "$result" | xargs + } + + # Phase 1: Scan global mirrors first (independent of local CDN issues) + OTHERS_OK=$(scan_reachable "$OTHERS") + OTHERS_PICK=$(printf "%s\n" $OTHERS_OK | shuf | head -3 | xargs) + + for mirror in $OTHERS_PICK; do + echo " Attempting mirror: $mirror" + try_mirrors "$mirror" && exit 0 + done + + # Phase 2: Try primary mirror + if [ "$DISTRO" = "ubuntu" ]; then + PRIMARY="archive.ubuntu.com" + else + PRIMARY="ftp.debian.org" + fi + if timeout 2 bash -c "echo >/dev/tcp/$PRIMARY/80" 2>/dev/null; then + echo " Attempting mirror: $PRIMARY" + try_mirrors "$PRIMARY" && exit 0 + fi + + # Phase 3: Fall back to regional mirrors + REGIONAL_OK=$(scan_reachable "$REGIONAL") + REGIONAL_PICK=$(printf "%s\n" $REGIONAL_OK | shuf | head -3 | xargs) + + for mirror in $REGIONAL_PICK; do + echo " Attempting mirror: $mirror" + try_mirrors "$mirror" && exit 0 + done + + exit 2 + ' && mirror_exit=0 || mirror_exit=$? + if [[ $mirror_exit -eq 2 ]]; then + msg_warn "Multiple mirrors failed (possible CDN synchronization issue)." + if [[ "$var_os" == "ubuntu" ]]; then + msg_info "Find Ubuntu mirrors at: https://launchpad.net/ubuntu/+archivemirrors" + else + msg_info "Find Debian mirrors at: https://www.debian.org/mirror/list" + fi + local custom_mirror="" + while true; do + read -rp " Enter a mirror hostname (or 'skip' to abort): " custom_mirror /dev/null 2>&1 && apt-get install -y sudo curl mc gnupg2 jq >/dev/null 2>&1 + " && break + msg_warn "Mirror '${custom_mirror}' also failed. Try another or type 'skip'." + done + if [[ "$custom_mirror" == "skip" ]]; then + msg_error "apt-get base packages installation failed" + install_exit_code=1 + fi + elif [[ $mirror_exit -ne 0 ]]; then + msg_error "apt-get base packages installation failed" + install_exit_code=1 + fi } fi diff --git a/misc/install.func b/misc/install.func index 024e0633d..f7beb956b 100644 --- a/misc/install.func +++ b/misc/install.func @@ -210,6 +210,173 @@ network_check() { # SECTION 3: OS UPDATE & PACKAGE MANAGEMENT # ============================================================================== +# ------------------------------------------------------------------------------ +# apt_update_safe() +# +# - Runs apt-get update with CDN mirror fallback +# - On failure, detects distro (Debian/Ubuntu) and tries alternate mirrors +# - Three-phase approach: global mirrors → primary mirror → regional mirrors +# - Falls back to manual user prompt if all auto mirrors fail +# - Detects hash mismatch, SSL errors, and generic apt failures +# ------------------------------------------------------------------------------ +apt_update_safe() { + if $STD apt-get update; then + return 0 + fi + + local failed_mirror + failed_mirror=$(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 || echo "unknown") + msg_warn "apt-get update failed (${failed_mirror}), trying alternate mirrors..." + + local distro + distro=$(. /etc/os-release 2>/dev/null && echo "$ID" || echo "debian") + + local eu_mirrors us_mirrors ap_mirrors + if [[ "$distro" == "ubuntu" ]]; then + eu_mirrors="de.archive.ubuntu.com fr.archive.ubuntu.com se.archive.ubuntu.com nl.archive.ubuntu.com it.archive.ubuntu.com ch.archive.ubuntu.com mirrors.xtom.de" + us_mirrors="us.archive.ubuntu.com archive.ubuntu.com mirrors.edge.kernel.org mirror.csclub.uwaterloo.ca mirrors.ocf.berkeley.edu mirror.math.princeton.edu" + ap_mirrors="au.archive.ubuntu.com jp.archive.ubuntu.com kr.archive.ubuntu.com tw.archive.ubuntu.com mirror.aarnet.edu.au" + else + eu_mirrors="ftp.de.debian.org ftp.fr.debian.org ftp.nl.debian.org ftp.uk.debian.org ftp.ch.debian.org ftp.se.debian.org ftp.it.debian.org ftp.fau.de ftp.halifax.rwth-aachen.de debian.mirror.lrz.de mirror.init7.net debian.ethz.ch mirrors.dotsrc.org debian.mirrors.ovh.net" + us_mirrors="ftp.us.debian.org ftp.ca.debian.org debian.csail.mit.edu mirrors.ocf.berkeley.edu mirrors.wikimedia.org debian.osuosl.org mirror.cogentco.com" + ap_mirrors="ftp.au.debian.org ftp.jp.debian.org ftp.tw.debian.org ftp.kr.debian.org ftp.hk.debian.org ftp.sg.debian.org mirror.aarnet.edu.au mirror.nitc.ac.in" + fi + + local tz regional others + tz=$(cat /etc/timezone 2>/dev/null || echo "UTC") + case "$tz" in + Europe/* | Arctic/*) + regional="$eu_mirrors" + others="$us_mirrors $ap_mirrors" + ;; + America/*) + regional="$us_mirrors" + others="$eu_mirrors $ap_mirrors" + ;; + Asia/* | Australia/* | Pacific/*) + regional="$ap_mirrors" + others="$eu_mirrors $us_mirrors" + ;; + *) + regional="" + others="$eu_mirrors $us_mirrors $ap_mirrors" + ;; + esac + + echo 'Acquire::By-Hash "no";' >/etc/apt/apt.conf.d/99no-by-hash + + _try_apt_mirror() { + local m=$1 + for src in /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list; do + [[ -f "$src" ]] && sed -i "s|URIs: http[s]*://[^/]*/|URIs: http://${m}/|g; s|deb http[s]*://[^/]*/|deb http://${m}/|g" "$src" + done + rm -rf /var/lib/apt/lists/* + local out + out=$(apt-get update 2>&1) + if echo "$out" | grep -qi "hashsum\|hash sum"; then + msg_warn "Mirror ${m} failed (hash mismatch)" + return 1 + elif echo "$out" | grep -qi "SSL\|certificate"; then + msg_warn "Mirror ${m} failed (SSL/certificate error)" + return 1 + elif echo "$out" | grep -q "^E:"; then + msg_warn "Mirror ${m} failed (apt-get update error)" + return 1 + else + msg_ok "CDN set to ${m}: tests passed" + return 0 + fi + } + + _scan_reachable() { + local result="" + for m in $1; do + if timeout 2 bash -c "echo >/dev/tcp/$m/80" 2>/dev/null; then + result="$result $m" + fi + done + echo "$result" | xargs + } + + local apt_ok=false + + # Phase 1: Scan global mirrors first (independent of local CDN issues) + local others_ok + others_ok=$(_scan_reachable "$others") + local others_pick + others_pick=$(printf '%s\n' $others_ok | shuf | head -3 | xargs) + + for mirror in $others_pick; do + msg_info "Attempting mirror: ${mirror}" + if _try_apt_mirror "$mirror"; then + apt_ok=true + break + fi + done + + # Phase 2: Try primary mirror + if [[ "$apt_ok" != true ]]; then + local primary + if [[ "$distro" == "ubuntu" ]]; then + primary="archive.ubuntu.com" + else + primary="ftp.debian.org" + fi + if timeout 2 bash -c "echo >/dev/tcp/$primary/80" 2>/dev/null; then + msg_info "Attempting mirror: ${primary}" + if _try_apt_mirror "$primary"; then + apt_ok=true + fi + fi + fi + + # Phase 3: Fall back to regional mirrors + if [[ "$apt_ok" != true ]]; then + local regional_ok + regional_ok=$(_scan_reachable "$regional") + local regional_pick + regional_pick=$(printf '%s\n' $regional_ok | shuf | head -3 | xargs) + + for mirror in $regional_pick; do + msg_info "Attempting mirror: ${mirror}" + if _try_apt_mirror "$mirror"; then + apt_ok=true + break + fi + done + fi + + # Phase 4: All auto mirrors failed, prompt user + if [[ "$apt_ok" != true ]]; then + msg_warn "Multiple mirrors failed (possible CDN synchronization issue)." + if [[ "$distro" == "ubuntu" ]]; then + msg_info "Find Ubuntu mirrors at: https://launchpad.net/ubuntu/+archivemirrors" + else + msg_info "Find Debian mirrors at: https://www.debian.org/mirror/list" + fi + local custom_mirror + while true; do + read -rp " Enter a mirror hostname (or 'skip' to abort): " custom_mirror