mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-23 06:15:01 +03:00
61b129d5b3
Bump xtls/xray-core to 1b1647cd9ddf (v26.10.21) and move the three binary pins (DockerInit.sh, both release.yml fetches) in lockstep. The v26.10.10 release was pulled upstream, so the old pins already 404. XDRIVE (#7182): the core now runs XDRIVE only for VLESS inside VLESS encryption, with no flow, and an outbound only over Mux; it refuses the whole config otherwise. The panel offers XDRIVE only for VLESS, refuses to save an inbound without decryption, never emits a client flow on an XDRIVE stream (config, JSON subscription, forms), and turns Mux on when an outbound picks XDRIVE. An inbound or template outbound stored before the upgrade that the core would refuse is left out of the generated config with a warning, so it no longer stops every other inbound. The service "Google Drive" is now "google-drive" (old name read as a deprecated alias); saves, the schema and both form adapters store the new name. XDRIVE and MASQUE gain a client xmux; both schemas keep it instead of stripping it on an inbound save. Lua scripts (#7124, #7136): the core refuses a routing script beside rules or domainStrategy, and a dns script beside the fallback/parallel switches or a domain-scoped server. The panel always keeps its api routing rule, so a template save now refuses any routing script and a dns script in a refused combination, and a stored one is dropped from the generated config so the core still starts. Noise masks gain a Lua script that the loader compiles inside Build: the in-process build (template save check, hot add) now resolves a relative script the way the xray child does, beside the xray binary, instead of failing it. The mask editor gets a Script field that replaces the noise list. XDNS (#7140): a resolver takes https:// DoH addresses with worker, tlsConfig and quicConfig. The mask editor registered only addrs, so a save dropped the rest; it now edits all four. Finalmask filter (#7206): a new TCP/UDP mask screens an inbound's source IPs. The inbound mask editor offers it with its allow and block lists; it is a no-op on a dialer and an older client core refuses the type, so the share link and JSON subscription drop it like the Go link already does.
406 lines
18 KiB
YAML
406 lines
18 KiB
YAML
name: Release 3X-UI
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
# Only main (dev channel) and version tags ship binaries; build any other
|
|
# branch on demand via workflow_dispatch.
|
|
push:
|
|
branches:
|
|
- main
|
|
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"
|
|
|
|
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.10.21/"
|
|
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
|
|
|
|
# Version info + manifest resource: a bare stripped Go exe scores high on AV heuristics.
|
|
PANEL_VERSION=$(tr -d '[:space:]' < internal/config/version)
|
|
go run github.com/tc-hib/go-winres@v0.3.3 make --arch amd64 \
|
|
--product-version "$PANEL_VERSION" --file-version "$PANEL_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\3x-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.10.21/"
|
|
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
|