Files
MailUI4Agents/client/electron/BUILD.md
JianFeeeee dacf6c0e1f feat(client): 打通 Electron 安装包打包,并留下构建文档
# 背景

Electron 桌面安装包一直没打出来过(`release/` 为空,只有 web bundle)。
这次把它跑通,并验证了产物本身而不只是"文件存在"。

# 改动

**package.json 补齐 electron-builder 需要的元数据**(缺哪个就会让某个 target
直接失败,而报错不一定指向字段本身):

| 字段 | 位置 | 不填的后果 |
|---|---|---|
| `description` | 根 | deb 描述为空 |
| `author`(含 email) | 根 | deb 缺 maintainer |
| `homepage` | 根 | **deb 直接失败**:`Please specify project homepage` |
| `desktopName` | 根 | 窗口无法与 .desktop 关联(缺 `StartupWMClass`) |
| `linux.syncDesktopName` | `build.linux` | 同上 |
| `linux.synopsis` | `build.linux` | deb 描述只有一行 |

**新增 `client/electron/BUILD.md`**:记录构建命令、本机两个坑(见下)、
元数据清单、两个产物的实质区别、以及验证产物的方法。

# 两个产物(已在 release/,被 .gitignore 排除)

- `AgentMail-0.1.0.AppImage` 122MB,有效 x86-64 ELF、可执行位已设
- `agentmail-web_0.1.0_amd64.deb` 100MB,Maintainer/Homepage/Depends/两行 Description 齐全

**实测的实质区别(不是猜测,来自解包对照)**:

| | AppImage | deb |
|---|---|---|
| `.desktop` Exec | `AppRun --no-sandbox %U` | `/opt/AgentMail/agentmail-web %U` |
| Chromium 沙箱 | **禁用**(squashfs 无法保留 setuid 的 chrome-sandbox) | **启用**(postinst 按能力 `0755` 或 `4755`,并装 AppArmor 配置) |
| 卸载 | 删文件 | postrm 清理 alternatives / AppArmor / desktop-mime 库 |

结论写进文档:**对外分发优先 deb**。

# 验证(不只查存在性)

- AppImage:`--appimage-extract` 解包成功;`resources/app.asar` 8.5MB;
  asar 清单里 `/dist/index.html`、`/dist/assets/{index,CalendarView}.js`、
  `/dist/assets/{agentmail.svg,favicon.ico,apple-touch-icon.png}`、
  `/electron/{main,preload}.cjs` 齐全;`index.html` 里 DOCTYPE 仍是大写
  (即格式化器修复也进了包)
- deb:`dpkg-deb --info/--contents` 核对元数据与布局;7 档图标尺寸齐全;
  读 postinst 确认沙箱策略与 AppArmor 安装

# 环境坑(已写进 BUILD.md)

**本网络下 `SHASUMS256.txt` 不可达**(直连 20s 超时、走代理也失败),
而 electron 构件本身 0.8s 就拿到(HTTP 206)。`@electron/get` 即使命中缓存
也会取校验文件 → 构建卡 10 分钟后失败。用命令行覆盖绕过:

    npx electron-builder --linux -c.electronDownload.isVerifyChecksum=false

**刻意不写进 package.json** —— 那会让今后每次构建都跳过完整性校验。一次性绕过
网络限制不该固化成永久弱化的默认值。代价已写进文档(关校验后可被篡改镜像在
TLS 之外替换构件)。

# 待用户确认的占位值

- `author.email` 用了仓库自身的 git 身份 `jianf@noreply.localhost` —— 容器占位邮箱,
  **不是真实联系地址**。项目里没有可用的真实邮箱,我没有编造一个。
- `homepage` 用 git remote 的唯一真实地址(内网 Gitea `192.168.2.106:3000`)。

两者对外分发前都应替换。
2026-09-12 01:17:49 +08:00

97 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 桌面安装包构建
生成 Linux / Windows 安装包。日常开发只需 `npm run build`(出 web 产物),
本文只讲**分发用安装包**。
## 命令
```bash
npm run build:linux # AppImage + deb
npm run build:win # NSIS 安装器
```
产物在 `release/`(已在 `.gitignore` 里,不要提交 —— AppImage 约 122MB、deb 约 100MB
## 本机环境的两个坑
### 1. `SHASUMS256.txt` 不可达 → 构建会卡 10 分钟后失败
现象:
```
• downloaded label=electron progress=100%
Timeout awaiting 'request' for 600000ms failedTask=build
```
**不是构件下载失败**。实测该网络下:
| URL | 结果 |
|---|---|
| `…/releases/download/v44.2.0/electron-v44.2.0-linux-x64.zip` | HTTP 2060.8s ✅ |
| `…/releases/download/v44.2.0/SHASUMS256.txt` | 直连 20s 超时、走 Clash 代理也失败 ❌ |
`@electron/get` 即使命中本地缓存,也会去取 `SHASUMS256.txt` 校验,于是卡死。
**绕过**(命令行覆盖,不要写进 `package.json`
```bash
npx electron-builder --linux -c.electronDownload.isVerifyChecksum=false
```
**为什么不写进配置**:那会让今后**每一次**构建都跳过完整性校验。一次性绕过
网络限制不该固化成永久弱化的默认值。关校验后,被篡改的镜像可以在 TLS 之外替换
构件而无人察觉 —— 本场景可接受(构件来自官方 GitHub 的 TLS 连接,且本地缓存
已验证可读),但不该成为默认。
### 2. 依赖缓存齐全,可离线完成
`~/.cache/electron-builder/` 下以下条目的 `.state` 均为 `complete` 即可离线:
`appimage-12.0.1`AppImage 运行时)、`fpm@2.1.4`deb`7zip@1.0.0`
`nsis-resources-3.4.1` + `nsis-3.0.4.1`Windows 安装器)。
Electron 二进制在 `~/.cache/electron/`(首次需要网络,`electron-v44.2.0-linux-x64.zip` 约 123MB
## package.json 里与打包有关的元数据
这些字段缺失会让某个 target 直接失败,报错信息不一定指向字段本身:
| 字段 | 位置 | 不填的后果 |
|---|---|---|
| `description` | 根 | deb 描述为空 |
| `author`(含 email | 根 | deb 缺 maintainer 信息 |
| `homepage` | 根 | **deb 直接构建失败**`Please specify project homepage` |
| `desktopName` | 根 | 窗口无法与 `.desktop` 关联(`StartupWMClass` 缺失) |
| `linux.syncDesktopName` | `build.linux` | 同上,配合 `desktopName` 一起用 |
| `linux.synopsis` | `build.linux` | deb 描述只有一行 |
> ⚠️ 当前 `author.email` 是容器占位值 `jianf@noreply.localhost`
> `homepage` 是内网 Gitea 地址。**正式对外分发前必须替换成真实值。**
## 两个产物的实质区别(实测)
| | AppImage | deb |
|---|---|---|
| `.desktop` 的 Exec | `AppRun --no-sandbox %U` | `/opt/AgentMail/agentmail-web %U` |
| Chromium 沙箱 | **禁用** —— squashfs 里无法保留 setuid 的 `chrome-sandbox` | **启用** —— postinst 按能力决定:能跑用户命名空间则 `0755`,否则设 `4755`;并安装 AppArmor 配置Ubuntu 24+ 需要) |
| 卸载 | 删文件即可 | postrm 清理 alternatives、AppArmor 配置、desktop/mime 数据库 |
**要给用户装,优先分发 deb**:沙箱可用、依赖声明完整、可被包管理器卸载。
## 验证产物的方法(别只看文件存在)
```bash
# deb 元数据与依赖
dpkg-deb --info release/*.deb
# deb 内容布局(含 chrome-sandbox 权限、desktop 文件、图标尺寸)
dpkg-deb --contents release/*.deb | grep -E '\.desktop|chrome-sandbox|app\.asar'
# postinst 的沙箱策略
dpkg-deb --control release/*.deb /tmp/ctl && cat /tmp/ctl/postinst
# AppImage不依赖 FUSE 解包并看载荷
./release/AgentMail-0.1.0.AppImage --appimage-extract
ls squashfs-root/resources/app.asar
# 确认 asar 里是真正构建的应用(而不是空壳)
node -e "const a=require('@electron/asar');console.log(a.listPackage('release/linux-unpacked/resources/app.asar').filter(p=>/^\/(dist|electron)\//.test(p)).join('\n'))"
```