build.yml 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. name: Build
  2. on:
  3. workflow_dispatch:
  4. inputs:
  5. version:
  6. description: "Version name"
  7. required: true
  8. type: string
  9. build:
  10. description: "Build type"
  11. required: true
  12. type: choice
  13. default: "All"
  14. options:
  15. - All
  16. - Binary
  17. - Android
  18. - Apple
  19. - app-store
  20. - iOS
  21. - macOS
  22. - tvOS
  23. - macOS-standalone
  24. - publish-android
  25. push:
  26. branches:
  27. - main-next
  28. - dev-next
  29. concurrency:
  30. group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
  31. cancel-in-progress: true
  32. jobs:
  33. calculate_version:
  34. name: Calculate version
  35. runs-on: ubuntu-latest
  36. outputs:
  37. version: ${{ steps.outputs.outputs.version }}
  38. steps:
  39. - name: Checkout
  40. uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
  41. with:
  42. fetch-depth: 0
  43. - name: Setup Go
  44. uses: actions/setup-go@v5
  45. with:
  46. go-version: ^1.25.3
  47. - name: Check input version
  48. if: github.event_name == 'workflow_dispatch'
  49. run: |-
  50. echo "version=${{ inputs.version }}"
  51. echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
  52. - name: Calculate version
  53. if: github.event_name != 'workflow_dispatch'
  54. run: |-
  55. go run -v ./cmd/internal/read_tag --ci --nightly
  56. - name: Set outputs
  57. id: outputs
  58. run: |-
  59. echo "version=$version" >> "$GITHUB_OUTPUT"
  60. build:
  61. name: Build binary
  62. if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
  63. runs-on: ubuntu-latest
  64. needs:
  65. - calculate_version
  66. strategy:
  67. matrix:
  68. include:
  69. - { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
  70. - { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
  71. - { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
  72. - { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
  73. - { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
  74. - { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
  75. - { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
  76. - { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
  77. - { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
  78. - { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
  79. - { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
  80. - { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
  81. - { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
  82. - { os: linux, arch: s390x, debian: s390x, rpm: s390x }
  83. - { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
  84. - { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
  85. - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
  86. - { os: windows, arch: amd64 }
  87. - { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
  88. - { os: windows, arch: "386" }
  89. - { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
  90. - { os: windows, arch: arm64 }
  91. - { os: darwin, arch: amd64 }
  92. - { os: darwin, arch: arm64 }
  93. - { os: darwin, arch: amd64, legacy_go124: true, legacy_name: "macos-11" }
  94. - { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
  95. - { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
  96. - { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
  97. - { os: android, arch: "386", ndk: "i686-linux-android21" }
  98. steps:
  99. - name: Checkout
  100. uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
  101. with:
  102. fetch-depth: 0
  103. - name: Setup Go
  104. if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
  105. uses: actions/setup-go@v5
  106. with:
  107. go-version: ^1.25.3
  108. - name: Setup Go 1.24
  109. if: matrix.legacy_go124
  110. uses: actions/setup-go@v5
  111. with:
  112. go-version: ~1.24.6
  113. - name: Cache Go for Windows 7
  114. if: matrix.legacy_win7
  115. id: cache-go-for-windows7
  116. uses: actions/cache@v4
  117. with:
  118. path: |
  119. ~/go/go_win7
  120. key: go_win7_1253
  121. - name: Setup Go for Windows 7
  122. if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
  123. run: |-
  124. .github/setup_go_for_windows7.sh
  125. - name: Setup Go for Windows 7
  126. if: matrix.legacy_win7
  127. run: |-
  128. echo "PATH=$HOME/go/go_win7/bin:$PATH" >> $GITHUB_ENV
  129. echo "GOROOT=$HOME/go/go_win7" >> $GITHUB_ENV
  130. - name: Setup Android NDK
  131. if: matrix.os == 'android'
  132. uses: nttld/setup-ndk@v1
  133. with:
  134. ndk-version: r28
  135. local-cache: true
  136. - name: Set tag
  137. run: |-
  138. git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
  139. git tag v${{ needs.calculate_version.outputs.version }} -f
  140. - name: Set build tags
  141. run: |
  142. set -xeuo pipefail
  143. TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
  144. echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
  145. - name: Build
  146. if: matrix.os != 'android'
  147. run: |
  148. set -xeuo pipefail
  149. mkdir -p dist
  150. go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
  151. -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
  152. ./cmd/sing-box
  153. env:
  154. CGO_ENABLED: "0"
  155. GOOS: ${{ matrix.os }}
  156. GOARCH: ${{ matrix.arch }}
  157. GO386: ${{ matrix.go386 }}
  158. GOARM: ${{ matrix.goarm }}
  159. GOMIPS: ${{ matrix.gomips }}
  160. GOMIPS64: ${{ matrix.gomips }}
  161. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  162. - name: Build Android
  163. if: matrix.os == 'android'
  164. run: |
  165. set -xeuo pipefail
  166. go install -v ./cmd/internal/build
  167. export CC='${{ matrix.ndk }}-clang'
  168. export CXX="${CC}++"
  169. mkdir -p dist
  170. GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
  171. -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
  172. ./cmd/sing-box
  173. env:
  174. CGO_ENABLED: "1"
  175. BUILD_GOOS: ${{ matrix.os }}
  176. BUILD_GOARCH: ${{ matrix.arch }}
  177. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  178. - name: Set name
  179. run: |-
  180. DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
  181. if [[ -n "${{ matrix.goarm }}" ]]; then
  182. DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
  183. elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
  184. DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
  185. elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
  186. DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
  187. elif [[ -n "${{ matrix.legacy_name }}" ]]; then
  188. DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
  189. fi
  190. echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
  191. PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
  192. PKG_VERSION="${PKG_VERSION//-/\~}"
  193. echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
  194. - name: Package DEB
  195. if: matrix.debian != ''
  196. run: |
  197. set -xeuo pipefail
  198. sudo gem install fpm
  199. sudo apt-get update
  200. sudo apt-get install -y debsigs
  201. cp .fpm_systemd .fpm
  202. fpm -t deb \
  203. -v "$PKG_VERSION" \
  204. -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
  205. --architecture ${{ matrix.debian }} \
  206. dist/sing-box=/usr/bin/sing-box
  207. curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
  208. sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
  209. rm -rf $HOME/.gnupg
  210. gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
  211. ${{ secrets.GPG_KEY }}
  212. EOF
  213. debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
  214. - name: Package RPM
  215. if: matrix.rpm != ''
  216. run: |-
  217. set -xeuo pipefail
  218. sudo gem install fpm
  219. cp .fpm_systemd .fpm
  220. fpm -t rpm \
  221. -v "$PKG_VERSION" \
  222. -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
  223. --architecture ${{ matrix.rpm }} \
  224. dist/sing-box=/usr/bin/sing-box
  225. cat > $HOME/.rpmmacros <<EOF
  226. %_gpg_name ${{ secrets.GPG_KEY_ID }}
  227. %_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
  228. EOF
  229. gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
  230. ${{ secrets.GPG_KEY }}
  231. EOF
  232. rpmsign --addsign dist/*.rpm
  233. - name: Package Pacman
  234. if: matrix.pacman != ''
  235. run: |-
  236. set -xeuo pipefail
  237. sudo gem install fpm
  238. sudo apt-get update
  239. sudo apt-get install -y libarchive-tools
  240. cp .fpm_systemd .fpm
  241. fpm -t pacman \
  242. -v "$PKG_VERSION" \
  243. -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
  244. --architecture ${{ matrix.pacman }} \
  245. dist/sing-box=/usr/bin/sing-box
  246. - name: Package OpenWrt
  247. if: matrix.openwrt != ''
  248. run: |-
  249. set -xeuo pipefail
  250. sudo gem install fpm
  251. cp .fpm_openwrt .fpm
  252. fpm -t deb \
  253. -v "$PKG_VERSION" \
  254. -p "dist/openwrt.deb" \
  255. --architecture all \
  256. dist/sing-box=/usr/bin/sing-box
  257. for architecture in ${{ matrix.openwrt }}; do
  258. .github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
  259. done
  260. rm "dist/openwrt.deb"
  261. - name: Archive
  262. run: |
  263. set -xeuo pipefail
  264. cd dist
  265. mkdir -p "${DIR_NAME}"
  266. cp ../LICENSE "${DIR_NAME}"
  267. if [ '${{ matrix.os }}' = 'windows' ]; then
  268. cp sing-box "${DIR_NAME}/sing-box.exe"
  269. zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
  270. else
  271. cp sing-box "${DIR_NAME}"
  272. tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
  273. fi
  274. rm -r "${DIR_NAME}"
  275. - name: Cleanup
  276. run: rm dist/sing-box
  277. - name: Upload artifact
  278. uses: actions/upload-artifact@v4
  279. with:
  280. name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
  281. path: "dist"
  282. build_android:
  283. name: Build Android
  284. if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
  285. runs-on: ubuntu-latest
  286. needs:
  287. - calculate_version
  288. steps:
  289. - name: Checkout
  290. uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
  291. with:
  292. fetch-depth: 0
  293. submodules: 'recursive'
  294. - name: Setup Go
  295. uses: actions/setup-go@v5
  296. with:
  297. go-version: ^1.25.3
  298. - name: Setup Android NDK
  299. id: setup-ndk
  300. uses: nttld/setup-ndk@v1
  301. with:
  302. ndk-version: r28
  303. - name: Setup OpenJDK
  304. run: |-
  305. sudo apt update && sudo apt install -y openjdk-17-jdk-headless
  306. /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
  307. - name: Set tag
  308. run: |-
  309. git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
  310. git tag v${{ needs.calculate_version.outputs.version }} -f
  311. - name: Build library
  312. run: |-
  313. make lib_install
  314. export PATH="$PATH:$(go env GOPATH)/bin"
  315. make lib_android
  316. env:
  317. JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
  318. ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
  319. - name: Checkout main branch
  320. if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
  321. run: |-
  322. cd clients/android
  323. git checkout main
  324. - name: Checkout dev branch
  325. if: github.ref == 'refs/heads/dev-next'
  326. run: |-
  327. cd clients/android
  328. git checkout dev
  329. - name: Gradle cache
  330. uses: actions/cache@v4
  331. with:
  332. path: ~/.gradle
  333. key: gradle-${{ hashFiles('**/*.gradle') }}
  334. - name: Update version
  335. if: github.event_name == 'workflow_dispatch'
  336. run: |-
  337. go run -v ./cmd/internal/update_android_version --ci
  338. - name: Update nightly version
  339. if: github.event_name != 'workflow_dispatch'
  340. run: |-
  341. go run -v ./cmd/internal/update_android_version --ci --nightly
  342. - name: Build
  343. run: |-
  344. mkdir clients/android/app/libs
  345. cp libbox.aar clients/android/app/libs
  346. cd clients/android
  347. ./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
  348. env:
  349. JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
  350. ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
  351. LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
  352. - name: Prepare upload
  353. run: |-
  354. mkdir -p dist
  355. cp clients/android/app/build/outputs/apk/play/release/*.apk dist
  356. cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
  357. - name: Upload artifact
  358. uses: actions/upload-artifact@v4
  359. with:
  360. name: binary-android-apks
  361. path: 'dist'
  362. publish_android:
  363. name: Publish Android
  364. if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
  365. runs-on: ubuntu-latest
  366. needs:
  367. - calculate_version
  368. steps:
  369. - name: Checkout
  370. uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
  371. with:
  372. fetch-depth: 0
  373. submodules: 'recursive'
  374. - name: Setup Go
  375. uses: actions/setup-go@v5
  376. with:
  377. go-version: ^1.25.3
  378. - name: Setup Android NDK
  379. id: setup-ndk
  380. uses: nttld/setup-ndk@v1
  381. with:
  382. ndk-version: r28
  383. - name: Setup OpenJDK
  384. run: |-
  385. sudo apt update && sudo apt install -y openjdk-17-jdk-headless
  386. /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
  387. - name: Set tag
  388. run: |-
  389. git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
  390. git tag v${{ needs.calculate_version.outputs.version }} -f
  391. - name: Build library
  392. run: |-
  393. make lib_install
  394. export PATH="$PATH:$(go env GOPATH)/bin"
  395. make lib_android
  396. env:
  397. JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
  398. ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
  399. - name: Checkout main branch
  400. if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
  401. run: |-
  402. cd clients/android
  403. git checkout main
  404. - name: Checkout dev branch
  405. if: github.ref == 'refs/heads/dev-next'
  406. run: |-
  407. cd clients/android
  408. git checkout dev
  409. - name: Gradle cache
  410. uses: actions/cache@v4
  411. with:
  412. path: ~/.gradle
  413. key: gradle-${{ hashFiles('**/*.gradle') }}
  414. - name: Build
  415. run: |-
  416. go run -v ./cmd/internal/update_android_version --ci
  417. mkdir clients/android/app/libs
  418. cp libbox.aar clients/android/app/libs
  419. cd clients/android
  420. echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
  421. ./gradlew :app:publishPlayReleaseBundle
  422. env:
  423. JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
  424. ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
  425. LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
  426. SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
  427. build_apple:
  428. name: Build Apple clients
  429. runs-on: macos-26
  430. if: false
  431. needs:
  432. - calculate_version
  433. strategy:
  434. matrix:
  435. include:
  436. - name: iOS
  437. if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
  438. platform: ios
  439. scheme: SFI
  440. destination: 'generic/platform=iOS'
  441. archive: build/SFI.xcarchive
  442. upload: SFI/Upload.plist
  443. - name: macOS
  444. if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
  445. platform: macos
  446. scheme: SFM
  447. destination: 'generic/platform=macOS'
  448. archive: build/SFM.xcarchive
  449. upload: SFI/Upload.plist
  450. - name: tvOS
  451. if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
  452. platform: tvos
  453. scheme: SFT
  454. destination: 'generic/platform=tvOS'
  455. archive: build/SFT.xcarchive
  456. upload: SFI/Upload.plist
  457. - name: macOS-standalone
  458. if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
  459. platform: macos
  460. scheme: SFM.System
  461. destination: 'generic/platform=macOS'
  462. archive: build/SFM.System.xcarchive
  463. export: SFM.System/Export.plist
  464. export_path: build/SFM.System
  465. steps:
  466. - name: Checkout
  467. if: matrix.if
  468. uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
  469. with:
  470. fetch-depth: 0
  471. submodules: 'recursive'
  472. - name: Setup Go
  473. if: matrix.if
  474. uses: actions/setup-go@v5
  475. with:
  476. go-version: ^1.25.3
  477. - name: Set tag
  478. if: matrix.if
  479. run: |-
  480. git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
  481. git tag v${{ needs.calculate_version.outputs.version }} -f
  482. echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
  483. - name: Checkout main branch
  484. if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
  485. run: |-
  486. cd clients/apple
  487. git checkout main
  488. - name: Checkout dev branch
  489. if: matrix.if && github.ref == 'refs/heads/dev-next'
  490. run: |-
  491. cd clients/apple
  492. git checkout dev
  493. - name: Setup certificates
  494. if: matrix.if
  495. run: |-
  496. CERTIFICATE_PATH=$RUNNER_TEMP/Certificates.p12
  497. KEYCHAIN_PATH=$RUNNER_TEMP/certificates.keychain-db
  498. echo -n "$CERTIFICATES_P12" | base64 --decode -o $CERTIFICATE_PATH
  499. security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
  500. security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
  501. security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
  502. security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
  503. security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
  504. security list-keychain -d user -s $KEYCHAIN_PATH
  505. PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
  506. echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH
  507. PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
  508. mkdir -p "$PROFILES_PATH"
  509. unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"
  510. ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
  511. echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
  512. xcrun notarytool store-credentials "notarytool-password" \
  513. --key $ASC_KEY_PATH \
  514. --key-id $ASC_KEY_ID \
  515. --issuer $ASC_KEY_ISSUER_ID
  516. echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
  517. echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
  518. echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV"
  519. env:
  520. CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
  521. P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
  522. KEYCHAIN_PASSWORD: ${{ secrets.P12_PASSWORD }}
  523. PROVISIONING_PROFILES: ${{ secrets.PROVISIONING_PROFILES }}
  524. ASC_KEY: ${{ secrets.ASC_KEY }}
  525. ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
  526. ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
  527. - name: Build library
  528. if: matrix.if
  529. run: |-
  530. make lib_install
  531. export PATH="$PATH:$(go env GOPATH)/bin"
  532. go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }}
  533. mv Libbox.xcframework clients/apple
  534. - name: Update macOS version
  535. if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch'
  536. run: |-
  537. MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
  538. echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
  539. echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
  540. - name: Update version
  541. if: matrix.if && matrix.name != 'iOS'
  542. run: |-
  543. go run -v ./cmd/internal/update_apple_version --ci
  544. - name: Build
  545. if: matrix.if
  546. run: |-
  547. cd clients/apple
  548. xcodebuild archive \
  549. -scheme "${{ matrix.scheme }}" \
  550. -configuration Release \
  551. -destination "${{ matrix.destination }}" \
  552. -archivePath "${{ matrix.archive }}" \
  553. -allowProvisioningUpdates \
  554. -authenticationKeyPath $ASC_KEY_PATH \
  555. -authenticationKeyID $ASC_KEY_ID \
  556. -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
  557. - name: Upload to App Store Connect
  558. if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch'
  559. run: |-
  560. go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }}
  561. cd clients/apple
  562. xcodebuild -exportArchive \
  563. -archivePath "${{ matrix.archive }}" \
  564. -exportOptionsPlist ${{ matrix.upload }} \
  565. -allowProvisioningUpdates \
  566. -authenticationKeyPath $ASC_KEY_PATH \
  567. -authenticationKeyID $ASC_KEY_ID \
  568. -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
  569. - name: Publish to TestFlight
  570. if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
  571. run: |-
  572. go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
  573. - name: Build image
  574. if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
  575. run: |-
  576. pushd clients/apple
  577. xcodebuild -exportArchive \
  578. -archivePath "${{ matrix.archive }}" \
  579. -exportOptionsPlist ${{ matrix.export }} \
  580. -exportPath "${{ matrix.export_path }}"
  581. brew install create-dmg
  582. create-dmg \
  583. --volname "sing-box" \
  584. --volicon "${{ matrix.export_path }}/SFM.app/Contents/Resources/AppIcon.icns" \
  585. --icon "SFM.app" 0 0 \
  586. --hide-extension "SFM.app" \
  587. --app-drop-link 0 0 \
  588. --skip-jenkins \
  589. SFM.dmg "${{ matrix.export_path }}/SFM.app"
  590. xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
  591. cd "${{ matrix.archive }}"
  592. zip -r SFM.dSYMs.zip dSYMs
  593. popd
  594. mkdir -p dist
  595. cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
  596. cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
  597. - name: Upload image
  598. if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
  599. uses: actions/upload-artifact@v4
  600. with:
  601. name: binary-macos-dmg
  602. path: 'dist'
  603. upload:
  604. name: Upload builds
  605. if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')"
  606. runs-on: ubuntu-latest
  607. needs:
  608. - calculate_version
  609. - build
  610. - build_android
  611. - build_apple
  612. steps:
  613. - name: Checkout
  614. uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
  615. with:
  616. fetch-depth: 0
  617. - name: Cache ghr
  618. uses: actions/cache@v4
  619. id: cache-ghr
  620. with:
  621. path: |
  622. ~/go/bin/ghr
  623. key: ghr
  624. - name: Setup ghr
  625. if: steps.cache-ghr.outputs.cache-hit != 'true'
  626. run: |-
  627. cd $HOME
  628. git clone https://github.com/nekohasekai/ghr ghr
  629. cd ghr
  630. go install -v .
  631. - name: Set tag
  632. run: |-
  633. git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
  634. git tag v${{ needs.calculate_version.outputs.version }} -f
  635. echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
  636. - name: Download builds
  637. uses: actions/download-artifact@v5
  638. with:
  639. path: dist
  640. merge-multiple: true
  641. - name: Upload builds
  642. if: ${{ env.PUBLISHED == 'false' }}
  643. run: |-
  644. export PATH="$PATH:$HOME/go/bin"
  645. ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist
  646. env:
  647. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  648. - name: Replace builds
  649. if: ${{ env.PUBLISHED != 'false' }}
  650. run: |-
  651. export PATH="$PATH:$HOME/go/bin"
  652. ghr --replace -p 5 "v${VERSION}" dist
  653. env:
  654. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}