draw.io Desktop 个人 Fork 构建实战:DRAWIO_UNSIGNED 未签名构建与 electron-builder 全流程
draw.io Desktop 对外部贡献关闭,但采用 Apache 2.0 许可——你可以 Fork 它、修改它并为个人用途自行构建。本文基于仓库内 doc/BUILDING_FOR_PERSONAL_USE.md 的完整流程展开,覆盖从递归克隆、修改 Electron 外壳、免打包调试,到 macOS/Windows/Linux 三平台本地构建、GitHub Actions 无人值守构建,以及 Fork 维护与编辑器子模块定制的每一步,并逐一对应到仓库源码(sync.cjs、src/main/electron.js、build/sign-trusted.mjs 等)中的真实实现。读完后你能够独立完成一次“签名豁免、自动更新禁用、可维护”的 draw.io 自定义桌面构建。
1. 背景:为什么个人构建是“未签名”的
官方二进制产物由 GitHub Actions 的 release 工作流产出,其中代码签名与公证(notarization)依赖私有基础设施:Apple Developer 证书、Azure Trusted Signing(Windows)、以及私有仓库 drawio-dev(CI 从中获取内建好的编辑器产物)。这些资源对 Fork 全部不可用——因此个人构建必然是**未签名(unsigned)**的。
构建系统为此提供了显式开关。设置环境变量:
DRAWIO_UNSIGNED=true
即可跳过代码签名与公证。从源码看,这个开关在两处生效:
- build/sign-trusted.mjs#L29-L33:Windows 签名钩子在签名每个文件前先检查该变量,命中则打印
DRAWIO_UNSIGNED=true: skipping code signing并直接返回;未设置时会抛出含 “Trusted Signing dlib not found” 字样的错误(build/sign-trusted.mjs#L38-L41)——这就是文档中提到的“缺少DRAWIO_UNSIGNED会以 Trusted Signing dlib 报错失败”的根源。 - build/notarize.mjs#L256-L260:macOS 的
afterSign公证钩子,命中时打印skipping notarization并跳过公证。
官方签名体系的更多细节(Azure 资源、Apple 组织级 secrets、失败排查表)可参见 doc/RELEASE_PROCESS.md 第 11 节。
设置该变量即代表你接受两条约定:
- 操作系统会警告(或拦截)未签名应用——macOS Gatekeeper 与 Windows SmartScreen 有其存在的理由,你是在为自己亲手构建的版本选择绕过它们;
- 该构建只归你所有:非官方构建不受支持,不要针对修改后的版本提 issue,也不要把自己的构建当作 draw.io 分发给他人。
Linux 产物本身就不签名,因此该变量在 Linux 构建中并非必需(设置也无害)。
2. Fork 与克隆:递归子模块与 Node 版本
在 GitHub 上 Fork 上游 jgraph/drawio-desktop 仓库,然后递归克隆——核心编辑器是一个 git 子模块,缺少它构建会失败:
git clone --recursive https://github.com/<your-username>/drawio-desktop.git
cd drawio-desktop
若已经克隆但漏了 --recursive,补一句 git submodule update --init 即可。
子模块定义见 .gitmodules:
[submodule "drawio"]
path = drawio
url = https://github.com/jgraph/drawio.git
branch = dev
为什么子模块不可或缺?sync.cjs#L8-L12 在找不到 drawio/VERSION 时会直接报错退出,提示“Did you clone with --recursive or run git submodule update --init?”——版本号正是从子模块中读取的。
环境要求:Node.js 22.12 或更高版本(package.json 中 engines.node 声明为 >=22.12.0),以及 npm;官方构建使用 Node 24。
3. 修改应用外壳:给窗口标题加后缀(完整示例)
文档给出的实战示例是:为所有窗口标题追加一个后缀,让你一眼确认当前运行的是自己的构建。
在 src/main/electron.js 中找到主窗口创建处(当前仓库位于第 427 行,文档写作“around line 442”):
let mainWindow = new BrowserWindow(options)
windowsRegistry.push(mainWindow)
在其后添加:
mainWindow.on('page-title-updated', (event, title) =>
{
event.preventDefault();
mainWindow.setTitle(title + ' (my build)');
});
代码风格注意:缩进使用 Tab,开括号独占一行——这与 src/main/electron.js#L429-L431 等处的既有写法一致(如 if (lastWinSize.maximized) 块)。
此修改作用对象是 Electron 外壳(src/main/),与第 8 节的编辑器修改是两条不同的路径。
4. 免打包验证:npm start 与单实例锁
不需要打安装包就能验证改动:
npm install
npm start
package.json 中 start 脚本即 electron .,直接从源码树运行应用。若改动生效,每个窗口标题都应以 (my build) 结尾。设置 DRAWIO_ENV=dev(PowerShell 写法:$env:DRAWIO_ENV="dev")可顺带打开 DevTools——对应 src/main/electron.js#L188 的 const __DEV__ = process.env.DRAWIO_ENV === 'dev'。
一个必须知道的坑:draw.io 每个用户只允许运行单实例。src/main/electron.js#L1292 通过 app.requestSingleInstanceLock() 实现——如果官方应用已在运行,你启动自己这份代码只会往官方应用里开一个新窗口(没有你的改动)。所以先退出所有正在运行的 draw.io。
如果你的日常使用就在开发机上,可以到此为止,长期使用 npm start——打包只是要得到一个“正常安装版”应用时才需要。
5. 本地构建安装程序
5.1 任何平台都要先做:同步版本并禁用自动更新
npm install
npm run sync -- disableUpdate
sync 的底层实现是 sync.cjs:它从 drawio/VERSION 读取版本(校验必须形如 X.Y.Z),写入 package.json 的 version 字段(sync.cjs#L14-L27),并根据命令行参数重新生成 src/main/disableUpdate.js。当前仓库中该文件内容为 export function disableUpdate() { return false;},执行 sync -- disableUpdate 后会被改写为 return true(sync.cjs#L28-L29)。
为什么要禁用更新?否则应用会通过 electron-updater 自动升级回官方(未修改的)版本,静默覆盖你的改动。
另注意:npm run release-* 脚本是 CI 用的,除 release-snap(--publish never)外均带 --publish always,会尝试把产物发布到 GitHub Releases——本地构建请直接调用 electron-builder,输出位于 dist/ 目录。
5.2 macOS
DRAWIO_UNSIGNED=true CSC_IDENTITY_AUTO_DISCOVERY=false npx electron-builder --config electron-builder-linux-mac.json --mac dmg --arm64 --publish never
- Intel 机器改用
--x64; - 想构建官方完整产物集(x64 + arm64 的 zip,x64 + arm64 + universal 的 dmg,耗时明显更久),把命令中的
--mac dmg --arm64整体去掉:命令行给出的目标会替换配置文件中的目标列表,只有“裸”命令才会继承 electron-builder-linux-mac.json 的完整目标清单; CSC_IDENTITY_AUTO_DISCOVERY=false阻止 electron-builder 去 keychain 里搜索签名证书。
产物为 ad-hoc 签名:构建机上可直接打开。若拷贝到另一台 Mac(或从 GitHub Actions 下载,见第 6 节),Gatekeeper 会隔离它并常报 “damaged”——拷入 /Applications 后清除一次隔离标记:
xattr -cr /Applications/draw.io.app
Quick Look 预览扩展会被打包并 ad-hoc 签名,但 macOS 可能拒绝加载未签名应用扩展——个人构建中请视为不支持 Quick Look。
5.3 Windows
PowerShell:
$env:DRAWIO_UNSIGNED="true"
npx electron-builder --config electron-builder-win.json --publish never
cmd.exe:
set DRAWIO_UNSIGNED=true
npx electron-builder --config electron-builder-win.json --publish never
该变量只对当前终端会话有效——新开终端要重新设置(缺失时构建会以 “Trusted Signing dlib” 相关错误失败,见第 1 节的源码出处)。
产物为 NSIS 安装程序与 MSI。SmartScreen 会在运行安装程序时警告(“Windows protected your PC”)——选择 More info → Run anyway。注意 NSIS 安装程序是**整机级(per-machine)**安装的,会替换已有的官方安装。
5.4 Linux
npx electron-builder --config electron-builder-linux-mac.json --linux AppImage deb --x64 --publish never
- Linux 二进制不签名,
DRAWIO_UNSIGNED不需要(设了也无害); - 按需把
deb换成rpm、--x64换成--arm64;构建 rpm 目标要求系统已安装rpm包。
一个命名细节:官方 Linux 包(以及第 6 节的 Actions 工作流)会把产品名设为 drawio,而本地“裸”构建使用 draw.io——因此本地 deb/rpm 会与官方 drawio 包并存而非替换。想与官方命名一致,可在构建前向 electron-builder-linux-mac.json 中加入 "productName": "drawio"。
6. 替代方案:在你的 Fork 上用 GitHub Actions 构建
没有对应平台的机器(或不想到处装工具链)时,可以在 Fork 上用 .github/workflows/personal-build.yml 工作流构建。它不需要任何 secrets、从不发布任何东西,且仅在你手动触发时运行。工作流文件本身印证了这一点:workflow_dispatch 触发、permissions: contents: read,并在环境变量中固化了 DRAWIO_UNSIGNED: 'true' 与 CSC_IDENTITY_AUTO_DISCOVERY: 'false'。
操作步骤:
-
提交并推送改动到你的 Fork——Actions 构建的是 GitHub 上的代码,不是你本地工作区:
git add -A git commit -m "my change" git push -
在 Fork 的 GitHub 页面打开 Actions 标签并启用工作流(GitHub 对新 Fork 默认禁用,需手动确认);
-
左侧选择 Personal Unsigned Build,点击 Run workflow;在弹窗中确认 Use workflow from 分支就是你推送改动的那个分支,选择目标平台(linux / macos / windows),运行;
-
运行结束后,从运行页面的 Artifacts 区下载安装包。
从工作流源码还可以看到几个实用细节(.github/workflows/personal-build.yml):
- 按所选平台选择
macos-latest/windows-latest/ubuntu-latestrunner,Node 固定为 24; - Linux 分支会安装打包工具(icnsutils、graphicsmagick、xz-utils、rpm),并用
sed把productName置为drawio以对齐官方 Linux 构建——这解释了为什么 Actions 产出的 Linux 包与官方同名、而本地裸构建默认不同名; - 产物以
drawio-<platform>-unsigned为前缀上传,路径覆盖dist/下的 dmg、zip、exe、msi、AppImage、deb、rpm。
同样的未签名注意事项依然适用;尤其从 GitHub 下载的 macOS .dmg 一定处于隔离状态,请预留 xattr -cr 这一步。
另外:tag 触发的 release 工作流(.github/workflows/electron-builder.yml、.github/workflows/electron-builder-win.yml)在 Fork 上无法工作——它们依赖私有 drawio-dev 仓库与签名 secrets,且仅在你推送 v* 标签时才会运行。因此:不要打 v* 标签(或直接从 Fork 中删掉这两个工作流)。
7. 保持 Fork 更新
先提交自己的改动,然后丢弃构建过程重写过、git 拒绝合并的文件——npm run sync 会再生成 package.json 的 version 与 src/main/disableUpdate.js:
git checkout -- package.json src/main/disableUpdate.js
git remote add upstream https://github.com/jgraph/drawio-desktop.git
git fetch upstream
git merge upstream/dev
git submodule update --init
最后一步把 drawio 子模块移动到桌面应用所期望的提交点。更新完成后需要重新构建。
两个版本上的小坑:
- 你的构建版本号来自公开子模块中的
drawio/VERSION,它可能略滞后于由内部仓库构建的官方桌面版本(CI 会先将内部产出的编辑器与 VERSION 拷入子模块目录再跑npm run sync,见 CLAUDE.md 中关于 CI override 的说明); - 由于自动更新已被禁用,更新永远是:拉取、重新构建、重装。
8. 修改编辑器而非外壳
至此的全部内容都在改 Electron 外壳(src/main/)。而图表编辑器本身位于 drawio 子模块中。关键区别在于两种构建路径取子模块的方式不同:
- 本地构建:electron-builder 打包的是你子模块工作区里的现状——所以本地构建时可以直接编辑
drawio/src/main/webapp/下的文件。例如js/PreConfig.js在编辑器加载前执行,是官方支持的配置覆写位置; - GitHub Actions 构建:子模块会从
jgraph/drawio全新检出(actions/checkout的submodules: true),你本地的子模块编辑不生效。此时需要把drawio也 Fork 一份,把编辑器改动推送到那里,再把桌面 Fork 的子模块指向它——编辑 .gitmodules 并提交新的子模块引用。
9. 速查表
| 目标 | 命令 / 操作 |
|---|---|
| 克隆(含子模块) | git clone --recursive <fork-url>;补救:git submodule update --init |
| 免打包运行 + DevTools | npm install && npm start,DRAWIO_ENV=dev |
| 同步版本 + 禁用自动更新 | npm run sync -- disableUpdate |
| macOS 本地构建 | DRAWIO_UNSIGNED=true CSC_IDENTITY_AUTO_DISCOVERY=false npx electron-builder --config electron-builder-linux-mac.json --mac dmg --arm64 --publish never |
| Windows 本地构建 | 设置 DRAWIO_UNSIGNED=true 后 npx electron-builder --config electron-builder-win.json --publish never |
| Linux 本地构建 | npx electron-builder --config electron-builder-linux-mac.json --linux AppImage deb --x64 --publish never |
| 跨 Mac 传播未签名 app | xattr -cr /Applications/draw.io.app |
| 无本地机器 | Fork Actions 中手动运行 Personal Unsigned Build,下载 Artifacts |
| 同步上游 | git checkout -- package.json src/main/disableUpdate.js → fetch/merge upstream/dev → git submodule update --init |
核心要点回顾:DRAWIO_UNSIGNED=true 是签名/公证的显式豁免(build/sign-trusted.mjs、build/notarize.mjs),sync -- disableUpdate 防止自定义版被官方更新覆盖(sync.cjs),--publish never 与直接调用 electron-builder 保证构建只落盘在 dist/、绝不外发。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00