#!/usr/bin/env bash # compile-macos.sh — build Flutter macOS desktop, package into dist/ set -euo pipefail # shellcheck source=scripts/ci/_env.sh . "$(dirname "$0")/_env.sh" TAG="$1" VER="${TAG#client-v}" echo "==> compile-macos: version=${VER}" # macOS 通过官网下载 + 应用内自更新分发,未签名/未公证的包会被 Gatekeeper 拦截, # 因此【强制】签名+公证:缺任一关键 secret 立即报错、流水线红,绝不产出未签名包。 # 在跑昂贵的 flutter build 之前先做这一检查,快速失败。详见 docs/macos-signing.md。 # # 需要的 CI secrets: # MACOS_DEVELOPER_ID_CERT_P12_BASE64 Developer ID Application 证书 (.p12) 的 base64 # MACOS_DEVELOPER_ID_CERT_PASSWORD .p12 导出密码 # APPSTORE_API_KEY_ID / APPSTORE_API_ISSUER_ID / APPSTORE_API_KEY_P8_BASE64 复用 iOS 的 App Store Connect API key(公证用) if [ -z "${MACOS_DEVELOPER_ID_CERT_P12_BASE64:-}" ] || [ -z "${APPSTORE_API_KEY_P8_BASE64:-}" ]; then echo "ERROR: macOS 签名/公证所需 secret 未配置,拒绝产出未签名包。" >&2 echo " 需要 MACOS_DEVELOPER_ID_CERT_P12_BASE64 + APPSTORE_API_KEY_P8_BASE64,详见 docs/macos-signing.md" >&2 exit 1 fi # Sync Flutter pubspec version (BSD sed on macOS) sed -i '' "s/^version:.*/version: ${VER}+1/" client/pubspec.yaml # Build Flutter macOS cd client flutter build macos --release \ "--dart-define=BASE_URL=https://jiu.51yanmei.com" \ "--dart-define=PUBLIC_URL=https://jiu.51yanmei.com" \ "--dart-define=APP_VERSION=v${VER}" cd .. APP_SRC="client/build/macos/Build/Products/Release/jiu_client.app" # --- 代码签名 + 公证(Developer ID + Notarization)--- # secret 已在脚本开头校验过(缺失会 fail-fast),此处必有证书与 API key。 echo "==> compile-macos: signing + notarizing" WORK="$(mktemp -d)" KEYCHAIN="$WORK/jiu-mac.keychain-db" cleanup_mac() { security delete-keychain "$KEYCHAIN" 2>/dev/null || true rm -rf "$WORK" } trap cleanup_mac EXIT # 1. 临时 keychain 导入 Developer ID Application 证书 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 "${MACOS_DEVELOPER_ID_CERT_P12_BASE64}" | base64 --decode > "$WORK/devid.p12" security import "$WORK/devid.p12" -k "$KEYCHAIN" \ -P "${MACOS_DEVELOPER_ID_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 # 解出签名身份 "Developer ID Application: NAME (TEAMID)" IDENTITY="$(security find-identity -v -p codesigning "$KEYCHAIN" \ | grep 'Developer ID Application' | head -1 | sed -E 's/.*"(.*)"/\1/')" if [ -z "$IDENTITY" ]; then echo "ERROR: Developer ID Application identity not found in keychain" >&2 exit 1 fi echo "==> compile-macos: signing identity '${IDENTITY}'" # 2. inside-out 签名:先签嵌套 framework/dylib,再签 app(均启用 hardened runtime + 安全时间戳) if [ -d "${APP_SRC}/Contents/Frameworks" ]; then find "${APP_SRC}/Contents/Frameworks" \( -name '*.framework' -o -name '*.dylib' \) -print0 \ | xargs -0 -I{} codesign --force --options runtime --timestamp --sign "$IDENTITY" {} fi codesign --force --options runtime --timestamp \ --entitlements client/macos/Runner/Release.entitlements \ --sign "$IDENTITY" "${APP_SRC}" codesign --verify --deep --strict --verbose=2 "${APP_SRC}" # 3. 提交公证(复用 App Store Connect API key),阻塞等待结果 ditto -c -k --keepParent "${APP_SRC}" "$WORK/notarize.zip" echo "${APPSTORE_API_KEY_P8_BASE64}" | base64 --decode > "$WORK/AuthKey.p8" xcrun notarytool submit "$WORK/notarize.zip" \ --key "$WORK/AuthKey.p8" \ --key-id "${APPSTORE_API_KEY_ID}" \ --issuer "${APPSTORE_API_ISSUER_ID}" \ --wait # 4. staple 公证票据进 .app(离线也能通过 Gatekeeper),并核验 xcrun stapler staple "${APP_SRC}" xcrun stapler validate "${APP_SRC}" spctl -a -vvv -t install "${APP_SRC}" || true # 期望 source=Notarized Developer ID echo "==> compile-macos: signed + notarized + stapled" # Package .app bundle into zip(此时 APP_SRC 已签名+stapled) # ditto preserves symlinks, Unix permissions, and extended attributes (code-signing). # Python's zipfile follows symlinks and drops permissions — do NOT use it for .app bundles. mkdir -p dist ditto -c -k --keepParent "${APP_SRC}" dist/jiu-macos-x64.zip echo "Packaged dist/jiu-macos-x64.zip ($(du -sh dist/jiu-macos-x64.zip | cut -f1))" echo "==> compile-macos: done — dist/ contents:" ls -lh dist/