跳转至正文

Electron App 自动更新:2026 实战指南

交付 Electron 应用自动更新,告别静默失败。为生产团队提供真实的 code、签名技巧、发布防护和回滚策略。

Electron App 自动更新:2026 实战指南

你发布了 Electron build,发布页面也上线了,咖啡机还没煮好,第一张支持工单就来了。一个用户说应用始终没发现更新;另一个下载了却装不上;第三个还在跑旧版二进制文件,身份验证流程已经坏了,而你的 logs 里几乎看不到有用信息。

这就是 Electron 应用自动更新的残酷现实。API 更新器只是其中一个组件。生产环境的发布还取决于平台签名、传输策略、manifest、托管、生命周期事件、可观测性、发布控制以及回滚路径。任何一个环节被当成可选,一次常规补丁就可能变成通宵事故。

目录

引发本指南的凌晨 2 点更新事故

该版本通过了 CI,看起来一切正常。周五深夜,一名开发者推送了未签名的 Electron build,发布任务上传了足够多的 assets,让发布看起来已经完整。应用在测试中能启动,但没人实际走过从已安装的生产 build 执行更新的路径。

凌晨 2:08,PagerDuty 向值班工程师发出警报。部分设备群的新认证流程失败,收到更新的用户无法完成登录。其他用户则停留在旧版本,因为 updater 无法验证或安装该 artifact。部分客户拿到的是有问题的发布,而其余设备运行着另一个版本,且原因不明。

调查按五项检查展开:

  1. 检查发布 feed。 二进制文件存在,但预期的 metadata 并未明确说明哪些客户端应接收它。Manifest 是发布 pipeline 与已安装客户端之间的契约,而不是可选的上传细节。
  2. 检查签名。 发布未正确签名,导致受影响平台上的验证失败。签名缺失或无效时,必须阻止发布。
  3. 对比客户端 logs。 更新错误从未到达中央遥测系统。应用吞掉了该事件并继续运行,导致团队缺乏可靠的证据。
  4. 检查发布控制。 没有内部渠道或分阶段群组。所有符合条件的客户端都使用同一个 feed,因此故障在没有遏制点的情况下扩散。
  5. 寻找回滚方案。 团队没有经过测试的流程来重新发布之前的版本,或将客户端引导至损坏版本之外。

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()之前,因为否则普通的窗口关闭处理器可能会阻止安装程序接管。

截图来自 https://raw.githubusercontent.com/electron-userland/electron-builder/master/docs/electron-builder-autoupdate.png

有两类故障值得编写显式测试。首先,已在运行的客户端必须调用 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 发布流水线一起评估。

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。