Windows 构建任务显示成功,但生成的 macOS 产物无法签名,用户安装时还触发 Gatekeeper 拦截。
最快解法:日常开发和通用测试继续放在 Windows 或 Linux;面向用户交付的 Electron 44 macOS 版本,在真实 Mac 上完成最终打包、代码签名、公证、票据装订和安装验证。2026 年更稳妥的 CI 结构,是让通用节点跑前置任务,远程 Mac 只承担 macOS 发布链路。
这篇文章适合三类人:
- 主要在 Windows 或 Linux 上开发 Electron 桌面应用,首次准备发布 macOS 版本的工程师。
- 需要把 Electron Forge、签名和公证接入自动化流水线的 DevOps 与发布工程师。
- 正在比较购买 Mac、短期租用远程 Mac,或长期保留专用发布节点的技术负责人。
最后更新于 2026 年 9 月 11 日,版本与流程核实自 Electron 官方文档、Electron Forge 文档、@electron/osx-sign 文档及 Apple Developer 公证文档。
⚠️ 我们讨论的是“可以交付给用户、能够通过 macOS 安全检查并支持后续更新”的生产制品,不是临时生成一个
.app目录。
先把“生成文件”和“完成发布”分开
Electron 官方把分发拆成多个阶段:先打包资源,再进行代码签名,之后发布安装包,并单独处理更新。Electron Forge 可以统一这些工具,但它不会消除 macOS 对证书、钥匙串、公证和最终安装环境的要求。Electron 分发总览
因此,Windows 或 Linux 能否生成一个 macOS 文件,只能回答“跨平台打包是否成功”,不能直接回答“这个文件是否可以交付”。
更准确的判断是:
- 纯 JavaScript、无原生依赖的项目:Windows 或 Linux 可能可以生成未经签名的 macOS 产物,用于早期结构检查。
- 包含原生 Node 模块的项目:必须确认模块是否提供
darwin-arm64、darwin-x64,或能在目标 Mac 上重新编译。 - 需要公开分发的项目:代码签名、公证、票据装订和 Gatekeeper 验证不能用“CI Job 成功”替代。
- 需要自动更新、Keychain 或登录项的项目:签名身份必须稳定,否则更新后可能出现权限提示、登录项失效或密钥访问异常。
Electron 44 已于 2026 年 8 月 25 日正式发布,发布公告列出的关键组件版本包括 Chromium 152.0.7977.54、V8 15.2 和 Node 24.18.1。因此,构建记录不能只写“使用 Electron 44”,还应固定 Node、包管理器、目标架构和依赖锁文件。Electron 44 发布公告
Electron 44 macOS 打包的真正阻塞点
原生模块会改变跨平台结论
Electron 的 JavaScript 代码可以跨平台复用,但原生模块不是普通源码。SQLite、图像处理、加密、串口、USB、系统托盘扩展等依赖,可能带有 .node 文件、预编译二进制或平台专用辅助进程。
我们建议在进入发布节点前做三项核对:
- 检查锁文件中所有原生依赖的目标平台和架构。
- 在全新工作区执行安装,不复用 Windows 或 Linux 的
node_modules。 - 在目标 Mac 上重新执行原生模块重建,并保存构建日志和模块加载结果。
Electron 官方安装文档将 macOS 目标区分为 darwin,并将 x64 与 arm64 作为不同架构处理。Apple Silicon 兼容不是把 x64 文件夹改名为 arm64,也不是看到应用能启动就算验收完成。Electron 平台与架构安装说明
建议在 CI 中增加一个原生模块检查脚本,至少输出:
process.platform
process.arch
Electron 版本
关键 .node 文件路径
关键模块加载结果
如果项目同时发布 Intel 和 Apple Silicon 版本,还要明确是分别构建两个安装包,还是构建 Universal 产物。不要在没有记录构建输入的情况下,把一台架构的 node_modules 复制到另一台架构继续使用。
签名不是打包后的装饰步骤
面向 macOS 用户分发时,Electron 官方建议对应用进行代码签名。签名会覆盖主应用、Helper、Framework、动态库和其他嵌套执行文件;某个内部组件未正确签名,最终仍可能在启动或公证阶段失败。Electron 代码签名文档
对于站外分发,通常需要使用 Developer ID Application 身份。签名配置还可能涉及:
entitlements.plist与继承 entitlements;- Hardened Runtime;
- Bundle ID 与 Team ID;
- 存放证书私钥的受控钥匙串;
- 执行 CI 任务的账户及其钥匙串解锁权限。
@electron/osx-sign 的文档列出了 Hardened Runtime、entitlements 和不同文件级签名选项。不要把证书私钥放进普通构建节点,也不要把真实 Team ID、密钥文件名或主机路径直接写进仓库。@electron/osx-sign 类型与配置说明
这里容易出现一个误判:公证服务可以通过自动化接口访问,不代表签名阶段可以脱离 macOS 随意完成。签名之后仍需在 macOS 上检查嵌套组件、权限、Keychain 访问、登录项和实际启动行为。
公证成功仍不等于可以交付
代码签名后,还要把制品提交到公证服务。Electron Forge 通过 @electron/notarize 接入公证,并支持应用专用密码、API 密钥或钥匙串配置文件等认证方式。Electron Forge macOS 签名指南
公证链路至少要分成以下状态:
- 制品上传成功;
- 公证服务返回通过或失败;
- 下载并保存公证日志;
- 使用
stapler将票据装订到应用或磁盘映像; - 在干净用户账户下重新安装并启动;
- 检查自动更新是否仍能识别签名身份。
Apple 已确认可以使用 notarytool 或 Notary API 接入自动化公证。官方文档还说明,自 2023 年 11 月 1 日起,公证服务不再接受 altool 或 Xcode 13 以前版本提交的上传任务。Apple 公证说明
公证结果通过后,仍应执行类似以下检查。示例中的路径全部使用占位符:
xcrun stapler validate "/path/to/Example.app"
spctl --assess --type execute --verbose=4 "/path/to/Example.app"
codesign --verify --deep --strict --verbose=4 "/path/to/Example.app"
不要只保留最后一行“发布成功”。公证日志中的警告、提交标识、制品校验值和失败原因,都应进入 CI 构建归档。Apple 文档建议保存并检查公证日志;票据装订则用于让离线环境也能验证分发文件。Apple 公证工作流说明
Windows/Linux 节点和远程 Mac 的职责对照
下面这张对照卡可以直接用于确定 CI 拓扑。
方案 A:全部在 Windows 或 Linux 完成
适合:只做早期开发,或只需要生成未经签名的临时产物。
- ✅ 代码编辑、Lint、单元测试和通用构建简单。
- ✅ 不需要马上管理 Mac 钥匙串。
- ❌ 原生模块目标架构验证不完整。
- ❌ 无法把真实签名、公证、票据和 Gatekeeper 验收纳入同一环境。
- ❌ 一旦公开发布,通常还要临时寻找 Mac 节点补步骤。
判断结果:不适合作为生产发布方案。
方案 B:Windows/Linux 加短周期远程 Mac
适合:低频发布、首次接入 macOS 分发、仍在验证产品市场的团队。
- ✅ 通用 CI 不被 Mac 任务长期占用。
- ✅ 可从干净工作区验证 Electron 44、原生模块和 Apple Silicon。
- ✅ 不需要立即购买并维护一台专用 Mac。
- ⚠️ 发布时必须传递固定提交标识和制品校验值。
- ⚠️ 每次发布都要重新检查 Xcode、命令行工具、钥匙串和安装环境。
判断结果:大多数首次发布团队应先选这一方案。
方案 C:Windows/Linux 加长期专用 Mac 发布节点
适合:发布频率高、签名凭据风险高、需要持续保留构建缓存或专用环境的团队。
- ✅ 可固定 macOS、Xcode、Node 和 Forge 配置。
- ✅ 适合将签名、公证、自动更新和回滚流程长期固化。
- ✅ 可以设置独立构建账户、专用钥匙串和审计日志。
- ❌ 需要承担节点更新、重启恢复、磁盘清理和凭据轮换。
- ❌ 仍然需要备用路径,避免单台 Mac 故障阻塞发版。
判断结果:稳定高频发布后再升级,不要一开始就把所有 CI 迁过去。
Electron Forge CI 接入的 6 个操作步骤
1.先固定提交和构建输入
Windows 或 Linux 节点完成:
git rev-parse HEAD
node --version
npm --version
npm ci
npm run lint
npm test
随后生成待发布清单,至少包括:
- Git 提交标识;
package-lock.json或其他锁文件校验值;- Electron 版本;
- 目标平台与架构;
- 构建脚本版本;
- 需要传给远程 Mac 的源码包或制品校验值。
不要让 Mac 节点在任务开始时重新拉取默认分支。否则两个节点可能使用不同提交,最后即使签名成功,也无法证明签名对象就是测试过的对象。
2.在全新工作区检查原生模块
远程 Mac 收到固定输入后,先清理工作区,再执行安装:
rm -rf node_modules
npm ci
npm rebuild
npm run verify-native
verify-native 可以由项目自行实现,重点是实际加载关键 .node 模块,并输出 process.platform、process.arch 与模块版本。失败时停止签名,不要把缺少架构文件的问题拖到公证阶段。
3.把 Forge 配置拆成签名和公证两部分
配置示例只保留占位符:
module.exports = {
packagerConfig: {
osxSign: {
identity: process.env.MAC_SIGNING_IDENTITY,
entitlements: './build/entitlements.mac.plist',
entitlementsInherit: './build/entitlements.mac.inherit.plist',
hardenedRuntime: true
},
osxNotarize: {
tool: 'notarytool',
appleApiKey: process.env.APPLE_API_KEY,
appleApiKeyId: process.env.APPLE_API_KEY_ID,
appleApiIssuer: process.env.APPLE_API_ISSUER
}
}
};
实际字段应以当前 Forge 和 @electron/osx-sign 文档为准。认证信息通过 CI 密钥注入,不写入 forge.config.js、日志或构建产物。Forge 的构建生命周期本身就包含原生 Node 插件重建、macOS 签名和公证等发布职责。Forge 构建生命周期
4.先验证签名身份,再执行打包
在 Mac 节点上先确认钥匙串状态:
security find-identity -p codesigning -v
xcode-select --print-path
xcrun --find notarytool
如果没有预期的签名身份,或者命令行工具指向了错误的 Xcode,流水线应立即失败。不要为了让 Job 继续而改用 ad-hoc 签名;那只能帮助定位打包问题,不能代表生产制品已经具备公开分发条件。
5.等待公证结果并保存原始日志
公证提交不要只执行上传命令。应保存提交 ID,并在同一任务中等待结果或通过后续轮询获取最终状态:
xcrun notarytool submit "/path/to/Example.zip" \
--keychain-profile "PLACEHOLDER_PROFILE" \
--wait \
--output-format json > notarization-result.json
失败时使用提交 ID 下载日志:
xcrun notarytool log "PLACEHOLDER_SUBMISSION_ID" \
--keychain-profile "PLACEHOLDER_PROFILE" \
notarization-log.json
Apple 也支持直接通过 Notary API 提交软件。如果团队已经有统一的凭据服务和审计层,可以考虑 API 方案;但无论使用命令行还是 API,都不能省略日志归档和最终制品验证。Apple Notary API 文档
6.在干净账户和重启后完成验收
最后不要只在执行签名的构建账户下打开应用。至少勾选以下项目:
- [ ] 全新克隆后成功执行
npm ci。 - [ ] 原生 Node 模块在目标架构下可加载。
- [ ]
codesign --verify通过。 - [ ] 公证提交状态为通过。
- [ ]
stapler validate通过。 - [ ]
spctl --assess不报告阻塞性问题。 - [ ] 干净用户账户可以安装并启动。
- [ ] 应用内登录、Keychain、通知和登录项功能正常。
- [ ] 自动更新可以识别当前版本并完成升级。
- [ ] Mac 重启后,构建账户、钥匙串权限和常驻任务仍符合预期。
- [ ] 上一版可发布制品仍被保留,可作为回滚入口。
Electron 官方特别提醒,safeStorage、登录项和部分依赖 Keychain 的能力会受到代码签名状态影响。只验证主窗口能启动,无法覆盖这些真实发布风险。
发布节点应该购买、租用,还是双轨保留
我们的建议不是把所有任务都搬到 Mac,而是按发布风险分层。
低频发布:先租用远程 Mac
如果团队每周或每月才发布一次,短周期远程 Mac 更适合做第一次真实验收。重点不是追求某个未经实测的构建耗时,而是确认:
- Electron 44 的目标架构是否正确;
- 原生模块是否能在干净环境加载;
- 签名身份和 entitlements 是否匹配;
- 公证日志是否可追踪;
- 票据和 Gatekeeper 验证是否通过;
- 更新链路是否能从上一版升级。
如果需要查看不同地区的 Mac 节点选项,可以从 MESHLAUNCH 的远程 Mac 租赁方案开始比较,再根据发布时段和团队网络选择节点。
高频发布:再评估专用节点
当每天都有发布任务,或者多个项目共用同一套签名身份时,长期专用 Mac 的价值会增加。但专用节点不等于“配置一次就结束”,仍要安排:
- Xcode 和命令行工具更新窗口;
- 钥匙串权限审计;
- 构建目录和缓存清理;
- 重启后的自动恢复测试;
- 证书和 API 密钥轮换;
- 主节点不可用时的备用发布路径。
高风险发布:保留双轨路径
涉及大规模用户、关键客户或严格回滚要求时,建议保留短周期备用远程 Mac,或准备第二个经过验收的发布节点。真正需要避免的是把唯一签名身份、唯一钥匙串和唯一构建主机绑定在一台没有恢复记录的机器上。
FAQ:5 个发布判断
Windows 节点能否产出 macOS 安装文件?
可以生成部分未签名产物,但不能把它直接视为生产安装包。Electron 官方的分发流程还包括代码签名、发布与更新;只要涉及原生模块、Keychain、公证和 Gatekeeper,最终任务就应转移到真实 Mac 环境完成。
macOS 签名环节为何通常放在 Mac 上?
签名依赖 macOS 的钥匙串、证书私钥、Xcode 工具链和嵌套组件处理。公证可以通过命令行或 Notary API 自动化,但这不代表其他系统能够可靠替代 macOS 完成签名后的启动、权限、登录项和安装验收。
Forge 项目怎样在自动化流水线中接入公证?
在 Forge 的 packagerConfig 中配置 osxSign 与 osxNotarize,将认证信息注入受控环境,再等待公证结果。发布任务还要下载日志、执行 stapler validate 和 spctl,并把提交 ID 与最终制品校验值保存到构建记录。
Windows 主力团队怎样交付 Mac 版本?
先在 Windows 节点完成测试并固定提交标识,再把同一份源码或制品发送到远程 Mac。远程 Mac 负责目标架构安装、原生模块重建、Forge 打包、签名、公证、票据装订和干净账户安装测试,最后才上传给用户。
发布节点是否必须全年在线?
低频项目不需要。可以在发布窗口使用短周期远程 Mac,完成一次完整的生产验收;高频项目则更适合长期专用节点,但必须配置钥匙串隔离、重启恢复、日志归档和备用路径,而不是让节点无限承担其他 CI 任务。
如果当前方案只有 Windows 或 Linux,继续让它独自承担 macOS 发布,会遇到原生模块架构无法确认、签名凭据缺少安全边界、公证后无法在真实系统验收等问题。购买 Mac 能解决物理环境问题,却会带来闲置硬件、系统维护和单节点故障;普通云服务器也不能替代完整的 macOS 签名链路。
对低频或首次发布的团队,我们更建议先通过 MESHLAUNCH 的 Mac 租赁入口跑通一次从干净工作区到安装更新的 Electron 44 流程,再根据发布频率决定是否保留长期专用节点。