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
          - Apple
          - app-store
          - iOS
          - macOS
          - tvOS
          - macOS-standalone
          - publish-android
  push:
    branches:
      - main-next
      - dev-next

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.7
      - 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-latest
    needs:
      - calculate_version
    strategy:
      matrix:
        include:
          - { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
          - { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
          - { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
          - { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
          - { 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: 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" }
          - { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
          - { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
          - { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
          - { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
          - { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
          - { 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, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
          - { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }

          - { os: windows, arch: amd64 }
          - { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
          - { os: windows, arch: "386" }
          - { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
          - { os: windows, arch: arm64 }

          - { os: darwin, arch: amd64 }
          - { os: darwin, arch: arm64 }
          - { os: darwin, arch: amd64, legacy_go124: true, legacy_name: "macos-11" }

          - { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
          - { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
          - { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
          - { os: android, arch: "386", ndk: "i686-linux-android21" }
    steps:
      - name: Checkout
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
        with:
          fetch-depth: 0
      - name: Setup Go
        if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }}
        uses: actions/setup-go@v5
        with:
          go-version: ^1.25.7
      - name: Setup Go 1.24
        if: matrix.legacy_go124
        uses: actions/setup-go@v5
        with:
          go-version: ~1.24.10
      - 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_1255
      - name: Setup Go for Windows 7
        if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
        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
          local-cache: true
      - 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
          TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
          echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
      - name: Build
        if: matrix.os != 'android'
        run: |
          set -xeuo pipefail
          mkdir -p dist
          go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
          -ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
          ./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 '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
          ./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
          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 update
          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
          curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
          sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
          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 update
          sudo apt-get install -y libarchive-tools
          cp .fpm_systemd .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: 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}"
            tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
          fi
          rm -r "${DIR_NAME}"
      - name: Cleanup
        run: rm dist/sing-box
      - 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) }}
          path: "dist"
  build_android:
    name: Build Android
    if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
    runs-on: ubuntu-latest
    needs:
      - calculate_version
    steps:
      - name: Checkout
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
        with:
          fetch-depth: 0
          submodules: 'recursive'
      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: ^1.25.7
      - name: Setup Android NDK
        id: setup-ndk
        uses: nttld/setup-ndk@v1
        with:
          ndk-version: r28
      - name: Setup OpenJDK
        run: |-
          sudo apt update && sudo apt install -y openjdk-17-jdk-headless
          /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
      - 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"
          make lib_android
        env:
          JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
          ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
      - name: Checkout main branch
        if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
        run: |-
          cd clients/android
          git checkout main
      - name: Checkout dev branch
        if: github.ref == 'refs/heads/dev-next'
        run: |-
          cd clients/android
          git checkout dev
      - name: Gradle cache
        uses: actions/cache@v4
        with:
          path: ~/.gradle
          key: gradle-${{ hashFiles('**/*.gradle') }}
      - 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 clients/android/app/libs
          cp libbox.aar clients/android/app/libs
          cd clients/android
          ./gradlew :app:assemblePlayRelease
        env:
          JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
          ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
          LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
      - name: Prepare upload
        run: |-
          mkdir -p dist
          cp clients/android/app/build/outputs/apk/play/release/*.apk dist
          cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: binary-android-apks
          path: 'dist'
  publish_android:
    name: Publish Android
    if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
    runs-on: ubuntu-latest
    needs:
      - calculate_version
    steps:
      - name: Checkout
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
        with:
          fetch-depth: 0
          submodules: 'recursive'
      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: ^1.25.7
      - name: Setup Android NDK
        id: setup-ndk
        uses: nttld/setup-ndk@v1
        with:
          ndk-version: r28
      - name: Setup OpenJDK
        run: |-
          sudo apt update && sudo apt install -y openjdk-17-jdk-headless
          /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
      - 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"
          make lib_android
        env:
          JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
          ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
      - name: Checkout main branch
        if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
        run: |-
          cd clients/android
          git checkout main
      - name: Checkout dev branch
        if: github.ref == 'refs/heads/dev-next'
        run: |-
          cd clients/android
          git checkout dev
      - name: Gradle cache
        uses: actions/cache@v4
        with:
          path: ~/.gradle
          key: gradle-${{ hashFiles('**/*.gradle') }}
      - name: Build
        run: |-
          go run -v ./cmd/internal/update_android_version --ci
          mkdir clients/android/app/libs
          cp libbox.aar clients/android/app/libs
          cd clients/android
          echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
          ./gradlew :app:publishPlayReleaseBundle
        env:
          JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
          ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
          LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
          SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
  build_apple:
    name: Build Apple clients
    runs-on: macos-26
    if: false
    needs:
      - calculate_version
    strategy:
      matrix:
        include:
          - name: iOS
            if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'iOS' }}
            platform: ios
            scheme: SFI
            destination: 'generic/platform=iOS'
            archive: build/SFI.xcarchive
            upload: SFI/Upload.plist
          - name: macOS
            if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'macOS' }}
            platform: macos
            scheme: SFM
            destination: 'generic/platform=macOS'
            archive: build/SFM.xcarchive
            upload: SFI/Upload.plist
          - name: tvOS
            if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store'|| inputs.build == 'tvOS' }}
            platform: tvos
            scheme: SFT
            destination: 'generic/platform=tvOS'
            archive: build/SFT.xcarchive
            upload: SFI/Upload.plist
          - name: macOS-standalone
            if: ${{ github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone' }}
            platform: macos
            scheme: SFM.System
            destination: 'generic/platform=macOS'
            archive: build/SFM.System.xcarchive
            export: SFM.System/Export.plist
            export_path: build/SFM.System
    steps:
      - name: Checkout
        if: matrix.if
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
        with:
          fetch-depth: 0
          submodules: 'recursive'
      - name: Setup Go
        if: matrix.if
        uses: actions/setup-go@v5
        with:
          go-version: ^1.25.7
      - 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/main-next' && github.event_name != 'workflow_dispatch'
        run: |-
          cd clients/apple
          git checkout main
      - name: Checkout dev branch
        if: matrix.if && github.ref == 'refs/heads/dev-next'
        run: |-
          cd clients/apple
          git checkout dev
      - name: Setup certificates
        if: matrix.if
        run: |-
          CERTIFICATE_PATH=$RUNNER_TEMP/Certificates.p12
          KEYCHAIN_PATH=$RUNNER_TEMP/certificates.keychain-db
          echo -n "$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-keychain -d user -s $KEYCHAIN_PATH

          PROFILES_ZIP_PATH=$RUNNER_TEMP/Profiles.zip
          echo -n "$PROVISIONING_PROFILES" | base64 --decode -o $PROFILES_ZIP_PATH

          PROFILES_PATH="$HOME/Library/MobileDevice/Provisioning Profiles"
          mkdir -p "$PROFILES_PATH"
          unzip $PROFILES_ZIP_PATH -d "$PROFILES_PATH"

          ASC_KEY_PATH=$RUNNER_TEMP/Key.p12
          echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH

          xcrun notarytool store-credentials "notarytool-password" \
            --key $ASC_KEY_PATH \
            --key-id $ASC_KEY_ID \
            --issuer $ASC_KEY_ISSUER_ID

          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:
          CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
          KEYCHAIN_PASSWORD: ${{ secrets.P12_PASSWORD }}
          PROVISIONING_PROFILES: ${{ secrets.PROVISIONING_PROFILES }}
          ASC_KEY: ${{ secrets.ASC_KEY }}
          ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
          ASC_KEY_ISSUER_ID: ${{ secrets.ASC_KEY_ISSUER_ID }}
      - name: Build library
        if: matrix.if
        run: |-
          make lib_install
          export PATH="$PATH:$(go env GOPATH)/bin"
          go run ./cmd/internal/build_libbox -target apple -platform ${{ matrix.platform }}
          mv Libbox.xcframework clients/apple
      - 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: Build
        if: matrix.if
        run: |-
          cd clients/apple
          xcodebuild archive \
            -scheme "${{ matrix.scheme }}" \
            -configuration Release \
            -destination "${{ matrix.destination }}" \
            -archivePath "${{ matrix.archive }}" \
            -allowProvisioningUpdates \
            -authenticationKeyPath $ASC_KEY_PATH \
            -authenticationKeyID $ASC_KEY_ID \
            -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
      - name: Upload to App Store Connect
        if: matrix.if && matrix.name != 'macOS-standalone' && 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 }} \
            -allowProvisioningUpdates \
            -authenticationKeyPath $ASC_KEY_PATH \
            -authenticationKeyID $ASC_KEY_ID \
            -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
      - name: Publish to TestFlight
        if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
        run: |-
          go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
      - name: Build image
        if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
        run: |-
          pushd clients/apple
          xcodebuild -exportArchive \
          	-archivePath "${{ matrix.archive }}" \
          	-exportOptionsPlist ${{ matrix.export }} \
          	-exportPath "${{ matrix.export_path }}"
          brew install create-dmg
          create-dmg \
          	--volname "sing-box" \
          	--volicon "${{ matrix.export_path }}/SFM.app/Contents/Resources/AppIcon.icns" \
          	--icon "SFM.app" 0 0 \
          		--hide-extension "SFM.app" \
          		--app-drop-link 0 0 \
          		--skip-jenkins \
          	SFM.dmg "${{ matrix.export_path }}/SFM.app"
          xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"          
          cd "${{ matrix.archive }}"
          zip -r SFM.dSYMs.zip dSYMs
          popd

          mkdir -p dist
          cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
          cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
      - name: Upload image
        if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
        uses: actions/upload-artifact@v4
        with:
          name: binary-macos-dmg
          path: 'dist'
  upload:
    name: Upload builds
    if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')"
    runs-on: ubuntu-latest
    needs:
      - calculate_version
      - build
      - build_android
      - build_apple
    steps:
      - name: Checkout
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
        with:
          fetch-depth: 0
      - 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:
          path: dist
          merge-multiple: true
      - 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 }}
