Electron 桌面应用自己打包自己:一键生成多平台安装包
最近我把自己一直在用的 DeepSeek Harness 桌面端重新整理了一遍。Harness 这个词在工程里通常指“工具集 / 操作台”,在 AI 应用场景下,就是用一个可视化桌面壳把命令行 AI 工具统一管起来。目前这套桌面端已经能完成模型配置、会话管理、工具调用记录等功能,但在给朋友内测时遇到一个尴尬问题:每个人拿到代码后都要自己装 Node 环境、拉依赖、起服务,步骤太长,非技术朋友根本玩不起来。
为了解决这个问题,我决定让应用“自己打包自己”,生成一个一键安装版。更直白一点说:我在桌面端里内置了一个自动打包模块,点击按钮后,它会检查本机环境、补齐 electron-builder 依赖、生成安装包,最后输出 Windows / macOS / Linux 对应的安装文件。这个过程不需要手动敲命令,也不需要用户理解构建工具链。整个方案已经跑通,本篇文章完整记录我的实现步骤、核心代码以及踩过的几个坑,希望能帮到同样在用 Electron 开发桌面工具、又想省去分发成本的人。
1. 背景与核心概念
1.1 什么是 DeepSeek Harness 桌面端
先统一一下语境。DeepSeek Harness 桌面端本质是一个基于 Electron 构建的本地桌面工具,它把 DeepSeek 的 API 能力封装成可视化操作界面,同时预留了扩展能力,可以接入多个命令行 AI 工具。你可以把它理解成:
- 一个统一管理 API Key 和模型参数的配置面板。
- 一个启动、停止、观察命令行 AI 任务的调度台。
- 一个把会话记录、输出日志、工具调用结果集中展示的控制台。
我最初用命令行方式直接调 DeepSeek API,后来发现命令一多、参数一长,就非常容易乱。Harness 的思路是把这些散落的命令和参数统一收到一个“工具架”里,桌面端负责图形化调用和结果展示。这样做的好处是降低使用门槛,让不熟悉 CLI 的人也能通过输入框完成同等操作。
1.2 为什么要让应用“自己打包自己”
桌面端应用开发完成后,面临的第一件事是分发。以前常见的做法是:
- 开发者在自己电脑上用 electron-builder 或 electron-packager 手动打包。
- 把压缩包传到网盘或服务器。
- 用户下载后解压运行。
手动打包的痛点很明显。第一,每次更新都要重复执行命令、等待产物、手动改名;第二,不同平台需要不同安装包格式,Windows 用 NSIS 安装程序,macOS 用 dmg,Linux 用 AppImage,手动处理容易漏;第三,参与内测的人拿到的可能不是最新版。
所以我就想,干脆在应用里内置一个“自动打包”功能。点击之后,应用自己读取自身版本号、动态构造 package.json、安装打包依赖、调用 electron-builder、最后把生成的安装包放到指定目录。因为打包动作本身就是 Electron 生态里的标准流程,所以这个思路完全可行,相当于“让程序驱动工具链完成自己的构建”。
1.3 适合哪些读者阅读
本文适合以下人群:
- 已经在用 Electron 做桌面端,但还没有解决一键分发问题。
- 想了解 electron-builder 自动打包与 NSIS 安装包配置的开发者。
- 想基于 DeepSeek API 或类似模型 API 做桌面工具,但不知道如何组织工程结构的人。
- 想把自己写的 AI 工具分享给朋友,但不想让他们折腾环境的人。
如果你对 Electron 完全陌生,建议先掌握主进程、渲染进程、IPC 通信这三个基础概念,再阅读本文会更顺畅。
2. 环境准备与版本说明
2.1 软件环境
由于打包动作依赖 Node.js 生态,我先说明一下本机环境。版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。
- 操作系统:Windows 11 64 位(macOS 和 Linux 的配置大同小异,后面会补充差异点)。
- Node.js:建议 18 或 20 的 LTS 版本,我本地使用的是 Node.js 20。
- npm:随 Node.js 自带,版本 10 左右。
- Electron:项目中使用的 Electron 版本以本地安装为准,我基于 Electron 28 验证。
- electron-builder:动态打包时安装到临时目录,版本为 24.x。
- 开发 IDE:VS Code。
2.2 关键依赖说明
桌面端核心依赖如下:
| 依赖 | 作用 |
|---|---|
| electron | 提供桌面端运行时环境 |
| electron-builder | 负责将应用打包成安装程序 |
| electron-log | 记录主进程日志,方便排查打包问题 |
| @electron/remote | 可选,用于简化渲染进程与主进程的通信 |
这些依赖中,最核心的是 electron-builder。它支持多平台打包,Windows 下默认使用 NSIS 生成安装程序,macOS 下生成 dmg,Linux 下可以生成 AppImage 和 deb。配置信息写在 package.json 的 build 字段中,也可以通过 electron-builder.yml 单独管理。
2.3 项目基础目录结构
这里给出一个简化后的目录结构,后续的代码都会基于这个结构讲解:
需要注意的是,scripts/builder.js 负责执行打包逻辑。它既可以被主进程调用,也可以单独在命令行运行。之所以单独拆分,是为了降低耦合,方便本地调试。
3. 让应用自己打包自己的整体设计
3.1 动态生成与固定配置的区别
常规 Electron 项目的 package.json 是写死的,里面包含应用名称、版本、依赖、build 配置等字段。但“自己打包自己”需要更灵活一点:
- 每次打包时,要根据当前应用的版本号自动同步 package.json 中的 version。
- 构建所需的依赖项要按需安装,不能假设用户电脑上已经有 electron-builder。
- 打包工具的内部文件路径要动态计算,避免写死绝对路径。
所以我在设计时采取“模板 + 动态写入”的方式:项目根目录有一份基础的 package.template.json,打包前程序读取它,替换版本号和应用名,再写入一份临时 package.json,作为 electron-builder 的输入。
3.2 自动打包流程
整个自动打包流程如下:
- 用户在桌面端点击“生成安装包”按钮。
- 渲染进程通过 IPC 通知主进程。
- 主进程启动 scripts/builder.js 脚本。
- 脚本检查 Node.js 和 npm 是否可用。
- 脚本读取当前应用版本号。
- 动态生成临时 package.json。
- 如果 node_modules 中缺少 electron-builder,自动执行 npm install --save-dev electron-builder。
- 调用 electron-builder 的 JavaScript API 执行打包。
- 打包完成后,将安装包输出到 dist 目录。
- 主进程把结果返回给渲染进程,界面显示安装包路径和文件大小。
这个流程把原来需要手动执行的几条命令,全部封装成了应用内的一个按钮事件。
3.3 package.json 中的 electron-builder 配置
electron-builder 的配置项很多,但最开始只需要关心几个关键字段。下面是我的模板配置,实际使用时按需调整:
配置项解释:
- appId:应用的唯一标识,建议使用反域名格式。
- productName:安装后显示的应用名称。
- directories.output:安装包输出目录。
- files:需要打包进应用的文件列表。这里必须包含 main.js 和 renderer,否则打包后的应用无法正常启动。
- win.target:Windows 下生成 NSIS 安装包。
- nsis.oneClick:设为 false,可以让用户选择安装目录。
- nsis.allowToChangeInstallationDirectory:允许用户修改安装路径,对内测分发很有用。
- mac.target:macOS 下生成 dmg。
- linux.target:Linux 下生成 AppImage。
4. 完整实战代码
下面进入正题。我会把核心代码分段放出来,并标注每个文件应该放在哪里。
4.1 渲染进程:触发打包按钮
桌面端界面很简单,一个按钮加一个状态区域。renderer/index.html 中定义按钮:
renderer/renderer.js 中通过 preload 暴露的 API 调用主进程:
这里使用了 window.harnessAPI,它由 preload.js 注入,是一种相对安全的 IPC 通信方式。接下来看 preload 和主进程。
4.2 preload.js:安全暴露 IPC 接口
preload.js 位于项目根目录:
关键点在于 contextBridge,它把渲染进程的能力限制在最小范围,渲染进程只能调用 startBuild,不能直接访问 Node.js 能力。这是 Electron 安全实践中的基础要求。
4.3 主进程:接收渲染进程请求
main.js 负责创建窗口,并监听渲染进程发来的打包消息:
这里有一个很关键的细节:spawn 的第一个参数我传的是 process.execPath。因为在 Electron 主进程中,process.execPath 指向的是 Electron 可执行文件,用它来运行 Node.js 脚本可以保证脚本运行在 Electron 自带的 Node 运行时中,不需要用户额外安装 Node.js。但这只适用于开发模式下,打成正式安装包之后,应用内可能无法继续使用这个方式打包,后文会专门说明。
4.4 自动打包脚本:scripts/builder.js
这是整套方案的核心。builder.js 会完成环境检查、依赖安装、动态写入 package.json、调用 electron-builder 打包。
这段代码的逻辑可以用一句话概括:把本来需要手动执行的 npm install 和 electron-builder --win nsis 两条命令,封装到 Node.js 脚本中,由主进程触发。
4.5 运行与验证
在开发模式下,启动应用:
点击界面上的“生成一键安装包”按钮,观察控制台输出。预期会看到类似下面的日志:
打包完成后,dist 目录下会生成:
- DeepSeek Harness Setup 1.0.0.exe:Windows 安装程序。
- builder-debug.yml:NSIS 构建过程的调试文件。
- builder-effective-config.yaml:electron-builder 最终生效的配置,可用于排查配置问题。
如果你使用 macOS 或 Linux 环境,脚本会自动切换 target 参数,分别生成 dmg 或 AppImage。
5. 一键安装版的行为说明
5.1 安装流程
生成的 Windows NSIS 安装包是标准的引导式安装界面。由于 nsis.oneClick 设为 false,用户可以选择安装目录,也可以决定是否创建桌面快捷方式和开始菜单快捷方式。安装完成后,桌面会生成“DeepSeek Harness”快捷方式,用户双击即可运行。
5.2 安装后的目录结构
安装目录默认为 C:\Users\用户名\AppData\Local\Programs\deepseek-harness-desktop。这个路径由 NSIS 根据 oneClick 配置和 allowToChangeInstallationDirectory 决定。目录中会包含 resources、renderer 和可执行文件。
5.3 卸载清理
Windows 的“设置 -> 应用”列表中会出现 DeepSeek Harness 条目,用户可以通过标准方式卸载。electron-builder 默认生成 uninstaller,它会删除安装目录、快捷方式和开始菜单项。需要清理的主要是应用运行过程中写入的配置数据,这部分在 userData 目录下,如果用户希望彻底清理,可以手动删除:
如果你在应用中存储了 API Key 或会话记录,卸载后需要提醒用户清理该目录,避免配置残留。
6. 常见问题与排查
在让应用“自己打包自己”的实现过程中,我遇到过不少问题。下面整理成表格,再逐个详细说明。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 打包按钮点击后没有反应 | preload 或 IPC 通道名称不一致 | 检查 preload 中暴露的方法名与 renderer 中调用名是否一致 |
| 打包过程卡在安装依赖 | 网络慢或 npm 源不稳定 | 切换为国内镜像源,如 npmmirror |
| electron-builder 报 icon 格式错误 | Windows 图标必须是 ico 格式且尺寸足够 | 使用 256x256 以上的 ico 文件 |
| 生成的安装包双击后闪退 | files 配置遗漏了 renderer 目录 | 检查 build.files 是否包含所有运行需要的文件 |
| 打包日志中出现 EPERM 错误 | 安装包被安全软件拦截 | 暂时关闭实时防护或添加白名单 |
| 在已安装的正式版中执行打包失败 | 正式版无法直接使用 Node 运行时调用 electron-builder | 改为生成安装包时携带额外打包脚本,或提供独立打包工具 |
下面展开几个重点问题。
6.1 图标格式错误
electron-builder 对 Windows 安装包图标要求比较严格,必须是 .ico 文件。如果使用 PNG 文件,在打包时会出现类似下面的报错:
解决方案是,在 resources 目录放置一个 256x256 或更高分辨率的 icon.ico。推荐使用在线工具将 PNG 转为多尺寸 ICO,确保包含 16、32、48、64、128、256 等常见尺寸。
6.2 打包输出目录空间不足
electron-builder 打包过程中,会先在系统临时目录生成大量文件,再复制到 dist 目录。如果 C 盘空间不足,可能会在打包后半段失败。建议:
- 确保 C 盘剩余空间在 2GB 以上。
- 在配置中通过 directories.output 指定输出目录。
- 必要时使用 buildResources 指定构建资源目录。
6.3 安装包被杀毒软件拦截
NSIS 安装包比较容易被杀毒软件误报。这是因为 electron-builder 生成的安装程序带有自解压逻辑,某些杀毒产品会把它识别为潜在风险。处理办法:
- 使用官方代码签名证书对 exe 进行签名。
- 内测阶段可以建议用户将安装包加入白名单。
- 正式分发时务必签名,否则 Windows SmartScreen 会拦截。
6.4 通过 process.execPath 启动脚本的限制
开发模式下,process.execPath 指向 Electron 开发版可执行文件,可以运行 Node 脚本,所以“自己打包自己”在开发进程中没问题。但打成正式安装包后,应用运行时的 process.execPath 指向已打包好的 exe,它虽然内部也包含了 Node 运行时,但并不是标准 node 命令,可能导致脚本执行异常。
一个可行的解决方案是,在应用菜单中放一个“生成打包命令”功能,它把构建命令复制给用户,让用户在命令行执行:
这样既保留了自动化能力,又避开了正式版运行时环境受限的问题。
7. 最佳实践与工程建议
7.1 应用内打包时的安全边界
应用内执行打包脚本,本质上是在用户机器上启动子进程并安装依赖。虽然开发工具类应用可以接受,但需要注意安全边界:
- 不要用管理员权限启动整个应用,打包脚本尽量以普通权限运行。
- 如果打包过程中需要安装 NSIS 插件或写系统目录,再动态申请权限。
- 渲染进程永远不要直接执行 shell 命令,必须先通过 preload 暴露的接口转发给主进程。
还有一个细节:打包脚本会自动执行 npm install,如果用户电脑上的 npm 被污染或源配置异常,可能引入恶意依赖。在自动安装依赖之前,建议检查 npm registry 配置,或在脚本中强制指定 registry。
7.2 配置管理与版本一致性
自己打包自己最怕的问题之一是“版本漂移”。如果界面里显示的版本和 package.json 中的版本不一致,用户安装后看到的版本会混乱。我建议:
- 版本号统一由一个文件维护,例如 version.json。
- 入口页面启动时读取 version.json 展示版本信息。
- 打包脚本也读取 version.json,再写入 package.json。
- 打包成功后,在 dist 目录生成一份 build-info.json,记录打包时间、版本号、Git 提交哈希。
下面是简化版 build-info.json 示例:
7.3 状态提示与失败可视化
脚本执行耗时较长,尤其是首次安装依赖时,可能需要几分钟。前端的 status 区域应该能实时显示日志。简单做法是把子进程 stdout 的 data 事件逐行发送给渲染进程。
渲染进程中监听:
这样可以让用户看到打包进度,而不是面对一个长时间无响应的按钮。
7.4 校验与分发
安装包生成后,建议在发布前做一次完整性校验,尤其是通过网盘或社交软件发送的场景。可以在打包完成后,自动计算安装包的 SHA256 值,生成校验文件。
将校验值输出到 dist 目录下的 sha256sums.txt,在分享时把这个文件一起发给用户,方便用户验证下载文件是否被篡改或损坏。
7.5 分发前检查清单
在发布一键安装版之前,建议按下面的清单检查一遍:
- productName 是否正确,避免安装后显示英文默认名。
- 图标是否清晰,Windows 安装包图标、桌面图标是否统一。
- 首次启动时,如果应用需要配置 API Key,是否有引导页面。
- 安装包大小是否合理,正常情况下 Electron 应用约 80MB 到 120MB。
- 是否已经清理 node_modules 中的调试依赖,安装包中只保留运行所需文件。
- 是否在不同分辨率下验证过界面布局,尤其是日志输出区域滚动条。
- 是否需要代码签名,正式对外发布建议购买代码签名证书。
8. 结语
让 DeepSeek Harness 桌面端“自己打包自己”,本质上不是复杂的技术魔法,而是把一套成熟的 Electron 构建流程封装成应用内的可视化操作。核心价值在于,当你不确定用户是否具备 Node.js 环境、是否了解 npm 命令时,直接在界面里点一个按钮就能得到安装包,大大降低分发成本。这套方案已经在我本机跑通,生成的一键安装版可以作为每日构建产物分享给内测用户。
下一步你可以继续优化几个方向:
- 将打包逻辑进一步下沉,生成独立命令,支持 CI 流水线调用。
- 加入多架构打包,比如 Windows 同时打包 x64 和 arm64。
- 增加增量更新能力,让用户安装后通过应用内更新获取新版本,而不是每次都重新下载安装包。
如果有其他问题,欢迎在评论区交流你的 Electron 打包经验。