本文是可直接复用的发布 SOP。目标是产出一个可以在其他 Mac 上正常打开的安装包,而不是只能在本机运行的临时构建。
1. 前置条件
必须满足以下条件:
- Apple Developer 付费账号(免费账号无法做 Notarization)。
- Keychain 中已安装
Developer ID Application证书,并且证书带私钥。 - 已安装 Xcode Command Line Tools(含
xcrun、notarytool、stapler)。 - Tauri 项目可正常本地构建。
检查证书:
security find-identity -v -p codesigning | grep "Developer ID Application"
如果没有输出 Developer ID Application: ...,先去 Apple Developer 后台申请并安装证书。
2. 准备 Notarization 凭据
2.1 生成 App-Specific Password
在 appleid.apple.com 生成应用专用密码(不是 Apple ID 登录密码)。
2.2 设置环境变量(示例)
export APPLE_SIGNING_IDENTITY="Developer ID Application: AHAKNOW LLC (HC559NT2NP)"
export APPLE_ID="ahaknow@gmail.com"
export APPLE_PASSWORD="你的 app-specific-password"
export APPLE_TEAM_ID="HC559NT2NP"
建议不要把示例占位符(如 YOUR NAME、TEAMID、...)原样复制进命令。
3. 生成多平台图标(避免 bundle 失败)
如果只放单一 icon.png,可能在 macOS 打包时报 No matching IconType。
在项目目录执行:
cd /Users/changkunyang/CKProjects/CodexSwitch/apps/desktop
npm run tauri icon -- src-tauri/icons/icon.png
这会生成 icon.icns、icon.ico、多尺寸 PNG 等资源。
4. 构建 universal macOS 包
先安装双架构 target:
rustup target add aarch64-apple-darwin x86_64-apple-darwin
然后构建:
cd /Users/changkunyang/CKProjects/CodexSwitch/apps/desktop
npm run tauri build -- --target universal-apple-darwin --bundles app --ignore-version-mismatches
产物路径(示例):
src-tauri/target/universal-apple-darwin/release/bundle/macos/Auth Switch Desktop.app
5. 验证签名是否为 Developer ID(不是 ad-hoc)
APP="/Users/changkunyang/CKProjects/CodexSwitch/apps/desktop/src-tauri/target/universal-apple-darwin/release/bundle/macos/Auth Switch Desktop.app"
codesign -dv --verbose=4 "$APP" 2>&1 | egrep "Authority=|TeamIdentifier=|Signature="
正确特征:
- 有
Authority=Developer ID Application: ... TeamIdentifier=你的 Team ID- 不能是
Signature=adhoc
6. 打 zip 并提交公证
建议使用绝对路径,避免 ditto: Cannot get the real path for source。
APP="/Users/changkunyang/CKProjects/CodexSwitch/apps/desktop/src-tauri/target/universal-apple-darwin/release/bundle/macos/Auth Switch Desktop.app"
ZIP="/Users/changkunyang/CKProjects/CodexSwitch/apps/desktop/src-tauri/target/universal-apple-darwin/release/bundle/macos/Auth-Switch-Desktop_0.1.0_macOS_universal.zip"
rm -f "$ZIP"
ditto -c -k --sequesterRsrc --keepParent "$APP" "$ZIP"
提交 Notarization:
xcrun notarytool submit "$ZIP" \
--apple-id "$APPLE_ID" \
--password "$APPLE_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait
返回 status: Accepted 才表示公证通过。
7. Staple 与最终 Gatekeeper 验证
xcrun stapler staple "$APP"
xcrun stapler validate "$APP"
spctl --assess --type execute -vv "$APP"
发布前应看到类似:
source=Notarized Developer IDaccepted
8. 上传 GitHub Release
上传第 6 步生成的 zip:
Auth-Switch-Desktop_0.1.0_macOS_universal.zip
建议同时附上 SHA256:
shasum -a 256 "$ZIP"
9. 常见错误与修复
9.1 source=no usable signature
原因:包是 ad-hoc 签名(未使用 Developer ID 证书)。
处理:回到第 5 步检查 codesign 输出,确认 Authority=Developer ID Application。
9.2 Team ID must be at least 3 characters
原因:--team-id 为空或变量未生效。
处理:命令里直接写死 --team-id "HC559NT2NP" 进行验证。
9.3 HTTP status code: 401. Invalid credentials
原因:--password 用了占位符或错误密码;必须是 app-specific password。
处理:重新生成 app-specific password 再提交。
9.4 Failed to create app icon: No matching IconType
原因:缺少 macOS 所需图标类型(.icns)。
处理:执行第 3 步 npm run tauri icon。
9.5 invalid icon / 像素数量不匹配
原因:PNG 位深或像素格式异常(例如 16-bit RGBA)导致解码不兼容。
处理:转成 8-bit RGBA 再生成图标。
9.6 ditto: Cannot get the real path for source
原因:当前目录不在 .app 所在目录。
处理:改用绝对路径。
10. 安全建议
- 不要把
APPLE_PASSWORD明文提交到仓库。 - 如果口令在聊天记录或日志泄露,立即在 Apple ID 页面撤销并重建。
- 可使用
notarytool store-credentials存到钥匙串,减少明文环境变量暴露。
xcrun notarytool store-credentials "ac-notary" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_PASSWORD"
xcrun notarytool submit "$ZIP" --keychain-profile "ac-notary" --wait
按本文流程执行后,最终验证标准只有两条:
spctl --assess返回accepted- 输出含
source=Notarized Developer ID
达到这两条就可以安全发布到 GitHub Releases。