docs: document Windows build and packaging

This commit is contained in:
yonglin
2026-09-17 16:25:55 +08:00
parent b9098ece5c
commit 2697bff24d
2 changed files with 105 additions and 38 deletions
+63 -24
View File
@@ -2,7 +2,7 @@
**English** · [中文](README.zh-CN.md)
A desktop SSH session manager for Linux — connection tree, tabbed terminals,
A desktop SSH session manager for Windows and Linux — connection tree, tabbed terminals,
tunnels, local shell and serial.
Think Xshell's session management with tmux's multiplexing, local-first: your
@@ -28,7 +28,24 @@ Early. Everything below reflects what actually works today.
## Prerequisites
### Build toolchain (for native modules)
### Windows
Node.js 22.12 or newer is required. The dependencies ship Windows x64/ARM64
prebuilds for `better-sqlite3` and `node-pty`, so Visual Studio is normally not
required. If `npm install` still falls back to `node-gyp`, use the verified
no-compile installation path:
```powershell
npm ci --ignore-scripts
node node_modules/electron/install.js
```
With this path, `ssh2` uses its JavaScript crypto implementation. Functionality
is unchanged, but the optional native acceleration is absent. To compile every
optional native module, install Python 3 and Visual Studio Build Tools with the
Desktop development with C++ workload.
### Linux build toolchain (for native modules)
```sh
sudo apt install build-essential python3 pkg-config libudev-dev
@@ -39,7 +56,8 @@ M8, but installing it up front avoids a confusing build failure later.
### Runtime keyring
Credential storage depends on an OS keyring daemon. Verify one is running:
Windows uses DPAPI and needs no additional keyring service. On Linux,
credential storage depends on an OS keyring daemon. Verify one is running:
```sh
ps -e -o comm= | grep -E 'gnome-keyring|kwalletd'
@@ -82,7 +100,8 @@ Other scripts:
npm test # vitest — see "Testing" below
npm run typecheck # main/preload and renderer are checked separately
npm run build # production bundles into out/
npm run dist # AppImage + deb into release/
npm run dist # package for the current platform into release/
npm run dist:win # explicitly build the Windows x64 NSIS installer
npm run rebuild:native # rebuild native modules against Electron's ABI
```
@@ -119,6 +138,12 @@ npm run build
npm run smoke
```
On Windows PowerShell, replace the second line with:
```powershell
.\node_modules\electron\dist\electron.exe --remote-debugging-port=9222 .
```
`scripts/ipc-smoke.mjs` attaches to the running app over the Chrome DevTools
Protocol and calls `window.nexusterm` — the same object the UI calls. That makes
it a real end-to-end check of preload bridge → `ipcRenderer.invoke` → handler →
@@ -127,7 +152,8 @@ UI by hand.
It currently verifies folder and host creation, nesting, rename, moving to the
root layer, refusal of a folder move into its own descendant, rejection of an
invalid payload, and that it cleans up after itself.
invalid payload, opening and closing a local PTY, and that it cleans up after
itself.
### SSH, against a real server
@@ -156,39 +182,49 @@ remote wrote — which is the return path proven separately.
## Packaging
```sh
npm run dist # AppImage + deb into release/
npm run dist # package for the current platform into release/
npm run dist:win # Windows x64 NSIS installer into release/
npm run verify:package # check the result can actually run
```
The Windows installer is written to `release/NexusTerm-<version>-x64.exe`. The
uninstalled layout is kept in `release/win-unpacked/` for quick testing before
running the installer.
`scripts/verify-package.mjs` exists because a package that is broken in the
ways that matter still builds, still installs, and still starts. Native modules
cannot be loaded from inside an asar archive, and when they are left there the
failure appears on a user's machine the first time they open a terminal — not
in CI, and not in `npm test`. So the script extracts the AppImage and asserts
that each native binary really is in `app.asar.unpacked`.
in CI, and not in `npm test`. The script checks a Linux AppImage or the Windows
`win-unpacked` directory and asserts that each current-platform native binary
really is in `app.asar.unpacked`.
Two things it checks that are easy to get wrong:
- **`ssh2` carries a native module.** `sshcrypto.node` is easy to miss because
the library reads as pure JavaScript — and because a failed load falls back
to a JS implementation silently, so nothing would ever report it.
- **`node-pty` must be unpacked whole, not just its `.node` files.** On macOS
and Windows it ships `spawn-helper`, a real executable loaded from disk rather
than `dlopen`ed, which stays trapped in the archive if only `*.node` is
unpacked.
- **`ssh2` carries an optional native module.** `sshcrypto.node` is easy to miss
because the library reads as pure JavaScript. A no-compile Windows install
warns when it is absent and uses the JS implementation.
- **`node-pty` must be unpacked whole, not just its `.node` files.** Windows
also needs `winpty-agent.exe`, `OpenConsole.exe`, and their DLLs; unpacking
only `*.node` produces a terminal that fails after installation.
`npmRebuild` is off: every native module here is built on `node-addon-api`, so
the binaries from `npm install` load unchanged in Electron's Node. Rebuilding
would cost time and risk replacing working binaries for no gain.
Both `.deb` metadata and `desktopName` are set, and the AppImage's
`.desktop` entry is generated from `linux.desktop.entry`.
`.desktop` entry is generated from `linux.desktop.entry`. Windows uses an NSIS
installer with a selectable install directory plus Start menu and desktop
shortcuts.
### Packaging notes for whoever picks this up
- The `author`, `homepage` and `maintainer` fields are **placeholders**
(`ylhan@example.com`, `example.com`). They are required by the deb format and
must be replaced before distributing.
- The Windows installer is currently **not digitally signed**, so SmartScreen
may report an unknown publisher. Configure a code-signing certificate before
public distribution.
- `asarUnpack` lists `node_modules/@serialport/**` for a backend that does not
exist yet. Harmless now; load-bearing once the serial transport lands.
@@ -224,7 +260,8 @@ need a module declaration — `"types": ["vite/client"]` supplies it.
**`better-sqlite3` ships N-API prebuilds.**
`node_modules/better-sqlite3/prebuilds/` contains `linux-x64.node` and friends,
`node_modules/better-sqlite3/prebuilds/` contains `linux-x64.node`,
`win32-x64.node`, and friends,
built with `node-addon-api`. Because N-API is ABI-stable across Node and Electron
versions, this normally removes the need for an `electron-rebuild` step. If a
native load ever fails at runtime, check here first before reaching for a
@@ -232,7 +269,8 @@ rebuild.
**The Electron binary may need a manual download.**
If `npm install` completes but `node_modules/electron/dist/electron` is missing,
If `npm install` completes but `node_modules/electron/dist/electron` (or
`electron.exe` on Windows) is missing,
the package's postinstall was skipped. Run:
```sh
@@ -350,13 +388,14 @@ multi-byte sequences across reads rather than mangling them. Re-encoding with
uniform — everything above speaks bytes.
**node-pty needs no rebuild against Electron.** It is built on `node-addon-api`,
so the binary compiled during `npm install` loads unchanged in Electron's Node —
verified by spawning a real PTY from inside Electron 44. This retired what the
plan had ranked as the project's highest risk.
so the binary selected or compiled during dependency installation loads
unchanged in Electron's Node — verified by spawning a real PTY from inside
Electron 44. This retired what the plan had ranked as the project's highest
risk.
On Linux there is also no `spawn-helper`: that exists only for macOS and
Windows. The `asarUnpack` rule covers the whole package anyway, so the
executable surviving the asar boundary is handled without a special case.
Linux has no additional terminal helper executable. The Windows prebuilds carry
`winpty-agent.exe` and `OpenConsole.exe`; the `asarUnpack` rule covers the whole
package so both survive the asar boundary.
### The sidebar, and where sessions live
+42 -14
View File
@@ -1,6 +1,6 @@
# NexusTerm
Linux 桌面上的 SSH 会话管理器 —— 连接树、按主机分组的标签页、端口转发、本地终端与串口。
Windows 与 Linux 桌面上的 SSH 会话管理器 —— 连接树、按主机分组的标签页、端口转发、本地终端与串口。
可以理解为:Xshell 的会话管理 + tmux 的多路复用,且本地优先 —— 你的连接资产不出本机,凭据由操作系统钥匙串保管,而不是托管在厂商的云上。
@@ -25,7 +25,22 @@ Linux 桌面上的 SSH 会话管理器 —— 连接树、按主机分组的标
## 前置条件
### 构建工具链(原生模块需要)
### Windows
需要 Node.js 22.12 或更高版本。依赖包自带 Windows x64/ARM64 的
`better-sqlite3` 与 `node-pty` 预编译文件,因此通常不需要 Visual Studio。
若 `npm install` 仍回退到 `node-gyp`,可以使用已经验证过的无编译安装方式:
```powershell
npm ci --ignore-scripts
node node_modules/electron/install.js
```
这种方式下 `ssh2` 使用 JavaScript 加密实现;功能不受影响,但没有可选的原生加速。
若要编译所有可选原生模块,请安装 Python 3 和带“使用 C++ 的桌面开发”工作负载的
Visual Studio Build Tools。
### Linux 构建工具链(原生模块需要)
```sh
sudo apt install build-essential python3 pkg-config libudev-dev
@@ -35,7 +50,7 @@ sudo apt install build-essential python3 pkg-config libudev-dev
### 运行时钥匙串
凭据存储依赖系统钥匙串守护进程。先确认它在运行:
Windows 使用 DPAPI,不需要额外的钥匙串服务。Linux 上的凭据存储依赖系统钥匙串守护进程;先确认它在运行:
```sh
ps -e -o comm= | grep -E 'gnome-keyring|kwalletd'
@@ -72,7 +87,8 @@ npm run dev
npm test # vitest,见下文「测试」
npm run typecheck # 主进程/preload 与渲染进程分开检查
npm run build # 产物打包到 out/
npm run dist # AppImage + deb 到 release/
npm run dist # 当前平台的安装包到 release/
npm run dist:win # 明确构建 Windows x64 NSIS 安装器
npm run rebuild:native # 按 Electron 的 ABI 重建原生模块
```
@@ -98,9 +114,15 @@ npm run build
npm run smoke
```
Windows PowerShell 中第二行改为:
```powershell
.\node_modules\electron\dist\electron.exe --remote-debugging-port=9222 .
```
`scripts/ipc-smoke.mjs` 通过 Chrome DevTools 协议接入运行中的应用,调用 `window.nexusterm`——**和界面调用的是同一个对象**。于是它成了对 preload 桥 → `ipcRenderer.invoke` → 处理器 → zod 校验 → 仓储 → SQLite → 回包的完整端到端检查,且不需要用手去点界面。
它目前验证:创建文件夹与主机、嵌套、重命名、移回根层、拒绝把文件夹移入自己的子孙、拒绝非法载荷,以及跑完自己清理干净。
它目前验证:创建文件夹与主机、嵌套、重命名、移回根层、拒绝把文件夹移入自己的子孙、拒绝非法载荷、本地 PTY 能启动和关闭,以及跑完自己清理干净。
### SSH:对真实服务端
@@ -123,24 +145,29 @@ npm run e2e:ssh
## 打包
```sh
npm run dist # AppImage + deb 到 release/
npm run dist # 当前平台的安装包到 release/
npm run dist:win # Windows x64 NSIS 安装器到 release/
npm run verify:package # 检查产物是否真能运行
```
`scripts/verify-package.mjs` 的存在理由:**一个在关键之处坏掉的包,照样能构建、能安装、能启动**。原生模块无法从 asar 归档内部加载,而当它们被留在里面时,故障出现在**用户机器上、第一次打开终端的那一刻**——不在 CI 里,也不在 `npm test` 里。所以这个脚本会解开 AppImage,逐一断言每个原生二进制**确实在 `app.asar.unpacked` 里**。
Windows 安装器输出为 `release/NexusTerm-<version>-x64.exe`;未安装的展开版本位于
`release/win-unpacked/`,便于在制作安装器前快速试运行。
`scripts/verify-package.mjs` 的存在理由:**一个在关键之处坏掉的包,照样能构建、能安装、能启动**。原生模块无法从 asar 归档内部加载,而当它们被留在里面时,故障出现在**用户机器上、第一次打开终端的那一刻**——不在 CI 里,也不在 `npm test` 里。脚本会检查 Linux AppImage 或 Windows 的 `win-unpacked` 目录,逐一断言当前平台的原生二进制**确实在 `app.asar.unpacked` 里**。
它检查的两件事都属于容易搞错的那类:
- **`ssh2` 带原生模块。** `sshcrypto.node` 容易被忽略,因为这个库读起来像纯 JavaScript——而且加载失败时它会**静默**退回 JS 实现,所以没有任何东西会报告这件事。
- **`node-pty` 必须整包解出,而不只是它的 `.node` 文件。** 在 macOS 和 Windows 上它带一个 `spawn-helper`——一个从磁盘加载的真实可执行文件,不是 `dlopen` 进来的;只解包 `*.node` 会让它困在归档里。
- **`ssh2` 带可选原生模块。** `sshcrypto.node` 容易被忽略,因为这个库读起来像纯 JavaScript;Windows 无编译安装缺少它时会明确告警,并使用 JS 实现。
- **`node-pty` 必须整包解出,而不只是它的 `.node` 文件。** Windows 上还需要 `winpty-agent.exe`、`OpenConsole.exe` 与配套 DLL;只解包 `*.node` 会让终端在安装后失效。
`npmRebuild` 是关闭的:这里每个原生模块都基于 `node-addon-api`,所以 `npm install` 时编译出的二进制在 Electron 的 Node 下可原样加载。重建只会花时间、并冒着用不同方式编译的产物替换掉可用产物的风险,没有任何收益。
`.deb` 元数据与 `desktopName` 都已设置,AppImage 的 `.desktop` 条目由 `linux.desktop.entry` 生成。
`.deb` 元数据与 `desktopName` 都已设置,AppImage 的 `.desktop` 条目由 `linux.desktop.entry` 生成。Windows 使用可选择安装目录的 NSIS 安装器,并创建开始菜单和桌面快捷方式。
### 给接手的人
- `author`、`homepage`、`maintainer` 三个字段目前是**占位值**(`ylhan@example.com`、`example.com`)。deb 格式要求这些字段存在,正式分发前必须替换。
- Windows 安装包目前**没有数字签名**,因此 SmartScreen 可能显示“未知发布者”。正式分发前应配置代码签名证书。
- `asarUnpack` 里列了 `node_modules/@serialport/**`,而那个后端尚不存在。现在无害;串口传输落地后它就是承重的了。
## 工具链约束
@@ -165,11 +192,11 @@ Electron 44.4.1 内置 Node 24.21.0 与 Chrome 152——这是在运行时确认
**`better-sqlite3` 自带 N-API 预编译产物。**
`node_modules/better-sqlite3/prebuilds/` 里有 `linux-x64.node` 等文件,基于 `node-addon-api` 构建。由于 N-API 的 ABI 在 Node 与 Electron 版本间稳定,这通常免掉了 `electron-rebuild` 这一步。若某次运行时原生模块加载失败,先查这里,再去考虑重建。
`node_modules/better-sqlite3/prebuilds/` 里有 `linux-x64.node`、`win32-x64.node` 等文件,基于 `node-addon-api` 构建。由于 N-API 的 ABI 在 Node 与 Electron 版本间稳定,这通常免掉了 `electron-rebuild` 这一步。若某次运行时原生模块加载失败,先查这里,再去考虑重建。
**Electron 二进制可能需要手动下载。**
如果 `npm install` 完成但 `node_modules/electron/dist/electron` 不存在,说明该包的 postinstall 被跳过了。执行:
如果 `npm install` 完成但 `node_modules/electron/dist/electron`(Windows 为 `electron.exe`)不存在,说明该包的 postinstall 被跳过了。执行:
```sh
node node_modules/electron/install.js
@@ -235,9 +262,10 @@ preload contextBridge,窄白名单 —— 这个文件**就是**
两者只在一个地方值得注意地不同:ssh2 交出 `Buffer`,node-pty 交出 `string`——因为它自己解码 PTY 流,能在跨读取时接住被切断的多字节序列而不是损坏它。因此 `Buffer.from(chunk, 'utf8')` 是无损的,也就让边界保持一致:上层一律说字节。
**node-pty 不需要针对 Electron 重建。** 它基于 `node-addon-api`,所以 `npm install` 时编译的二进制在 Electron 的 Node 下原样加载——这一点已在 Electron 44 内部实际拉起一个 PTY 验证过。这退休了计划中原先排名第一的风险。
**node-pty 不需要针对 Electron 重建。** 它基于 `node-addon-api`,所以依赖安装时选取或编译的二进制在 Electron 的 Node 下原样加载——这一点已在 Electron 44 内部实际拉起一个 PTY 验证过。这退休了计划中原先排名第一的风险。
Linux 上也没有 `spawn-helper`:那只存在于 macOS 和 Windows。`asarUnpack` 规则无论如何覆盖整个包,所以"可执行文件能否穿过 asar 边界"无需特例。
Linux 上没有额外的终端辅助可执行文件;Windows 预编译包则带有
`winpty-agent.exe` 和 `OpenConsole.exe`。`asarUnpack` 规则覆盖整个包,所以这些文件都能正确穿过 asar 边界。
### 侧边栏,以及会话住在哪里