写代码只是发布前的一半。要让别人放心地下载、双击就能用,还需要签名、公证,以及一轮很容易被忽略的隐私检查。

这一篇把发布链路上踩到的三个坑放在一起:权限跟着签名走、公证凭据、二进制里的本机路径。

权限跟着签名走:开关开着却不生效

辅助功能权限是绑定在 App 的签名上的。用临时签名(ad-hoc)重新编译,每次都会被系统当成一个新 App,权限失效;换一张签名证书,也一样。所以我把打包脚本固定成按顺序选证书:Developer ID、开发证书、最后才是临时签名,让本机安装和发布用的是同一个签名。

还遇到过一次很迷惑的现象:系统设置里 DockTouchBar 的开关是开着的,菜单里却仍然提示“需要授权”。排查发现,运行的和安装的是同一个版本、同一个签名,把 App 彻底退出再打开也一样,说明不是“授权后要重启”,而是系统对这个签名的回答就是“没授权”。原因是旧记录是用另一张证书授权的,列表里那一行看起来开着,其实对不上新签名。用 tccutil 把旧记录清掉,重新授权,问题就解决了。

这件事还带来一个界面上的教训:原来授权之后,这一项菜单会直接隐藏,用户看不到“有没有生效”,还以为选项没了。后来改成始终显示,有权限时打勾。状态类的设置,应该显示状态,而不是在满足条件时消失。

公证:先 App,后 DMG,凭据只能自己输入

公证分两步:先把 App 压成 zip 提交,通过后装订到 App 上;再做成 DMG,签名后再提交、装订。这样 App 从 DMG 里拖出来之后,离线也能通过系统检查。脚本会先确认凭据可用,不可用就立刻报错,不会等编译完了才发现。

保存凭据的过程有两个坑。第一,notarytool 的第一个问题是“API 私钥的路径”,用 Apple ID 的方式要直接回车留空,我一开始把 Apple ID 填在了这一行。第二,连续两次报 401:一是团队对应的是一个个人开发者账号,用的邮箱不是注册它的那个;二是 App 专用密码必须由同一个 Apple ID 生成。换成对的账号重新生成,才保存成功。

还有一条原则:App 专用密码属于凭据,只能直接输入到终端里,不要贴到聊天或文件里。一旦出现在别的地方,就当作泄露,立刻作废重建。AI 助手能做的,是准备好脚本和验证流程;输入密码这一步,必须由本人来。

验证结果:App 和 DMG 各提交一次,都是通过,都装订并验证成功。我把 App 从 DMG 里复制出来,再加上“来自网络”的隔离标记,系统检查仍然显示“已公证的开发者 ID”。

差点公开出去的:二进制里的本机路径

公开仓库之前,我对暂存区做了一轮安全检查:证书名、团队 ID、邮箱、本机路径、密码和密钥关键字,都是空的。但检查刚打好的 DMG 里的二进制时,发现了一个 strings 命令搜不到的问题:用 nm 看符号表,里面有调试映射条目,写着编译这台机器上的完整源码路径,包括用户名和项目文件夹的名字。

如果不处理,这些路径会随着 DMG 一起公开。修复很简单,在签名之前对二进制执行一次去除调试符号,这也让文件更小。因为二进制变了,需要重新编译、重新公证。复查时,从 DMG 里取出的 App,原始字节里的用户名、项目目录名、邮箱全部是零命中,符号表里也是零,整个 App 包扫描也是空的。

公开仓库的提交身份也要留意:用 GitHub 提供的 noreply 邮箱,而不是真实邮箱;最后再对全新克隆的仓库做一次扫描,包括整个 git 历史。教训是:发布二进制之前,要检查二进制本身,而不只是源码。

DMG:系统自带的工具就够

DMG 里放 App 和一个指向应用程序文件夹的快捷方式,用系统自带的 hdiutil 生成,DMG 本身也要签名。macOS 27 上 hdiutil 会提示“已弃用,请改用 diskutil image”,但仍然可用;为了兼容更早版本上的构建,暂时没有更换。