aa7099ba94
将单一 CI/CD 流水线拆成三条互不影响的发布流水线,各有 tag 前缀、独立版本
序列与独立 CHANGELOG:
- client(client-v*):Flutter 全平台 + version.yaml 自更新清单
- site(site-v*):web/ Eleventy 营销站,不含 web 版 app
- server(server-v*):backend Go 服务 + 共享基建 nginx/systemd
新增 3 个 workflow(deploy-client/site/server.yml)替换 deploy.yml;CI 脚本
按 part 拆分为 compile-/release-/deploy-{client,site,server}.sh,抽出公共函数
lib-forgejo.sh;compile-{macos,android,ios,windows}.sh 改去 client-v 前缀;
manual.yml 按前缀路由回滚。
跨流水线解耦:version.yaml 归 client,后端每请求实时读取(不重启、不触发
server 流水线);官网下载页的版本徽章/下载链接/更新日志时间线运行时经
/api/v1/public/release 动态拉取(API 不可达回退构建时静态内容)。为此一次性
扩展后端 changelog 字段(version.go/public.go)与 download.njk 动态渲染。
CHANGELOG.md 重命名为 CHANGELOG-client.md,新增 CHANGELOG-site/server.md;
重写 /release 命令为 /release <part> [version];同步更新 CLAUDE.md 与部署文档。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
125 lines
4.9 KiB
Bash
Executable File
125 lines
4.9 KiB
Bash
Executable File
#!/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 ID(10 位)
|
||
# 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}"
|
||
|
||
# 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}"
|
||
|
||
# --- 凭证缺失则跳过(账号/证书尚未配置时不阻塞发版)---
|
||
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 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
|
||
# 把临时 keychain 加入搜索列表(保留默认 login keychain)
|
||
security list-keychains -d user -s "$KEYCHAIN" login.keychain-db
|
||
|
||
# --- 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 与 UUID(ExportOptions 需要)
|
||
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.plist(manual 签名,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. 上传 TestFlight(App 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"
|