From ab1a9228061c99fb3f3a2295b93ee394037357a9 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 17 Jul 2026 01:06:14 +0200 Subject: [PATCH] fix(ci): survive transient GitHub 5xx outages in the release workflow A GitHub API 503 storm failed the release run four attempts in a row: the mtg-multi latest-release lookup died on every platform (even s390x, which never packages the sidecar), and the matrix default fail-fast then cancelled the six healthy builds alongside the one that hit the outage. Retry every external fetch with backoff (curl --retry-all-errors, wget --retry-on-http-error, Invoke-* -MaximumRetryCount), scope the mtg-multi tag lookup to the platforms that actually package the sidecar, disable fail-fast on the build matrix, and retry the idempotent dev-latest publish commands. The workflow file itself now triggers the run's path filters, so editing it exercises the release build immediately instead of failing silently at the next code push. --- .github/workflows/release.yml | 84 +++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fea1ba05f..ad6027ad7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,6 +16,7 @@ on: - "x-ui.service.debian" - "x-ui.service.arch" - "x-ui.service.rhel" + - ".github/workflows/release.yml" pull_request: paths: - "**.go" @@ -26,12 +27,15 @@ on: - "x-ui.service.debian" - "x-ui.service.arch" - "x-ui.service.rhel" + - ".github/workflows/release.yml" jobs: build: permissions: contents: write strategy: + # One platform hitting a transient outage must not cancel the other six. + fail-fast: false matrix: platform: - amd64 @@ -71,6 +75,8 @@ jobs: - name: Build 3X-UI run: | + CURL_RETRY="--retry 5 --retry-all-errors --retry-delay 3" + fetch() { wget -q --tries=5 --waitretry=10 --retry-on-http-error=429,500,502,503 "$@"; } export CGO_ENABLED=1 export GOOS=linux export GOARCH=${{ matrix.platform }} @@ -86,11 +92,11 @@ jobs: esac echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})" TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/" - TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1) + TARBALL_URL=$(curl -fsSL $CURL_RETRY "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1) [ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; } echo "Downloading: $TARBALL_URL" cd /tmp - curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL" + curl -fL -sS $CURL_RETRY -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL" tar -xf "$(basename "$TARBALL_URL")" TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1) export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH" @@ -120,53 +126,54 @@ jobs: # Download dependencies Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.7.11/" if [ "${{ matrix.platform }}" == "amd64" ]; then - wget -q ${Xray_URL}Xray-linux-64.zip + fetch ${Xray_URL}Xray-linux-64.zip unzip Xray-linux-64.zip rm -f Xray-linux-64.zip elif [ "${{ matrix.platform }}" == "arm64" ]; then - wget -q ${Xray_URL}Xray-linux-arm64-v8a.zip + fetch ${Xray_URL}Xray-linux-arm64-v8a.zip unzip Xray-linux-arm64-v8a.zip rm -f Xray-linux-arm64-v8a.zip elif [ "${{ matrix.platform }}" == "armv7" ]; then - wget -q ${Xray_URL}Xray-linux-arm32-v7a.zip + fetch ${Xray_URL}Xray-linux-arm32-v7a.zip unzip Xray-linux-arm32-v7a.zip rm -f Xray-linux-arm32-v7a.zip elif [ "${{ matrix.platform }}" == "armv6" ]; then - wget -q ${Xray_URL}Xray-linux-arm32-v6.zip + fetch ${Xray_URL}Xray-linux-arm32-v6.zip unzip Xray-linux-arm32-v6.zip rm -f Xray-linux-arm32-v6.zip elif [ "${{ matrix.platform }}" == "386" ]; then - wget -q ${Xray_URL}Xray-linux-32.zip + fetch ${Xray_URL}Xray-linux-32.zip unzip Xray-linux-32.zip rm -f Xray-linux-32.zip elif [ "${{ matrix.platform }}" == "armv5" ]; then - wget -q ${Xray_URL}Xray-linux-arm32-v5.zip + fetch ${Xray_URL}Xray-linux-arm32-v5.zip unzip Xray-linux-arm32-v5.zip rm -f Xray-linux-arm32-v5.zip elif [ "${{ matrix.platform }}" == "s390x" ]; then - wget -q ${Xray_URL}Xray-linux-s390x.zip + fetch ${Xray_URL}Xray-linux-s390x.zip unzip Xray-linux-s390x.zip rm -f Xray-linux-s390x.zip fi rm -f geoip.dat geosite.dat - wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat - wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat - wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat - wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat - wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat - wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat + fetch https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat + fetch https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat + fetch -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat + fetch -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat + fetch -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat + fetch -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat mv xray xray-linux-${{ matrix.platform }} # mtg-multi (MTProto sidecar) ships prebuilt release binaries whose # platform labels match our matrix, so download and unpack the matching - # archive. Only the platforms the fork publishes are packaged. The tag - # is resolved from the fork's latest release so it never needs bumping - # here; the token only lifts the API rate limit for a public read. - MTG_MULTI_VER=$(curl -sfL -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/mhsanaei/mtg-multi/releases/latest" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n 1) - if [ -z "$MTG_MULTI_VER" ]; then echo "could not resolve the latest mtg-multi release tag"; exit 1; fi + # archive. Only the platforms the fork publishes are packaged — the tag + # lookup lives inside that branch so unpackaged platforms (s390x) never + # depend on it. The tag is resolved from the fork's latest release so it + # never needs bumping here; the token only lifts the API rate limit. case "${{ matrix.platform }}" in amd64|arm64|armv7|armv6|386) + MTG_MULTI_VER=$(curl -sfL $CURL_RETRY -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/mhsanaei/mtg-multi/releases/latest" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n 1) + if [ -z "$MTG_MULTI_VER" ]; then echo "could not resolve the latest mtg-multi release tag"; exit 1; fi MTG_PKG="mtg-multi-${MTG_MULTI_VER#v}-linux-${{ matrix.platform }}" - curl -sfLRO "https://github.com/mhsanaei/mtg-multi/releases/download/${MTG_MULTI_VER}/${MTG_PKG}.tar.gz" + curl -sfLRO $CURL_RETRY "https://github.com/mhsanaei/mtg-multi/releases/download/${MTG_MULTI_VER}/${MTG_PKG}.tar.gz" tar -xzf "${MTG_PKG}.tar.gz" mv "${MTG_PKG}/mtg-multi" "mtg-linux-${{ matrix.platform }}" rm -rf "${MTG_PKG}" "${MTG_PKG}.tar.gz" @@ -267,6 +274,7 @@ jobs: - name: Copy and download resources shell: pwsh run: | + $retry = @{ MaximumRetryCount = 5; RetryIntervalSec = 10 } mkdir x-ui Copy-Item xui-release.exe x-ui\x-ui.exe mkdir x-ui\bin @@ -274,25 +282,25 @@ jobs: # Download Xray for Windows $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.7.11/" - Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip" + Invoke-WebRequest @retry -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip" Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath . Remove-Item "Xray-windows-64.zip" Remove-Item geoip.dat, geosite.dat -ErrorAction SilentlyContinue - Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat" - Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat" - Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat" - Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat" - Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip_RU.dat" - Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite_RU.dat" + Invoke-WebRequest @retry -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat" + Invoke-WebRequest @retry -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat" + Invoke-WebRequest @retry -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat" + Invoke-WebRequest @retry -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat" + Invoke-WebRequest @retry -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip_RU.dat" + Invoke-WebRequest @retry -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite_RU.dat" Rename-Item xray.exe xray-windows-amd64.exe # mtg-multi (MTProto sidecar) publishes a prebuilt Windows binary, so # download and unpack it instead of compiling. The tag tracks the # fork's latest release so it never needs bumping here. - $MTG_MULTI_VER = (Invoke-RestMethod -Uri "https://api.github.com/repos/mhsanaei/mtg-multi/releases/latest" -Headers @{ Authorization = "Bearer ${{ secrets.GITHUB_TOKEN }}"; "User-Agent" = "x-ui-release" }).tag_name + $MTG_MULTI_VER = (Invoke-RestMethod @retry -Uri "https://api.github.com/repos/mhsanaei/mtg-multi/releases/latest" -Headers @{ Authorization = "Bearer ${{ secrets.GITHUB_TOKEN }}"; "User-Agent" = "x-ui-release" }).tag_name if (-not $MTG_MULTI_VER) { throw "could not resolve the latest mtg-multi release tag" } $MTG_PKG = "mtg-multi-$($MTG_MULTI_VER.TrimStart('v'))-windows-amd64" - curl.exe -sfLRO "https://github.com/mhsanaei/mtg-multi/releases/download/$MTG_MULTI_VER/$MTG_PKG.zip" + curl.exe -sfLRO --retry 5 --retry-all-errors --retry-delay 3 "https://github.com/mhsanaei/mtg-multi/releases/download/$MTG_MULTI_VER/$MTG_PKG.zip" Expand-Archive -Path "$MTG_PKG.zip" -DestinationPath "mtg-tmp" -Force Move-Item "mtg-tmp/$MTG_PKG/mtg-multi.exe" "mtg-windows-amd64.exe" Remove-Item -Recurse -Force "mtg-tmp", "$MTG_PKG.zip" @@ -359,6 +367,14 @@ jobs: COMMIT: ${{ github.sha }} run: | set -e + retry() { + for i in 1 2 3 4 5; do + "$@" && return 0 + echo "attempt $i failed: $1" >&2 + sleep $((i * 5)) + done + return 1 + } short="${COMMIT::8}" notes="Rolling development build — installs via the panel's Dev update channel. @@ -369,14 +385,14 @@ jobs: # Force-move the dev-latest tag to this commit so the release tracks it. git tag -f dev-latest "${COMMIT}" - git push -f origin refs/tags/dev-latest + retry git push -f origin refs/tags/dev-latest if gh release view dev-latest >/dev/null 2>&1; then - gh release edit dev-latest --prerelease --latest=false \ + retry gh release edit dev-latest --prerelease --latest=false \ --title "Dev build ${short}" --notes "${notes}" else - gh release create dev-latest --prerelease --latest=false \ + retry gh release create dev-latest --prerelease --latest=false \ --target "${COMMIT}" --title "Dev build ${short}" --notes "${notes}" fi - gh release upload dev-latest dev-artifacts/*.tar.gz dev-artifacts/*.zip --clobber + retry gh release upload dev-latest dev-artifacts/*.tar.gz dev-artifacts/*.zip --clobber