Files
sing-box-extended-mirror/.github/workflows/build.yml
T
世界 e4a19a2a7c Enable ARM64 cryptographic extensions in iOS library builds
Go's internal/cpu never detects ARM64 features on GOOS=ios, so AES, AES-GCM
and SHA-256 use their generic implementations on iOS and tvOS. Patch the
Go source in the Apple library build to assert the ARMv8.0 cryptographic
extensions, as Go already does for macOS.

See https://github.com/SagerNet/sing-box/issues/4486
2026-09-03 13:30:52 +08:00

1802 lines
76 KiB
YAML

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.26.7
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.26.7
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_1267
- 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 && matrix.variant != 'purego'
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 sparse-checkout set --no-cone '/*' '!/lib'
git -C ~/cronet-go fetch --depth=1 --filter=blob:none 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 && matrix.variant != 'purego'
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 && matrix.variant != 'purego'
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 && matrix.variant != 'purego'
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: |
set -xeuo pipefail
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
LIBRARY_PATH="lib/${{ matrix.os }}_${{ matrix.arch }}/libcronet.so"
git init "$RUNNER_TEMP/cronet-go-lib"
git -C "$RUNNER_TEMP/cronet-go-lib" remote add origin https://github.com/sagernet/cronet-go.git
git -C "$RUNNER_TEMP/cronet-go-lib" sparse-checkout set --no-cone "/$LIBRARY_PATH"
git -C "$RUNNER_TEMP/cronet-go-lib" fetch --depth=1 --filter=blob:none origin "$CRONET_GO_VERSION"
git -C "$RUNNER_TEMP/cronet-go-lib" checkout FETCH_HEAD
cp "$RUNNER_TEMP/cronet-go-lib/$LIBRARY_PATH" 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
echo '%_rpmformat 4' > "$HOME/.rpmmacros"
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 || true
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.26.7
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_1267
- 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.26.7
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
$LIBRARY_PATH = "lib/windows_${{ matrix.arch }}/libcronet.dll"
git init "$env:RUNNER_TEMP/cronet-go-lib"
git -C "$env:RUNNER_TEMP/cronet-go-lib" remote add origin https://github.com/sagernet/cronet-go.git
git -C "$env:RUNNER_TEMP/cronet-go-lib" sparse-checkout set --no-cone "/$LIBRARY_PATH"
git -C "$env:RUNNER_TEMP/cronet-go-lib" fetch --depth=1 --filter=blob:none origin "$CRONET_GO_VERSION"
git -C "$env:RUNNER_TEMP/cronet-go-lib" checkout FETCH_HEAD
Copy-Item "$env:RUNNER_TEMP/cronet-go-lib/$LIBRARY_PATH" 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.26.7
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.26.7
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.26.7
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/stable' && github.event_name != 'workflow_dispatch'
run: git -C clients/desktop checkout main
- name: Checkout dev branch
if: github.ref == 'refs/heads/testing'
run: git -C clients/desktop checkout dev
- 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.26.7
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/stable' && github.event_name != 'workflow_dispatch'
run: git -C clients/desktop checkout main
- name: Checkout dev branch
if: github.ref == 'refs/heads/testing'
run: git -C clients/desktop checkout dev
- 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.26.7
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 update || true
sudo apt-get install -y debsigs libarchive-tools rpm zstd
echo '%_rpmformat 4' > "$HOME/.rpmmacros"
- 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.26.7
cache: false
- name: Patch Go for iOS
if: matrix.build && (matrix.platform == 'ios/arm64' || matrix.platform == 'tvos/arm64')
run: .github/patch_go_for_ios.sh
- 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.26.7-${{ hashFiles('go.sum') }}
restore-keys: libbox-go-build-${{ matrix.artifact }}-1.26.7-
- name: Set tag
if: matrix.build
run: git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build library
if: matrix.build
env:
GOTOOLCHAIN: local
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: ''
upload_extension: ''
altool_platform: ''
- 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: ''
upload_extension: ''
altool_platform: ''
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.26.7
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: |-
# altool selects individual key authentication by the ApiKey_ file name prefix;
# an AuthKey_ prefixed file is signed as a team key and rejected.
ASC_PRIVATE_KEYS_DIR=$RUNNER_TEMP/private_keys
mkdir -p $ASC_PRIVATE_KEYS_DIR
ASC_KEY_PATH=$ASC_PRIVATE_KEYS_DIR/ApiKey_$ASC_KEY_ID.p8
echo -n "$ASC_KEY" | base64 --decode -o $ASC_KEY_PATH
echo "API_PRIVATE_KEYS_DIR=$ASC_PRIVATE_KEYS_DIR" >> "$GITHUB_ENV"
echo "ASC_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
echo "ASC_KEY_ID=$ASC_KEY_ID" >> "$GITHUB_ENV"
env:
ASC_KEY: ${{ secrets.ASC_KEY }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
- name: Install Apple provisioning profiles
if: matrix.if && matrix.type != 'jailbreak'
run: |-
PROFILES_ZIP_PATH=$RUNNER_TEMP/AppleProvisioningProfiles.zip
for variable in $(compgen -v | grep -E '^PROVISIONING_PROFILES_[0-9]+$' | sort -t_ -k3 -n); do
printf '%s' "${!variable}"
done | 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 }}
PROVISIONING_PROFILES_9: ${{ secrets.APPLE_PROVISIONING_PROFILES_9 }}
PROVISIONING_PROFILES_10: ${{ secrets.APPLE_PROVISIONING_PROFILES_10 }}
PROVISIONING_PROFILES_11: ${{ secrets.APPLE_PROVISIONING_PROFILES_11 }}
PROVISIONING_PROFILES_12: ${{ secrets.APPLE_PROVISIONING_PROFILES_12 }}
PROVISIONING_PROFILES_13: ${{ secrets.APPLE_PROVISIONING_PROFILES_13 }}
PROVISIONING_PROFILES_14: ${{ secrets.APPLE_PROVISIONING_PROFILES_14 }}
PROVISIONING_PROFILES_15: ${{ secrets.APPLE_PROVISIONING_PROFILES_15 }}
PROVISIONING_PROFILES_16: ${{ secrets.APPLE_PROVISIONING_PROFILES_16 }}
- 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 Apple Distribution certificate
if: matrix.if && matrix.type == 'app-store'
run: |-
CERTIFICATE_PATH=$RUNNER_TEMP/AppleDistributionCertificates.p12
KEYCHAIN_PATH=$RUNNER_TEMP/apple-distribution.keychain-db
echo -n "$APPLE_DISTRIBUTION_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
env:
APPLE_DISTRIBUTION_CERTIFICATES_P12: ${{ secrets.APPLE_DISTRIBUTION_CERTIFICATES_P12 }}
P12_PASSWORD: ${{ secrets.APPLE_DISTRIBUTION_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
# store-credentials prompts for the issuer ID even when --issuer is absent;
# an empty answer selects individual key authentication.
printf '\n' | xcrun notarytool store-credentials "notarytool-password" \
--key $ASC_KEY_PATH \
--key-id $ASC_KEY_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 project version
if: matrix.if && matrix.type == 'app-store' && github.event_name == 'workflow_dispatch'
run: |-
PROJECT_VERSION_VARIABLE=$(echo "${{ matrix.platform }}" | tr '[:lower:]' '[:upper:]')_PROJECT_VERSION
PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_project_version ${{ matrix.platform }})
echo "$PROJECT_VERSION_VARIABLE=$PROJECT_VERSION"
echo "$PROJECT_VERSION_VARIABLE=$PROJECT_VERSION" >> "$GITHUB_ENV"
- name: Update version
if: matrix.if
run: |-
go run -v ./cmd/internal/update_apple_version --ci ${{ matrix.type == 'app-store' && '--testflight' || '' }}
- 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 }}" \
-exportPath "$RUNNER_TEMP/export" \
-exportOptionsPlist ${{ matrix.upload }}
# altool requires --api-issuer even for an individual key, and ignores its value.
xcrun altool --upload-app \
-f "$RUNNER_TEMP"/export/*.${{ matrix.upload_extension }} \
-t ${{ matrix.altool_platform }} \
--api-key $ASC_KEY_ID \
--api-issuer 00000000-0000-0000-0000-000000000000
- 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/AppStoreExport.plist
upload_extension: ipa
altool_platform: ios
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/AppStoreExport.plist
upload_extension: pkg
altool_platform: macos
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/AppStoreExport.plist
upload_extension: ipa
altool_platform: appletvos
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