如何编写跨平台Electron asar更新器?解决Linux权限难题
跨平台Electron ASAR文件更新器实现问题(无需Root权限)
问题描述
我想做一个仅下载最新app.asar并替换旧文件的Electron更新器,不想使用electron-updater这类主要针对Windows和Mac的方案,要实现支持Linux在内所有平台的「ASAR更新器」,逻辑和Discord更新器类似:对比本地版本与服务器最新版本。
但Linux下多数包格式会将文件挂载在需要Root权限的目录,比如AppImage挂载到/tmp下的随机目录,Flatpak挂载到/app/lib/(暂未找到该目录),核心问题是:如何无需Root权限替换旧的asar文件为更新后的版本?
测试代码
app.whenReady().then(() => { // Updater should only be run in "production" if (isDev) return; // Get path of asar file & resource dir stored on the system const asarPath = app.getAppPath(); // => /tmp/.APPNAMExlsjda345/resources/app.asar/src/electron/ const resourcesPath = path.join(asarPath, ".."); // => /tmp/.APPNAMExlsjda345/resources/ // TODO: compare current version with newest on webserver // TODO: download new version & call it something like "update.asar" // TODO: remove app.asar & rename update.asar to app.asar // TODO: restart the whole app // This is only for testing if I can modify the content of the resources directory fs.copyFile(asarPath, path.join(resourcesPath, "update.asar"), (err) => { if (err) return console.log({ err }); // throws error, see below! console.log("copied: ", fs.readdirSync(resourcesPath)); // if it would be successfull print all the content of resources dir }); } });
错误信息
使用fs.copyFile时的错误
{ err: Error: ENOENT, not found in /tmp/.mount_Babbel2t0KoE/resources/app.asar at createError (node:electron/js2c/asar_bundle:5:1255) at Object.t.<computed> [as copyFile] (node:electron/js2c/asar_bundle:5:2526) at /tmp/.mount_Babbel2t0KoE/resources/app.asar/src/electron/main.js:44:6 { code: 'ENOENT', errno: -2 } }
尝试original-fs后的错误
{ err: [Error: EROFS: read-only file system, copyfile '/tmp/.mount_BabbelMjuGx3/resources/app.asar' -> '/tmp/.mount_BabbelMjuGx3/resources/update.asar'] { errno: -30, code: 'EROFS', syscall: 'copyfile', path: '/tmp/.mount_BabbelMjuGx3/resources/app.asar', dest: '/tmp/.mount_BabbelMjuGx3/resources/update.asar' } }
解决方案
核心问题原因
AppImage/Flatpak这类Linux包格式会将应用挂载为只读文件系统,这是安全设计,防止运行时篡改应用文件,所以你无法直接修改挂载目录内的app.asar。
可行实现方案
1. 将更新的asar存储到用户可写目录
不要直接修改挂载目录的文件,而是:
- 获取用户专属可写目录:用
app.getPath('userData'),这个路径在所有平台都可写,无需Root权限。 - 版本对比与下载:检查本地版本和服务器版本,确认更新后,将新asar下载到该用户目录,命名为
app-updated.asar。 - 启动加载优先级:应用启动时优先检查用户目录是否存在更新后的asar,存在则加载该文件,否则加载默认挂载目录的
app.asar。
2. 启动加载逻辑示例
const { app, BrowserWindow } = require('electron'); const path = require('path'); const fs = require('fs'); // 用户目录下的更新asar路径 const updatedAsarPath = path.join(app.getPath('userData'), 'app-updated.asar'); // 默认的asar路径 const defaultAsarPath = app.getAppPath(); // 优先加载更新后的asar let loadPath = defaultAsarPath; if (fs.existsSync(updatedAsarPath)) { loadPath = updatedAsarPath; } function createWindow() { const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(loadPath, 'src', 'electron', 'preload.js') // 根据项目结构调整路径 } }); mainWindow.loadFile(path.join(loadPath, 'index.html')); } app.whenReady().then(() => { if (!isDev) { // 执行版本检查与下载逻辑 // 1. 请求服务器获取最新版本号 // 2. 对比本地版本(可从package.json或用户目录配置文件读取) // 3. 需更新则下载新asar到updatedAsarPath // 4. 下载完成后提示用户重启应用 } createWindow(); app.on('activate', function () { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit(); });
3. 应用重启处理
下载完成更新的asar后,提示用户重启应用,用app.relaunch() + app.quit()实现,重启后应用会加载用户目录内的新asar。
4. Flatpak兼容注意事项
Flatpak环境下,app.getPath('userData')指向~/.var/app/[你的应用ID]/config/[应用名],该目录完全可写,无需特殊权限,按上述逻辑处理即可,无需手动查找/app/lib目录。
优化点
- 下载时添加文件校验(如MD5/SHA256),确保文件完整。
- 若更新失败,保留旧更新文件,下次启动重新下载。
- 启动时若更新asar加载失败,自动回退到默认asar,避免应用无法启动。
内容的提问来源于stack exchange,提问作者wjatscheslaw
相关产品推荐
相关产品推荐

