Files
jiu/docs/macos-signing.md
T
wangjia 48e282dd97 feat(devops): macOS 构建接入 Developer ID 签名 + 公证
- compile-macos.sh:flutter build 后 codesign(hardened runtime + timestamp
  + Release.entitlements)→ notarytool 公证(复用 App Store Connect API key)
  → stapler staple → 重新打包,产出已签名+公证+stapled 的 .app
- 强制策略:缺 MACOS_DEVELOPER_ID_CERT_P12_BASE64 / APPSTORE_API_KEY_P8_BASE64
  时在 flutter build 前 fail-fast,绝不产出未签名包
- deploy-client.yml:build-macos 步骤注入证书与 API key secrets
- 新增 docs/macos-signing.md:Developer ID Application 证书创建与 secret 配置

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 12:56:08 +08:00

72 lines
4.6 KiB
Markdown
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.
# 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 <submission-id> --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。