Files
jiu/scripts/ci/compile-ios.sh
T
wangjia dccf3610b5 fix(ci): iOS 改用本机 login keychain 证书+本地 profile+本地 .p8 签名
CI iOS 报 'No valid code signing certificates':原 IOS_DIST_CERT_P12_BASE64 secret
里的证书过期。改用本机 login keychain 的有效 Apple Distribution 证书(到2027-07)
+ 自动发现的 App Store profile + 本机 .p8,不再依赖 base64 secret。
team/key-id 带默认,issuer 走 gitea secret APPSTORE_API_ISSUER_ID(已设)。
本地实测:过了证书+profile 检查、进入 archive(签名修复生效),后续 iOS 平台组件另议。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
2026-07-20 09:26:49 +08:00

123 lines
4.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# compile-ios.sh <tag> — build signed iOS IPA and upload to TestFlight (App Store Connect)
#
# 用**本机 login keychain 的 Apple Distribution 证书** + **本机已装的 App Store
# provisioning profile** + **本机 App Store Connect key(.p8)** 签名/上传。
# mac-runner 就是开发机,这些分发材料常驻本机,不再从 base64 secret 导入
# (原方案 IOS_DIST_CERT_P12_BASE64 里的证书过期 → archive 报 "No valid code
# signing certificates";本机 login 里那张有效,2026-07 改用本机材料)。
#
# 需要 env(非机密标识,gitea secret/变量;缺则用已知默认):
# IOS_TEAM_ID(默认 BYL4KQHMTN)/ APPSTORE_API_KEY_ID(默认 3PZTHR8YMJ)/
# APPSTORE_API_ISSUER_ID(必填,无默认)
# 需要本机:Apple Distribution 证书在 login keychain;App Store profile 已装;
# ~/.appstoreconnect/private_keys/AuthKey_<KEY_ID>.p8 存在。
set -euo pipefail
# shellcheck source=scripts/ci/_env.sh
. "$(dirname "$0")/_env.sh"
TAG="$1"
VER="${TAG#client-v}"
# CFBundleVersion(build 号)必须单调递增,否则 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}"
TEAM_ID="${IOS_TEAM_ID:-BYL4KQHMTN}"
KEY_ID="${APPSTORE_API_KEY_ID:-3PZTHR8YMJ}"
ISSUER="${APPSTORE_API_ISSUER_ID:-}"
BUNDLE_ID="com.yanmei.jiu"
P8="$HOME/.appstoreconnect/private_keys/AuthKey_${KEY_ID}.p8"
# --- 前置检查:本机分发材料齐备(缺则 fail-fast,不产出未签名/无法上传的包)---
if ! security find-identity -v -p codesigning \
| grep -qF "Apple Distribution: Yanmei (beijing) Technology Co., Ltd (${TEAM_ID})"; then
echo "ERROR: login keychain 未找到 Apple Distribution 证书(团队 ${TEAM_ID})——无法签名 iOS。" >&2
echo " 在本机 Xcode 登录 Apple ID / 装好分发证书后重试。" >&2
exit 1
fi
if [ ! -f "$P8" ]; then
echo "ERROR: 缺 App Store Connect key: $P8" >&2
exit 1
fi
if [ -z "$ISSUER" ]; then
echo "ERROR: 缺 APPSTORE_API_ISSUER_ID(App Store Connect Issuer ID)——公证/上传需要。" >&2
exit 1
fi
WORK="$(mktemp -d)"
# shellcheck disable=SC2064
trap "rm -rf '$WORK'" EXIT
# --- 自动发现已装的 App Store 分发 profile(AppID=TEAM.BUNDLE、无 ProvisionedDevices)---
PROFILE_NAME=""
PDIR="$HOME/Library/MobileDevice/Provisioning Profiles"
if [ -d "$PDIR" ]; then
for p in "$PDIR"/*.mobileprovision; do
[ -e "$p" ] || continue
plist="$(security cms -D -i "$p" 2>/dev/null)" || continue
appid="$(printf '%s' "$plist" | /usr/libexec/PlistBuddy -c 'Print :Entitlements:application-identifier' /dev/stdin 2>/dev/null || true)"
# App Store profile:AppID 匹配且无 ProvisionedDevices(区别于开发/AdHoc profile)
if [ "$appid" = "${TEAM_ID}.${BUNDLE_ID}" ] \
&& ! printf '%s' "$plist" | /usr/libexec/PlistBuddy -c 'Print :ProvisionedDevices' /dev/stdin >/dev/null 2>&1; then
PROFILE_NAME="$(printf '%s' "$plist" | /usr/libexec/PlistBuddy -c 'Print :Name' /dev/stdin 2>/dev/null || true)"
[ -n "$PROFILE_NAME" ] && break
fi
done
fi
[ -n "$PROFILE_NAME" ] || PROFILE_NAME="Jiu App Store" # 已知已装 profile 名兜底
echo "==> compile-ios: 使用 provisioning profile '${PROFILE_NAME}'"
# --- 同步版本号 ---
sed -i '' "s/^version:.*/version: ${VER}+${BUILD}/" client/pubspec.yaml
# --- ExportOptions.plist(manual 签名,app-store 分发)---
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>${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
# --- 构建签名 IPA(用 login keychain 的分发证书 + 上面的 profile)---
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 2>/dev/null | head -1)"
if [ -z "$IPA" ] || [ ! -f "$IPA" ]; then
echo "ERROR: IPA not found" >&2
exit 1
fi
echo "==> compile-ios: built ${IPA}"
# --- 上传 TestFlight(App Store Connect;本机 .p8 由 key-id 自动定位)---
echo "==> compile-ios: uploading to TestFlight"
xcrun altool --upload-app -f "$IPA" -t ios \
--apiKey "${KEY_ID}" \
--apiIssuer "${ISSUER}"
echo "==> compile-ios: done — uploaded build ${BUILD} to TestFlight"