mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-10 20:27:15 +00:00
2ec6c73613
Bump xtls/xray-core to 37ceb8b4b6 (v26.9.8) and the three binary pins (DockerInit.sh, release.yml Linux + Windows) in lockstep. No deleted symbols; the impact is entirely on the JSON config surface. Outbound "proxySettings" is now refused by the config loader (moved to streamSettings.sockopt.dialerProxy) and a freedom outbound rejects sockopt.addressPortStrategy. Either key in a stored template would keep the core from starting after the upgrade, so a new OutboundRemovedKeysFix seeder rewrites xrayTemplateConfig once: proxySettings.tag becomes sockopt.dialerProxy (an existing dialerProxy wins) and addressPortStrategy is dropped from freedom outbounds. Template saves and outbound subscriptions already run through the vendored loader, so the new refusals surface there with the core's own message. REALITY no longer applies a built-in minClientVer (26.3.27) when the field is empty. The form placeholder and the min/max hints in all 13 locales now say that empty means no minimum. New upstream keys the Zod schemas would otherwise strip, with form support where a sibling field already had it: - blackhole response type "custom" with base64 customResponseData - realm finalmask ipMode (dual/v4/v6) and portMapping (UPnP / NAT-PMP) - quicParams brutalDisableLossCompensation, disableChromeParrot, disableGSO, disableStatelessReset - hysteria masquerade proxy xForwarded - wireguard outbound remoteDNS - routing rule localOS freedom.domainStrategy is only deprecated upstream (auto-migrated to sockopt.domainStrategy with a warning) and is left untouched.
410 lines
17 KiB
YAML
410 lines
17 KiB
YAML
name: Release 3X-UI
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
push:
|
|
branches:
|
|
- "**"
|
|
tags:
|
|
- "v*.*.*"
|
|
paths:
|
|
- "**.go"
|
|
- "go.mod"
|
|
- "go.sum"
|
|
- "**.sh"
|
|
- "frontend/**"
|
|
- "x-ui.service.debian"
|
|
- "x-ui.service.arch"
|
|
- "x-ui.service.rhel"
|
|
- ".github/workflows/release.yml"
|
|
pull_request:
|
|
paths:
|
|
- "**.go"
|
|
- "go.mod"
|
|
- "go.sum"
|
|
- "**.sh"
|
|
- "frontend/**"
|
|
- "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
|
|
- arm64
|
|
- armv7
|
|
- armv6
|
|
- 386
|
|
- armv5
|
|
- s390x
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v7
|
|
with:
|
|
go-version-file: go.mod
|
|
check-latest: true
|
|
|
|
# Frontend dist must be built BEFORE go build — Go's //go:embed
|
|
# all:dist directive in internal/web/web.go requires internal/web/dist/ to exist
|
|
# at compile time. internal/web/dist/ is .gitignored, so on a fresh CI
|
|
# checkout it doesn't exist until vite emits it.
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: 'npm'
|
|
cache-dependency-path: frontend/package-lock.json
|
|
|
|
- name: Build frontend bundle
|
|
run: |
|
|
npm ci
|
|
npm run build
|
|
working-directory: frontend
|
|
|
|
- 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 }}
|
|
# 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
|
|
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 $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 $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"
|
|
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; }
|
|
cd -
|
|
# Stamp the commit into per-commit (dev channel) builds only; tagged
|
|
# stable releases stay unstamped so config.IsDevBuild() returns false.
|
|
LDFLAGS="-w -s -linkmode external -extldflags '-static'"
|
|
if [[ "$GITHUB_REF" != refs/tags/* ]]; then
|
|
LDFLAGS="$LDFLAGS -X github.com/mhsanaei/3x-ui/v3/internal/config.buildCommit=${GITHUB_SHA::8} -X github.com/mhsanaei/3x-ui/v3/internal/config.buildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
fi
|
|
go build -buildvcs=true -ldflags "$LDFLAGS" -o xui-release -v .
|
|
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
|
|
|
|
# Download dependencies
|
|
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.9.8/"
|
|
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
|
fetch ${Xray_URL}Xray-linux-64.zip
|
|
unzip Xray-linux-64.zip
|
|
rm -f Xray-linux-64.zip
|
|
elif [ "${{ matrix.platform }}" == "arm64" ]; then
|
|
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
|
|
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
|
|
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
|
|
fetch ${Xray_URL}Xray-linux-32.zip
|
|
unzip Xray-linux-32.zip
|
|
rm -f Xray-linux-32.zip
|
|
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
|
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
|
|
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
|
|
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
|
|
# lookup lives inside that branch so unpackaged platforms (s390x) never
|
|
# depend on it. The tag comes from the release-page redirect on
|
|
# github.com — the host the downloads need anyway — because api.github.com
|
|
# has 503'd whole release runs while asset downloads kept working.
|
|
case "${{ matrix.platform }}" in
|
|
amd64|arm64|armv7|armv6|386)
|
|
MTG_MULTI_VER=$(curl -sf $CURL_RETRY -o /dev/null -w '%{redirect_url}' "https://github.com/mhsanaei/mtg-multi/releases/latest" | sed -n 's#.*/releases/tag/##p')
|
|
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 $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"
|
|
;;
|
|
esac
|
|
cd ../..
|
|
|
|
- name: Package
|
|
run: |
|
|
tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
|
sha256sum x-ui-linux-${{ matrix.platform }}.tar.gz > x-ui-linux-${{ matrix.platform }}.tar.gz.sha256
|
|
|
|
- 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
|
|
./x-ui-linux-${{ matrix.platform }}.tar.gz.sha256
|
|
|
|
- name: Upload files to GH release
|
|
uses: svenstaro/upload-release-action@v2
|
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
with:
|
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
|
tag: ${{ github.ref_name }}
|
|
file: x-ui-linux-${{ matrix.platform }}.tar.gz*
|
|
file_glob: true
|
|
overwrite: true
|
|
prerelease: true
|
|
|
|
# =================================
|
|
# Windows Build
|
|
# =================================
|
|
build-windows:
|
|
name: Build for Windows
|
|
permissions:
|
|
contents: write
|
|
strategy:
|
|
matrix:
|
|
platform:
|
|
- amd64
|
|
runs-on: windows-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v7
|
|
with:
|
|
go-version-file: go.mod
|
|
check-latest: true
|
|
|
|
# Frontend dist must be built BEFORE go build — see comment on the
|
|
# Linux job above. This step is identical except npm runs on the
|
|
# Windows runner here.
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: 'npm'
|
|
cache-dependency-path: frontend/package-lock.json
|
|
|
|
- name: Build frontend bundle
|
|
shell: pwsh
|
|
run: |
|
|
npm ci
|
|
npm run build
|
|
working-directory: frontend
|
|
|
|
- name: Install MSYS2
|
|
uses: msys2/setup-msys2@v2
|
|
with:
|
|
msystem: MINGW64
|
|
update: true
|
|
install: >-
|
|
git
|
|
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
|
|
|
|
# Stamp the commit into per-commit (dev channel) builds only.
|
|
LDFLAGS="-w -s"
|
|
if [[ "$GITHUB_REF" != refs/tags/* ]]; then
|
|
LDFLAGS="$LDFLAGS -X github.com/mhsanaei/3x-ui/v3/internal/config.buildCommit=${GITHUB_SHA:0:8} -X github.com/mhsanaei/3x-ui/v3/internal/config.buildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
fi
|
|
go build -buildvcs=true -ldflags "$LDFLAGS" -o xui-release.exe -v .
|
|
|
|
- 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
|
|
cd x-ui\bin
|
|
|
|
# Download Xray for Windows
|
|
$Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.9.8/"
|
|
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 @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 comes from the
|
|
# release-page redirect on github.com — not api.github.com, whose
|
|
# outages have failed release runs while asset downloads kept working.
|
|
$MTG_MULTI_VER = (curl.exe -sf --retry 5 --retry-all-errors --retry-delay 3 -o NUL -w '%{redirect_url}' "https://github.com/mhsanaei/mtg-multi/releases/latest") -replace '^.*/releases/tag/', ''
|
|
if (-not $MTG_MULTI_VER -or $MTG_MULTI_VER -notmatch '^v[\d.]+$') { throw "could not resolve the latest mtg-multi release tag" }
|
|
$MTG_PKG = "mtg-multi-$($MTG_MULTI_VER.TrimStart('v'))-windows-amd64"
|
|
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"
|
|
|
|
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"
|
|
$hash = (Get-FileHash x-ui-windows-amd64.zip -Algorithm SHA256).Hash.ToLower()
|
|
[IO.File]::WriteAllText("$PWD\x-ui-windows-amd64.zip.sha256", "$hash x-ui-windows-amd64.zip`n")
|
|
|
|
- name: Upload files to Artifacts
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: x-ui-windows-amd64
|
|
path: |
|
|
./x-ui-windows-amd64.zip
|
|
./x-ui-windows-amd64.zip.sha256
|
|
|
|
- name: Upload files to GH release
|
|
uses: svenstaro/upload-release-action@v2
|
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
with:
|
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
|
tag: ${{ github.ref_name }}
|
|
file: x-ui-windows-amd64.zip*
|
|
file_glob: true
|
|
overwrite: true
|
|
prerelease: true
|
|
|
|
# =================================
|
|
# Rolling dev channel (per-commit)
|
|
# =================================
|
|
# Publishes/overwrites the build artifacts to a single fixed-tag pre-release
|
|
# `dev-latest`, force-moved to the new commit on every push to main. The panel's
|
|
# "Dev" update channel installs from this tag. `--latest=false` is load-bearing:
|
|
# it keeps releases/latest pointing at the real stable tag, so the stable
|
|
# channel is unaffected.
|
|
publish-dev:
|
|
name: Publish rolling dev release
|
|
needs: [build, build-windows]
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
# Serialize racing pushes; never cancel an in-flight upload, or the dev
|
|
# release could be left with a partial asset set.
|
|
concurrency:
|
|
group: dev-release
|
|
cancel-in-progress: false
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Download all build artifacts
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
path: dev-artifacts
|
|
merge-multiple: true
|
|
|
|
- name: Publish dev-latest pre-release
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
COMMIT: ${{ github.sha }}
|
|
run: |
|
|
set -e
|
|
retry() {
|
|
for i in 1 2 3 4 5; do
|
|
"$@" && return 0
|
|
echo "attempt $i failed: ${*:1:3}" >&2
|
|
sleep $((i * 5))
|
|
done
|
|
return 1
|
|
}
|
|
short="${COMMIT::8}"
|
|
notes="Rolling development build — installs via the panel's Dev update channel.
|
|
|
|
commit=${COMMIT}
|
|
built=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
|
|
Automated per-commit build from main. Not a stable release."
|
|
|
|
# Force-move the dev-latest tag to this commit so the release tracks it.
|
|
git tag -f dev-latest "${COMMIT}"
|
|
retry git push -f origin refs/tags/dev-latest
|
|
|
|
# The release exists on every run but the first; edit-first avoids an
|
|
# existence probe that can 503 and mis-route into create (422).
|
|
if ! retry gh release edit dev-latest --prerelease --latest=false \
|
|
--title "Dev build ${short}" --notes "${notes}"; then
|
|
retry gh release create dev-latest --prerelease --latest=false \
|
|
--target "${COMMIT}" --title "Dev build ${short}" --notes "${notes}"
|
|
fi
|
|
|
|
retry gh release upload dev-latest dev-artifacts/*.tar.gz dev-artifacts/*.zip dev-artifacts/*.sha256 --clobber
|