Files
jiu/scripts/ci/compile-ios.sh
T
wangjia 3810876395
Design Source Checks / design-source (push) Successful in 2m23s
Deploy Client / build-client-web (push) Successful in 3m4s
Deploy Client / build-windows (push) Successful in 3m57s
Deploy Client / build-macos (push) Successful in 4m16s
Deploy Client / build-android (push) Successful in 2m29s
Deploy Client / build-ios (push) Failing after 1m22s
Deploy Client / release-deploy-client (push) Has been skipped
ci: iOS 构建期临时钥匙串独占搜索列表——隔离本机其他项目的同名分发证书
根因:mac runner 即本机,login 钥匙串出现第二张「Apple Distribution」证书
(另一项目 2026-07-07 新建)后,xcodebuild 优先选中新证书,与 Jiu App Store
profile(嵌 6/16 正牌证书)不配对 → archive 失败。
修复:构建期 list-keychains 只留临时 keychain(fastlane 同款),trap 恢复 login。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-07 14:04:49 +08:00

130 lines
5.3 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# compile-ios.sh <tag> — build signed iOS IPA and upload to TestFlight (App Store Connect)
#
# 需要的 CI secrets(缺任意一个则优雅跳过,不阻塞流水线):
# IOS_DIST_CERT_P12_BASE64 Apple Distribution 证书 (.p12) 的 base64
# IOS_DIST_CERT_PASSWORD .p12 导出密码
# IOS_PROVISIONING_PROFILE_BASE64 App Store 类型 provisioning profile (.mobileprovision) 的 base64
# IOS_TEAM_ID Apple Developer Team ID10 位)
# APPSTORE_API_KEY_ID App Store Connect API Key ID
# APPSTORE_API_ISSUER_ID App Store Connect API Issuer ID
# APPSTORE_API_KEY_P8_BASE64 API Key (.p8) 的 base64
#
# 详见 docs/ios-signing.md。
set -euo pipefail
# shellcheck source=scripts/ci/_env.sh
. "$(dirname "$0")/_env.sh"
TAG="$1"
VER="${TAG#client-v}"
# CFBundleVersionbuild 号)必须单调递增,否则 TestFlight 拒绝重复上传。
MAJOR="$(echo "$VER" | cut -d. -f1)"
MINOR="$(echo "$VER" | cut -d. -f2)"
PATCH="$(echo "$VER" | cut -d. -f3)"
BUILD=$(( MAJOR * 10000 + MINOR * 100 + PATCH ))
echo "==> compile-ios: version=${VER} build=${BUILD}"
# --- 凭证缺失则跳过(账号/证书尚未配置时不阻塞发版)---
if [ -z "${IOS_DIST_CERT_P12_BASE64:-}" ] || \
[ -z "${IOS_PROVISIONING_PROFILE_BASE64:-}" ] || \
[ -z "${APPSTORE_API_KEY_P8_BASE64:-}" ]; then
echo "==> compile-ios: SKIP — iOS signing secrets not configured (see docs/ios-signing.md)"
exit 0
fi
WORK="$(mktemp -d)"
cleanup() {
# 恢复默认搜索列表(构建期临时独占,见下)——失败路径也必须恢复
security list-keychains -d user -s login.keychain-db 2>/dev/null || true
security delete-keychain "$KEYCHAIN" 2>/dev/null || true
rm -rf "$WORK"
rm -f "$HOME/Library/MobileDevice/Provisioning Profiles/jiu-ci.mobileprovision" 2>/dev/null || true
}
trap cleanup EXIT
# --- 1. 临时 keychain 导入发行证书 ---
KEYCHAIN="$WORK/jiu-ci.keychain-db"
KEYCHAIN_PWD="ci-temp-$$"
security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
echo "${IOS_DIST_CERT_P12_BASE64}" | base64 --decode > "$WORK/dist.p12"
security import "$WORK/dist.p12" -k "$KEYCHAIN" \
-P "${IOS_DIST_CERT_PASSWORD:-}" -T /usr/bin/codesign -T /usr/bin/security
# 允许 codesign 非交互访问私钥
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PWD" "$KEYCHAIN" >/dev/null
# 构建期独占搜索列表(fastlane 同款):只见临时 keychain,隔离本机其他项目的
# 同名「Apple Distribution」证书(2026-07-07 事故:login 里另一项目的新证书被
# xcodebuild 优先选中,与 Jiu App Store profile 不配对 → archive 失败)。
# trap cleanup 恢复 login,失败路径也不遗留。
security list-keychains -d user -s "$KEYCHAIN"
# --- 2. 安装 provisioning profile ---
PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PROFILES_DIR"
echo "${IOS_PROVISIONING_PROFILE_BASE64}" | base64 --decode > "$WORK/profile.mobileprovision"
# 解出 profile 的 Name 与 UUIDExportOptions 需要)
security cms -D -i "$WORK/profile.mobileprovision" > "$WORK/profile.plist"
PROFILE_NAME="$(/usr/libexec/PlistBuddy -c 'Print :Name' "$WORK/profile.plist")"
PROFILE_UUID="$(/usr/libexec/PlistBuddy -c 'Print :UUID' "$WORK/profile.plist")"
cp "$WORK/profile.mobileprovision" "$PROFILES_DIR/${PROFILE_UUID}.mobileprovision"
echo "==> compile-ios: profile '${PROFILE_NAME}' (${PROFILE_UUID})"
# --- 3. App Store Connect API Key(供上传使用)---
API_KEYS_DIR="$HOME/.appstoreconnect/private_keys"
mkdir -p "$API_KEYS_DIR"
echo "${APPSTORE_API_KEY_P8_BASE64}" | base64 --decode > "$API_KEYS_DIR/AuthKey_${APPSTORE_API_KEY_ID}.p8"
# --- 4. 同步版本号 ---
sed -i '' "s/^version:.*/version: ${VER}+${BUILD}/" client/pubspec.yaml
# --- 5. 生成 ExportOptions.plistmanual 签名,app-store 分发)---
BUNDLE_ID="com.yanmei.jiu"
cat > "$WORK/ExportOptions.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key><string>app-store</string>
<key>teamID</key><string>${IOS_TEAM_ID}</string>
<key>signingStyle</key><string>manual</string>
<key>uploadBitcode</key><false/>
<key>uploadSymbols</key><true/>
<key>provisioningProfiles</key>
<dict>
<key>${BUNDLE_ID}</key><string>${PROFILE_NAME}</string>
</dict>
</dict>
</plist>
EOF
# --- 6. 构建签名 IPA ---
cd client
flutter build ipa --release \
--build-name="${VER}" \
--build-number="${BUILD}" \
--export-options-plist="$WORK/ExportOptions.plist" \
"--dart-define=BASE_URL=https://jiu.51yanmei.com" \
"--dart-define=PUBLIC_URL=https://jiu.51yanmei.com" \
"--dart-define=APP_VERSION=v${VER}"
cd ..
IPA="$(ls client/build/ios/ipa/*.ipa | head -1)"
if [ -z "$IPA" ] || [ ! -f "$IPA" ]; then
echo "ERROR: IPA not found" >&2
exit 1
fi
echo "==> compile-ios: built ${IPA}"
# --- 7. 上传 TestFlightApp Store Connect---
echo "==> compile-ios: uploading to TestFlight"
xcrun altool --upload-app -f "$IPA" -t ios \
--apiKey "${APPSTORE_API_KEY_ID}" \
--apiIssuer "${APPSTORE_API_ISSUER_ID}"
echo "==> compile-ios: done — uploaded build ${BUILD} to TestFlight"