你发布了 Electron build,发布页面也上线了,咖啡机还没煮好,第一张支持工单就来了。一个用户说应用始终没发现更新;另一个下载了却装不上;第三个还在跑旧版二进制文件,身份验证流程已经坏了,而你的 logs 里几乎看不到有用信息。
这就是 Electron 应用自动更新的残酷现实。API 更新器只是其中一个组件。生产环境的发布还取决于平台签名、传输策略、manifest、托管、生命周期事件、可观测性、发布控制以及回滚路径。任何一个环节被当成可选,一次常规补丁就可能变成通宵事故。
目录
- 引发本指南的凌晨 2 点更新事故
- 选择合适的 Electron 更新方案
- 在主进程中实现自动更新流程
- 为签名发布和清单文件配置 CI/CD
- 灰度发布、渠道与回滚策略
- 将自动更新视为一种安全控制手段
- 生产环境更新指南与检查清单
引发本指南的凌晨 2 点更新事故
该版本通过了 CI,看起来一切正常。周五深夜,一名开发者推送了未签名的 Electron build,发布任务上传了足够多的 assets,让发布看起来已经完整。应用在测试中能启动,但没人实际走过从已安装的生产 build 执行更新的路径。
凌晨 2:08,PagerDuty 向值班工程师发出警报。部分设备群的新认证流程失败,收到更新的用户无法完成登录。其他用户则停留在旧版本,因为 updater 无法验证或安装该 artifact。部分客户拿到的是有问题的发布,而其余设备运行着另一个版本,且原因不明。
调查按五项检查展开:
- 检查发布 feed。 二进制文件存在,但预期的 metadata 并未明确说明哪些客户端应接收它。Manifest 是发布 pipeline 与已安装客户端之间的契约,而不是可选的上传细节。
- 检查签名。 发布未正确签名,导致受影响平台上的验证失败。签名缺失或无效时,必须阻止发布。
- 对比客户端 logs。 更新错误从未到达中央遥测系统。应用吞掉了该事件并继续运行,导致团队缺乏可靠的证据。
- 检查发布控制。 没有内部渠道或分阶段群组。所有符合条件的客户端都使用同一个 feed,因此故障在没有遏制点的情况下扩散。
- 寻找回滚方案。 团队没有经过测试的流程来重新发布之前的版本,或将客户端引导至损坏版本之外。
Electron 官方文档清楚地说明了平台的限制。 Linux 没有内置的自动更新支持,且 macOS 的更新请求必须满足 App Transport Security 要求。文档还将签名列为可靠 macOS 更新和发布验证的先决条件。该 Electron autoUpdater 文档 定义了 API 约束,而发布系统必须落实配套的运维控制。
事故复盘教训: 一个无法说明发生了什么事的更新器,就是一次缺少遥测数据的远程安装尝试。
代价不止是工程时间。客户对桌面客户端失去信心,支持团队不得不解释不一致的行为,团队又花了一个工作日重建本应在事故前就有的发布流程。
将自动更新视为一套 运维系统。签名是发布门禁,清单定义客户端契约,发布渠道限制暴露面,回滚是经过测试的路径,而不是紧急情况下的临时发明。
选择合适的 Electron 更新方案
凌晨两点,选错更新器就会变成运维问题。原生二进制更新必须处理签名、清单、安装程序和回滚。仅涉及渲染进程的 JavaScript 或 CSS 更改走的是另一条路径。托管限制也很重要:一个小型 GitHub 托管项目不需要与企业分发服务相同的发布控制。
对于使用 electron-builder 并采用签名产物发布的项目, electron-updater 通常是实用的默认选择。其生态系统涵盖发布目标、发布清单、制品下载以及下次启动时的安装。它支持多种托管模式,但您的团队仍需负责签名、Feed 可用性、渠道策略、灰度控制和监控。 Capgo 的 Electron 更新器集成 在评估 Web 层 bundle 与原生发布并行的混合交付模式时具有参考价值。
update-electron-app 适用于希望围绕 GitHub Releases 做轻量集成的团队。它会在启动时检查,之后按固定间隔轮询,配置简单,但在高级 channel 选择、分阶段放量和自定义回滚规则上空间较小。只要 GitHub Releases 及其可用性符合你的运维要求,这个包用于小型发布流程是合理的。
| 选项 | 托管控制 | 签名支持 | Channels 与分阶段发布 | 维护成本 |
|---|---|---|---|---|
| electron-updater | S3、GitHub、通用 HTTPS 及其他发布目标 | 与打包后的发布签名集成 | 基础扎实,自定义策略通常围绕 feed 展开 | 中等 |
| update-electron-app | 主要依赖简单的 GitHub Releases 工作流 | 使用 Electron 底层的签名模型 | 功能有限,除非添加周边服务 | 低 |
| Squirrel.Windows 或 Squirrel.Mac | 面向平台的分发流程 | 依赖平台签名要求 | 可行,但通常需要额外的发布基础设施 | 对遗留应用而言适中 |
| 自定义服务 | 完全控制清单、授权、群组及 feed | 自行负责验证设计与密钥管理 | 最大灵活性 | 高 |
| Capgo 实时更新 | Web 层 bundle 的托管分发 | 使用其更新器和分发模型 | 受众定向和基于渠道的分发 | 与原生二进制更新分离的运维模型 |
当发布授权、租户定向、审计记录或受监管的部署规则证明实施成本合理时,Hazel、Nuts 或内部 feed 等自定义服务是合适的选择。权衡在于持续的维护责任。您的团队必须定义 manifest 语义、保护签名密钥、保持客户端兼容性,并测试下载失败、发布被拒和回滚行为。
Live Update 可以在不重新构建原生外壳的情况下发布仅涉及 renderer 的更改。当 Electron、原生模块、权限或安装程序行为发生变化时,它不能替代二进制更新。 除非需要自定义 rollout 逻辑,否则请使用 electron-updater。 如果需要自定义逻辑,请围绕既定的 manifest 和 artifact 约定进行构建,而不是重新创建下载和差分更新行为。可靠的路径是您的团队在压力下能够观察、分阶段发布和回滚的路径。
在主进程中实现自动更新流程
主进程应负责更新检查和安装。Renderer 可以显示状态,但不应决定可执行更新是否可信或应用何时退出。
首先配置发布目标
一个最小的 electron-builder 配置可能如下所示:
{
"build": {
"appId": "com.example.desktop",
"publish": [
{
"provider": "s3",
"bucket": "example-electron-releases",
"channel": "stable"
}
],
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
}
}
保持 beta 和 stable 通道分离。通道是一种发布策略,而非 UI 中的标签。每个通道都应解析到正确的签名产物和 manifest。
设置检查周期并暴露生命周期事件
正在调用 checkForUpdates() 仅在启动时检查是常见的生产环境错误。用户可能让应用保持打开状态数天,因此主进程需要受控的间隔和尊重离线操作的重试策略。
const { app, BrowserWindow, ipcMain } = require('electron');
const { autoUpdater } = require('electron-updater');
let mainWindow;
let isQuitting = false;
let retryDelay = 60 * 1000;
function sendUpdateStatus(status, payload = {}) {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-status', { status, ...payload });
}
}
function scheduleUpdateCheck() {
setTimeout(async () => {
try {
await autoUpdater.checkForUpdates();
retryDelay = 60 * 1000;
} catch (error) {
sendUpdateStatus('error', { message: error.message });
retryDelay = Math.min(retryDelay * 2, 30 * 60 * 1000);
}
scheduleUpdateCheck();
}, retryDelay);
}
app.whenReady().then(() => {
mainWindow = new BrowserWindow({
webPreferences: {
preload: require('path').join(__dirname, 'preload.js')
}
});
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = false;
autoUpdater.on('checking-for-update', () => {
sendUpdateStatus('checking');
});
autoUpdater.on('update-available', info => {
sendUpdateStatus('available', { version: info.version });
});
autoUpdater.on('download-progress', progress => {
sendUpdateStatus('progress', { percent: progress.percent });
});
autoUpdater.on('update-downloaded', info => {
sendUpdateStatus('downloaded', { version: info.version });
});
autoUpdater.on('error', error => {
sendUpdateStatus('error', { message: error.message });
});
autoUpdater.checkForUpdates().catch(error => {
sendUpdateStatus('error', { message: error.message });
});
scheduleUpdateCheck();
});
ipcMain.handle('install-update', () => {
isQuitting = true;
autoUpdater.quitAndInstall(false, true);
});
app.on('before-quit', event => {
if (!isQuitting) {
return;
}
});
具体的更新事件行为因平台和打包配置而异,因此请从已安装的产物而非开发模式进行测试。Electron 文档也指出了 Windows 上的启动时序问题,包括 Squirrel 首次运行的情况。不要在应用完成所需的平台特定初始化之前触发更新检查。
在不阻塞工作的情况下通知渲染进程
preload 桥接应暴露一个窄范围的 API:
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('updates', {
onStatus(callback) {
ipcRenderer.on('update-status', (_event, status) => callback(status));
},
install() {
return ipcRenderer.invoke('install-update');
}
});
渲染器端的进度条可以刻意保持简单:
window.updates.onStatus(status => {
const progress = document.querySelector('#update-progress');
const message = document.querySelector('#update-message');
if (status.status === 'progress') {
progress.hidden = false;
progress.value = status.percent;
message.textContent = `Downloading update, ${Math.round(status.percent)}%`;
}
if (status.status === 'downloaded') {
message.textContent = `Version ${status.version} is ready to install`;
}
if (status.status === 'error') {
message.textContent = 'The update could not be downloaded. We will retry later.';
}
});
在生产环境中,除非应用有强烈理由立即重启,否则应在用户同意后才进行安装。设置一个 isQuitting 标志在 quitAndInstall()之前,因为否则普通的窗口关闭处理器可能会阻止安装程序接管。

有两类故障值得编写显式测试。首先,已在运行的客户端必须调用 checkForUpdates() ,且需按固定周期执行,而不仅仅是在启动时。其次, error 事件必须记录到日志和遥测数据中。如果应用吞掉了该事件而未上报,你的 应用故障排查流程 将始于猜测,而非证据。
为签名发布和清单文件配置 CI/CD
发布流水线是用户安装内容的唯一事实来源。在单个开发者机器上运行正常的本地构建,并不能证明已发布的二进制文件、清单、签名和渠道都指向同一个版本。
Electron-builder 的发布模型要求发布元数据和更新目标一起传输。对于许多配置而言,这意味着诸如 latest.yml 适用于 Windows 和 latest-mac.yml 适用于 macOS,同时包含平台特定的软件包和 blockmap 文件。缺少 manifest 会让完全有效的二进制文件对客户端不可见。
在 CI 中明确签名步骤
简化的 GitHub Actions 模式如下:
name: release
on:
push:
tags:
- "v*"
jobs:
build:
strategy:
matrix:
os: [macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run test
- run: npm run build
- name: Build and publish
shell: bash
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: npx electron-builder --publish always
使用平台特定的 secrets,并将签名材料保存在仓库之外。签名失败应当停止任务,而不是生成一个未签名的备选版本供人手动上传。
| 变量 | 用途 |
|---|---|
CSC_LINK |
macOS 证书或证书引用 |
CSC_KEY_PASSWORD |
macOS 签名材料的密码 |
WIN_CSC_LINK |
Windows 证书或证书引用 |
AWS_ACCESS_KEY_ID |
权限范围受限的发布凭据 |
AWS_SECRET_ACCESS_KEY |
与发布凭据配对的 Secret |
发布配置应一致地指定提供商和频道:
{
"build": {
"publish": {
"provider": "s3",
"bucket": "example-electron-releases",
"channel": "stable",
"publishAutoUpdate": true,
"updaterCacheDirName": "example-desktop-updater"
}
}
}
发布前,以包版本、tag、commit SHA 和冒烟测试结果作为任务门禁。发布后,验证 feed 包含预期的 manifest,且 manifest 指向该任务生成的确切 artifact。这套 持续集成设置指南 在把这些检查规范化时很有用;正在比较 pipeline 编排的团队,也可以通过了解 何时结合使用 Jenkins 和 Ansible.
那些常能暴露发布不完整的命令,故意设计得很无聊:
npx electron-builder --publish never
test -f dist/latest.yml
test -f dist/latest-mac.yml
find dist -name "*.blockmap" -print
这些检查不能替代签名安装测试,但确实能抓到一种运维错误:上传二进制文件时缺少客户端发现它所需的元数据。
灰度发布、渠道与回滚策略
Release feed 的行为应该更像是一个部署目标,而不是一个下载文件夹。保持 内部, 测试版,以及 最新 通道相互隔离,每个通道都有自己的 manifest 和签名 artifact 集。晋升应把经过测试的版本在策略之间移动,而不是在客户端下载时覆盖文件。
通道隔离还能防止测试构建误入生产环境。更新器在评估 feed 前,应先判断客户端属于内部测试组、Beta 用户还是稳定版用户。
在大范围发布前先按用户分组
可以通过自定义 manifest 字段来实现分阶段发布:
version: 4.8.0
path: Example-Setup-4.8.0.exe
sha512: signed-artifact-hash
rolloutPercentage: 10
主进程可以为每个用户分配一个稳定的 bucket,然后将其与 rolloutPercentage。稳定的分配很重要。如果用户在每次检查时都在符合条件和不符合条件之间切换,行为会不可预测,支持报告也难以解读。
只有在版本度过观察期后,才扩大测试组。具体观察期应反映你的使用模式,但决策应基于指标,而不是只看日历。跟踪更新检查结果、下载完成情况、启动健康度、崩溃、渲染器异常和身份验证成功率。
| 信号 | 操作 | 原因 |
|---|---|---|
| Feed 或签名错误超过团队设定的阈值 | 暂停发布 | 客户端可能无法验证或发现该版本 |
| 更新后启动检查失败 | 回滚 feed | 二进制文件可能安装成功,但在启动时失败 |
| 提升后渲染器异常增加 | 保持当前群组 | 原生安装程序可能运行正常,但新的应用程序 code 却有问题 |
| 信号保持在发布预算范围内 | 扩大群组 | 证据支持更广泛的曝光 |
不要将回滚与删除制品混淆。现有客户端可能已缓存元数据,部分可能已在运行错误版本。回滚计划需要一个之前的签名版本、一个 feed 变更,以及一种能够恢复的客户端行为。
运维规则: 回滚必须能由值班工程师在故障期间执行,无需重新构建应用。
实践中,runbook 应将之前的 release manifest 重新提升到受影响的 channel,使 staging 标记失效,并确认新的检查解析到安全版本。如果问题出在 renderer code 而不是 native shell,针对 web 层的回滚可能更快。像 Capgo 分阶段发布 这样的平台可能适用于该独立交付层,但不应模糊 native binary 回滚与 web-bundle 回滚之间的界限。
将自动更新视为一种安全控制手段
Electron updater 会下载可执行的 code,并能在几乎无需用户交互的情况下完成安装。这使更新路径成为一个 安全边界,而不仅仅是一个便利功能。Electron 官方文档描述了 macOS ATS 等平台限制,安全报道也记录了 2022 年的一个场景:控制更新基础设施的攻击者可以分发恶意包,而这些包仍能通过 code 签名检查,正如 Electron Builder 自动更新安全文档中所述.
Code 签名仍是基础,但并非信任模型的全部。为每个版本签名,在 CI 中验证证书和身份,并维护有文档记录的密钥轮换流程。在 macOS 上,将签名与公证(notarization)以及适合你应用的 hardened runtime 结合使用。在 Windows 上,确保证书所有权、续期和构建访问权限可审计。Linux 则需要针对具体发行版的策略,因为 Electron 在 Linux 上不提供内置的通用更新器。
像保护二进制文件一样保护元数据
如果元数据通道被攻破或配置错误,已签名的二进制文件仍可能与错误的发布版本关联。建议添加 manifest 签名,并用嵌入应用中的公钥验证;强制最低允许版本,并拒绝非预期降级,除非授权的恢复路径明确允许。
更新源(feed)也需要生产级控制:
- 限制发布权限: 仅授予 CI 发布 release assets 所需的权限。
- 保护签名密钥: 将证书和私钥保存在托管的 secret 存储中,而不是仓库文件中。
- 锁定依赖项: 在 CI 中锁定 Electron、electron-builder 和传递依赖。
- 审查构建产物: 扫描生成的 package,并与预期的 commit 和版本进行比对。
- 要求安全传输: 遵循 ATS 和严格的 HTTPS 要求,用于更新请求。
- 监控验证失败: 将重复出现的签名或清单失败视为安全事件,而非普通的网络噪声。
Electron 的维护工具生态系统不断扩展打包和更新器覆盖范围,但维护并不能消除进行威胁建模的需求。实际目标是确保即使攻击者攻陷了存储桶、CDN 或构建步骤,客户端也不会接受未授权的发布。 签名验证指南 为设计该额外验证层提供了有用的背景信息。

生产环境更新指南与检查清单
只有当另一位工程师能在压力下操作时,发布才算就绪。将检查清单放在部署任务和事件频道附近。
发布前门禁
- 版本标识: 确认包版本、发布标签、commit SHA 及更新日志一致。
- 签名: 验证所有平台的构建产物均已签名,且已完成公证或同等验证。
- Manifest 约定: 确认
latest.yml,latest-mac.yml,哈希值、路径及 blockmap 与上传的产物一致。 - 渠道安全: 在提升发布渠道前,先发布到内部或 beta 渠道。
- 遥测数据: 确认更新检查、下载进度、安装完成、启动健康状态及错误均正常上报。
金丝雀发布与全量发布
- 分批控制: 先从有意保持小范围的内部或 Beta 用户群开始。
- 健康预算: 若启动失败、更新下载失败、渲染器异常或身份验证失败超过团队批准的限值,请暂停推广。
- 推广审批: 在将版本推送到稳定版 feed 前,必须做出明确的 go/no-go 决策。
- 客户影响: 应在广泛分发前准备好支持话术,而不是等首起事故发生后才准备。
事件响应
如果新二进制包无法启动、进程创建失败或更新下载无法完成,请立即停止推广。恢复之前的已签名 manifest,让 staging 标记失效,并验证新客户端能解析到上一版本。然后通过遥测确认设备群正在恢复,再宣布结束。
具体 rollback 命令取决于你的服务商,但步骤顺序应始终记录在文档中: 将 channel manifest 切回上一版本,让 staging 标签失效;如果恢复需要,推送强制更新标记;并通过实时遥测验证降级路径。未经测试的 rollback 只是空想。

Capgo 提供 Electron 更新器,用于交付签名的 Web 层变更、定向渠道、灰度发布控制及更新可观测性,无需为每次渲染器变更重新构建原生外壳。如果您希望将原生二进制发布与受控的 JavaScript 和 CSS 交付分离,请访问 Capgo 并将其与现有的 Electron 发布流水线一起评估。