From f2d3eee4d8005a4e28b08af8118bd4c8183d4680 Mon Sep 17 00:00:00 2001 From: bluebluesoda <300733005+bluebluesoda@users.noreply.github.com> Date: Fri, 4 Sep 2026 08:53:34 +0000 Subject: [PATCH] fix(install): fall back to IPv4 when go.dev download fails over IPv6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some hosts resolve go.dev/dl through an IPv6 path that serves the site and manifest fine but returns 404 for the tarball CDN (dl.google.com). curl prefers IPv6 by default, so ensure_go's download loop failed on every candidate and aborted the local build. - add curl_retry: fetch with the default address family, fall back to 'curl -4' on failure; use it for both the manifest and the tarball - the IPv4 fallback is a pure retry — hosts with working IPv6 are unaffected, and pure-IPv4 hosts were never broken - drop an unused 'latest' variable in the download loop --- scripts/40-panel.sh | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/scripts/40-panel.sh b/scripts/40-panel.sh index 56b4e46..33148bf 100755 --- a/scripts/40-panel.sh +++ b/scripts/40-panel.sh @@ -16,6 +16,11 @@ ensure_go(){ # already exists; otherwise install the official Go from go.dev directly. # No distro package: Debian 12's golang-go is 1.19 (can't even parse # go.mod's `toolchain` directive) and the official tarball is one curl away. + # + # Some hosts resolve go.dev/dl through an IPv6 path that returns 404 for the + # download files (the site and manifest work, the tarball CDN does not), so + # every fetch below first tries the default address family and falls back to + # forcing IPv4 (`curl -4`) on failure. if command -v go >/dev/null 2>&1; then if GO_VER=$(go version 2>/dev/null | awk '{print $3}' | sed 's/^go//'); then major=${GO_VER%%.*}; minor=${GO_VER#*.}; minor=${minor%%.*} @@ -36,22 +41,37 @@ ensure_go(){ aarch64) arch=arm64 ;; *) die "no official Go for arch $(uname -m)" ;; esac + # curl_retry: fetch a URL, trying the default address family first (works on + # normal hosts and IPv6-first ones) and falling back to forcing IPv4 when the + # IPv6 path is broken. Args: . + curl_retry(){ + local max_time="$1" url="$2" out="$3" + if curl -fsSL --max-time "$max_time" -o "$out" "$url" 2>/dev/null; then + return 0 + fi + rm -f "$out" + log " IPv6 path failed, retrying via IPv4: $url" + curl -4 -fsSL --max-time "$max_time" -o "$out" "$url" 2>/dev/null + } # Read the official stable-download manifest instead of trusting the # VERSION endpoint alone. During a new release, VERSION can briefly name a # version whose architecture tarball is not available through every CDN # edge yet, which otherwise turns a transient 404 into a failed install. - local candidates candidate latest filename - mapfile -t candidates < <(curl -fsSL --max-time 30 https://go.dev/dl/?mode=json 2>/dev/null | \ - python3 -c 'import json,sys + local candidates candidate filename manifest + manifest="/tmp/go-manifest-$$.json" + if curl_retry 30 "https://go.dev/dl/?mode=json" "$manifest"; then + mapfile -t candidates < <(python3 -c 'import json,sys try: - releases=json.load(sys.stdin) + releases=json.load(open(sys.argv[1])) except Exception: sys.exit(1) for release in releases: for f in release.get("files", []): - if f.get("os") == "linux" and f.get("arch") == sys.argv[1] and f.get("kind") == "archive": + if f.get("os") == "linux" and f.get("arch") == sys.argv[2] and f.get("kind") == "archive": print(release["version"] + " " + f["filename"]) - break' "$arch") + break' "$manifest" "$arch") + rm -f "$manifest" + fi # Keep a known previous toolchain as a last-resort candidate if the manifest # endpoint is temporarily unavailable. The download loop below still retries # every candidate before failing. @@ -60,13 +80,12 @@ for release in releases: fi local downloaded=0 for candidate in "${candidates[@]}"; do - latest="${candidate%% *}" filename="${candidate#* }" tarball="/tmp/${filename}" url="https://go.dev/dl/${filename}" for attempt in 1 2 3; do log " downloading $url (attempt $attempt/3)" - if curl -fsSL --max-time 300 -o "$tarball" "$url"; then + if curl_retry 300 "$url" "$tarball"; then downloaded=1 break 2 fi