Files
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

5.6 KiB

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 双击导入钥匙串。
  • 验证已就位:
    security find-identity -v -p codesigning
    # 应能看到:  "Developer ID Application: Your Name (TEAMID1234)"
    
    把引号里的全名填到 .env.signingAPPLE_SIGNING_IDENTITY

2. 生成 App Store Connect API Key(用于公证,推荐)

  • App Store Connect → Users and AccessIntegrationsKeys → 生成一个 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。三选一组,二者填其一即可。


本地配置 + 构建

# 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。

验证(发出去前自检)

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.jsonbundle.macOS.entitlements 指定 plist。 查看具体原因:xcrun notarytool log <submission-id> --key ... --key-id ... --issuer ...
  • 只签名没公证:别人会看到「无法验证开发者」(不是「已损坏」)。补上公证变量即可。