build.yml 27 KB

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