Files
NetMesh/docs/research/issue-2848-linux-deb-auto-update.md
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

19 KiB
Raw Blame History

Issue #2848Linux .deb 应用内自更新研究

研究日期2026-08-10

研究范围Issue #2848、electron-builder / electron-updater 官方文档与源码,以及 Element Desktop、Joplin、Beekeeper Studio 三个开源 Electron 项目的固定提交源码。本文只记录已经在一手来源中确认的行为,不把“发布了 .deb”等同于“支持应用内自更新”。

结论先行

  1. electron-updater@6.8.3 已经有 Linux .deb 的完整更新路径。它不是只能更新 AppImageelectron-builder 为可发布的 FPM Linux 包写入 app-update.ymlresources/package-typeelectron-updater 再根据 package-type=deb 实例化 DebUpdaterFpmTarget.tsmain.ts

  2. 官方 .deb 路径的边界很清楚:检查更新时读取 Linux 更新清单,下载清单中匹配架构的 .deb,下载过程校验哈希并缓存;安装时调用 dpkgapt,普通用户通过 pkexec、图形化 sudo 工具或 sudo 提权。它不是“把新文件复制到应用目录”,也不提供应用级的旧版本回滚。DebUpdater.tsLinuxUpdater.ts

  3. 官方库本身不会替应用设计确认界面。autoDownload 默认开启,下载完成后 autoInstallOnAppQuit 默认也开启;如果产品要求“用户确认下载”和“用户确认重启安装”,应用层应关闭自动下载/退出安装,收到事件后由界面触发下载和 quitAndInstallAppUpdater.tsBaseUpdater.ts

  4. 三个成熟项目都没有把 Linux .deb 做成应用内自更新Element Desktop 在 Linux 直接关闭更新Joplin 的桌面更新入口打开下载地址Linux 安装脚本更新的是用户目录中的 AppImageBeekeeper Studio 明确只对 Linux AppImage 启用 electron-updater,非 AppImage Linux 包直接跳过。Element updater.tsJoplin checkForUpdates.tsBeekeeper update_manager.ts

  5. 对 Issue #2848 来说,关键问题不是 electron-updater 能不能安装 .deb,而是发布物和运行时是否成对满足条件:.deb 内要有正确的 package-typeapp-update.yml,更新服务器要有对应的 latest-linux[-架构].yml.deb,安装时要处理提权确认、取消、失败和手动降级入口。当前工作区的桥接层已经按这个方向读取 package-type,构建配置也包含 .deb 和 GitHub 发布配置;但本次是只读研究,没有把它表述为已在实际安装包中验证或已随正式版本发布。Netcatty autoUpdateBridge.cjsNetcatty electron-builder.config.cjsNetcatty package.json

1. 官方实现:从安装包识别到安装

1.1 如何识别 .deb

electron-builder 的 FPM 目标在有发布配置时,向 Linux 应用资源目录写入两份更新文件:

  • app-update.yml:发布服务配置;
  • package-type:目标名,例如 debrpmpacman

electron-updater 在 Linux 启动时先创建 AppImageUpdater,然后读取 process.resourcesPath/package-type。读取到 deb 时改用 DebUpdater;没有该标记时保持 AppImage 路径。AppImage 则通过 APPIMAGE 环境变量识别。FpmTarget.tsmain.tsAppImageUpdater.ts

这意味着不能只按 process.platform === "linux" 判断是否支持更新,也不能只看当前安装文件的扩展名。运行时真正依赖的是构建时写入的安装类型标记和发布配置。

1.2 如何选择清单和安装包

Linux 更新清单的默认名称带有平台和架构后缀x64 使用 latest-linux.yml,其他架构使用 latest-linux-架构.yml。清单中的文件会经过扩展名筛选,并优先选择名称中包含当前 process.arch 的文件。Provider.tsProvider.ts 的 findFile

DebUpdater 只选择 .deb 文件,下载任务把文件扩展名固定为 deb,并把下载进度向外发出。与 AppImage 路径不同,这里没有差分下载逻辑;它下载完整的 .debDebUpdater.ts

1.3 下载、校验和缓存

通用下载流程会把清单中的 sha2/sha512 传给 HTTP 下载器,先写入缓存目录中的临时文件,下载成功后再改名为最终缓存文件。网络错误、取消或改名失败会清理临时下载;已缓存的更新会在再次使用前检查清单哈希和文件哈希,校验不一致时清空缓存并重新下载。AppUpdater.tsDownloadedUpdateHelper.tsbuilder-util-runtime HTTP 校验

所以,下载失败时的可靠回退是“删除坏的临时/缓存文件,等待下一次检查重新下载”,不是切换到另一个 Linux 安装格式。安装失败后也不会自动把旧 .deb 重新安装一遍。

1.4 root 权限和用户确认

安装逻辑先检查 dpkgapt 是否存在,并优先选择 dpkg。如果当前进程 UID 是 0直接运行包管理命令普通用户则按顺序寻找 gksudokdesudopkexecbeesu,都没有时使用 sudo,并以应用名生成“希望更新”的提示文本。LinuxUpdater.ts

源码没有弹出产品层“是否现在更新”的对话框。它的默认值是发现更新后自动下载,下载完成后在正常退出时自动安装;显式 quitAndInstall 则立即进入安装并退出应用。AppUpdater.tsBaseUpdater.ts

因此,若 Netcatty 要求用户确认,确认应由 Netcatty 自己的界面控制:确认下载后调用 downloadUpdate(),确认重启后调用 quitAndInstall();同时关闭默认的退出即安装,避免用户只是退出应用时突然触发提权。

1.5 安装失败和回退

官方 DebUpdater 的失败处理是:

  • 找不到下载文件,或找不到 dpkg/apt:发出错误并返回失败;
  • 使用 dpkg -i 时失败:记录警告,再执行 apt-get install -f -y 尝试修复依赖;
  • 没有 dpkg、只有 apt:执行本地 .deb 安装,并带上 --allow-unauthenticated--allow-downgrades--allow-change-held-packages
  • 安装命令仍失败:发出错误,不会在应用层恢复旧版本。DebUpdater.ts

其中 --allow-unauthenticated 是当前 6.8.3 源码的实际行为,不能被描述成安全的签名验证方案。它说明官方实现把 .deb 安装交给系统包管理器,但没有替应用建立完整的包签名信任链。

2. 开源项目对照

项目 发布的 Linux 格式 实际更新路径 权限 / 用户确认 失败或回退行为
electron-updater@6.8.3 官方 .deb、AppImage、RPM、Pacman package-type 选择 updater.deb 下载完整包,调用 dpkg/apt root 直接安装;普通用户走 pkexec/sudo 等;确认界面由应用层决定,默认下载和退出安装 下载临时文件和缓存有哈希校验与清理;dpkg 失败尝试 apt-get -f;没有应用级旧版本回滚
Element Desktop tar.gz.deb;构建配置确认发布 .deb Linux available() 直接返回 false只对 macOS/Windows 配置 Electron 自带 updater Linux 不进入更新流程,因此没有应用内提权或确认逻辑 Linux 的回退就是不自更新,用户使用外部下载/安装方式
Joplin AppImage、.deb 更新检查弹窗确认后打开下载 URLLinux 安装脚本实际下载和替换 $HOME/.joplin/Joplin.AppImage 用户目录安装不需要 root脚本默认拒绝 root必须显式 --allow-root 才能运行 版本比较会避免重复安装和降级;先下载到临时目录,之后删除旧 AppImage 并移动新文件;没有确认到的 .deb 应用内安装路径
Beekeeper Studio Snap、.deb、AppImage、RPM、Flatpak、Pacman manageUpdates() 对 Linux 非 AppImage 直接返回;只有 AppImage 走 electron-updater AppImage 使用“下载 / 稍后 / 立即重启”界面;.deb 不进入该流程 Portable 有打开官网手动下载的分支;本次确认的 Linux .deb 路径没有独立的提权或回滚实现

2.1 Element Desktop发布 .deb,但不做 Linux 应用内更新

Element 的构建配置把 Linux 目标设为 tar.gzdebelectron-builder.ts

但其更新入口明确写着 Linux 不支持自动更新,并在 available() 中直接返回 falsestart() 也只为 macOS 和 Windows 生成更新地址Linux 不会设置更新源。updater.ts

项目自己的更新文档也只说桌面应用能在 macOS 和 Windows 自更新。docs/updates.md

已确认的结论Element 的 .deb 是发布/安装格式不是应用内更新格式。Linux 没有可供比较的 root 提权、安装确认或旧版本回退实现;不能从源码推断它对这些情况有额外处理。

2.2 JoplinLinux 实际更新的是用户目录 AppImage

Joplin 的构建配置同时发布 AppImage 和 .debpackage.json

桌面更新入口从 Joplin 自己的发布地址取版本列表,比较版本后弹窗;用户点击 Download 时打开 release.downloadUrlrelease.pageUrl,而不是把 .deb 交给应用内安装器。checkForUpdates.tscheckForUpdates.ts

其 Linux 安装/更新脚本把安装目录默认设为 $HOME/.joplinroot 默认被拒绝;脚本先把新版 AppImage 下载到临时目录,之后删除旧 AppImage移动新版并补执行权限。脚本使用 set -e 和错误提示,但源码中没有确认到包管理器提权或应用内 .deb 安装流程。Joplin_install_and_update.shJoplin_install_and_update.shJoplin_install_and_update.sh

另外Joplin 的构建后处理明确只为 AppImage 生成 SHA-512 文件。afterAllArtifactBuild.js

已确认的结论Joplin 采用“用户确认后打开下载地址 + 用户目录 AppImage 替换”的路线,避免了 .deb 的 root 安装问题;这不是可直接复用的 .deb 应用内自更新方案。

2.3 Beekeeper Studio只给 AppImage 接上 electron-updater

Beekeeper 的 Linux 构建配置包含 Snap、.deb、AppImage、RPM、Flatpak 和 Pacman并为 Linux 发布到 GitHub。electron-builder-config.js

运行时代码在 Linux 且不是 AppImage 时直接跳过更新Snap 也在入口处跳过。只有 AppImage 才会处理 APPIMAGE 路径、检查更新、下载和安装。它关闭了自动下载,界面提供 Download、Later 和 Restart Now 三步确认。update_manager.tsAutoUpdater.vue

已确认的结论Beekeeper 的 UI 确认流程值得参考,但它只服务于 AppImage。对 Linux .deb,源码没有单独的提权确认、包管理器安装或回滚路径,不能把 AppImage 的行为直接外推到 .deb

3. 对 Netcatty Issue #2848 的直接启示

Issue #2848 描述的是“.deb 应用检测到新版后仍需手动下载 .deb 安装”。Issue #2848

本工作区固定提交 3f67ccf3b2766e7757e3d2ca63b1044808a8363c 中,已经能确认以下事实:

  • autoUpdateBridge.cjsAPPIMAGE 识别为 AppImage否则读取 resources/package-type,接受 deb/rpm/pacman;无标记时返回不支持。autoUpdateBridge.cjs
  • electron-builder.config.cjs 的 Linux 目标包含 AppImagedebrpmpacman,发布配置为 GitHub。electron-builder.config.cjs
  • 应用依赖范围是 electron-updater ^6.8.3,锁文件解析到 6.8.3;这与上面确认的 DebUpdater 实现相符。package.json
  • 桥接层把 autoInstallOnAppQuit 设为 false,把检查、下载和安装暴露为显式操作;安装前还会检查未保存编辑。autoUpdateBridge.cjsautoUpdateBridge.cjs

因此,后续若要验证 Issue 是否真正完成,最小证据链应是:

  1. 从实际 .deb 安装包解出 resources/package-type,确认内容为 deb,同时确认存在 app-update.yml
  2. 用对应发布版本检查 latest-linux.yml.deb 文件名/架构是否匹配;
  3. 以普通用户执行一次“检查 → 用户确认下载 → 用户确认重启”,确认提权对话框出现且取消不会让应用退出;
  4. 分别验证下载哈希错误、无 dpkg/apt、权限取消和依赖修复失败时,界面能保留可见错误并提供手动 Releases 入口;
  5. 验证安装失败后不会把“已安装成功”写入 UI 状态,也不把“下载成功”误报成“安装成功”。

这份研究没有执行实际安装包测试,因此不把上述运行时证据当作已满足。

来源与版本边界