Logo
概览
从一次「App 已损坏」理解 macOS 软件发布

从一次「App 已损坏」理解 macOS 软件发布

2026年9月15日
4 分钟阅读

一次 Tauri 应用的真实发布问题:开发机上运行正常,用户从浏览器下载后,macOS 却提示“App 已损坏,无法打开”。

原始 Issue:macOS 无法打开应用的反馈 #1

这类问题很适合用来理解桌面软件发布。写完程序只是第一步,真正交付给用户时,还要处理构建、打包、签名、系统信任、安装测试和后续更新。


1. 先搞清楚:能运行,不代表能发布

开发时,我们通常只关心:

写代码 → 编译 → 运行

发布给别人后,链路会变长:

写代码
↓
Release 构建
↓
打包
↓
签名
↓
公证(正式 macOS 分发)
↓
上传
↓
用户下载
↓
系统检查
↓
安装与首次启动

所以“我电脑上能跑”只能说明程序本身基本可用,不能说明它已经适合分发。

这次反馈把排查重点指向了 分发与系统信任。Issue 中提到了 ad-hoc 签名,但仅凭报错和这条建议,还不能确认具体发行包的根因;仍需结合签名检查与下载测试判断。

本文以 Tauri 2 为例,整理这次反馈引出的发布知识。以下 macOS 命令需在 Mac 的终端中执行;构建命令在 Tauri 项目根目录执行,应用路径按实际名称替换。官方资料核对日期为 2026 年 9 月 15 日。


2. 为什么下载回来以后,macOS 的态度变了

同一个 .app,直接从开发目录启动和从浏览器下载回来,系统看到的并不完全一样。

浏览器下载的文件通常会带一个扩展属性:

com.apple.quarantine

它可以理解为一个来源标签:

这个文件来自互联网,首次运行时需要额外检查。

查看一个 App 的扩展属性:

Terminal window
xattr -l "/Applications/Codex Session Manager.app"

如果看到 com.apple.quarantine,说明它带有隔离标记。

有时网上会看到这条命令:

Terminal window
xattr -dr com.apple.quarantine "/Applications/Codex Session Manager.app"

它的含义只是:删除 quarantine 扩展属性。

  • xattr:操作 macOS 扩展属性
  • -d:删除
  • -r:递归处理整个 .app 包
  • com.apple.quarantine:要删除的属性

它可能绕过由隔离标记触发的部分检查,但不会修复损坏的文件、补齐签名或完成公证。仅在已确认来源与完整性的排查场景中使用,不应把它作为正常安装流程。


3. macOS 到底在检查什么

可以先记住四个概念:

Quarantine → 它从哪里来?
Code Signing → 谁发布的?内容有没有被改?
Notarization → Apple 是否检查过这个发行版本?
Gatekeeper → 综合以上信息,决定是否允许运行

3.1 Quarantine:记录来源

它主要告诉系统“这个文件来自外部”。它本身不等于病毒判断。

3.2 Code Signing:身份与完整性

代码签名可以近似理解成给发行包加一层可验证的封条。

系统能够检查:

  • 谁签了这个程序;
  • 签名后程序有没有被修改。

.app 其实是一个目录,里面还有可执行文件、资源、 Framework 等内容,例如:

MyApp.app/
└── Contents/
├── Info.plist
├── MacOS/
├── Resources/
├── Frameworks/
└── _CodeSignature/

因此,签名完成后再修改包内文件,很可能让签名失效。

3.3 Notarization:Apple 对发行版本的检查

正式在 Mac App Store 外分发应用时,常见流程是:

Developer ID 签名
↓
提交 Apple Notary Service
↓
检查通过
↓
Staple 公证凭据

Developer ID 主要建立开发者身份;Notarization 针对具体发行版本做自动化安全检查。

3.4 Gatekeeper:最后的入口检查

Gatekeeper 会综合来源、签名、公证等信息,决定是否允许软件运行。

因此用户看到“已损坏”,可能涉及文件损坏、签名失效或系统安全检查失败。不能直接把它等同于“缺少公证”,也不能承诺删除隔离标记就能解决。不同提示及手动允许条件见 Apple 的安全打开应用说明。


4. 没有 Apple Developer 账号时,Tauri 可以怎么做

没有付费开发者账号时,可以先使用 ad-hoc signing。

在 src-tauri/tauri.conf.json 中配置:

{
"bundle": {
"macOS": {
"signingIdentity": "-"
}
}
}

或者构建时指定(此处假设项目的 package.json 已定义 tauri:build 脚本,并调用 Tauri CLI 的 build 命令):

Terminal window
APPLE_SIGNING_IDENTITY="-" npm run tauri:build

这里的 - 表示 ad-hoc 签名。

ad-hoc 到底解决了什么

可以把两种签名简单区分为:

方式完整性签名Apple 可验证开发者身份适合场景
ad-hoc有无开发、测试、早期开源项目
Developer ID有有面向普通用户正式分发

ad-hoc 能让应用具备基本的签名结构,对 Tauri/macOS 构建很有价值,尤其是 Apple Silicon;但它不能替代 Developer ID 和 notarization。

Tauri 官方签名文档明确说明,ad-hoc 不会免除 macOS 的手动允许运行要求。系统是否提供“仍要打开”,还取决于具体提示和安全策略,不能把它视为所有“已损坏”报错的通用修复。

当前阶段比较合理的做法

Tauri Release Build
↓
ad-hoc signing
↓
生成 .app / .dmg
↓
实际通过浏览器下载测试
↓
在 README 写清楚首次启动方式

xattr -dr com.apple.quarantine ... 可以作为高级故障排查方案,但不建议把它写成普通用户的第一安装步骤。


5. 将来正式发布时,应该升级成什么流程

当项目用户变多,希望用户下载后直接获得更正常的 macOS 安装体验,就需要完整发布链路:

Release Build 与 App Bundle 准备
↓
Developer ID Application 签名
↓
按工具链准备提交包(如 ZIP / DMG)
↓
Notarization
↓
对支持的 App / DMG 执行 Staple
↓
验证最终发行物并发布

这里有一个很重要的顺序原则:

准备 App 内容
↓
完成签名
↓
按实际打包流程提交公证并 Staple
↓
验证用户最终拿到的发行物
↓
发布

签名之后再改二进制、Framework、资源或配置,都可能破坏签名。Staple 是附加公证票据的标准步骤,不等于任意修改包内内容。

公证提交包和最终下载包的关系取决于分发格式:不能把“生成 DMG”固定放在所有公证步骤之后。使用 Tauri 的自动签名、公证流程时,应按工具链处理打包顺序,再验证实际发布产物。参见 Apple 公证流程说明。


6. 遇到“已损坏”时,怎么查,而不是靠猜

用户界面的报错通常为了易懂,会隐藏很多底层细节。开发者应该直接检查发行物。

看 quarantine

Terminal window
xattr -l "/Applications/Codex Session Manager.app"

看签名信息

Terminal window
codesign -dv --verbose=4 "/Applications/Codex Session Manager.app"

验证签名

Terminal window
codesign --verify --deep --strict --verbose=4 \
"/Applications/Codex Session Manager.app"

让 macOS 安全策略评估

Terminal window
spctl --assess --type execute --verbose=4 \
"/Applications/Codex Session Manager.app"

可以这样理解:

codesign → 重点看签名本身是否有效
spctl → 重点看 macOS 是否愿意放行它

ad-hoc 签名的应用可能通过 codesign 完整性验证,却被 spctl 拒绝;这两项结果并不矛盾。策略评估也不能代替真实的下载与首次启动测试。

排查时记录 macOS 版本、CPU 架构、应用版本及命令输出,不要只盯着“已损坏”三个字,要继续追到系统真正拒绝的原因。


7. 发布测试:测用户拿到的版本

最容易犯的错误是:

npm run tauri:build
↓
直接双击 target 目录里的 .app
↓
能打开
↓
发布

这里没有模拟用户真正经历的过程。

更可靠的测试方式是:

生成最终发行物
↓
上传到 Release
↓
用浏览器重新下载
↓
解压 / 挂载 DMG
↓
拖入 Applications
↓
首次启动
↓
测试核心功能

最好再用另一台 Mac、另一个 macOS 用户或干净环境测试一次。

关键原则只有一句:

测试用户最终拿到的发行物,不要只测试构建目录中的产物。


8. 软件作者还需要考虑什么

桌面软件做到“功能能用”之后,还需要继续考虑这些问题。

构建与兼容性

  • Release 是否可以稳定复现;
  • Apple Silicon / Intel 是否都支持;
  • .app、.dmg 是否正确;
  • 依赖是否完整。

安全与信任

  • 是否签名;
  • 签名使用 ad-hoc 还是 Developer ID;
  • 正式版本是否 notarized;
  • 签名密钥和 CI Secret 是否安全;
  • 发布后是否还能验证文件完整性。

安装体验

用户不应该为了运行普通软件,被迫先学习一堆终端命令。

好的安装体验通常意味着:

下载 → 安装 → 打开 → 使用

如果必须有额外步骤,应在 README 和 Release Notes 中明确解释原因。

数据与升级

需要提前想清楚:

  • 配置和用户数据存在哪里;
  • 升级是否会丢数据;
  • 卸载会留下什么;
  • 自动更新如何验证更新包;
  • 新版本失败时能不能回退。

日志与诊断

开发机出错时可以看终端,用户通常只会说:

“打不开。”

因此软件最好能提供版本号、日志位置、诊断信息和清晰的 Issue 模板。


9. 一份够用的发布 Checklist

构建前

  • 版本号正确
  • Release 配置正确
  • Bundle Identifier 正确
  • 图标、名称正确
  • 确认目标 CPU 架构

构建与签名

  • 使用 Release Build
  • 没账号时至少配置 ad-hoc signing
  • 正式发布时使用 Developer ID
  • 正式发布完成 notarization / staple
  • 签名完成后不再修改包内代码与资源,仅执行标准公证 / staple 步骤

验证

  • codesign --verify 通过
  • spctl --assess 结果符合预期
  • 检查 quarantine 场景
  • 浏览器重新下载后测试
  • Apple Silicon / Intel 按支持范围测试

发布后

  • Release Notes
  • 安装说明
  • 已知问题
  • Issue 模板
  • 日志 / 诊断方法
  • 更新策略

10. 从这次问题真正学到的东西

这次经历可以浓缩成几条:

  1. 编译成功只是开发完成,不等于发布完成。
  2. 代码签名解决的是身份和完整性问题。
  3. Quarantine、Code Signing、Notarization、Gatekeeper 是不同层。
  4. ad-hoc signing 适合没有 Developer ID 时的过渡阶段,但不等于正式信任链。
  5. 发布测试必须覆盖“上传 → 下载 → 安装 → 首次启动”。
  6. 软件作者要为用户拿到软件之后的整个生命周期负责:安装、升级、数据、安全、诊断和维护。

把它们串起来,就是一条完整的软件交付链:

源码
↓
构建
↓
打包
↓
签名
↓
平台信任
↓
分发
↓
安装
↓
更新与维护

当开始关心这条链路时,就已经从“把程序写出来”进入了“把软件可靠地交付给别人”的阶段。


参考资料