feat(ci/release): iOS — raw codesign + altool upload (bypass xcodebuild)
ci / web (push) Successful in 29s
ci / rust (push) Failing after 44s
ci / docs-site (push) Successful in 31s
apple / swift (push) Successful in 1m17s
ci / bench (push) Successful in 1m36s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
deb / build-publish (push) Successful in 2m0s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 5m3s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 5m2s
docker / deploy-docs (push) Successful in 18s
ci / web (push) Successful in 29s
ci / rust (push) Failing after 44s
ci / docs-site (push) Successful in 31s
apple / swift (push) Successful in 1m17s
ci / bench (push) Successful in 1m36s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
deb / build-publish (push) Successful in 2m0s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 5m3s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 5m2s
docker / deploy-docs (push) Successful in 18s
xcodebuild's signing-identity selection enforces an online revocation/OCSP check that excludes the freshly-minted Apple Distribution cert (find-identity -v drops it) even though verify-cert confirms it's valid and codesign signs with it fine. So sign iOS the same way as the macOS DMG: archive CODE_SIGNING_ALLOWED=NO, embed the profile, raw 'codesign --keychain' with the profile's entitlements (extracted via plutil), package the .ipa, and upload with 'xcrun altool --upload-app'. Drops the xcodebuild manual-signing path entirely — no profile-dir install, no Xcode-quit, no provisioning-profile discovery. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -273,13 +273,13 @@ jobs:
|
|||||||
echo "::warning::iOS platform SDK not installed on this runner — skipping iOS/TestFlight."
|
echo "::warning::iOS platform SDK not installed on this runner — skipping iOS/TestFlight."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
# App Store signing: MANUAL with the Apple Distribution identity + an App Store
|
# App Store signing: archive UNSIGNED, then raw `codesign` with the Apple Distribution
|
||||||
# provisioning profile (IOS_PROFILE_B64). Automatic signing during `archive` resolves
|
# identity + the profile's entitlements — exactly like the macOS DMG. NOT xcodebuild
|
||||||
# to iOS App *Development* — it wants an Apple Development cert (and tries to revoke
|
# manual signing: its signing-identity selection enforces an online revocation/OCSP
|
||||||
# the account's orphaned one) plus a dev profile, neither of which we have or want.
|
# check that drops a freshly-minted cert (find-identity -v excludes it) even though the
|
||||||
# Manual distribution signing skips all of that. Gate on the MATCHING identity list
|
# cert is genuinely valid (verify-cert passes) and codesign signs with it fine. This
|
||||||
# (find-identity without -v): a fresh cert can be dropped from -v by a pending
|
# also means no provisioning-profile discovery, so no Xcode-pruning / profile-dir dance.
|
||||||
# revocation check that codesign does NOT enforce.
|
# Gate on the MATCHING identity list.
|
||||||
if ! security find-identity -p codesigning "$KEYCHAIN" | grep -q "Apple Distribution"; then
|
if ! security find-identity -p codesigning "$KEYCHAIN" | grep -q "Apple Distribution"; then
|
||||||
echo "::warning::no Apple Distribution identity present — set IOS_DIST_CERT_P12_B64. Skipping iOS/TestFlight."
|
echo "::warning::no Apple Distribution identity present — set IOS_DIST_CERT_P12_B64. Skipping iOS/TestFlight."
|
||||||
exit 0
|
exit 0
|
||||||
@@ -288,86 +288,60 @@ jobs:
|
|||||||
echo "::warning::IOS_PROFILE_B64 not set — need an App Store provisioning profile for io.unom.punktfunk. Skipping iOS/TestFlight."
|
echo "::warning::IOS_PROFILE_B64 not set — need an App Store provisioning profile for io.unom.punktfunk. Skipping iOS/TestFlight."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
# A running Xcode.app actively MANAGES ~/Library/Developer/Xcode/UserData/Provisioning
|
# Decode the App Store provisioning profile + its embedded entitlements. A
|
||||||
# Profiles — it prunes manually-installed profiles it doesn't recognise from its
|
# .mobileprovision is a CMS-signed plist; security cms is flaky on this runner, so
|
||||||
# account, deleting ours from the very dir xcodebuild reads right before signing
|
# openssl smime is the fallback that actually works.
|
||||||
# (hence 'No profile matching ...' even though the file was installed). Quit it;
|
|
||||||
# headless CI doesn't need the GUI Xcode and xcodebuild runs independently.
|
|
||||||
osascript -e 'tell application "Xcode" to quit' >/dev/null 2>&1 || true
|
|
||||||
pkill -x Xcode 2>/dev/null || true
|
|
||||||
sleep 2
|
|
||||||
# Stage the App Store provisioning profile + read its Name/UUID (the archive +
|
|
||||||
# export reference it by name; Xcode finds it by UUID under the profiles dirs).
|
|
||||||
printf '%s' "$IOS_PROFILE_B64" | tr -d '\r\n ' | base64 -d > "$RUNNER_TEMP/appstore.mobileprovision" \
|
printf '%s' "$IOS_PROFILE_B64" | tr -d '\r\n ' | base64 -d > "$RUNNER_TEMP/appstore.mobileprovision" \
|
||||||
|| { echo "::warning::IOS_PROFILE_B64 is not valid base64 — skipping iOS"; exit 0; }
|
|| { echo "::warning::IOS_PROFILE_B64 is not valid base64 — skipping iOS"; exit 0; }
|
||||||
echo "profile bytes: $(wc -c < "$RUNNER_TEMP/appstore.mobileprovision")"
|
|
||||||
# A .mobileprovision is a CMS-signed plist — extract it (security, openssl fallback).
|
|
||||||
security cms -D -i "$RUNNER_TEMP/appstore.mobileprovision" \
|
security cms -D -i "$RUNNER_TEMP/appstore.mobileprovision" \
|
||||||
-o "$RUNNER_TEMP/appstore-profile.plist" 2>"$RUNNER_TEMP/cms.err" \
|
-o "$RUNNER_TEMP/appstore-profile.plist" 2>/dev/null \
|
||||||
|| openssl smime -inform DER -verify -noverify \
|
|| openssl smime -inform DER -verify -noverify \
|
||||||
-in "$RUNNER_TEMP/appstore.mobileprovision" \
|
-in "$RUNNER_TEMP/appstore.mobileprovision" \
|
||||||
-out "$RUNNER_TEMP/appstore-profile.plist" 2>>"$RUNNER_TEMP/cms.err" || true
|
-out "$RUNNER_TEMP/appstore-profile.plist" 2>/dev/null || true
|
||||||
if [ ! -s "$RUNNER_TEMP/appstore-profile.plist" ]; then
|
if [ ! -s "$RUNNER_TEMP/appstore-profile.plist" ]; then
|
||||||
echo "::warning::could not extract the plist from the profile — is IOS_PROFILE_B64 the base64 of the .mobileprovision FILE?"
|
echo "::warning::could not extract the profile plist — is IOS_PROFILE_B64 the base64 of the .mobileprovision FILE? Skipping iOS."
|
||||||
cat "$RUNNER_TEMP/cms.err" 2>/dev/null || true
|
|
||||||
echo "first bytes of decoded profile:"; head -c 64 "$RUNNER_TEMP/appstore.mobileprovision" | xxd | head -2 || true
|
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
PROFILE_NAME=$(/usr/libexec/PlistBuddy -c 'Print :Name' "$RUNNER_TEMP/appstore-profile.plist" 2>/dev/null)
|
plutil -extract Entitlements xml1 -o "$RUNNER_TEMP/ios-entitlements.plist" \
|
||||||
PROFILE_UUID=$(/usr/libexec/PlistBuddy -c 'Print :UUID' "$RUNNER_TEMP/appstore-profile.plist" 2>/dev/null)
|
"$RUNNER_TEMP/appstore-profile.plist" \
|
||||||
case "$PROFILE_UUID" in
|
|| { echo "::warning::profile has no Entitlements — skipping iOS"; exit 0; }
|
||||||
""|*[!A-Fa-f0-9-]*) echo "::warning::profile UUID not readable (got: '$PROFILE_UUID') — skipping iOS"; exit 0;;
|
|
||||||
esac
|
|
||||||
for d in "$HOME/Library/MobileDevice/Provisioning Profiles" \
|
|
||||||
"$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"; do
|
|
||||||
mkdir -p "$d"
|
|
||||||
cp "$RUNNER_TEMP/appstore.mobileprovision" "$d/$PROFILE_UUID.mobileprovision"
|
|
||||||
done
|
|
||||||
echo "iOS App Store profile: '$PROFILE_NAME' ($PROFILE_UUID)"
|
|
||||||
# Where did it land, and where does xcodebuild look? (manual-signing profile lookup
|
|
||||||
# reads ~/Library/MobileDevice + ~/Library/Developer/Xcode/UserData; confirm HOME.)
|
|
||||||
echo "HOME=$HOME whoami=$(whoami)"
|
|
||||||
echo "--- MobileDevice profiles ---"; ls -la "$HOME/Library/MobileDevice/Provisioning Profiles/" 2>&1 | tail -4
|
|
||||||
echo "--- UserData profiles ---"; ls -la "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/" 2>&1 | tail -4
|
|
||||||
echo "--- profile team(s) (want F4H37KF6WC) + name ---"
|
|
||||||
/usr/libexec/PlistBuddy -c 'Print :TeamIdentifier' "$RUNNER_TEMP/appstore-profile.plist" 2>/dev/null | head -4
|
|
||||||
security list-keychains -d user -s "$KEYCHAIN" login.keychain-db
|
security list-keychains -d user -s "$KEYCHAIN" login.keychain-db
|
||||||
security default-keychain -d user -s "$KEYCHAIN"
|
security default-keychain -d user -s "$KEYCHAIN"
|
||||||
|
# Archive UNSIGNED — no xcodebuild signing/provisioning at all.
|
||||||
DEVELOPER_DIR="$XCODE_DEV_DIR" xcodebuild archive \
|
DEVELOPER_DIR="$XCODE_DEV_DIR" xcodebuild archive \
|
||||||
-project "$PROJECT" -scheme Punktfunk-iOS \
|
-project "$PROJECT" -scheme Punktfunk-iOS \
|
||||||
-destination 'generic/platform=iOS' \
|
-destination 'generic/platform=iOS' \
|
||||||
-archivePath "$RUNNER_TEMP/Punktfunk-ios.xcarchive" \
|
-archivePath "$RUNNER_TEMP/Punktfunk-ios.xcarchive" \
|
||||||
MARKETING_VERSION="$VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUM" \
|
MARKETING_VERSION="$VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUM" \
|
||||||
CODE_SIGN_STYLE=Manual \
|
CODE_SIGNING_ALLOWED=NO
|
||||||
CODE_SIGN_IDENTITY="Apple Distribution" \
|
APP=$(ls -d "$RUNNER_TEMP/Punktfunk-ios.xcarchive/Products/Applications/"*.app | head -1)
|
||||||
DEVELOPMENT_TEAM="$TEAM_ID" \
|
echo "iOS app bundle: $APP"
|
||||||
PROVISIONING_PROFILE_SPECIFIER="$PROFILE_NAME" \
|
cp "$RUNNER_TEMP/appstore.mobileprovision" "$APP/embedded.mobileprovision"
|
||||||
|| { echo "=== archive failed — profile dirs AT FAILURE TIME ==="; \
|
# Inside-out: sign any nested Mach-O first (the static build usually has none), then
|
||||||
ls -la "$HOME/Library/MobileDevice/Provisioning Profiles/" 2>&1 | tail -4; \
|
# the app with the profile's entitlements + the Apple Distribution identity.
|
||||||
ls -la "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/" 2>&1 | tail -4; \
|
if [ -d "$APP/Frameworks" ]; then
|
||||||
exit 1; }
|
find "$APP/Frameworks" -depth \( -name '*.framework' -o -name '*.dylib' \) -print0 \
|
||||||
cat > "$RUNNER_TEMP/export-appstore.plist" <<EOF
|
| while IFS= read -r -d '' f; do
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
codesign --force --keychain "$KEYCHAIN" --sign "Apple Distribution" "$f"
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
done
|
||||||
<plist version="1.0">
|
fi
|
||||||
<dict>
|
codesign --force --keychain "$KEYCHAIN" \
|
||||||
<key>method</key><string>app-store-connect</string>
|
--entitlements "$RUNNER_TEMP/ios-entitlements.plist" \
|
||||||
<key>destination</key><string>upload</string>
|
--sign "Apple Distribution" "$APP"
|
||||||
<key>teamID</key><string>$TEAM_ID</string>
|
codesign --verify --strict --verbose=2 "$APP"
|
||||||
<key>signingStyle</key><string>manual</string>
|
# Package the .ipa.
|
||||||
<key>signingCertificate</key><string>Apple Distribution</string>
|
rm -rf "$RUNNER_TEMP/Payload" "$RUNNER_TEMP/Punktfunk.ipa"
|
||||||
<key>provisioningProfiles</key>
|
mkdir -p "$RUNNER_TEMP/Payload"
|
||||||
<dict><key>io.unom.punktfunk</key><string>$PROFILE_NAME</string></dict>
|
cp -R "$APP" "$RUNNER_TEMP/Payload/"
|
||||||
</dict>
|
( cd "$RUNNER_TEMP" && zip -qry Punktfunk.ipa Payload )
|
||||||
</plist>
|
# Upload to App Store Connect (TestFlight). altool reads the key from
|
||||||
EOF
|
# ~/.appstoreconnect/private_keys/AuthKey_<id>.p8.
|
||||||
DEVELOPER_DIR="$XCODE_DEV_DIR" xcodebuild -exportArchive \
|
ASC_KEY_ID="${{ secrets.ASC_API_KEY_ID }}"
|
||||||
-archivePath "$RUNNER_TEMP/Punktfunk-ios.xcarchive" \
|
mkdir -p "$HOME/.appstoreconnect/private_keys"
|
||||||
-exportOptionsPlist "$RUNNER_TEMP/export-appstore.plist" \
|
cp "$RUNNER_TEMP/asc.p8" "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8"
|
||||||
-exportPath "$RUNNER_TEMP/export-appstore" \
|
DEVELOPER_DIR="$XCODE_DEV_DIR" xcrun altool --upload-app -f "$RUNNER_TEMP/Punktfunk.ipa" \
|
||||||
-authenticationKeyPath "$RUNNER_TEMP/asc.p8" \
|
-t ios --apiKey "$ASC_KEY_ID" --apiIssuer "${{ secrets.ASC_API_ISSUER_ID }}"
|
||||||
-authenticationKeyID "${{ secrets.ASC_API_KEY_ID }}" \
|
rm -f "$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8"
|
||||||
-authenticationKeyIssuerID "${{ secrets.ASC_API_ISSUER_ID }}"
|
|
||||||
|
|
||||||
- name: Clean up keychain + API key
|
- name: Clean up keychain + API key
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
Reference in New Issue
Block a user