# macOS 分发(Developer ID + 公证)— 一次性配置 macOS 客户端通过**官网下载 zip + 应用内自动更新**分发(非 Mac App Store)。要让用户双击/自更新重启时不被 Gatekeeper 拦截(「已损坏」「无法验证开发者」),CI 构建出的 `.app` 必须 **用 Developer ID Application 证书签名 + 提交 Apple 公证(Notarization)+ staple 票据**。 所有凭证通过 Forgejo Secrets 注入,**不入库**。macOS **强制签名+公证**:未配置证书 secret 时 `build-macos` job 会**直接报错、流水线红**,绝不产出未签名包(避免把会被 Gatekeeper 拦截的包发出去)。 > 与 iOS 的关系:公证复用 iOS 那套 **App Store Connect API key**,无需新建。只需**额外**做一张 macOS 专用的 **Developer ID Application** 证书(iOS 的 Apple Distribution 证书在这里不可用)。 ## 前置 - Apple Developer Program 账号(与 iOS 同一个,Team ID `BYL4KQHMTN`)。 - 已按 `docs/ios-signing.md` 配好 `APPSTORE_API_KEY_ID` / `APPSTORE_API_ISSUER_ID` / `APPSTORE_API_KEY_P8_BASE64`(公证直接复用,本文不再重复)。 ## 1. 创建 Developer ID Application 证书 **为什么不是 Apple Distribution**:Apple Distribution 证书只用于 App Store / TestFlight;官网自分发必须用 **Developer ID Application**,否则公证会失败。 两种方式任选: - **Xcode(推荐,最省事)**:Xcode → Settings → Accounts → 选中团队 → Manage Certificates → 左下 `+` → **Developer ID Application**。生成后证书带私钥落在「钥匙串访问」的「登录」里。 - **开发者后台**:Certificates, Identifiers & Profiles → Certificates → `+` → **Developer ID Application** → 用钥匙串「证书助理」生成的 CSR 上传 → 下载 `.cer` 双击导入钥匙串。 ## 2. 导出为 .p12 「钥匙串访问」→「我的证书」里找到 **Developer ID Application: …(BYL4KQHMTN)**,**连同私钥**一起右键导出为 `.p12`,设一个导出密码: ```bash base64 -i devid.p12 | pbcopy # 复制 base64 到剪贴板 ``` ## 3. 在 Forgejo 仓库配置 Secrets 仓库 → Settings → Actions → Secrets,**新增 2 项**: | Secret 名 | 值 | |-----------|-----| | `MACOS_DEVELOPER_ID_CERT_P12_BASE64` | 第 2 步 `.p12` 的 base64 | | `MACOS_DEVELOPER_ID_CERT_PASSWORD` | `.p12` 导出密码 | 公证用的 `APPSTORE_API_KEY_ID` / `APPSTORE_API_ISSUER_ID` / `APPSTORE_API_KEY_P8_BASE64` 已在 iOS 配置时存在,**复用即可**。 ## 4. 验证 打 `client-v*` tag 触发流水线,确认 `build-macos` job: - 日志出现 `signing + notarizing`(非 `SKIP`); - 出现 `signed + notarized + stapled`; - 在一台**没装过本 app 的干净 Mac** 上双击官网下载的 `jiu-macos-x64.zip` 解出的 `.app`,**不弹** Gatekeeper 警告直接打开; - 断网再双击仍能打开(票据已 staple 进 bundle)。 本地手动验证已签名包: ```bash codesign --verify --deep --strict --verbose=2 Jiu.app # 应无报错 xcrun stapler validate Jiu.app # The validate action worked! spctl -a -vvv -t install Jiu.app # source=Notarized Developer ID, accepted ``` ## 故障排查 - **公证失败**:`xcrun notarytool log --key … --key-id … --issuer …` 看详细原因。最常见三类: 1. 没用 `--options runtime`(hardened runtime 未启用); 2. 没带 `--timestamp`(安全时间戳缺失); 3. 用了 Apple Distribution 而非 **Developer ID Application** 证书。 - **`security import` 报私钥缺失**:导出 `.p12` 时没勾选私钥,重新从「我的证书」节点(不是单独的证书条目)导出。 - **`spctl` 显示 rejected**:通常是 staple 没成功或公证未通过——先确认 notarytool 返回 `Accepted`。 ## 工作原理 - `scripts/ci/compile-macos.sh`:开头先校验证书/API key secret,缺失立即 `exit 1`(fail-fast,不浪费一次 build);`flutter build macos` 后建临时 keychain 导入 Developer ID 证书 → inside-out `codesign`(hardened runtime + timestamp + Release.entitlements)→ `notarytool submit --wait`(复用 App Store Connect API key)→ `stapler staple` → 重新 `ditto` 打 `dist/jiu-macos-x64.zip`。**强制签名+公证,不产出未签名包。** - `.gitea/workflows/deploy-client.yml` 的 `build-macos` job:通过 `env` 注入上述 secrets。 - 自更新链路:`client/lib/core/update/app_updater_io.dart` 下载该 zip → `ditto` 解压 → 替换 `.app` → `open` 重启;因票据已 staple,重启的新 app 离线也通过 Gatekeeper。