1
0

release.yml 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. name: Release 3X-UI
  2. on:
  3. workflow_dispatch:
  4. push:
  5. branches:
  6. - "**"
  7. tags:
  8. - "v*.*.*"
  9. paths:
  10. - "**.js"
  11. - "**.css"
  12. - "**.html"
  13. - "**.sh"
  14. - "**.go"
  15. - "go.mod"
  16. - "go.sum"
  17. - "x-ui.service.debian"
  18. - "x-ui.service.arch"
  19. - "x-ui.service.rhel"
  20. pull_request:
  21. jobs:
  22. analyze:
  23. name: Analyze Go code
  24. permissions:
  25. contents: read
  26. runs-on: ubuntu-latest
  27. timeout-minutes: 20
  28. steps:
  29. - name: Checkout repository
  30. uses: actions/checkout@v6
  31. - name: Set up Go
  32. uses: actions/setup-go@v6
  33. with:
  34. go-version-file: go.mod
  35. cache: true
  36. - name: Check formatting
  37. run: |
  38. unformatted=$(gofmt -l .)
  39. if [ -n "$unformatted" ]; then
  40. echo "These files are not gofmt-formatted:"
  41. echo "$unformatted"
  42. exit 1
  43. fi
  44. - name: Run go vet
  45. run: go vet ./...
  46. - name: Run staticcheck
  47. uses: dominikh/staticcheck-action@v1
  48. with:
  49. version: "latest"
  50. install-go: false
  51. - name: Run tests
  52. run: go test -race -shuffle=on ./...
  53. build:
  54. needs: analyze
  55. permissions:
  56. contents: write
  57. strategy:
  58. matrix:
  59. platform:
  60. - amd64
  61. - arm64
  62. - armv7
  63. - armv6
  64. - 386
  65. - armv5
  66. - s390x
  67. runs-on: ubuntu-latest
  68. steps:
  69. - name: Checkout repository
  70. uses: actions/checkout@v6
  71. - name: Setup Go
  72. uses: actions/setup-go@v6
  73. with:
  74. go-version-file: go.mod
  75. check-latest: true
  76. # Frontend dist must be built BEFORE go build — Go's //go:embed
  77. # all:dist directive in web/web.go requires web/dist/ to exist
  78. # at compile time. web/dist/ is .gitignored, so on a fresh CI
  79. # checkout it doesn't exist until vite emits it.
  80. - name: Setup Node.js
  81. uses: actions/setup-node@v4
  82. with:
  83. node-version: '22'
  84. cache: 'npm'
  85. cache-dependency-path: frontend/package-lock.json
  86. - name: Build frontend bundle
  87. run: |
  88. npm ci
  89. npm run build
  90. working-directory: frontend
  91. - name: Build 3X-UI
  92. run: |
  93. export CGO_ENABLED=1
  94. export GOOS=linux
  95. export GOARCH=${{ matrix.platform }}
  96. # Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
  97. case "${{ matrix.platform }}" in
  98. amd64) BOOTLIN_ARCH="x86-64" ;;
  99. arm64) BOOTLIN_ARCH="aarch64" ;;
  100. armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
  101. armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
  102. armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
  103. 386) BOOTLIN_ARCH="x86-i686" ;;
  104. s390x) BOOTLIN_ARCH="s390x-z13" ;;
  105. esac
  106. echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
  107. TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/"
  108. TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
  109. [ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
  110. echo "Downloading: $TARBALL_URL"
  111. cd /tmp
  112. curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
  113. tar -xf "$(basename "$TARBALL_URL")"
  114. TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
  115. export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH"
  116. export CC=$(realpath "$(find "$TOOLCHAIN_DIR/bin" -name '*-gcc.br_real' -type f -executable | head -n1)")
  117. [ -z "$CC" ] && { echo "No gcc.br_real found in $TOOLCHAIN_DIR/bin" >&2; exit 1; }
  118. cd -
  119. go build -ldflags "-w -s -linkmode external -extldflags '-static'" -o xui-release -v main.go
  120. file xui-release
  121. ldd xui-release || echo "Static binary confirmed"
  122. mkdir x-ui
  123. cp xui-release x-ui/
  124. cp x-ui.service.debian x-ui/
  125. cp x-ui.service.arch x-ui/
  126. cp x-ui.service.rhel x-ui/
  127. cp x-ui.sh x-ui/
  128. mv x-ui/xui-release x-ui/x-ui
  129. mkdir x-ui/bin
  130. cd x-ui/bin
  131. # Download dependencies
  132. Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.4.25/"
  133. if [ "${{ matrix.platform }}" == "amd64" ]; then
  134. wget -q ${Xray_URL}Xray-linux-64.zip
  135. unzip Xray-linux-64.zip
  136. rm -f Xray-linux-64.zip
  137. elif [ "${{ matrix.platform }}" == "arm64" ]; then
  138. wget -q ${Xray_URL}Xray-linux-arm64-v8a.zip
  139. unzip Xray-linux-arm64-v8a.zip
  140. rm -f Xray-linux-arm64-v8a.zip
  141. elif [ "${{ matrix.platform }}" == "armv7" ]; then
  142. wget -q ${Xray_URL}Xray-linux-arm32-v7a.zip
  143. unzip Xray-linux-arm32-v7a.zip
  144. rm -f Xray-linux-arm32-v7a.zip
  145. elif [ "${{ matrix.platform }}" == "armv6" ]; then
  146. wget -q ${Xray_URL}Xray-linux-arm32-v6.zip
  147. unzip Xray-linux-arm32-v6.zip
  148. rm -f Xray-linux-arm32-v6.zip
  149. elif [ "${{ matrix.platform }}" == "386" ]; then
  150. wget -q ${Xray_URL}Xray-linux-32.zip
  151. unzip Xray-linux-32.zip
  152. rm -f Xray-linux-32.zip
  153. elif [ "${{ matrix.platform }}" == "armv5" ]; then
  154. wget -q ${Xray_URL}Xray-linux-arm32-v5.zip
  155. unzip Xray-linux-arm32-v5.zip
  156. rm -f Xray-linux-arm32-v5.zip
  157. elif [ "${{ matrix.platform }}" == "s390x" ]; then
  158. wget -q ${Xray_URL}Xray-linux-s390x.zip
  159. unzip Xray-linux-s390x.zip
  160. rm -f Xray-linux-s390x.zip
  161. fi
  162. rm -f geoip.dat geosite.dat
  163. wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
  164. wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
  165. wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
  166. wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
  167. wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
  168. wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
  169. mv xray xray-linux-${{ matrix.platform }}
  170. cd ../..
  171. - name: Package
  172. run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
  173. - name: Upload files to Artifacts
  174. uses: actions/upload-artifact@v7
  175. with:
  176. name: x-ui-linux-${{ matrix.platform }}
  177. path: ./x-ui-linux-${{ matrix.platform }}.tar.gz
  178. - name: Upload files to GH release
  179. uses: svenstaro/upload-release-action@v2
  180. if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
  181. with:
  182. repo_token: ${{ secrets.GITHUB_TOKEN }}
  183. tag: ${{ github.ref_name }}
  184. file: x-ui-linux-${{ matrix.platform }}.tar.gz
  185. asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
  186. overwrite: true
  187. prerelease: true
  188. # =================================
  189. # Windows Build
  190. # =================================
  191. build-windows:
  192. name: Build for Windows
  193. needs: analyze
  194. permissions:
  195. contents: write
  196. strategy:
  197. matrix:
  198. platform:
  199. - amd64
  200. runs-on: windows-latest
  201. steps:
  202. - name: Checkout repository
  203. uses: actions/checkout@v6
  204. - name: Setup Go
  205. uses: actions/setup-go@v6
  206. with:
  207. go-version-file: go.mod
  208. check-latest: true
  209. # Frontend dist must be built BEFORE go build — see comment on the
  210. # Linux job above. This step is identical except npm runs on the
  211. # Windows runner here.
  212. - name: Setup Node.js
  213. uses: actions/setup-node@v4
  214. with:
  215. node-version: '22'
  216. cache: 'npm'
  217. cache-dependency-path: frontend/package-lock.json
  218. - name: Build frontend bundle
  219. shell: pwsh
  220. run: |
  221. npm ci
  222. npm run build
  223. working-directory: frontend
  224. - name: Install MSYS2
  225. uses: msys2/setup-msys2@v2
  226. with:
  227. msystem: MINGW64
  228. update: true
  229. install: >-
  230. mingw-w64-x86_64-gcc
  231. mingw-w64-x86_64-sqlite3
  232. mingw-w64-x86_64-pkg-config
  233. - name: Build 3X-UI for Windows (CGO)
  234. shell: msys2 {0}
  235. run: |
  236. export PATH="/c/hostedtoolcache/windows/go/$(ls /c/hostedtoolcache/windows/go | sort -V | tail -n1)/x64/bin:$PATH"
  237. export CGO_ENABLED=1
  238. export GOOS=windows
  239. export GOARCH=amd64
  240. export CC=x86_64-w64-mingw32-gcc
  241. which go
  242. go version
  243. gcc --version
  244. go build -ldflags "-w -s" -o xui-release.exe -v main.go
  245. - name: Copy and download resources
  246. shell: pwsh
  247. run: |
  248. mkdir x-ui
  249. Copy-Item xui-release.exe x-ui\x-ui.exe
  250. mkdir x-ui\bin
  251. cd x-ui\bin
  252. # Download Xray for Windows
  253. $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.4.25/"
  254. Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
  255. Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
  256. Remove-Item "Xray-windows-64.zip"
  257. Remove-Item geoip.dat, geosite.dat -ErrorAction SilentlyContinue
  258. Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat"
  259. Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat"
  260. Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat"
  261. Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat"
  262. Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip_RU.dat"
  263. Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite_RU.dat"
  264. Rename-Item xray.exe xray-windows-amd64.exe
  265. cd ..
  266. Copy-Item -Path ..\windows_files\* -Destination . -Recurse
  267. cd ..
  268. - name: Package to Zip
  269. shell: pwsh
  270. run: |
  271. Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip"
  272. - name: Upload files to Artifacts
  273. uses: actions/upload-artifact@v7
  274. with:
  275. name: x-ui-windows-amd64
  276. path: ./x-ui-windows-amd64.zip
  277. - name: Upload files to GH release
  278. uses: svenstaro/upload-release-action@v2
  279. if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
  280. with:
  281. repo_token: ${{ secrets.GITHUB_TOKEN }}
  282. tag: ${{ github.ref_name }}
  283. file: x-ui-windows-amd64.zip
  284. asset_name: x-ui-windows-amd64.zip
  285. overwrite: true
  286. prerelease: true