3x-ui/.github/workflows/release.yml
root badbbf71f5 fix: add retry for release upload to handle API propagation delay
gh release upload can fail with "release not found" when called
immediately after gh release create due to GitHub API propagation
delay. Add a 5s sleep after create and retry the upload up to 3 times.
2026-04-25 10:54:34 +08:00

368 lines
13 KiB
YAML

name: Release 3X-UI
on:
workflow_dispatch:
push:
tags:
- "v*.*"
pull_request:
paths:
- '**.js'
- '**.css'
- '**.html'
- '**.sh'
- '**.go'
- 'go.mod'
- 'go.sum'
- 'x-ui.service.debian'
- 'x-ui.service.arch'
- 'x-ui.service.rhel'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze Go code
permissions:
contents: read
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- name: Check formatting
run: |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "These files are not gofmt-formatted:"
echo "$unformatted"
exit 1
fi
- name: Run go vet
run: go vet ./...
- name: Run staticcheck
uses: dominikh/staticcheck-action@v1
with:
version: "latest"
install-go: false
- name: Run tests
run: go test -race -shuffle=on ./...
build:
timeout-minutes: 30
permissions:
contents: write
strategy:
matrix:
platform:
- amd64
# 手动注释,请勿更改
# - arm64
# 手动注释,请勿更改
# - armv7
# 手动注释,请勿更改
# - armv6
# 手动注释,请勿更改
# - 386
# 手动注释,请勿更改
# - armv5
# 手动注释,请勿更改
# - s390x
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
- name: Cache Go build
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
key: gobuild-${{ matrix.platform }}-${{ hashFiles('**/*.go', 'go.mod', 'go.sum') }}
restore-keys: |
gobuild-${{ matrix.platform }}-
- name: Get latest Xray version
id: xray_version
run: |
LATEST=$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest | jq -r '.tag_name')
echo "version=$LATEST" >> "$GITHUB_OUTPUT"
echo "Latest Xray version: $LATEST"
- name: Cache Xray binary
id: cache_xray
uses: actions/cache@v4
with:
path: /tmp/xray
key: xray-${{ matrix.platform }}-${{ steps.xray_version.outputs.version }}
restore-keys: |
xray-${{ matrix.platform }}-
- name: Download Xray
if: steps.cache_xray.outputs.cache-hit != 'true'
run: |
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/${{ steps.xray_version.outputs.version }}/"
declare -A XRAY_MAP=(
[amd64]="Xray-linux-64.zip"
[arm64]="Xray-linux-arm64-v8a.zip"
[armv7]="Xray-linux-arm32-v7a.zip"
[armv6]="Xray-linux-arm32-v6.zip"
[386]="Xray-linux-32.zip"
[armv5]="Xray-linux-arm32-v5.zip"
[s390x]="Xray-linux-s390x.zip"
)
XRAY_FILE="${XRAY_MAP[${{ matrix.platform }}]}"
[ -z "$XRAY_FILE" ] && { echo "Unknown platform: ${{ matrix.platform }}" >&2; exit 1; }
mkdir -p /tmp/xray
curl -fL -sS --retry 3 --retry-delay 5 -o "/tmp/xray/$XRAY_FILE" "${Xray_URL}${XRAY_FILE}" || { echo "Failed to download Xray" >&2; exit 1; }
cd /tmp/xray
unzip -q "$XRAY_FILE" || { echo "Failed to unzip Xray" >&2; exit 1; }
rm -f "$XRAY_FILE"
[ -f xray ] || { echo "xray binary not found after extraction" >&2; exit 1; }
- name: Get cache date
id: cache_date
run: echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT"
- name: Cache geo data
id: cache_geo
uses: actions/cache@v4
with:
path: /tmp/geo
key: geo-${{ steps.cache_date.outputs.date }}
- name: Download geo data
if: steps.cache_geo.outputs.cache-hit != 'true'
run: |
mkdir -p /tmp/geo
cd /tmp/geo
curl -fL -sS --retry 3 -o geoip.dat \
https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat &
curl -fL -sS --retry 3 -o geosite.dat \
https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat &
curl -fL -sS --retry 3 -o geoip_IR.dat \
https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat &
curl -fL -sS --retry 3 -o geosite_IR.dat \
https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat &
curl -fL -sS --retry 3 -o geoip_RU.dat \
https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat &
curl -fL -sS --retry 3 -o geosite_RU.dat \
https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat &
FAILED=0
for job in $(jobs -p); do wait "$job" || FAILED=1; done
if [ "$FAILED" -eq 1 ]; then
echo "One or more geo data downloads failed" >&2
exit 1
fi
for f in *.dat; do
if [ ! -s "$f" ]; then
echo "Geo file $f is empty" >&2; exit 1
fi
done
- name: Cache Bootlin toolchain
uses: actions/cache@v4
with:
path: /tmp/toolchain
key: bootlin-${{ matrix.platform }}-${{ hashFiles('.github/workflows/release.yml') }}
restore-keys: |
bootlin-${{ matrix.platform }}-
- name: Build 3X-UI
run: |
export CGO_ENABLED=1
export GOOS=linux
export GOARCH=${{ matrix.platform }}
# Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
case "${{ matrix.platform }}" in
amd64) BOOTLIN_ARCH="x86-64" ;;
arm64) BOOTLIN_ARCH="aarch64" ;;
armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
386) BOOTLIN_ARCH="x86-i686" ;;
s390x) BOOTLIN_ARCH="s390x-z13" ;;
esac
TOOLCHAIN_DIR=$(find /tmp/toolchain -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" 2>/dev/null | head -n1)
if [ -z "$TOOLCHAIN_DIR" ]; then
echo "Cache miss, downloading toolchain..."
mkdir -p /tmp/toolchain
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)
[ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
echo "Downloading: $TARBALL_URL"
cd /tmp/toolchain
curl -fL -sS --retry 3 --retry-delay 5 -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL" || { echo "Failed to download toolchain" >&2; exit 1; }
tar -xf "$(basename "$TARBALL_URL")" || { echo "Failed to extract toolchain tarball" >&2; exit 1; }
TOOLCHAIN_DIR=$(find /tmp/toolchain -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
cd -
else
echo "Using cached toolchain: $TOOLCHAIN_DIR"
fi
export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH"
export CC=$(realpath "$(find "$TOOLCHAIN_DIR/bin" -name '*-gcc.br_real' -type f -executable | head -n1)")
[ -z "$CC" ] && { echo "No gcc.br_real found in $TOOLCHAIN_DIR/bin" >&2; exit 1; }
go build -ldflags "-w -s -linkmode external -extldflags '-static'" -o xui-release -v main.go
file xui-release
ldd xui-release || echo "Static binary confirmed"
mkdir x-ui
cp xui-release x-ui/
cp x-ui.service.debian x-ui/
cp x-ui.service.arch x-ui/
cp x-ui.service.rhel x-ui/
cp x-ui.sh x-ui/
mv x-ui/xui-release x-ui/x-ui
mkdir x-ui/bin
cd x-ui/bin
# Copy Xray from cache
cp /tmp/xray/xray .
# Copy geo data from cache
cp /tmp/geo/* .
mv xray xray-linux-${{ matrix.platform }}
cd ../..
- name: Package
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
- name: Upload files to Artifacts
uses: actions/upload-artifact@v7
with:
name: x-ui-linux-${{ matrix.platform }}
path: ./x-ui-linux-${{ matrix.platform }}.tar.gz
- name: Create release and upload assets
if: startsWith(github.ref, 'refs/tags/')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ github.ref_name }}"
if ! gh release view "$TAG" > /dev/null 2>&1; then
gh release create "$TAG" --prerelease --title "$TAG" --generate-notes \
--notes "**Xray-core:** ${{ steps.xray_version.outputs.version }}"
sleep 5
fi
for i in 1 2 3; do
gh release upload "$TAG" x-ui-linux-${{ matrix.platform }}.tar.gz --clobber && break
echo "Upload attempt $i failed, retrying in 5s..."
sleep 5
done
# =================================
# Windows Build (disabled)
# =================================
# build-windows:
# name: Build for Windows
# permissions:
# contents: write
# strategy:
# matrix:
# platform:
# - amd64
# runs-on: windows-latest
# steps:
# - name: Checkout repository
# uses: actions/checkout@v6
#
# - name: Setup Go
# uses: actions/setup-go@v6
# with:
# go-version-file: go.mod
# check-latest: true
#
# - name: Install MSYS2
# uses: msys2/setup-msys2@v2
# with:
# msystem: MINGW64
# update: true
# install: >-
# mingw-w64-x86_64-gcc
# mingw-w64-x86_64-sqlite3
# mingw-w64-x86_64-pkg-config
#
# - name: Build 3X-UI for Windows (CGO)
# shell: msys2 {0}
# run: |
# export PATH="/c/hostedtoolcache/windows/go/$(ls /c/hostedtoolcache/windows/go | sort -V | tail -n1)/x64/bin:$PATH"
#
# export CGO_ENABLED=1
# export GOOS=windows
# export GOARCH=amd64
# export CC=x86_64-w64-mingw32-gcc
#
# which go
# go version
# gcc --version
#
# go build -ldflags "-w -s" -o xui-release.exe -v main.go
#
# - name: Copy and download resources
# shell: pwsh
# run: |
# mkdir x-ui
# Copy-Item xui-release.exe x-ui\x-ui.exe
# mkdir x-ui\bin
# cd x-ui\bin
#
# # Download Xray for Windows
# $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.2.6/"
# Invoke-WebRequest -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"
# Rename-Item xray.exe xray-windows-amd64.exe
# cd ..
# Copy-Item -Path ..\windows_files\* -Destination . -Recurse
# cd ..
#
# - name: Package to Zip
# shell: pwsh
# run: |
# Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip"
#
# - name: Upload files to Artifacts
# uses: actions/upload-artifact@v7
# with:
# name: x-ui-windows-amd64
# path: ./x-ui-windows-amd64.zip
#
# - name: Upload files to GH release
# uses: svenstaro/upload-release-action@v2
# if: github.event_name == 'release'
# with:
# repo_token: ${{ secrets.GITHUB_TOKEN }}
# tag: ${{ github.ref_name }}
# file: x-ui-windows-amd64.zip
# asset_name: x-ui-windows-amd64.zip
# overwrite: true
# prerelease: true