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 目录。

01

先把“生成文件”和“完成发布”分开

Electron 官方把分发拆成多个阶段:先打包资源,再进行代码签名,之后发布安装包,并单独处理更新。Electron Forge 可以统一这些工具,但它不会消除 macOS 对证书、钥匙串、公证和最终安装环境的要求。Electron 分发总览

因此,Windows 或 Linux 能否生成一个 macOS 文件,只能回答“跨平台打包是否成功”,不能直接回答“这个文件是否可以交付”。

更准确的判断是:

  • 纯 JavaScript、无原生依赖的项目:Windows 或 Linux 可能可以生成未经签名的 macOS 产物,用于早期结构检查。
  • 包含原生 Node 模块的项目:必须确认模块是否提供 darwin-arm64darwin-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 发布公告

02

Electron 44 macOS 打包的真正阻塞点

原生模块会改变跨平台结论

Electron 的 JavaScript 代码可以跨平台复用,但原生模块不是普通源码。SQLite、图像处理、加密、串口、USB、系统托盘扩展等依赖,可能带有 .node 文件、预编译二进制或平台专用辅助进程。

我们建议在进入发布节点前做三项核对:

  • 检查锁文件中所有原生依赖的目标平台和架构。
  • 在全新工作区执行安装,不复用 Windows 或 Linux 的 node_modules
  • 在目标 Mac 上重新执行原生模块重建,并保存构建日志和模块加载结果。

Electron 官方安装文档将 macOS 目标区分为 darwin,并将 x64arm64 作为不同架构处理。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 签名指南

公证链路至少要分成以下状态:

  1. 制品上传成功;
  2. 公证服务返回通过或失败;
  3. 下载并保存公证日志;
  4. 使用 stapler 将票据装订到应用或磁盘映像;
  5. 在干净用户账户下重新安装并启动;
  6. 检查自动更新是否仍能识别签名身份。

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 公证工作流说明

03

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 迁过去。

04

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.platformprocess.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 的能力会受到代码签名状态影响。只验证主窗口能启动,无法覆盖这些真实发布风险。

05

发布节点应该购买、租用,还是双轨保留

我们的建议不是把所有任务都搬到 Mac,而是按发布风险分层。

低频发布:先租用远程 Mac

如果团队每周或每月才发布一次,短周期远程 Mac 更适合做第一次真实验收。重点不是追求某个未经实测的构建耗时,而是确认:

  • Electron 44 的目标架构是否正确;
  • 原生模块是否能在干净环境加载;
  • 签名身份和 entitlements 是否匹配;
  • 公证日志是否可追踪;
  • 票据和 Gatekeeper 验证是否通过;
  • 更新链路是否能从上一版升级。

如果需要查看不同地区的 Mac 节点选项,可以从 MESHLAUNCH 的远程 Mac 租赁方案开始比较,再根据发布时段和团队网络选择节点。

高频发布:再评估专用节点

当每天都有发布任务,或者多个项目共用同一套签名身份时,长期专用 Mac 的价值会增加。但专用节点不等于“配置一次就结束”,仍要安排:

  • Xcode 和命令行工具更新窗口;
  • 钥匙串权限审计;
  • 构建目录和缓存清理;
  • 重启后的自动恢复测试;
  • 证书和 API 密钥轮换;
  • 主节点不可用时的备用发布路径。

高风险发布:保留双轨路径

涉及大规模用户、关键客户或严格回滚要求时,建议保留短周期备用远程 Mac,或准备第二个经过验收的发布节点。真正需要避免的是把唯一签名身份、唯一钥匙串和唯一构建主机绑定在一台没有恢复记录的机器上。

06

FAQ:5 个发布判断

Windows 节点能否产出 macOS 安装文件?

可以生成部分未签名产物,但不能把它直接视为生产安装包。Electron 官方的分发流程还包括代码签名、发布与更新;只要涉及原生模块、Keychain、公证和 Gatekeeper,最终任务就应转移到真实 Mac 环境完成。

macOS 签名环节为何通常放在 Mac 上?

签名依赖 macOS 的钥匙串、证书私钥、Xcode 工具链和嵌套组件处理。公证可以通过命令行或 Notary API 自动化,但这不代表其他系统能够可靠替代 macOS 完成签名后的启动、权限、登录项和安装验收。

Forge 项目怎样在自动化流水线中接入公证?

在 Forge 的 packagerConfig 中配置 osxSignosxNotarize,将认证信息注入受控环境,再等待公证结果。发布任务还要下载日志、执行 stapler validatespctl,并把提交 ID 与最终制品校验值保存到构建记录。

Windows 主力团队怎样交付 Mac 版本?

先在 Windows 节点完成测试并固定提交标识,再把同一份源码或制品发送到远程 Mac。远程 Mac 负责目标架构安装、原生模块重建、Forge 打包、签名、公证、票据装订和干净账户安装测试,最后才上传给用户。

发布节点是否必须全年在线?

低频项目不需要。可以在发布窗口使用短周期远程 Mac,完成一次完整的生产验收;高频项目则更适合长期专用节点,但必须配置钥匙串隔离、重启恢复、日志归档和备用路径,而不是让节点无限承担其他 CI 任务。

如果当前方案只有 Windows 或 Linux,继续让它独自承担 macOS 发布,会遇到原生模块架构无法确认、签名凭据缺少安全边界、公证后无法在真实系统验收等问题。购买 Mac 能解决物理环境问题,却会带来闲置硬件、系统维护和单节点故障;普通云服务器也不能替代完整的 macOS 签名链路。

对低频或首次发布的团队,我们更建议先通过 MESHLAUNCH 的 Mac 租赁入口跑通一次从干净工作区到安装更新的 Electron 44 流程,再根据发布频率决定是否保留长期专用节点。