Migrate Apple developer account

This commit is contained in:
世界
2026-08-30 17:41:47 +08:00
parent 3439be1bbb
commit 55b18b2271
6 changed files with 160 additions and 79 deletions
+72 -36
View File
@@ -54,7 +54,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.26.6
go-version: 1.26.7
cache: false
- name: Cache Go modules
uses: actions/cache/restore@v4
@@ -145,7 +145,7 @@ jobs:
if: ${{ ! matrix.legacy_win7 }}
uses: actions/setup-go@v5
with:
go-version: 1.26.6
go-version: 1.26.7
cache: false
- name: Cache Go modules
uses: actions/cache/restore@v4
@@ -160,7 +160,7 @@ jobs:
with:
path: |
~/go/go_win7
key: go_win7_1266
key: go_win7_1267
- name: Setup Go for Windows 7
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
env:
@@ -488,7 +488,7 @@ jobs:
if: ${{ ! matrix.legacy_osx }}
uses: actions/setup-go@v5
with:
go-version: 1.26.6
go-version: 1.26.7
cache: false
- name: Cache Go modules
uses: actions/cache/restore@v4
@@ -503,7 +503,7 @@ jobs:
with:
path: |
~/go/go_osx
key: go_osx_1266
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:
@@ -587,7 +587,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.26.6
go-version: 1.26.7
cache: false
- name: Cache Go modules
uses: actions/cache@v4
@@ -690,7 +690,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.26.6
go-version: 1.26.7
cache: false
- name: Cache Go modules
uses: actions/cache@v4
@@ -754,7 +754,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.26.6
go-version: 1.26.7
cache: false
- name: Cache Go modules
uses: actions/cache/restore@v4
@@ -862,7 +862,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.26.6
go-version: 1.26.7
cache: false
- name: Cache Go modules
uses: actions/cache/restore@v4
@@ -972,7 +972,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.26.6
go-version: 1.26.7
cache: false
- name: Cache Go modules
uses: actions/cache@v4
@@ -1081,7 +1081,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.26.6
go-version: 1.26.7
cache: false
- name: Cache Go modules
uses: actions/cache@v4
@@ -1238,7 +1238,7 @@ jobs:
if: matrix.build
uses: actions/setup-go@v5
with:
go-version: 1.26.6
go-version: 1.26.7
cache: false
- name: Cache Go modules
if: matrix.build
@@ -1252,8 +1252,8 @@ jobs:
uses: actions/cache@v4
with:
path: ~/Library/Caches/go-build
key: libbox-go-build-${{ matrix.artifact }}-1.26.6-${{ hashFiles('go.sum') }}
restore-keys: libbox-go-build-${{ matrix.artifact }}-1.26.6-
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
@@ -1293,6 +1293,8 @@ jobs:
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
@@ -1303,6 +1305,8 @@ jobs:
destination: ''
archive: ''
upload: ''
upload_extension: ''
altool_platform: ''
steps: &build_apple_steps
- name: Checkout
if: matrix.if
@@ -1316,7 +1320,7 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: 1.26.6
go-version: 1.26.7
cache: false
- name: Cache Go modules
if: matrix.if
@@ -1367,21 +1371,23 @@ jobs:
- 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
# 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"
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
for index in $(seq 1 16); do
variable="PROVISIONING_PROFILES_$index"
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"
@@ -1442,14 +1448,33 @@ jobs:
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
xcrun notarytool store-credentials "notarytool-password" \
# 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 \
--issuer $ASC_KEY_ISSUER_ID \
--keychain $SIGNING_KEYCHAIN_PATH
env:
KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
@@ -1483,16 +1508,17 @@ jobs:
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'
- name: Update project version
if: matrix.if && matrix.type == 'app-store' && 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"
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 && matrix.name != 'iOS'
if: matrix.if
run: |-
go run -v ./cmd/internal/update_apple_version --ci
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: |-
@@ -1513,10 +1539,14 @@ jobs:
cd clients/apple
xcodebuild -exportArchive \
-archivePath "${{ matrix.archive }}" \
-exportOptionsPlist ${{ matrix.upload }} \
-authenticationKeyPath $ASC_KEY_PATH \
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
-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: |-
@@ -1591,7 +1621,9 @@ jobs:
scheme: SFI
destination: 'generic/platform=iOS'
archive: build/SFI.xcarchive
upload: SFI/Upload.plist
upload: SFI/AppStoreExport.plist
upload_extension: ipa
altool_platform: ios
variant: ''
library_arch: ''
- name: macOS
@@ -1601,7 +1633,9 @@ jobs:
scheme: SFM
destination: 'generic/platform=macOS'
archive: build/SFM.xcarchive
upload: SFI/Upload.plist
upload: SFI/AppStoreExport.plist
upload_extension: pkg
altool_platform: macos
variant: ''
library_arch: ''
- name: tvOS
@@ -1611,7 +1645,9 @@ jobs:
scheme: SFT
destination: 'generic/platform=tvOS'
archive: build/SFT.xcarchive
upload: SFI/Upload.plist
upload: SFI/AppStoreExport.plist
upload_extension: ipa
altool_platform: appletvos
variant: ''
library_arch: ''
steps: *build_apple_steps
+44 -28
View File
@@ -19,8 +19,11 @@ import (
func main() {
ctx := context.Background()
switch os.Args[1] {
case "next_macos_project_version":
err := fetchMacOSVersion(ctx)
case "next_project_version":
if len(os.Args) < 3 {
log.Fatal("platform required: ios, macos, or tvos")
}
err := fetchNextProjectVersion(ctx, os.Args[2])
if err != nil {
log.Fatal(err)
}
@@ -50,8 +53,8 @@ func main() {
}
const (
appID = "6673731168"
groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda"
appID = "6785326793"
groupID = "39f9ebdc-05d4-421f-9595-dae71df227c4"
)
func createClient(expireDuration time.Duration) *asc.Client {
@@ -66,36 +69,49 @@ func createClient(expireDuration time.Duration) *asc.Client {
return asc.NewClient(tokenConfig.Client())
}
func fetchMacOSVersion(ctx context.Context) error {
func fetchNextProjectVersion(ctx context.Context, platformName string) error {
var platform asc.Platform
switch platformName {
case "ios":
platform = asc.PlatformIOS
case "macos":
platform = asc.PlatformMACOS
case "tvos":
platform = asc.PlatformTVOS
default:
return E.New("unknown platform: ", platformName)
}
query := &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
Limit: 200,
}
if platform != asc.PlatformMACOS {
tagVersion, err := build_shared.ReadTagVersion()
if err != nil {
return err
}
query.FilterPreReleaseVersionVersion = []string{build_shared.TestFlightVersion(tagVersion)}
}
client := createClient(time.Minute)
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{"MAC_OS"},
})
builds, _, err := client.Builds.ListBuilds(ctx, query)
if err != nil {
return err
}
var versionID string
findVersion:
for _, version := range versions.Data {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStateReadyForSale,
asc.AppStoreVersionStatePendingDeveloperRelease:
versionID = version.ID
break findVersion
nextProjectVersion := 1
var projectVersion int
for _, build := range builds.Data {
projectVersion, err = strconv.Atoi(*build.Attributes.Version)
if err != nil {
return E.Cause(err, "parse version code")
}
if projectVersion >= nextProjectVersion {
nextProjectVersion = projectVersion + 1
}
}
if versionID == "" {
return E.New("no version found")
}
latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})
if err != nil {
return err
}
versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)
if err != nil {
return E.Cause(err, "parse version code")
}
os.Stdout.WriteString(F.ToString(versionInt+1, "\n"))
os.Stdout.WriteString(F.ToString(nextProjectVersion, "\n"))
return nil
}
+5
View File
@@ -3,6 +3,7 @@ package build_shared
import (
"github.com/sagernet/sing-box/common/badversion"
"github.com/sagernet/sing/common"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/shell"
)
@@ -36,3 +37,7 @@ func ReadTagVersion() (badversion.Version, error) {
}
return version, nil
}
func TestFlightVersion(version badversion.Version) string {
return F.ToString(version.Major, ".", version.Minor, ".10")
}
+36 -12
View File
@@ -15,10 +15,14 @@ import (
"howett.net/plist"
)
var flagRunInCI bool
var (
flagRunInCI bool
flagTestFlight bool
)
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagTestFlight, "testflight", false, "Override the App Store marketing version with the version reserved for TestFlight")
}
func main() {
@@ -41,19 +45,39 @@ func main() {
common.Must(decoder.Decode(&project))
objectsMap := project["objects"].(map[string]any)
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfavt"}, newVersion.VersionString())
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfavt.standalone", "io.nekohasekai.sfavt.system"}, newVersion.String())
if updated0 || updated1 {
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
}
var updated2 bool
if macProjectVersion := os.Getenv("MACOS_PROJECT_VERSION"); macProjectVersion != "" {
newContent, updated2 = findAndReplaceProjectVersion(objectsMap, newContent, []string{"SFM"}, macProjectVersion)
if updated2 {
log.Info("updated macos project version to ", macProjectVersion)
newContent := projectContent
var marketingVersionUpdated bool
if flagTestFlight {
testFlightVersion := build_shared.TestFlightVersion(newVersion)
newContent, marketingVersionUpdated = findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfamt"}, testFlightVersion)
if marketingVersionUpdated {
log.Info("updated App Store version to ", testFlightVersion)
}
}
if updated0 || updated1 || updated2 {
var standaloneVersionUpdated bool
newContent, standaloneVersionUpdated = findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfamt.standalone", "io.nekohasekai.sfamt.system"}, newVersion.String())
if standaloneVersionUpdated {
marketingVersionUpdated = true
log.Info("updated version to ", newVersion.String())
}
var projectVersionUpdated bool
for environmentName, directory := range map[string]string{
"IOS_PROJECT_VERSION": "SFI",
"MACOS_PROJECT_VERSION": "SFM",
"TVOS_PROJECT_VERSION": "SFT",
} {
projectVersion := os.Getenv(environmentName)
if projectVersion == "" {
continue
}
var updated bool
newContent, updated = findAndReplaceProjectVersion(objectsMap, newContent, []string{directory}, projectVersion)
if updated {
projectVersionUpdated = true
log.Info("updated ", directory, " project version to ", projectVersion)
}
}
if marketingVersionUpdated || projectVersionUpdated {
common.Must(os.WriteFile("sing-box.xcodeproj/project.pbxproj", []byte(newContent), 0o644))
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ require (
github.com/openai/openai-go/v3 v3.26.0
github.com/oschwald/maxminddb-golang v1.13.1
github.com/pkg/sftp v1.13.10
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
github.com/sagernet/asc-go v0.0.0-20260827085112-8f8226245b0d
github.com/sagernet/bbolt v0.0.0-20260823094646-e24805439c9c
github.com/sagernet/cors v1.2.1
github.com/sagernet/cronet-go v0.0.0-20260824154604-9247da29c718
+2 -2
View File
@@ -236,8 +236,8 @@ github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9q
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4=
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU=
github.com/sagernet/asc-go v0.0.0-20260827085112-8f8226245b0d h1:r3JBMjxwI8BPLprJpklmcgoPT3r7rJfUg4uuGWky48A=
github.com/sagernet/asc-go v0.0.0-20260827085112-8f8226245b0d/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU=
github.com/sagernet/bbolt v0.0.0-20260823094646-e24805439c9c h1:JdEDkgiwsDxiZb4+dQYgVOU/lND+jZxFmN5y/q49EXk=
github.com/sagernet/bbolt v0.0.0-20260823094646-e24805439c9c/go.mod h1:7/qFPFH5fqK5XQeY8EA1Qpaly08Av77p38Zt50ieQww=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=