Improve parallel DNS support #3867
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version name" | |
| required: true | |
| type: string | |
| build: | |
| description: "Build type" | |
| required: true | |
| type: choice | |
| default: "All" | |
| options: | |
| - All | |
| - Binary | |
| - Android | |
| - Windows | |
| - Linux | |
| - Apple | |
| - Apple-release | |
| - iOS | |
| - macOS | |
| - tvOS | |
| - macOS-standalone | |
| - iOS-jailbreak | |
| - publish-android | |
| test_message: | |
| description: "TestFlight test message" | |
| required: false | |
| type: string | |
| push: | |
| branches: | |
| - stable | |
| - testing | |
| - unstable | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }} | |
| cancel-in-progress: true | |
| jobs: | |
| calculate_version: | |
| name: Calculate version | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.outputs.outputs.version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: 1.25.12 | |
| cache: false | |
| - name: Cache Go modules | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: go-mod-linux-${{ hashFiles('go.sum') }} | |
| restore-keys: go-mod-linux- | |
| - name: Cache Go tool builds | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: ~/.cache/go-build | |
| key: go-build-tools-linux-${{ hashFiles('go.sum') }} | |
| restore-keys: go-build-tools-linux- | |
| - name: Check input version | |
| if: github.event_name == 'workflow_dispatch' | |
| run: |- | |
| echo "version=${{ inputs.version }}" | |
| echo "version=${{ inputs.version }}" >> "$GITHUB_ENV" | |
| - name: Calculate version | |
| if: github.event_name != 'workflow_dispatch' | |
| run: |- | |
| go run -v ./cmd/internal/read_tag --ci --nightly | |
| - name: Set outputs | |
| id: outputs | |
| run: |- | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| build: | |
| name: Build binary | |
| if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary' | |
| runs-on: ubuntu-26.04 | |
| needs: | |
| - calculate_version | |
| strategy: | |
| max-parallel: 13 | |
| matrix: | |
| include: | |
| - { os: linux, arch: amd64, variant: purego, naive: true } | |
| - { os: linux, arch: amd64, variant: glibc, naive: true } | |
| - { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, alpine: x86_64, openwrt: "x86_64" } | |
| - { os: linux, arch: arm64, variant: purego, naive: true } | |
| - { os: linux, arch: arm64, variant: glibc, naive: true } | |
| - { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, alpine: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } | |
| - { os: linux, arch: "386", go386: sse2 } | |
| - { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 } | |
| - { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, alpine: x86, openwrt: "i386_pentium4" } | |
| - { os: linux, arch: arm, goarm: "7" } | |
| - { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" } | |
| - { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, alpine: armv7, 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" } | |
| - { os: linux, arch: mipsle, gomips: hardfloat, naive: true, variant: glibc } | |
| - { os: linux, arch: mipsle, gomips: softfloat, naive: true, variant: musl, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" } | |
| - { os: linux, arch: mips64le, gomips: hardfloat, naive: true, variant: glibc, debian: mips64el, rpm: mips64el } | |
| - { os: linux, arch: riscv64, naive: true, variant: glibc } | |
| - { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: riscv64, alpine: riscv64, openwrt: "riscv64_generic" } | |
| - { os: linux, arch: loong64, naive: true, variant: glibc } | |
| - { os: linux, arch: loong64, naive: true, variant: musl, debian: loongarch64, rpm: loongarch64, alpine: loongarch64, openwrt: "loongarch64_generic" } | |
| - { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" } | |
| - { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" } | |
| - { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" } | |
| - { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" } | |
| - { os: linux, arch: mipsle, gomips: hardfloat, openwrt: "mipsel_24kc_24kf" } | |
| - { os: linux, arch: mipsle, gomips: softfloat } | |
| - { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" } | |
| - { os: linux, arch: mips64le, gomips: hardfloat } | |
| - { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" } | |
| - { os: linux, arch: s390x, debian: s390x, rpm: s390x } | |
| - { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le } | |
| - { os: linux, arch: riscv64 } | |
| - { os: linux, arch: loong64 } | |
| - { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" } | |
| - { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" } | |
| - { os: android, arch: arm64, ndk: "aarch64-linux-android23" } | |
| - { os: android, arch: arm, ndk: "armv7a-linux-androideabi23" } | |
| - { os: android, arch: amd64, ndk: "x86_64-linux-android23" } | |
| - { os: android, arch: "386", ndk: "i686-linux-android23" } | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup Go | |
| if: ${{ ! matrix.legacy_win7 }} | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: 1.25.12 | |
| cache: false | |
| - name: Cache Go modules | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: go-mod-linux-${{ hashFiles('go.sum') }} | |
| restore-keys: go-mod-linux- | |
| - name: Cache Go for Windows 7 | |
| if: matrix.legacy_win7 | |
| id: cache-go-for-windows7 | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/go/go_win7 | |
| key: go_win7_12512 | |
| - name: Setup Go for Windows 7 | |
| if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: |- | |
| .github/setup_go_for_windows7.sh | |
| - name: Setup Go for Windows 7 | |
| if: matrix.legacy_win7 | |
| run: |- | |
| echo "PATH=$HOME/go/go_win7/bin:$PATH" >> $GITHUB_ENV | |
| echo "GOROOT=$HOME/go/go_win7" >> $GITHUB_ENV | |
| - name: Setup Android NDK | |
| if: matrix.os == 'android' | |
| uses: nttld/setup-ndk@v1 | |
| with: | |
| ndk-version: r28 | |
| - name: Clone cronet-go | |
| if: matrix.naive | |
| run: | | |
| set -xeuo pipefail | |
| CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION) | |
| git init ~/cronet-go | |
| git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git | |
| git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION" | |
| git -C ~/cronet-go checkout FETCH_HEAD | |
| git -C ~/cronet-go submodule update --init --recursive --depth=1 | |
| - name: Regenerate Debian keyring | |
| if: matrix.naive | |
| run: | | |
| set -xeuo pipefail | |
| rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg | |
| cd ~/cronet-go | |
| GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh | |
| - name: Download Chromium toolchain | |
| if: matrix.naive | |
| run: | | |
| set -xeuo pipefail | |
| cd ~/cronet-go | |
| if [[ "${{ matrix.variant }}" == "musl" ]]; then | |
| go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain | |
| else | |
| go run ./cmd/build-naive --target=linux/${{ matrix.arch }} download-toolchain | |
| fi | |
| - name: Set Chromium toolchain environment | |
| if: matrix.naive | |
| run: | | |
| set -xeuo pipefail | |
| cd ~/cronet-go | |
| if [[ "${{ matrix.variant }}" == "musl" ]]; then | |
| go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV | |
| else | |
| go run ./cmd/build-naive --target=linux/${{ matrix.arch }} env >> $GITHUB_ENV | |
| fi | |
| - name: Set tag | |
| run: |- | |
| git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" | |
| git tag v${{ needs.calculate_version.outputs.version }} -f | |
| - name: Set build tags | |
| run: | | |
| set -xeuo pipefail | |
| if [[ "${{ matrix.naive }}" == "true" ]]; then | |
| TAGS=$(cat release/DEFAULT_BUILD_TAGS) | |
| else | |
| TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) | |
| fi | |
| if [[ "${{ matrix.variant }}" == "purego" ]]; then | |
| TAGS="${TAGS},with_purego" | |
| elif [[ "${{ matrix.variant }}" == "musl" ]]; then | |
| TAGS="${TAGS},with_musl" | |
| fi | |
| echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" | |
| - name: Set shared ldflags | |
| run: | | |
| echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}" | |
| - name: Build (purego) | |
| if: matrix.variant == 'purego' | |
| run: | | |
| set -xeuo pipefail | |
| mkdir -p dist | |
| go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ | |
| -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ | |
| ./cmd/sing-box | |
| env: | |
| CGO_ENABLED: "0" | |
| GOOS: ${{ matrix.os }} | |
| GOARCH: ${{ matrix.arch }} | |
| GO386: ${{ matrix.go386 }} | |
| GOARM: ${{ matrix.goarm }} | |
| GOMIPS: ${{ matrix.gomips }} | |
| GOMIPS64: ${{ matrix.gomips }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract libcronet.so | |
| if: matrix.variant == 'purego' && matrix.naive | |
| run: | | |
| cd ~/cronet-go | |
| CGO_ENABLED=0 go build -v -o "$RUNNER_TEMP/build-naive" ./cmd/build-naive | |
| GOPROXY=direct GOSUMDB=off "$RUNNER_TEMP/build-naive" extract-lib --target ${{ matrix.os }}/${{ matrix.arch }} -o $GITHUB_WORKSPACE/dist | |
| - name: Build (glibc) | |
| if: matrix.variant == 'glibc' | |
| run: | | |
| set -xeuo pipefail | |
| mkdir -p dist | |
| go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ | |
| -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ | |
| ./cmd/sing-box | |
| env: | |
| CGO_ENABLED: "1" | |
| GOOS: linux | |
| GOARCH: ${{ matrix.arch }} | |
| GO386: ${{ matrix.go386 }} | |
| GOARM: ${{ matrix.goarm }} | |
| GOMIPS: ${{ matrix.gomips }} | |
| GOMIPS64: ${{ matrix.gomips }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build (musl) | |
| if: matrix.variant == 'musl' | |
| run: | | |
| set -xeuo pipefail | |
| mkdir -p dist | |
| go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ | |
| -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ | |
| ./cmd/sing-box | |
| env: | |
| CGO_ENABLED: "1" | |
| GOOS: linux | |
| GOARCH: ${{ matrix.arch }} | |
| GO386: ${{ matrix.go386 }} | |
| GOARM: ${{ matrix.goarm }} | |
| GOMIPS: ${{ matrix.gomips }} | |
| GOMIPS64: ${{ matrix.gomips }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build (non-variant) | |
| if: matrix.os != 'android' && matrix.variant == '' | |
| run: | | |
| set -xeuo pipefail | |
| mkdir -p dist | |
| go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ | |
| -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ | |
| ./cmd/sing-box | |
| env: | |
| CGO_ENABLED: "0" | |
| GOOS: ${{ matrix.os }} | |
| GOARCH: ${{ matrix.arch }} | |
| GO386: ${{ matrix.go386 }} | |
| GOARM: ${{ matrix.goarm }} | |
| GOMIPS: ${{ matrix.gomips }} | |
| GOMIPS64: ${{ matrix.gomips }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build Android | |
| if: matrix.os == 'android' | |
| run: | | |
| set -xeuo pipefail | |
| go install -v ./cmd/internal/build | |
| export CC='${{ matrix.ndk }}-clang' | |
| export CXX="${CC}++" | |
| mkdir -p dist | |
| GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ | |
| -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ | |
| ./cmd/sing-box | |
| env: | |
| CGO_ENABLED: "1" | |
| BUILD_GOOS: ${{ matrix.os }} | |
| BUILD_GOARCH: ${{ matrix.arch }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set name | |
| run: |- | |
| DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}" | |
| if [[ -n "${{ matrix.goarm }}" ]]; then | |
| DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}" | |
| elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then | |
| DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}" | |
| elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then | |
| DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}" | |
| elif [[ -n "${{ matrix.legacy_name }}" ]]; then | |
| DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}" | |
| fi | |
| if [[ "${{ matrix.variant }}" == "glibc" ]]; then | |
| DIR_NAME="${DIR_NAME}-glibc" | |
| elif [[ "${{ matrix.variant }}" == "musl" ]]; then | |
| DIR_NAME="${DIR_NAME}-musl" | |
| fi | |
| echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" | |
| PKG_VERSION="${{ needs.calculate_version.outputs.version }}" | |
| PKG_VERSION="${PKG_VERSION//-/\~}" | |
| echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}" | |
| - name: Package DEB | |
| if: matrix.debian != '' | |
| run: | | |
| set -xeuo pipefail | |
| sudo gem install fpm | |
| sudo apt-get install -y debsigs | |
| cp .fpm_systemd .fpm | |
| fpm -t deb \ | |
| -v "$PKG_VERSION" \ | |
| -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \ | |
| --architecture ${{ matrix.debian }} \ | |
| dist/sing-box=/usr/bin/sing-box | |
| rm -rf $HOME/.gnupg | |
| gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF | |
| ${{ secrets.GPG_KEY }} | |
| EOF | |
| debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb | |
| - name: Package RPM | |
| if: matrix.rpm != '' | |
| run: |- | |
| set -xeuo pipefail | |
| sudo gem install fpm | |
| cp .fpm_systemd .fpm | |
| fpm -t rpm \ | |
| -v "$PKG_VERSION" \ | |
| -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \ | |
| --architecture ${{ matrix.rpm }} \ | |
| dist/sing-box=/usr/bin/sing-box | |
| cat > $HOME/.rpmmacros <<EOF | |
| %_gpg_name ${{ secrets.GPG_KEY_ID }} | |
| %_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }} | |
| EOF | |
| gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF | |
| ${{ secrets.GPG_KEY }} | |
| EOF | |
| rpmsign --addsign dist/*.rpm | |
| - name: Package Pacman | |
| if: matrix.pacman != '' | |
| run: |- | |
| set -xeuo pipefail | |
| sudo gem install fpm | |
| sudo apt-get install -y libarchive-tools | |
| cp .fpm_pacman .fpm | |
| fpm -t pacman \ | |
| -v "$PKG_VERSION" \ | |
| -p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \ | |
| --architecture ${{ matrix.pacman }} \ | |
| dist/sing-box=/usr/bin/sing-box | |
| - name: Package OpenWrt | |
| if: matrix.openwrt != '' | |
| run: |- | |
| set -xeuo pipefail | |
| sudo gem install fpm | |
| cp .fpm_openwrt .fpm | |
| fpm -t deb \ | |
| -v "$PKG_VERSION" \ | |
| -p "dist/openwrt.deb" \ | |
| --architecture all \ | |
| dist/sing-box=/usr/bin/sing-box | |
| for architecture in ${{ matrix.openwrt }}; do | |
| .github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk" | |
| done | |
| rm "dist/openwrt.deb" | |
| - name: Install apk-tools | |
| if: matrix.openwrt != '' || matrix.alpine != '' | |
| run: |- | |
| docker run --rm -v /usr/local/bin:/mnt alpine:edge sh -c "apk add --no-cache apk-tools-static && cp /sbin/apk.static /mnt/apk && chmod +x /mnt/apk" | |
| - name: Package OpenWrt APK | |
| if: matrix.openwrt != '' | |
| run: |- | |
| set -xeuo pipefail | |
| for architecture in ${{ matrix.openwrt }}; do | |
| .github/build_openwrt_apk.sh \ | |
| "$architecture" \ | |
| "${{ needs.calculate_version.outputs.version }}" \ | |
| "dist/sing-box" \ | |
| "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.apk" | |
| done | |
| - name: Package Alpine APK | |
| if: matrix.alpine != '' | |
| run: |- | |
| set -xeuo pipefail | |
| .github/build_alpine_apk.sh \ | |
| "${{ matrix.alpine }}" \ | |
| "${{ needs.calculate_version.outputs.version }}" \ | |
| "dist/sing-box" \ | |
| "dist/sing-box_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.alpine }}.apk" | |
| - name: Archive | |
| run: | | |
| set -xeuo pipefail | |
| cd dist | |
| mkdir -p "${DIR_NAME}" | |
| cp ../LICENSE "${DIR_NAME}" | |
| if [ '${{ matrix.os }}' = 'windows' ]; then | |
| cp sing-box "${DIR_NAME}/sing-box.exe" | |
| zip -r "${DIR_NAME}.zip" "${DIR_NAME}" | |
| else | |
| cp sing-box "${DIR_NAME}" | |
| if [ -f libcronet.so ]; then | |
| cp libcronet.so "${DIR_NAME}" | |
| fi | |
| tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}" | |
| fi | |
| rm -r "${DIR_NAME}" | |
| - name: Cleanup | |
| run: rm -f dist/sing-box dist/libcronet.so | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| 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) }}${{ matrix.variant && format('-{0}', matrix.variant) }} | |
| path: "dist" | |
| build_darwin: | |
| name: Build Darwin binaries | |
| if: ${{ !failure() && !cancelled() && (github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary') }} | |
| runs-on: macos-latest | |
| needs: | |
| - calculate_version | |
| - build_apple_library | |
| strategy: | |
| matrix: | |
| include: | |
| - { arch: amd64 } | |
| - { arch: arm64 } | |
| - { arch: amd64, legacy_osx: true, legacy_name: "macos-10.13" } | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup Go | |
| if: ${{ ! matrix.legacy_osx }} | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: ^1.25.3 | |
| cache: false | |
| - name: Cache Go modules | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: go-mod-macos-${{ hashFiles('go.sum') }} | |
| restore-keys: go-mod-macos- | |
| - name: Cache Go for macOS 10.13 | |
| if: matrix.legacy_osx | |
| id: cache-go-for-macos1013 | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/go/go_osx | |
| key: go_osx_1258 | |
| - name: Setup Go for macOS 10.13 | |
| if: matrix.legacy_osx && steps.cache-go-for-macos1013.outputs.cache-hit != 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: |- | |
| .github/setup_go_for_macos1013.sh | |
| - name: Setup Go for macOS 10.13 | |
| if: matrix.legacy_osx | |
| run: |- | |
| echo "PATH=$HOME/go/go_osx/bin:$PATH" >> $GITHUB_ENV | |
| echo "GOROOT=$HOME/go/go_osx" >> $GITHUB_ENV | |
| - name: Set tag | |
| run: |- | |
| git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" | |
| git tag v${{ needs.calculate_version.outputs.version }} -f | |
| - name: Set build tags | |
| run: | | |
| set -xeuo pipefail | |
| if [[ "${{ matrix.legacy_osx }}" != "true" ]]; then | |
| TAGS=$(cat release/DEFAULT_BUILD_TAGS) | |
| else | |
| TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) | |
| fi | |
| echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}" | |
| - name: Set shared ldflags | |
| run: | | |
| echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}" | |
| - name: Build | |
| run: | | |
| set -xeuo pipefail | |
| mkdir -p dist | |
| go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \ | |
| -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \ | |
| ./cmd/sing-box | |
| env: | |
| CGO_ENABLED: "1" | |
| GOOS: darwin | |
| GOARCH: ${{ matrix.arch }} | |
| MACOSX_DEPLOYMENT_TARGET: ${{ matrix.legacy_osx && '10.13' || '' }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set name | |
| run: |- | |
| DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-darwin-${{ matrix.arch }}" | |
| if [[ -n "${{ matrix.legacy_name }}" ]]; then | |
| DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}" | |
| fi | |
| echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" | |
| - name: Archive | |
| run: | | |
| set -xeuo pipefail | |
| cd dist | |
| mkdir -p "${DIR_NAME}" | |
| cp ../LICENSE "${DIR_NAME}" | |
| cp sing-box "${DIR_NAME}" | |
| tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}" | |
| rm -r "${DIR_NAME}" | |
| - name: Cleanup | |
| run: rm dist/sing-box | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: binary-darwin_${{ matrix.arch }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }} | |
| path: "dist" | |
| build_windows: | |
| name: Build Windows binaries | |
| if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary' | |
| runs-on: windows-latest | |
| needs: | |
| - calculate_version | |
| strategy: | |
| matrix: | |
| include: | |
| - { arch: amd64, naive: true } | |
| - { arch: "386" } | |
| - { arch: arm64, naive: true } | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: ^1.25.4 | |
| cache: false | |
| - name: Cache Go modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: go-mod-windows-${{ hashFiles('go.sum') }} | |
| restore-keys: go-mod-windows- | |
| - name: Set tag | |
| run: |- | |
| git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$env:GITHUB_ENV" | |
| git tag v${{ needs.calculate_version.outputs.version }} -f | |
| - name: Build | |
| if: matrix.naive | |
| run: | | |
| $TAGS = Get-Content release/DEFAULT_BUILD_TAGS_WINDOWS | |
| $LDFLAGS_SHARED = Get-Content release/LDFLAGS | |
| mkdir -p dist | |
| go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" ` | |
| -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" ` | |
| ./cmd/sing-box | |
| env: | |
| CGO_ENABLED: "0" | |
| GOOS: windows | |
| GOARCH: ${{ matrix.arch }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build | |
| if: ${{ !matrix.naive }} | |
| run: | | |
| $TAGS = Get-Content release/DEFAULT_BUILD_TAGS_OTHERS | |
| $LDFLAGS_SHARED = Get-Content release/LDFLAGS | |
| mkdir -p dist | |
| go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" ` | |
| -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" ` | |
| ./cmd/sing-box | |
| env: | |
| CGO_ENABLED: "0" | |
| GOOS: windows | |
| GOARCH: ${{ matrix.arch }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract libcronet.dll | |
| if: matrix.naive | |
| run: | | |
| $CRONET_GO_VERSION = Get-Content .github/CRONET_GO_VERSION | |
| git init "$env:RUNNER_TEMP/cronet-go" | |
| git -C "$env:RUNNER_TEMP/cronet-go" remote add origin https://github.com/sagernet/cronet-go.git | |
| git -C "$env:RUNNER_TEMP/cronet-go" fetch --depth=1 origin "$CRONET_GO_VERSION" | |
| git -C "$env:RUNNER_TEMP/cronet-go" checkout FETCH_HEAD | |
| $env:CGO_ENABLED = "0" | |
| go -C "$env:RUNNER_TEMP/cronet-go" build -v -o "$env:RUNNER_TEMP/build-naive.exe" ./cmd/build-naive | |
| $env:GOPROXY = "direct" | |
| $env:GOSUMDB = "off" | |
| & "$env:RUNNER_TEMP/build-naive.exe" extract-lib --target windows/${{ matrix.arch }} -o dist | |
| - name: Archive | |
| if: matrix.naive | |
| run: | | |
| $DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}" | |
| mkdir "dist/$DIR_NAME" | |
| Copy-Item LICENSE "dist/$DIR_NAME" | |
| Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME" | |
| Copy-Item "dist/libcronet.dll" "dist/$DIR_NAME" | |
| Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip" | |
| Remove-Item -Recurse "dist/$DIR_NAME" | |
| - name: Archive | |
| if: ${{ !matrix.naive }} | |
| run: | | |
| $DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}" | |
| mkdir "dist/$DIR_NAME" | |
| Copy-Item LICENSE "dist/$DIR_NAME" | |
| Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME" | |
| Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip" | |
| Remove-Item -Recurse "dist/$DIR_NAME" | |
| - name: Cleanup | |
| if: matrix.naive | |
| run: Remove-Item dist/sing-box.exe, dist/libcronet.dll | |
| - name: Cleanup | |
| if: ${{ !matrix.naive }} | |
| run: Remove-Item dist/sing-box.exe | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: binary-windows_${{ matrix.arch }} | |
| path: "dist" | |
| build_android_library: | |
| name: Build Android library (${{ matrix.go_arch }}) | |
| if: (github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android' || inputs.build == 'publish-android') && github.ref != 'refs/heads/oldstable' | |
| runs-on: ubuntu-latest | |
| needs: | |
| - calculate_version | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| go_arch: | |
| - "386" | |
| - amd64 | |
| - arm | |
| - arm64 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: 1.25.12 | |
| cache: false | |
| - name: Cache Go modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: go-mod-linux-${{ hashFiles('go.sum') }} | |
| restore-keys: go-mod-linux- | |
| - name: Setup Android NDK | |
| id: setup-ndk | |
| uses: nttld/setup-ndk@v1 | |
| with: | |
| ndk-version: r28 | |
| - name: Setup OpenJDK | |
| uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 | |
| with: | |
| distribution: temurin | |
| java-version: 17 | |
| - name: Set tag | |
| run: |- | |
| git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" | |
| git tag v${{ needs.calculate_version.outputs.version }} -f | |
| - name: Build library | |
| run: |- | |
| make lib_install | |
| export PATH="$PATH:$(go env GOPATH)/bin" | |
| go run ./cmd/internal/build_libbox -target android -platform android/${{ matrix.go_arch }} | |
| mkdir -p dist/${{ matrix.go_arch }} | |
| mv libbox.aar libbox-legacy.aar dist/${{ matrix.go_arch }} | |
| env: | |
| ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} | |
| - name: Upload library | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: libbox-android-${{ matrix.go_arch }} | |
| path: dist | |
| compression-level: 0 | |
| build_android: | |
| name: Build Android (${{ matrix.variant }}) | |
| if: (github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android') && github.ref != 'refs/heads/oldstable' | |
| runs-on: ubuntu-latest | |
| needs: | |
| - calculate_version | |
| - build_android_library | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - variant: other | |
| task: :app:assembleOtherRelease | |
| output: app/build/outputs/apk/other/release/*.apk | |
| - variant: other-legacy | |
| task: :app:assembleOtherLegacyRelease | |
| output: app/build/outputs/apk/otherLegacy/release/*.apk | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Checkout Android client | |
| run: git submodule update --init clients/android | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: 1.25.12 | |
| cache: false | |
| - name: Cache Go modules | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: go-mod-linux-${{ hashFiles('go.sum') }} | |
| restore-keys: go-mod-linux- | |
| - name: Cache Go tool builds | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/go-build | |
| key: go-build-tools-linux-${{ hashFiles('go.sum') }} | |
| restore-keys: go-build-tools-linux- | |
| - name: Setup Android NDK | |
| id: setup-ndk | |
| uses: nttld/setup-ndk@v1 | |
| with: | |
| ndk-version: r28 | |
| - name: Setup OpenJDK | |
| uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 | |
| with: | |
| distribution: temurin | |
| java-version: 17 | |
| - name: Set tag | |
| run: |- | |
| git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" | |
| git tag v${{ needs.calculate_version.outputs.version }} -f | |
| - name: Download libraries | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: libbox-android-* | |
| path: libbox-inputs | |
| merge-multiple: true | |
| - name: Merge libraries | |
| run: |- | |
| go run ./cmd/internal/merge_aar -output libbox.aar libbox-inputs/*/libbox.aar | |
| go run ./cmd/internal/merge_aar -output libbox-legacy.aar libbox-inputs/*/libbox-legacy.aar | |
| - name: Checkout main branch | |
| if: github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch' | |
| run: |- | |
| cd clients/android | |
| git checkout main | |
| - name: Checkout dev branch | |
| if: github.ref == 'refs/heads/testing' | |
| run: |- | |
| cd clients/android | |
| git checkout dev | |
| - name: Checkout submodule recursive | |
| run: |- | |
| cd clients/android | |
| git submodule update --init --recursive -v | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 | |
| with: | |
| cache-provider: basic | |
| - name: Update version | |
| if: github.event_name == 'workflow_dispatch' | |
| run: |- | |
| go run -v ./cmd/internal/update_android_version --ci | |
| - name: Update nightly version | |
| if: github.event_name != 'workflow_dispatch' | |
| run: |- | |
| go run -v ./cmd/internal/update_android_version --ci --nightly | |
| - name: Build | |
| run: |- | |
| mkdir -p clients/android/app/libs | |
| cp *.aar clients/android/app/libs | |
| cd clients/android | |
| ./gradlew --build-cache --parallel ${{ matrix.task }} | |
| env: | |
| ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} | |
| LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} | |
| - name: Prepare upload | |
| run: |- | |
| mkdir -p dist | |
| cp clients/android/${{ matrix.output }} dist | |
| VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2) | |
| VERSION_NAME=$(grep VERSION_NAME clients/android/version.properties | cut -d= -f2) | |
| cat > dist/SFA-version-metadata.json << EOF | |
| { | |
| "version_code": ${VERSION_CODE}, | |
| "version_name": "${VERSION_NAME}" | |
| } | |
| EOF | |
| cat dist/SFA-version-metadata.json | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: binary-android-apks-${{ matrix.variant }} | |
| path: 'dist' | |
| publish_android: | |
| name: Publish Android | |
| if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android' && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/testing') | |
| runs-on: ubuntu-latest | |
| needs: | |
| - calculate_version | |
| - build_android_library | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Checkout Android client | |
| run: git submodule update --init clients/android | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: 1.25.12 | |
| cache: false | |
| - name: Cache Go modules | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: go-mod-linux-${{ hashFiles('go.sum') }} | |
| restore-keys: go-mod-linux- | |
| - name: Cache Go tool builds | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/go-build | |
| key: go-build-tools-linux-${{ hashFiles('go.sum') }} | |
| restore-keys: go-build-tools-linux- | |
| - name: Setup Android NDK | |
| id: setup-ndk | |
| uses: nttld/setup-ndk@v1 | |
| with: | |
| ndk-version: r28 | |
| - name: Setup OpenJDK | |
| uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 | |
| with: | |
| distribution: temurin | |
| java-version: 17 | |
| - name: Set tag | |
| run: |- | |
| git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" | |
| git tag v${{ needs.calculate_version.outputs.version }} -f | |
| - name: Download libraries | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: libbox-android-* | |
| path: libbox-inputs | |
| merge-multiple: true | |
| - name: Merge libraries | |
| run: |- | |
| go run ./cmd/internal/merge_aar -output libbox.aar libbox-inputs/*/libbox.aar | |
| go run ./cmd/internal/merge_aar -output libbox-legacy.aar libbox-inputs/*/libbox-legacy.aar | |
| - name: Checkout main branch | |
| if: github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch' | |
| run: |- | |
| cd clients/android | |
| git checkout main | |
| - name: Checkout dev branch | |
| if: github.ref == 'refs/heads/testing' | |
| run: |- | |
| cd clients/android | |
| git checkout dev | |
| - name: Checkout submodule recursive | |
| run: |- | |
| cd clients/android | |
| git submodule update --init --recursive -v | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 | |
| with: | |
| cache-provider: basic | |
| - name: Prepare build | |
| run: |- | |
| go run -v ./cmd/internal/update_android_version --ci | |
| mkdir -p clients/android/app/libs | |
| cp *.aar clients/android/app/libs | |
| cd clients/android | |
| echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json | |
| chmod 600 service-account-credentials.json | |
| jq -e ' | |
| .type == "service_account" and | |
| (.project_id | type == "string" and length > 0) and | |
| (.private_key | type == "string" and length > 0) and | |
| (.client_email | type == "string" and length > 0) and | |
| (.token_uri | type == "string" and length > 0) | |
| ' service-account-credentials.json > /dev/null | |
| jq -r '.private_key' service-account-credentials.json | openssl pkey -check -noout | |
| env: | |
| LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} | |
| SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }} | |
| - name: Publish to Google Play | |
| run: |- | |
| cd clients/android | |
| ./gradlew --build-cache --parallel :app:publishPlayReleaseBundle | |
| env: | |
| ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} | |
| LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} | |
| build_windows_client: | |
| name: Build Windows client (${{ matrix.desktop_arch }}) | |
| if: (github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Windows') && github.ref != 'refs/heads/oldstable' | |
| runs-on: windows-latest | |
| needs: | |
| - calculate_version | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { desktop_arch: x64, rust_target: x86_64-pc-windows-msvc } | |
| - { desktop_arch: x86, rust_target: i686-pc-windows-msvc } | |
| - { desktop_arch: arm64, rust_target: aarch64-pc-windows-msvc } | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Checkout desktop client | |
| run: git submodule update --init clients/desktop | |
| - name: Checkout main branch | |
| if: github.ref == 'refs/heads/testing' | |
| run: git -C clients/desktop checkout main | |
| - name: Checkout desktop submodules | |
| run: git -C clients/desktop submodule update --init --recursive | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: 1.25.12 | |
| cache: false | |
| - name: Cache Go modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: go-mod-desktop-windows-${{ hashFiles('go.sum') }} | |
| restore-keys: go-mod-desktop-windows- | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@1.88.0 | |
| with: | |
| targets: ${{ matrix.rust_target }} | |
| - name: Cache Windows sharing module | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/git | |
| ~/.cargo/registry | |
| clients/desktop/bin/windows-share-toolchain/cargo-target | |
| key: windows-share-${{ matrix.rust_target }}-${{ hashFiles('clients/desktop/native/windows-share/Cargo.lock') }} | |
| restore-keys: windows-share-${{ matrix.rust_target }}- | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| package_json_file: clients/desktop/package.json | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version-file: clients/desktop/package.json | |
| cache: pnpm | |
| cache-dependency-path: | | |
| clients/desktop/pnpm-lock.yaml | |
| clients/desktop/dashboard/pnpm-lock.yaml | |
| - name: Set tag | |
| run: |- | |
| git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} | |
| if ($LASTEXITCODE -ne 0) { | |
| "PUBLISHED=false" >> $env:GITHUB_ENV | |
| } | |
| git tag v${{ needs.calculate_version.outputs.version }} -f | |
| - name: Update version | |
| if: github.event_name == 'workflow_dispatch' | |
| run: |- | |
| go run -v ./cmd/internal/update_desktop_version --ci | |
| - name: Update nightly version | |
| if: github.event_name != 'workflow_dispatch' | |
| run: |- | |
| go run -v ./cmd/internal/update_desktop_version --ci --nightly | |
| - name: Setup signing | |
| run: |- | |
| $certificatePath = Join-Path $env:RUNNER_TEMP "windows-signing.p12" | |
| [IO.File]::WriteAllBytes($certificatePath, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATES_P12)) | |
| @{ | |
| windows = @{ | |
| certificateFile = $certificatePath | |
| certificatePassword = $env:WINDOWS_P12_PASSWORD | |
| } | |
| } | ConvertTo-Json -Depth 3 | Set-Content -Encoding utf8NoBOM clients/desktop/signing.local.json | |
| env: | |
| WINDOWS_CERTIFICATES_P12: ${{ secrets.WINDOWS_CERTIFICATES_P12 }} | |
| WINDOWS_P12_PASSWORD: ${{ secrets.WINDOWS_P12_PASSWORD }} | |
| - name: Install dependencies | |
| run: |- | |
| pnpm -C clients/desktop install | |
| - name: Build | |
| run: |- | |
| pnpm -C clients/desktop package:win -- ${{ matrix.desktop_arch }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Prepare upload | |
| run: |- | |
| $artifacts = @(Get-ChildItem clients/desktop/release/SFW-*.exe -File) | |
| if ($artifacts.Count -ne 1) { | |
| throw "expected 1 Windows installer for ${{ matrix.desktop_arch }}, found $($artifacts.Count)" | |
| } | |
| New-Item -ItemType Directory -Force dist | Out-Null | |
| Copy-Item $artifacts.FullName dist | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: binary-windows-client-${{ matrix.desktop_arch }} | |
| path: 'dist' | |
| build_linux_client: | |
| name: Build Linux client (${{ matrix.desktop_arch }}) | |
| if: (github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Linux') && github.ref != 'refs/heads/oldstable' | |
| runs-on: ubuntu-26.04 | |
| needs: | |
| - calculate_version | |
| strategy: | |
| matrix: | |
| include: | |
| - { desktop_arch: x64, go_arch: amd64 } | |
| - { desktop_arch: arm64, go_arch: arm64 } | |
| - { desktop_arch: armv7l, go_arch: arm, go_arm: "7" } | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Checkout desktop client | |
| run: git submodule update --init clients/desktop | |
| - name: Checkout main branch | |
| if: github.ref == 'refs/heads/testing' | |
| run: git -C clients/desktop checkout main | |
| - name: Checkout desktop submodules | |
| run: git -C clients/desktop submodule update --init --recursive | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: 1.25.12 | |
| cache: false | |
| - name: Cache Go modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: go-mod-desktop-linux-${{ hashFiles('go.sum') }} | |
| restore-keys: go-mod-desktop-linux- | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| package_json_file: clients/desktop/package.json | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version-file: clients/desktop/package.json | |
| cache: pnpm | |
| cache-dependency-path: | | |
| clients/desktop/pnpm-lock.yaml | |
| clients/desktop/dashboard/pnpm-lock.yaml | |
| - name: Set tag | |
| run: |- | |
| git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" | |
| git tag v${{ needs.calculate_version.outputs.version }} -f | |
| - name: Update version | |
| if: github.event_name == 'workflow_dispatch' | |
| run: |- | |
| go run -v ./cmd/internal/update_desktop_version --ci | |
| - name: Update nightly version | |
| if: github.event_name != 'workflow_dispatch' | |
| run: |- | |
| go run -v ./cmd/internal/update_desktop_version --ci --nightly | |
| - name: Clone cronet-go | |
| run: |- | |
| set -xeuo pipefail | |
| CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION) | |
| git init ~/cronet-go | |
| git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git | |
| git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION" | |
| git -C ~/cronet-go checkout FETCH_HEAD | |
| git -C ~/cronet-go submodule update --init --recursive --depth=1 | |
| - name: Regenerate Debian keyring | |
| run: |- | |
| set -xeuo pipefail | |
| rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg | |
| cd ~/cronet-go | |
| GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh | |
| - name: Download Chromium toolchain | |
| run: |- | |
| set -xeuo pipefail | |
| cd ~/cronet-go | |
| go run ./cmd/build-naive --target=linux/${{ matrix.go_arch }} download-toolchain | |
| - name: Set Chromium toolchain environment | |
| run: |- | |
| set -xeuo pipefail | |
| cd ~/cronet-go | |
| go run ./cmd/build-naive --target=linux/${{ matrix.go_arch }} env >> "$GITHUB_ENV" | |
| - name: Install package tools | |
| run: |- | |
| set -euo pipefail | |
| sudo apt-get install -y debsigs libarchive-tools rpm zstd | |
| - name: Install dependencies | |
| run: |- | |
| pnpm -C clients/desktop install | |
| - name: Build | |
| run: |- | |
| pnpm -C clients/desktop package:linux -- deb rpm pacman ${{ matrix.desktop_arch }} | |
| env: | |
| CGO_ENABLED: "0" | |
| GOARM: ${{ matrix.go_arm }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup package signing | |
| run: |- | |
| set -euo pipefail | |
| SIGNING_GNUPG_HOME="$RUNNER_TEMP/package-signing-gnupg" | |
| SIGNING_PASSPHRASE_FILE="$RUNNER_TEMP/package-signing-passphrase" | |
| mkdir -m 700 "$SIGNING_GNUPG_HOME" | |
| printf '%s' "$GPG_PASSPHRASE" > "$SIGNING_PASSPHRASE_FILE" | |
| chmod 600 "$SIGNING_PASSPHRASE_FILE" | |
| { | |
| echo "GNUPGHOME=$SIGNING_GNUPG_HOME" | |
| echo "SIGNING_PASSPHRASE_FILE=$SIGNING_PASSPHRASE_FILE" | |
| } >> "$GITHUB_ENV" | |
| printf '%s' "$GPG_KEY" | gpg \ | |
| --homedir "$SIGNING_GNUPG_HOME" \ | |
| --batch \ | |
| --pinentry-mode loopback \ | |
| --passphrase-file "$SIGNING_PASSPHRASE_FILE" \ | |
| --import | |
| cat > "$HOME/.rpmmacros" <<EOF | |
| %_gpg_name $GPG_KEY_ID | |
| %_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase-file $SIGNING_PASSPHRASE_FILE | |
| EOF | |
| env: | |
| GPG_KEY: ${{ secrets.GPG_KEY }} | |
| GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} | |
| GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} | |
| - name: Sign Linux packages | |
| run: |- | |
| set -euo pipefail | |
| debsigs \ | |
| --sign=origin \ | |
| -k "$GPG_KEY_ID" \ | |
| --gpgopts "--batch --pinentry-mode loopback --passphrase-file $SIGNING_PASSPHRASE_FILE" \ | |
| clients/desktop/release/SFL-*.deb | |
| rpmsign --addsign clients/desktop/release/SFL-*.rpm | |
| env: | |
| GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} | |
| - name: Prepare upload | |
| run: |- | |
| shopt -s nullglob | |
| artifacts=(clients/desktop/release/SFL-*.deb clients/desktop/release/SFL-*.rpm clients/desktop/release/SFL-*.pkg.tar.zst) | |
| if (( ${#artifacts[@]} != 3 )); then | |
| echo "expected 3 Linux packages, found ${#artifacts[@]}" >&2 | |
| exit 1 | |
| fi | |
| mkdir -p dist | |
| cp "${artifacts[@]}" dist | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: binary-linux-client-${{ matrix.desktop_arch }} | |
| path: 'dist' | |
| build_apple_library: | |
| name: Build Apple library (${{ matrix.artifact }}) | |
| runs-on: ${{ matrix.build && 'macos-26' || 'ubuntu-latest' }} | |
| if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'Apple-release' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone' || inputs.build == 'iOS-jailbreak' | |
| needs: | |
| - calculate_version | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - artifact: ios-arm64 | |
| platform: ios/arm64 | |
| build: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'Apple-release' || inputs.build == 'iOS' || inputs.build == 'iOS-jailbreak' }} | |
| - artifact: tvos-arm64 | |
| platform: tvos/arm64 | |
| build: ${{ github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'tvOS') }} | |
| - artifact: macos-arm64 | |
| platform: macos/arm64 | |
| build: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'Apple-release' || inputs.build == 'macOS' || inputs.build == 'macOS-standalone' }} | |
| - artifact: macos-amd64 | |
| platform: macos/amd64 | |
| build: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'Apple-release' || inputs.build == 'macOS' || inputs.build == 'macOS-standalone' }} | |
| steps: | |
| - name: Checkout | |
| if: matrix.build | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup Go | |
| if: matrix.build | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: 1.25.12 | |
| cache: false | |
| - name: Cache Go modules | |
| if: matrix.build | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: go-mod-macos-${{ hashFiles('go.sum') }} | |
| restore-keys: go-mod-macos- | |
| - name: Cache Go build | |
| if: matrix.build | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/Library/Caches/go-build | |
| key: libbox-go-build-${{ matrix.artifact }}-1.25.12-${{ hashFiles('go.sum') }} | |
| restore-keys: libbox-go-build-${{ matrix.artifact }}-1.25.12- | |
| - name: Set tag | |
| if: matrix.build | |
| run: git tag v${{ needs.calculate_version.outputs.version }} -f | |
| - name: Build library | |
| if: matrix.build | |
| run: |- | |
| make lib_install | |
| export PATH="$PATH:$(go env GOPATH)/bin" | |
| go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }} | |
| mkdir -p dist | |
| tar -cf dist/Libbox-${{ matrix.artifact }}.tar Libbox.xcframework | |
| - name: Upload library | |
| if: matrix.build | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: libbox-apple-${{ matrix.artifact }} | |
| path: dist | |
| compression-level: 1 | |
| build_apple: | |
| name: Build Apple release clients | |
| runs-on: ${{ matrix.if && 'macos-26' || 'ubuntu-latest' }} | |
| if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple-release' || inputs.build == 'macOS-standalone' || inputs.build == 'iOS-jailbreak' | |
| needs: | |
| - calculate_version | |
| - build_apple_library | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: macOS-standalone | |
| if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple-release' || inputs.build == 'macOS-standalone' }} | |
| type: standalone | |
| platform: macos | |
| variant: all | |
| library_arch: '*' | |
| scheme: '' | |
| destination: '' | |
| archive: '' | |
| upload: '' | |
| - name: iOS-jailbreak | |
| if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple-release' || inputs.build == 'iOS-jailbreak' }} | |
| type: jailbreak | |
| platform: ios | |
| variant: '' | |
| library_arch: '' | |
| scheme: '' | |
| destination: '' | |
| archive: '' | |
| upload: '' | |
| steps: &build_apple_steps | |
| - name: Checkout | |
| if: matrix.if | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Checkout Apple client | |
| if: matrix.if | |
| run: git submodule update --init --recursive clients/apple | |
| - name: Setup Go | |
| if: matrix.if | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: 1.25.12 | |
| cache: false | |
| - name: Cache Go modules | |
| if: matrix.if | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: go-mod-macos-${{ hashFiles('go.sum') }} | |
| restore-keys: go-mod-macos- | |
| - name: Cache Go tool builds | |
| if: matrix.if | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/Library/Caches/go-build | |
| key: go-build-tools-macos-${{ hashFiles('go.sum') }} | |
| restore-keys: go-build-tools-macos- | |
| - name: Set tag | |
| if: matrix.if | |
| run: |- | |
| git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" | |
| git tag v${{ needs.calculate_version.outputs.version }} -f | |
| echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" | |
| - name: Checkout main branch | |
| if: matrix.if && github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch' | |
| run: |- | |
| cd clients/apple | |
| git checkout main | |
| - name: Checkout dev branch | |
| if: matrix.if && github.ref == 'refs/heads/testing' | |
| run: |- | |
| cd clients/apple | |
| git checkout dev | |
| - name: Cache Swift packages | |
| if: matrix.if | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ runner.temp }}/SourcePackages | |
| key: apple-spm-${{ hashFiles('clients/apple/sing-box.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} | |
| restore-keys: apple-spm- | |
| - name: Cache Xcode build data | |
| if: matrix.if | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ matrix.type == 'standalone' && 'clients/apple/build/SFM.System-*.dd' || matrix.type == 'jailbreak' && 'clients/apple/build/jailbreak/DerivedData' || format('{0}/DerivedData', runner.temp) }} | |
| key: apple-derived-data-${{ matrix.type }}-${{ matrix.platform }}-${{ matrix.variant || 'default' }}-${{ hashFiles('clients/apple/sing-box.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}-${{ github.sha }} | |
| restore-keys: | | |
| apple-derived-data-${{ matrix.type }}-${{ matrix.platform }}-${{ matrix.variant || 'default' }}-${{ hashFiles('clients/apple/sing-box.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}- | |
| apple-derived-data-${{ matrix.type }}-${{ matrix.platform }}-${{ matrix.variant || 'default' }}- | |
| - name: Setup App Store Connect key | |
| if: matrix.if && matrix.type != 'jailbreak' && github.event_name == 'workflow_dispatch' | |
| run: |- | |
| ASC_KEY_PATH=$RUNNER_TEMP/AuthKey.p8 | |
| echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH | |
| echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV" | |
| echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV" | |
| echo "ASC_KEY_ISSUER_ID=$ASC_KEY_ISSUER_ID" >> "$GITHUB_ENV" | |
| env: | |
| ASC_KEY: ${{ secrets.ASC_KEY }} | |
| ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} | |
| ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }} | |
| - name: Install Apple provisioning profiles | |
| if: matrix.if && matrix.type != 'jailbreak' | |
| run: |- | |
| PROFILES_ZIP_PATH=$RUNNER_TEMP/AppleProvisioningProfiles.zip | |
| printf '%s%s%s%s%s%s%s%s' \ | |
| "$PROVISIONING_PROFILES_1" \ | |
| "$PROVISIONING_PROFILES_2" \ | |
| "$PROVISIONING_PROFILES_3" \ | |
| "$PROVISIONING_PROFILES_4" \ | |
| "$PROVISIONING_PROFILES_5" \ | |
| "$PROVISIONING_PROFILES_6" \ | |
| "$PROVISIONING_PROFILES_7" \ | |
| "$PROVISIONING_PROFILES_8" \ | |
| | base64 --decode -o $PROFILES_ZIP_PATH | |
| PROFILES_PATH="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles" | |
| mkdir -p "$PROFILES_PATH" | |
| unzip -q $PROFILES_ZIP_PATH -d "$PROFILES_PATH" | |
| env: | |
| PROVISIONING_PROFILES_1: ${{ secrets.APPLE_PROVISIONING_PROFILES_1 }} | |
| PROVISIONING_PROFILES_2: ${{ secrets.APPLE_PROVISIONING_PROFILES_2 }} | |
| PROVISIONING_PROFILES_3: ${{ secrets.APPLE_PROVISIONING_PROFILES_3 }} | |
| PROVISIONING_PROFILES_4: ${{ secrets.APPLE_PROVISIONING_PROFILES_4 }} | |
| PROVISIONING_PROFILES_5: ${{ secrets.APPLE_PROVISIONING_PROFILES_5 }} | |
| PROVISIONING_PROFILES_6: ${{ secrets.APPLE_PROVISIONING_PROFILES_6 }} | |
| PROVISIONING_PROFILES_7: ${{ secrets.APPLE_PROVISIONING_PROFILES_7 }} | |
| PROVISIONING_PROFILES_8: ${{ secrets.APPLE_PROVISIONING_PROFILES_8 }} | |
| - name: Setup Apple Development certificate | |
| if: matrix.if && matrix.type != 'jailbreak' | |
| run: |- | |
| CERTIFICATE_PATH=$RUNNER_TEMP/AppleDevelopment.p12 | |
| KEYCHAIN_PATH=$RUNNER_TEMP/apple-development.keychain-db | |
| echo -n "$APPLE_DEVELOPMENT_CERTIFICATES_P12" | base64 --decode -o $CERTIFICATE_PATH | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security list-keychains -d user -s $KEYCHAIN_PATH | |
| security default-keychain -d user -s $KEYCHAIN_PATH | |
| security find-identity -v -p codesigning $KEYCHAIN_PATH | |
| echo "APPLE_DEVELOPMENT_KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV" | |
| env: | |
| APPLE_DEVELOPMENT_CERTIFICATES_P12: ${{ secrets.APPLE_DEVELOPMENT_CERTIFICATES_P12 }} | |
| P12_PASSWORD: ${{ secrets.APPLE_DEVELOPMENT_P12_PASSWORD }} | |
| KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} | |
| - name: Setup Developer ID certificates | |
| if: matrix.if && matrix.type == 'standalone' | |
| run: |- | |
| CERTIFICATE_PATH=$RUNNER_TEMP/DeveloperIDCertificates.p12 | |
| KEYCHAIN_PATH=$RUNNER_TEMP/developer-id.keychain-db | |
| echo -n "$DEVELOPER_ID_CERTIFICATES_P12" | base64 --decode -o $CERTIFICATE_PATH | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security list-keychains -d user -s $KEYCHAIN_PATH $APPLE_DEVELOPMENT_KEYCHAIN_PATH | |
| security default-keychain -d user -s $KEYCHAIN_PATH | |
| security find-identity -v -p codesigning $KEYCHAIN_PATH | |
| echo "SIGNING_KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV" | |
| env: | |
| DEVELOPER_ID_CERTIFICATES_P12: ${{ secrets.DEVELOPER_ID_CERTIFICATES_P12 }} | |
| P12_PASSWORD: ${{ secrets.DEVELOPER_ID_P12_PASSWORD }} | |
| KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} | |
| - name: Setup notarization credentials | |
| if: matrix.if && matrix.type == 'standalone' && github.event_name == 'workflow_dispatch' | |
| run: |- | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" $SIGNING_KEYCHAIN_PATH | |
| xcrun notarytool store-credentials "notarytool-password" \ | |
| --key $ASC_KEY_PATH \ | |
| --key-id $ASC_KEY_ID \ | |
| --issuer $ASC_KEY_ISSUER_ID \ | |
| --keychain $SIGNING_KEYCHAIN_PATH | |
| env: | |
| KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }} | |
| - name: Install jailbreak packaging tools | |
| if: matrix.if && matrix.type == 'jailbreak' | |
| run: |- | |
| brew install dpkg ldid | |
| - name: Trust build plugins | |
| if: matrix.if && matrix.type != 'jailbreak' | |
| run: |- | |
| defaults write com.apple.dt.Xcode IDESkipPackagePluginFingerprintValidatation -bool YES | |
| - name: Download library | |
| if: matrix.if | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: ${{ matrix.type == 'standalone' && format('libbox-apple-macos-{0}', matrix.library_arch) || format('libbox-apple-{0}-*', matrix.platform) }} | |
| path: libbox-inputs | |
| merge-multiple: true | |
| - name: Prepare library | |
| if: matrix.if | |
| run: |- | |
| mkdir -p libbox-slices | |
| library_inputs=() | |
| for library_archive in libbox-inputs/*.tar; do | |
| slice_name=$(basename "$library_archive" .tar) | |
| slice_path="libbox-slices/$slice_name" | |
| mkdir -p "$slice_path" | |
| tar -xf "$library_archive" -C "$slice_path" | |
| library_inputs+=("$slice_path/Libbox.xcframework") | |
| done | |
| go run ./cmd/internal/merge_apple_xcframework \ | |
| -output clients/apple/Libbox.xcframework \ | |
| "${library_inputs[@]}" | |
| - name: Update macOS version | |
| if: matrix.if && matrix.name == 'macOS' && github.event_name == 'workflow_dispatch' | |
| run: |- | |
| MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version) | |
| echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" | |
| echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV" | |
| - name: Update version | |
| if: matrix.if && matrix.name != 'iOS' | |
| run: |- | |
| go run -v ./cmd/internal/update_apple_version --ci | |
| - name: Archive App Store build | |
| if: matrix.if && matrix.type == 'app-store' | |
| run: |- | |
| cd clients/apple | |
| xcodebuild archive \ | |
| -clonedSourcePackagesDirPath "$RUNNER_TEMP/SourcePackages" \ | |
| -derivedDataPath "$RUNNER_TEMP/DerivedData" \ | |
| -scheme "${{ matrix.scheme }}" \ | |
| -configuration Release \ | |
| -destination "${{ matrix.destination }}" \ | |
| -archivePath "${{ matrix.archive }}" | |
| env: | |
| DISABLE_SWIFTLINT: "1" | |
| - name: Upload to App Store Connect | |
| if: matrix.if && matrix.type == 'app-store' && github.event_name == 'workflow_dispatch' | |
| run: |- | |
| go run -v ./cmd/internal/app_store_connect cancel_app_store ${{ matrix.platform }} | |
| cd clients/apple | |
| xcodebuild -exportArchive \ | |
| -archivePath "${{ matrix.archive }}" \ | |
| -exportOptionsPlist ${{ matrix.upload }} \ | |
| -authenticationKeyPath $ASC_KEY_PATH \ | |
| -authenticationKeyID $ASC_KEY_ID \ | |
| -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID | |
| - name: Publish to TestFlight | |
| if: matrix.if && matrix.type == 'app-store' && github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/testing' | |
| run: |- | |
| arguments=(publish_testflight "${{ matrix.platform }}") | |
| if [[ -n "$TEST_MESSAGE" ]]; then | |
| arguments+=("$TEST_MESSAGE") | |
| fi | |
| go run -v ./cmd/internal/app_store_connect "${arguments[@]}" | |
| env: | |
| TEST_MESSAGE: ${{ inputs.test_message }} | |
| - name: Build standalone packages | |
| if: matrix.if && matrix.type == 'standalone' | |
| run: |- | |
| make -C clients/apple build_macos_pkg_${{ matrix.variant }} | |
| env: | |
| DISABLE_SWIFTLINT: "1" | |
| XCODEBUILD_FLAGS: -clonedSourcePackagesDirPath ${{ runner.temp }}/SourcePackages | |
| - name: Notarize standalone packages | |
| if: matrix.if && matrix.type == 'standalone' && github.event_name == 'workflow_dispatch' | |
| run: |- | |
| make -C clients/apple notarize_macos_pkg_${{ matrix.variant }} | |
| - name: Prepare standalone packages | |
| if: matrix.if && matrix.type == 'standalone' | |
| run: |- | |
| mkdir -p dist | |
| for label in Apple Intel Universal; do | |
| cp "clients/apple/build/SFM-${label}.pkg" "dist/SFM-${VERSION}-${label}.pkg" | |
| done | |
| pushd clients/apple/build/SFM.System-universal.xcarchive | |
| zip -r SFM.dSYMs.zip dSYMs | |
| popd | |
| cp clients/apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM-${VERSION}.dSYMs.zip" | |
| - name: Build jailbreak package | |
| if: matrix.if && matrix.type == 'jailbreak' | |
| run: |- | |
| make -C clients/apple build_ios_deb | |
| mkdir -p dist | |
| cp "clients/apple/build/jailbreak/SFI-${VERSION}-iphoneos-arm64.deb" dist | |
| env: | |
| XCODEBUILD_CLONED_SOURCE_PACKAGES_DIR_PATH: ${{ runner.temp }}/SourcePackages | |
| - name: Report cache directory sizes | |
| if: matrix.if | |
| run: |- | |
| du -sh "$RUNNER_TEMP/SourcePackages"/* 2>/dev/null || true | |
| du -sh "$RUNNER_TEMP/DerivedData" 2>/dev/null || true | |
| du -sh clients/apple/build/jailbreak/DerivedData 2>/dev/null || true | |
| du -sh clients/apple/build/SFM.System-*.dd 2>/dev/null || true | |
| - name: Prune Swift package repositories | |
| if: matrix.if | |
| run: rm -rf "$RUNNER_TEMP/SourcePackages/repositories" | |
| - name: Upload release artifact | |
| if: matrix.if && (matrix.type == 'standalone' || matrix.type == 'jailbreak') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: binary-apple-${{ matrix.type }}${{ matrix.variant && format('-{0}', matrix.variant) || '' }} | |
| path: 'dist' | |
| build_apple_app_store: | |
| name: Build Apple App Store clients | |
| runs-on: ${{ matrix.if && 'macos-26' || 'ubuntu-latest' }} | |
| if: github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS') | |
| needs: | |
| - calculate_version | |
| - build_apple_library | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: iOS | |
| if: ${{ inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'iOS' }} | |
| type: app-store | |
| platform: ios | |
| scheme: SFI | |
| destination: 'generic/platform=iOS' | |
| archive: build/SFI.xcarchive | |
| upload: SFI/Upload.plist | |
| variant: '' | |
| library_arch: '' | |
| - name: macOS | |
| if: ${{ inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS' }} | |
| type: app-store | |
| platform: macos | |
| scheme: SFM | |
| destination: 'generic/platform=macOS' | |
| archive: build/SFM.xcarchive | |
| upload: SFI/Upload.plist | |
| variant: '' | |
| library_arch: '' | |
| - name: tvOS | |
| if: ${{ inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'tvOS' }} | |
| type: app-store | |
| platform: tvos | |
| scheme: SFT | |
| destination: 'generic/platform=tvOS' | |
| archive: build/SFT.xcarchive | |
| upload: SFI/Upload.plist | |
| variant: '' | |
| library_arch: '' | |
| steps: *build_apple_steps | |
| upload: | |
| name: Upload builds | |
| if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Windows' || inputs.build == 'Linux' || inputs.build == 'Apple-release' || inputs.build == 'macOS-standalone' || inputs.build == 'iOS-jailbreak')" | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| id-token: write | |
| attestations: write | |
| artifact-metadata: write | |
| needs: | |
| - calculate_version | |
| - build | |
| - build_darwin | |
| - build_windows | |
| - build_android_library | |
| - build_android | |
| - build_windows_client | |
| - build_linux_client | |
| - build_apple_library | |
| - build_apple | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Cache ghr | |
| uses: actions/cache@v4 | |
| id: cache-ghr | |
| with: | |
| path: | | |
| ~/go/bin/ghr | |
| key: ghr | |
| - name: Setup ghr | |
| if: steps.cache-ghr.outputs.cache-hit != 'true' | |
| run: |- | |
| cd $HOME | |
| git clone https://github.com/nekohasekai/ghr ghr | |
| cd ghr | |
| go install -v . | |
| - name: Set tag | |
| run: |- | |
| git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" | |
| git tag v${{ needs.calculate_version.outputs.version }} -f | |
| echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" | |
| - name: Download builds | |
| uses: actions/download-artifact@v5 | |
| with: | |
| pattern: binary-* | |
| path: dist | |
| merge-multiple: true | |
| - name: Attest build provenance | |
| uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 | |
| with: | |
| subject-path: 'dist/**' | |
| - name: Upload builds | |
| if: ${{ env.PUBLISHED == 'false' }} | |
| run: |- | |
| export PATH="$PATH:$HOME/go/bin" | |
| ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Replace builds | |
| if: ${{ env.PUBLISHED != 'false' }} | |
| run: |- | |
| export PATH="$PATH:$HOME/go/bin" | |
| ghr --replace -p 5 "v${VERSION}" dist | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| cache_gc: | |
| name: Cache GC | |
| if: always() | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: write | |
| needs: | |
| - build | |
| - build_darwin | |
| - build_windows | |
| - build_android_library | |
| - build_android | |
| - build_windows_client | |
| - build_linux_client | |
| - build_apple_library | |
| - build_apple | |
| - build_apple_app_store | |
| - upload | |
| steps: | |
| - name: Delete superseded caches | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: |- | |
| set -euo pipefail | |
| gh api "repos/$GITHUB_REPOSITORY/actions/caches?per_page=100" --paginate \ | |
| --jq '.actions_caches[] | [.id, .ref, .created_at, .last_accessed_at, .size_in_bytes, .key] | @tsv' > "$RUNNER_TEMP/caches.tsv" | |
| python3 - "$GITHUB_REPOSITORY" "$RUNNER_TEMP/caches.tsv" <<'EOF' | |
| import re | |
| import subprocess | |
| import sys | |
| repository, list_path = sys.argv[1], sys.argv[2] | |
| budget = 9 * 1024 * 1024 * 1024 | |
| rows = [] | |
| with open(list_path) as list_file: | |
| for line in list_file: | |
| cache_id, ref, created_at, accessed_at, size, key = line.rstrip("\n").split("\t") | |
| family = key | |
| while True: | |
| stripped = re.sub(r"-[0-9a-f]{16,}$", "", family) | |
| if stripped == family: | |
| break | |
| family = stripped | |
| rows.append({ | |
| "id": cache_id, "ref": ref, "created": created_at, | |
| "accessed": accessed_at, "size": int(size), "key": key, | |
| "family": family, | |
| }) | |
| newest = {} | |
| for row in rows: | |
| group = (row["ref"], row["family"]) | |
| if group not in newest or row["created"] > newest[group]["created"]: | |
| newest[group] = row | |
| doomed = [row for row in rows if newest[(row["ref"], row["family"])] is not row] | |
| kept = [row for row in rows if newest[(row["ref"], row["family"])] is row] | |
| total = sum(row["size"] for row in kept) | |
| for row in sorted(kept, key=lambda entry: entry["accessed"]): | |
| if total <= budget: | |
| break | |
| doomed.append(row) | |
| total -= row["size"] | |
| for row in doomed: | |
| print(f"delete {row['size'] >> 20}MB {row['key']} ({row['ref']})") | |
| subprocess.run( | |
| ["gh", "api", "-X", "DELETE", f"repos/{repository}/actions/caches/{row['id']}"], | |
| check=False, | |
| ) | |
| print(f"kept {len(rows) - len(doomed)} caches, {total >> 20}MB total") | |
| EOF |