f7a720204a
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
114 lines
5.6 KiB
Markdown
114 lines
5.6 KiB
Markdown
# NomiFun 桌面 macOS 代码签名 + 公证(Gatekeeper)
|
|
|
|
> 解决「把安装包发给别人,对方打开提示**已损坏,无法打开**」的问题。
|
|
>
|
|
> 这跟 `updater/`(自动更新签名)是**两套完全不同的密钥**,别混。本目录只管
|
|
> Apple 的 **Developer ID 签名 + 公证(notarization)**,让 App 在任何 Mac 上双击即开。
|
|
|
|
## 发布责任边界
|
|
|
|
本仓库只提供签名脚本和无密钥模板。正式发布必须使用发布方自己的 Apple
|
|
Developer 账号、Developer ID 证书、App Store Connect API Key,以及独立的
|
|
自动更新签名密钥。不要把 fork、本地开发机或历史测试密钥当成官方发布凭据。
|
|
|
|
## 为什么会「已损坏」
|
|
|
|
默认 `bun run build` 产出的 App 只是 **ad-hoc 签名**(`Signature=adhoc`,无
|
|
`TeamIdentifier`)。别人下载/传输后,文件被打上 `com.apple.quarantine` 隔离标记;在
|
|
Apple 芯片 Mac 上,被隔离 + 未正规签名公证的 App,Gatekeeper 直接判为「已损坏」。
|
|
|
|
**根治办法只有一个**:用 **Developer ID Application** 证书签名 → 提交 Apple **公证** →
|
|
**staple** 把公证票据钉进 App。之后任何人下载双击即开,无任何提示。
|
|
|
|
## 密钥绝不入库(本仓库的约定)
|
|
|
|
| 东西 | 放哪 | 是否入库 |
|
|
|---|---|---|
|
|
| 模板 `.env.signing.example` | 本目录 | ✅ 入库(无密钥) |
|
|
| 真实 `.env.signing`(身份名 / Key ID / 路径) | 本目录 | ❌ 已 gitignore |
|
|
| App Store Connect API Key `AuthKey_*.p8` | `apps/desktop/.tauri/` 或仓库外 | ❌ 已 gitignore |
|
|
| Developer ID 证书私钥 | macOS **登录钥匙串**(不是文件) | ❌ 不在仓库里 |
|
|
|
|
构建脚本 `scripts/desktop-build-signed.sh`(可入库,无密钥)在运行时 `source` 本地
|
|
`.env.signing` 注入环境变量,Tauri 据此签名 + 公证。
|
|
|
|
---
|
|
|
|
## 一次性准备(在 Apple 侧)
|
|
|
|
### 1. 生成 Developer ID Application 证书并装进钥匙串
|
|
- 最简单:用 **Xcode**(Settings → Accounts → 选中团队 → Manage Certificates → `+` →
|
|
**Developer ID Application**),它会自动装进登录钥匙串。
|
|
- 或 developer.apple.com → Certificates → `+` → **Developer ID Application** → 按引导用
|
|
CSR 生成 → 下载 `.cer` 双击导入钥匙串。
|
|
- 验证已就位:
|
|
```bash
|
|
security find-identity -v -p codesigning
|
|
# 应能看到: "Developer ID Application: Your Name (TEAMID1234)"
|
|
```
|
|
把引号里的**全名**填到 `.env.signing` 的 `APPLE_SIGNING_IDENTITY`。
|
|
|
|
### 2. 生成 App Store Connect API Key(用于公证,推荐)
|
|
- App Store Connect → **Users and Access** → **Integrations** → **Keys** → 生成一个
|
|
**Developer** 角色的 Key。
|
|
- 下载 `AuthKey_XXXX.p8`(**只能下载一次**),放到 `apps/desktop/.tauri/`(已 gitignore)
|
|
或仓库外的安全目录。
|
|
- 记下两个值填进 `.env.signing`:
|
|
- **Issuer ID** = keys 表格**上方**那串 UUID → `APPLE_API_ISSUER`
|
|
- **Key ID** = 表格 "Key ID" 列 → `APPLE_API_KEY`
|
|
- `.p8` 路径 → `APPLE_API_KEY_PATH`
|
|
|
|
> 不想用 API Key 也可用 Apple ID 方式:`APPLE_ID` + `APPLE_PASSWORD`(App 专用密码,
|
|
> 在 appleid.apple.com 生成)+ `APPLE_TEAM_ID`。三选一组,二者填其一即可。
|
|
|
|
---
|
|
|
|
## 本地配置 + 构建
|
|
|
|
```bash
|
|
# 1. 复制模板(真实文件不入库)
|
|
cp apps/desktop/signing/.env.signing.example apps/desktop/signing/.env.signing
|
|
|
|
# 2. 按上面拿到的值填写 .env.signing,并把 AuthKey_*.p8 放到对应路径
|
|
|
|
# 3. 出带签名 + 公证的安装包(公证联网,首次几分钟,耐心等)
|
|
bun run build:signed
|
|
```
|
|
|
|
产物在 `target/release/bundle/{macos,dmg}/`。构建末尾会先由 Tauri 公证并 staple
|
|
`.app`,随后脚本会对最终分发用的 `.dmg` 再提交一次公证并 staple。
|
|
|
|
## 验证(发出去前自检)
|
|
|
|
```bash
|
|
APP=target/release/bundle/macos/NomiFun.app
|
|
DMG=target/release/bundle/dmg/NomiFun_0.1.0_aarch64.dmg
|
|
|
|
codesign -dvv "$APP" # 期望: Authority=Developer ID Application: ...
|
|
codesign --verify --deep --strict -v "$APP" # 期望: valid on disk / satisfies Designated Requirement
|
|
xcrun stapler validate "$APP" # 期望: The validate action worked!
|
|
spctl -a -vvv "$APP" # 期望: source=Notarized Developer ID → accepted
|
|
|
|
codesign --verify --strict -v "$DMG" # 期望: valid on disk / satisfies Designated Requirement
|
|
xcrun stapler validate "$DMG" # 期望: The validate action worked!
|
|
spctl -a -vvv -t open --context context:primary-signature "$DMG" # 期望: accepted
|
|
```
|
|
|
|
这些验证全过,就可以放心分发 DMG——别人下载双击即开,不再报「已损坏」。
|
|
|
|
## 常见报错
|
|
|
|
- **`The binary is not signed with a valid Developer ID certificate`**:钥匙串里没有
|
|
Developer ID Application 证书,或 `APPLE_SIGNING_IDENTITY` 名字写错。重看准备步骤 1。
|
|
- **`ambiguous (matches ... login.keychain-db and ... System.keychain)`**:同名
|
|
Developer ID Application 证书同时存在于多个钥匙串。删除多余副本,或把
|
|
`security find-identity -v -p codesigning` 输出中的 SHA-1 哈希填入
|
|
`APPLE_SIGNING_IDENTITY`。
|
|
- **`APPLE_API_KEY_PATH 必须指向 AuthKey_*.p8`**:`APPLE_API_KEY_PATH` 是 App Store
|
|
Connect API Key 路径,不要填 Developer ID `.p12` 证书路径。
|
|
- **公证被拒 / `Invalid` 状态**:多为「未启用 hardened runtime」或缺 entitlements。
|
|
Tauri 用 Developer ID 签名时默认开启 hardened runtime;若 App 需要特殊能力(JIT、
|
|
加载第三方动态库等),在 `tauri.conf.json` 的 `bundle.macOS.entitlements` 指定 plist。
|
|
查看具体原因:`xcrun notarytool log <submission-id> --key ... --key-id ... --issuer ...`。
|
|
- **只签名没公证**:别人会看到「无法验证开发者」(不是「已损坏」)。补上公证变量即可。
|