From 4d93eb24ff3ed7eb0b8c459cd22f6260cac585fb Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sat, 13 Jun 2026 14:35:16 +0000 Subject: [PATCH] fix(ci/release): archive unsigned + codesign Developer ID directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xcodebuild's archive gate demands a provisioning profile for the app's keychain-access-groups entitlement (the 'Keychain Sharing' capability) under both automatic AND manual signing — even though a Developer ID app honours that team-prefixed entitlement at runtime with no profile. So manual signing just traded the -61 keychain error for 'requires a provisioning profile'. Sidestep the gate: archive with CODE_SIGNING_ALLOWED=NO, then codesign the app bundle directly with the Developer ID identity, hardened runtime and a secure timestamp, applying the entitlements via --entitlements (with $(AppIdentifierPrefix) resolved to the team prefix, which codesign won't expand). Safe because the bundle is a single statically-linked binary — static PunktfunkCore.xcframework, SPM static products, macOS 14 target, no Embed-Frameworks phase — so there is no nested code to sign inside-out. No Apple Developer portal profile or new secret needed. iOS App Store path unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitea/workflows/release.yml | 73 ++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index db228d2..07d41ba 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -120,50 +120,51 @@ jobs: printf '%s' "$ASC_P8" > "$RUNNER_TEMP/asc.p8" chmod 600 "$RUNNER_TEMP/asc.p8" - - name: Archive macOS + - name: Archive macOS (unsigned — signed by codesign below) run: | - # Manual Developer ID signing — same reasoning as the export step below. With - # -allowProvisioningUpdates the archive runs AUTOMATIC signing, which tries to mint - # an Apple Development cert + a "Mac App Development" profile for io.unom.punktfunk: - # installing that cert into the CI keychain fails (DVTSecErrorDomain -61 "Write - # permissions error") and no such profile exists — a Developer ID DMG needs - # neither. Pin the Developer ID identity and no profile: the app is non-sandboxed - # and its lone entitlement (keychain-access-groups, team-prefixed) is authorized by - # the Developer ID team itself, so no provisioning profile is required. The ASC key - # is still staged above for notarytool + the iOS App Store archive. + # Archive WITHOUT signing, then codesign with Developer ID in the next step. We do + # NOT let xcodebuild sign during archive because the app's keychain-access-groups + # entitlement is the "Keychain Sharing" capability, and Xcode's archive gate demands + # a provisioning profile for it under BOTH automatic and manual signing — even + # though a Developer ID app honours that team-prefixed entitlement at RUNTIME with + # no profile (the gate is an Xcode build-phase check, not a real requirement). Raw + # codesign has no such gate. Safe because the bundle is a single statically-linked + # binary: static PunktfunkCore.xcframework, SPM static products, macOS 14 target (no + # embedded Swift dylibs), and no Embed-Frameworks phase — so nothing nested to sign. DEVELOPER_DIR="$XCODE_DEV_DIR" xcodebuild archive \ -project "$PROJECT" -scheme Punktfunk \ -destination 'generic/platform=macOS' \ -archivePath "$RUNNER_TEMP/Punktfunk-macos.xcarchive" \ MARKETING_VERSION="$VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUM" \ - CODE_SIGN_STYLE=Manual \ - CODE_SIGN_IDENTITY="Developer ID Application" \ - DEVELOPMENT_TEAM="$TEAM_ID" \ - PROVISIONING_PROFILE_SPECIFIER="" + CODE_SIGNING_ALLOWED=NO - - name: Export macOS (Developer ID) + - name: Sign macOS app (Developer ID, hardened runtime) run: | - cat > "$RUNNER_TEMP/export-devid.plist" < - - - - methoddeveloper-id - teamID$TEAM_ID - destinationexport - - signingStylemanual - signingCertificateDeveloper ID Application - - - EOF - DEVELOPER_DIR="$XCODE_DEV_DIR" xcodebuild -exportArchive \ - -archivePath "$RUNNER_TEMP/Punktfunk-macos.xcarchive" \ - -exportOptionsPlist "$RUNNER_TEMP/export-devid.plist" \ - -exportPath "$RUNNER_TEMP/export-devid" + APP="$RUNNER_TEMP/Punktfunk-macos.xcarchive/Products/Applications/Punktfunk.app" + # codesign does NOT expand $(AppIdentifierPrefix) (an Xcode build-setting var), so + # resolve it to the real team prefix — otherwise keychain-access-groups would be the + # literal string instead of the team-scoped group. + RESOLVED="$RUNNER_TEMP/Punktfunk.entitlements" + sed "s/\$(AppIdentifierPrefix)/${TEAM_ID}./g" \ + clients/apple/Config/Punktfunk.entitlements > "$RESOLVED" + # Inside-out: sign any nested Mach-O first (defensive — the static build normally + # has none), then the app bundle with the resolved entitlements + hardened runtime + + # secure timestamp, which is what notarization requires. + if [ -d "$APP/Contents/Frameworks" ]; then + find "$APP/Contents/Frameworks" -depth \( -name '*.framework' -o -name '*.dylib' \) \ + -print0 | while IFS= read -r -d '' f; do + codesign --force --options runtime --timestamp \ + --sign "Developer ID Application" "$f" + done + fi + codesign --force --options runtime --timestamp \ + --entitlements "$RESOLVED" \ + --sign "Developer ID Application" "$APP" + codesign --verify --strict --verbose=2 "$APP" + # Stage where the DMG step expects it ($RUNNER_TEMP/export-devid/Punktfunk.app). + mkdir -p "$RUNNER_TEMP/export-devid" + rm -rf "$RUNNER_TEMP/export-devid/Punktfunk.app" + cp -R "$APP" "$RUNNER_TEMP/export-devid/Punktfunk.app" - name: Notarized DMG run: |