☰
pkg 签名公证全流程:四个坑与一份能跑的脚本
2026/10/16 6:16:11 网站建设 项目流程

pkg 签名公证全流程:四个坑与一份能跑的脚本

适合读者:负责 macOS 应用分发与发布的工程师

流水线全景

一个要分发给企业外用户的 pkg,完整链路五步:

组件签名(codesign) → 组装(productbuild) → 重签(productsign) → 公证(notarytool submit) → 装订票据(stapler)

每一步都有自己的脾气。下面四个坑全部来自同一条构建流水线的真实日志。

坑一:productsign 不认识 codesign 的选项

productsign: unrecognized option `--options=runtime' productsign: invalid option -- f

–options=runtime 是 codesign 的选项,作用是给可执行文件启用 Hardened Runtime;productsign 签的是安装包,没有这个概念,也不支持这个版本的 -f 强制覆盖。当时这两个警告之后侥幸完成了签名,但依赖"不认识的选项被忽略"这种行为,换个 Xcode 版本就可能直接失败。正确写法:

productsign--timestamp\--sign"Developer ID Installer: Your Company (TEAMID)"\YourApp_Install.pkg YourApp_Install-$(date+%Y%m%d).pkg

另一个容易混的点:pkg 重签用 Developer ID Installer 证书,app 组件签名用 Developer ID Application 证书。两套证书不通用,拿错证书 productsign 会直接报找不到身份。

坑二:非交互环境下的 Keychain 授权失败

公证脚本的报错链是这样的:

Error: An error occurred while accessing the keychain. User interaction is not allowed. Validating your credentials... Success. Error: No Keychain password item found for profile: MY_PROFILE The staple and validate action failed! Error 65.

逐行翻译:notarytool 联网验证 Apple ID 凭证成功(第三行 Success),但把凭证写入 keychain 时需要用户授权弹窗,构建机跑在 SSH/CI 等非交互会话里弹不出窗(前两行的错误),凭证没存进去,submit 找不到 profile(第四行),stapler 跟着失败(第五行)。

修复选其一。首选方案,在能弹窗的 GUI 终端里手动执行一次初始化,弹窗选择"始终允许":

xcrun notarytool store-credentials"MY_PROFILE"\--apple-id"your-apple-id@example.com"\--team-id XXXXXXXXXX\--password"xxxx-xxxx-xxxx-xxxx"# App 专用密码,非 Apple ID 密码

凭证会永久保存在 keychain 里,此后所有构建直接引用 profile 名,脚本里那行 store-credentials 删掉。次选方案,无人值守机器在脚本里先解锁 keychain:

security unlock-keychain-p"登录密码"~/Library/Keychains/login.keychain-db

这里的关键认知:store-credentials 是一次性的初始化操作,并非每次构建的前置步骤。把它写进构建脚本,等于让每次构建都依赖一次注定失败的弹窗。

坑三:Hardened Runtime 与动态库签名链

app 启用 Hardened Runtime 后,加载的每一个 dylib 都必须有签名,未签名的直接拒绝加载——错误发生在 dyld 阶段,日志往往只给一个线程回溯,容易误判为代码缺陷。三个配套规则:

# 空气投送(AirDrop)来的 dylib 带 com.apple.quarantine 属性,三步处理xattr-cr/path/to/lib.dylibchmod755/path/to/lib.dylib codesign-f--deep-s- /path/to/lib.dylib# 没有正式证书时 ad-hoc 签名

动态库的 install name 用 @executable_path 前缀做 app 内相对加载,硬编码绝对路径在部署位置变化时全部失效。给 UI 子进程(Qt 程序)做 ad-hoc 签名还解决过一个具体崩溃:Hardened Runtime 下弹多设备/证书选择对话框时闪退,codesign -f --deep -s -一签就好。

坑四:从聊天工具复制命令引来的全角引号

构建脚本曾经"怎么都跑不起来",终端停在 dquote> 续行提示符。原因是从聊天工具复制命令时,-m 参数里的引号被替换成了全角字符:

-m “替换字样" ← 开头是全角“,结尾是半角"

shell 把结尾的半角引号当成开引号,等一个永远不来的闭引号。Ctrl+C 退出,引号手动输入,问题消失。从任何富文本环境(微信、网页、Word)往终端贴命令,引号、空格都可能被静默替换成全角,长命令建议只复制路径部分,参数手敲。

脚本模板

#!/bin/bashset-eDATE=$(date"+%Y%m%d")oldPKG=YourApp_InstallpkgName=$oldPKG-$DATE.pkg productsign--timestamp\--sign"Developer ID Installer: Your Company (TEAMID)"\"$oldPKG.pkg""$pkgName"rm-rf"$oldPKG.pkg"xcrun notarytool submit"$pkgName"\--keychain-profile"MY_PROFILE"--waitxcrun stapler staple"$pkgName"echo"Done:$pkgName"

前置条件只有两个:keychain 里已经存好 MY_PROFILE 凭证(见坑二),登录钥匙串处于可用状态。发布前验证:

spctl-a-vv--typeinstall"$pkgName"stapler validate"$pkgName"

最后一条安全提醒:App 专用密码一旦以明文形式出现在日志、脚本或聊天记录里,就应当视为已泄露,去 appleid.apple.com 撤销旧的、生成新的,再用 store-credentials 重新初始化 profile。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询