1
0

release.yml 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. name: Release 3X-UI
  2. on:
  3. workflow_dispatch:
  4. push:
  5. branches:
  6. - "**"
  7. tags:
  8. - "v*.*.*"
  9. paths:
  10. - "**.go"
  11. - "go.mod"
  12. - "go.sum"
  13. - "**.sh"
  14. - "frontend/**"
  15. - "x-ui.service.debian"
  16. - "x-ui.service.arch"
  17. - "x-ui.service.rhel"
  18. pull_request:
  19. paths:
  20. - "**.go"
  21. - "go.mod"
  22. - "go.sum"
  23. - "**.sh"
  24. - "frontend/**"
  25. - "x-ui.service.debian"
  26. - "x-ui.service.arch"
  27. - "x-ui.service.rhel"
  28. jobs:
  29. build:
  30. permissions:
  31. contents: write
  32. strategy:
  33. matrix:
  34. platform:
  35. - amd64
  36. - arm64
  37. - armv7
  38. - armv6
  39. - 386
  40. - armv5
  41. - s390x
  42. runs-on: ubuntu-latest
  43. steps:
  44. - name: Checkout repository
  45. uses: actions/checkout@v7
  46. - name: Setup Go
  47. uses: actions/setup-go@v6
  48. with:
  49. go-version-file: go.mod
  50. check-latest: true
  51. # Frontend dist must be built BEFORE go build — Go's //go:embed
  52. # all:dist directive in internal/web/web.go requires internal/web/dist/ to exist
  53. # at compile time. internal/web/dist/ is .gitignored, so on a fresh CI
  54. # checkout it doesn't exist until vite emits it.
  55. - name: Setup Node.js
  56. uses: actions/setup-node@v6
  57. with:
  58. node-version-file: .nvmrc
  59. cache: 'npm'
  60. cache-dependency-path: frontend/package-lock.json
  61. - name: Build frontend bundle
  62. run: |
  63. npm ci
  64. npm run build
  65. working-directory: frontend
  66. - name: Build 3X-UI
  67. run: |
  68. export CGO_ENABLED=1
  69. export GOOS=linux
  70. export GOARCH=${{ matrix.platform }}
  71. # Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
  72. case "${{ matrix.platform }}" in
  73. amd64) BOOTLIN_ARCH="x86-64" ;;
  74. arm64) BOOTLIN_ARCH="aarch64" ;;
  75. armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
  76. armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
  77. armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
  78. 386) BOOTLIN_ARCH="x86-i686" ;;
  79. s390x) BOOTLIN_ARCH="s390x-z13" ;;
  80. esac
  81. echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
  82. TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/"
  83. TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
  84. [ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
  85. echo "Downloading: $TARBALL_URL"
  86. cd /tmp
  87. curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
  88. tar -xf "$(basename "$TARBALL_URL")"
  89. TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
  90. export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH"
  91. export CC=$(realpath "$(find "$TOOLCHAIN_DIR/bin" -name '*-gcc.br_real' -type f -executable | head -n1)")
  92. [ -z "$CC" ] && { echo "No gcc.br_real found in $TOOLCHAIN_DIR/bin" >&2; exit 1; }
  93. cd -
  94. # Stamp the commit into per-commit (dev channel) builds only; tagged
  95. # stable releases stay unstamped so config.IsDevBuild() returns false.
  96. LDFLAGS="-w -s -linkmode external -extldflags '-static'"
  97. if [[ "$GITHUB_REF" != refs/tags/* ]]; then
  98. 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)"
  99. fi
  100. go build -ldflags "$LDFLAGS" -o xui-release -v main.go
  101. file xui-release
  102. ldd xui-release || echo "Static binary confirmed"
  103. mkdir x-ui
  104. cp xui-release x-ui/
  105. cp x-ui.service.debian x-ui/
  106. cp x-ui.service.arch x-ui/
  107. cp x-ui.service.rhel x-ui/
  108. cp x-ui.sh x-ui/
  109. mv x-ui/xui-release x-ui/x-ui
  110. mkdir x-ui/bin
  111. cd x-ui/bin
  112. # Download dependencies
  113. Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.6.27/"
  114. if [ "${{ matrix.platform }}" == "amd64" ]; then
  115. wget -q ${Xray_URL}Xray-linux-64.zip
  116. unzip Xray-linux-64.zip
  117. rm -f Xray-linux-64.zip
  118. elif [ "${{ matrix.platform }}" == "arm64" ]; then
  119. wget -q ${Xray_URL}Xray-linux-arm64-v8a.zip
  120. unzip Xray-linux-arm64-v8a.zip
  121. rm -f Xray-linux-arm64-v8a.zip
  122. elif [ "${{ matrix.platform }}" == "armv7" ]; then
  123. wget -q ${Xray_URL}Xray-linux-arm32-v7a.zip
  124. unzip Xray-linux-arm32-v7a.zip
  125. rm -f Xray-linux-arm32-v7a.zip
  126. elif [ "${{ matrix.platform }}" == "armv6" ]; then
  127. wget -q ${Xray_URL}Xray-linux-arm32-v6.zip
  128. unzip Xray-linux-arm32-v6.zip
  129. rm -f Xray-linux-arm32-v6.zip
  130. elif [ "${{ matrix.platform }}" == "386" ]; then
  131. wget -q ${Xray_URL}Xray-linux-32.zip
  132. unzip Xray-linux-32.zip
  133. rm -f Xray-linux-32.zip
  134. elif [ "${{ matrix.platform }}" == "armv5" ]; then
  135. wget -q ${Xray_URL}Xray-linux-arm32-v5.zip
  136. unzip Xray-linux-arm32-v5.zip
  137. rm -f Xray-linux-arm32-v5.zip
  138. elif [ "${{ matrix.platform }}" == "s390x" ]; then
  139. wget -q ${Xray_URL}Xray-linux-s390x.zip
  140. unzip Xray-linux-s390x.zip
  141. rm -f Xray-linux-s390x.zip
  142. fi
  143. rm -f geoip.dat geosite.dat
  144. wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
  145. wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
  146. wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
  147. wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
  148. wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
  149. wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
  150. mv xray xray-linux-${{ matrix.platform }}
  151. # mtg-multi (MTProto sidecar) ships prebuilt release binaries whose
  152. # platform labels match our matrix, so download and unpack the matching
  153. # archive. Only the platforms the fork publishes are packaged. The tag
  154. # is resolved from the fork's latest release so it never needs bumping
  155. # here; the token only lifts the API rate limit for a public read.
  156. 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)
  157. if [ -z "$MTG_MULTI_VER" ]; then echo "could not resolve the latest mtg-multi release tag"; exit 1; fi
  158. case "${{ matrix.platform }}" in
  159. amd64|arm64|armv7|armv6|386)
  160. MTG_PKG="mtg-multi-${MTG_MULTI_VER#v}-linux-${{ matrix.platform }}"
  161. curl -sfLRO "https://github.com/mhsanaei/mtg-multi/releases/download/${MTG_MULTI_VER}/${MTG_PKG}.tar.gz"
  162. tar -xzf "${MTG_PKG}.tar.gz"
  163. mv "${MTG_PKG}/mtg-multi" "mtg-linux-${{ matrix.platform }}"
  164. rm -rf "${MTG_PKG}" "${MTG_PKG}.tar.gz"
  165. ;;
  166. esac
  167. cd ../..
  168. - name: Package
  169. run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
  170. - name: Upload files to Artifacts
  171. uses: actions/upload-artifact@v7
  172. with:
  173. name: x-ui-linux-${{ matrix.platform }}
  174. path: ./x-ui-linux-${{ matrix.platform }}.tar.gz
  175. - name: Upload files to GH release
  176. uses: svenstaro/upload-release-action@v2
  177. if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
  178. with:
  179. repo_token: ${{ secrets.GITHUB_TOKEN }}
  180. tag: ${{ github.ref_name }}
  181. file: x-ui-linux-${{ matrix.platform }}.tar.gz
  182. asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
  183. overwrite: true
  184. prerelease: true
  185. # =================================
  186. # Windows Build
  187. # =================================
  188. build-windows:
  189. name: Build for Windows
  190. permissions:
  191. contents: write
  192. strategy:
  193. matrix:
  194. platform:
  195. - amd64
  196. runs-on: windows-latest
  197. steps:
  198. - name: Checkout repository
  199. uses: actions/checkout@v7
  200. - name: Setup Go
  201. uses: actions/setup-go@v6
  202. with:
  203. go-version-file: go.mod
  204. check-latest: true
  205. # Frontend dist must be built BEFORE go build — see comment on the
  206. # Linux job above. This step is identical except npm runs on the
  207. # Windows runner here.
  208. - name: Setup Node.js
  209. uses: actions/setup-node@v6
  210. with:
  211. node-version-file: .nvmrc
  212. cache: 'npm'
  213. cache-dependency-path: frontend/package-lock.json
  214. - name: Build frontend bundle
  215. shell: pwsh
  216. run: |
  217. npm ci
  218. npm run build
  219. working-directory: frontend
  220. - name: Install MSYS2
  221. uses: msys2/setup-msys2@v2
  222. with:
  223. msystem: MINGW64
  224. update: true
  225. install: >-
  226. mingw-w64-x86_64-gcc
  227. mingw-w64-x86_64-sqlite3
  228. mingw-w64-x86_64-pkg-config
  229. - name: Build 3X-UI for Windows (CGO)
  230. shell: msys2 {0}
  231. run: |
  232. export PATH="/c/hostedtoolcache/windows/go/$(ls /c/hostedtoolcache/windows/go | sort -V | tail -n1)/x64/bin:$PATH"
  233. export CGO_ENABLED=1
  234. export GOOS=windows
  235. export GOARCH=amd64
  236. export CC=x86_64-w64-mingw32-gcc
  237. which go
  238. go version
  239. gcc --version
  240. # Stamp the commit into per-commit (dev channel) builds only.
  241. LDFLAGS="-w -s"
  242. if [[ "$GITHUB_REF" != refs/tags/* ]]; then
  243. 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)"
  244. fi
  245. go build -ldflags "$LDFLAGS" -o xui-release.exe -v main.go
  246. - name: Copy and download resources
  247. shell: pwsh
  248. run: |
  249. mkdir x-ui
  250. Copy-Item xui-release.exe x-ui\x-ui.exe
  251. mkdir x-ui\bin
  252. cd x-ui\bin
  253. # Download Xray for Windows
  254. $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.6.27/"
  255. Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
  256. Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
  257. Remove-Item "Xray-windows-64.zip"
  258. Remove-Item geoip.dat, geosite.dat -ErrorAction SilentlyContinue
  259. Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat"
  260. Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat"
  261. Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat"
  262. Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat"
  263. Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip_RU.dat"
  264. Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite_RU.dat"
  265. Rename-Item xray.exe xray-windows-amd64.exe
  266. # mtg-multi (MTProto sidecar) publishes a prebuilt Windows binary, so
  267. # download and unpack it instead of compiling. The tag tracks the
  268. # fork's latest release so it never needs bumping here.
  269. $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
  270. if (-not $MTG_MULTI_VER) { throw "could not resolve the latest mtg-multi release tag" }
  271. $MTG_PKG = "mtg-multi-$($MTG_MULTI_VER.TrimStart('v'))-windows-amd64"
  272. curl.exe -sfLRO "https://github.com/mhsanaei/mtg-multi/releases/download/$MTG_MULTI_VER/$MTG_PKG.zip"
  273. Expand-Archive -Path "$MTG_PKG.zip" -DestinationPath "mtg-tmp" -Force
  274. Move-Item "mtg-tmp/$MTG_PKG/mtg-multi.exe" "mtg-windows-amd64.exe"
  275. Remove-Item -Recurse -Force "mtg-tmp", "$MTG_PKG.zip"
  276. cd ..
  277. Copy-Item -Path ..\windows_files\* -Destination . -Recurse
  278. cd ..
  279. - name: Package to Zip
  280. shell: pwsh
  281. run: |
  282. Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip"
  283. - name: Upload files to Artifacts
  284. uses: actions/upload-artifact@v7
  285. with:
  286. name: x-ui-windows-amd64
  287. path: ./x-ui-windows-amd64.zip
  288. - name: Upload files to GH release
  289. uses: svenstaro/upload-release-action@v2
  290. if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
  291. with:
  292. repo_token: ${{ secrets.GITHUB_TOKEN }}
  293. tag: ${{ github.ref_name }}
  294. file: x-ui-windows-amd64.zip
  295. asset_name: x-ui-windows-amd64.zip
  296. overwrite: true
  297. prerelease: true
  298. # =================================
  299. # Rolling dev channel (per-commit)
  300. # =================================
  301. # Publishes/overwrites the build artifacts to a single fixed-tag pre-release
  302. # `dev-latest`, force-moved to the new commit on every push to main. The panel's
  303. # "Dev" update channel installs from this tag. `--latest=false` is load-bearing:
  304. # it keeps releases/latest pointing at the real stable tag, so the stable
  305. # channel is unaffected.
  306. publish-dev:
  307. name: Publish rolling dev release
  308. needs: [build, build-windows]
  309. if: github.event_name == 'push' && github.ref == 'refs/heads/main'
  310. runs-on: ubuntu-latest
  311. permissions:
  312. contents: write
  313. # Serialize racing pushes; never cancel an in-flight upload, or the dev
  314. # release could be left with a partial asset set.
  315. concurrency:
  316. group: dev-release
  317. cancel-in-progress: false
  318. steps:
  319. - name: Checkout repository
  320. uses: actions/checkout@v7
  321. - name: Download all build artifacts
  322. uses: actions/download-artifact@v8
  323. with:
  324. path: dev-artifacts
  325. merge-multiple: true
  326. - name: Publish dev-latest pre-release
  327. env:
  328. GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  329. COMMIT: ${{ github.sha }}
  330. run: |
  331. set -e
  332. short="${COMMIT::8}"
  333. notes="Rolling development build — installs via the panel's Dev update channel.
  334. commit=${COMMIT}
  335. built=$(date -u +%Y-%m-%dT%H:%M:%SZ)
  336. Automated per-commit build from main. Not a stable release."
  337. # Force-move the dev-latest tag to this commit so the release tracks it.
  338. git tag -f dev-latest "${COMMIT}"
  339. git push -f origin refs/tags/dev-latest
  340. if gh release view dev-latest >/dev/null 2>&1; then
  341. gh release edit dev-latest --prerelease --latest=false \
  342. --title "Dev build ${short}" --notes "${notes}"
  343. else
  344. gh release create dev-latest --prerelease --latest=false \
  345. --target "${COMMIT}" --title "Dev build ${short}" --notes "${notes}"
  346. fi
  347. gh release upload dev-latest dev-artifacts/*.tar.gz dev-artifacts/*.zip --clobber