124 lines
10 KiB
Markdown
124 lines
10 KiB
Markdown
|
|
# NSIS 安装包临时目录安装冒烟
|
|||
|
|
|
|||
|
|
> round-44 并行 sub agent 方向 GG(矩阵 99 / 118 本地衔接部分)。
|
|||
|
|
> 执行日期:2026-08-02;环境:Windows(PowerShell),Node v22.13.0。
|
|||
|
|
|
|||
|
|
## 1. 验收矩阵与本方向范围
|
|||
|
|
|
|||
|
|
| 矩阵行 | 条目 | 本地可测部分 | 剩余(外部项) |
|
|||
|
|
|--------|------|--------------|----------------|
|
|||
|
|
| 99 | Defender 和真实 APS 求解通过 | 衔接 round-43 CC:Defender 静态扫描 4/4 clean(见 packaging-smoke.md);真实 APS 求解由四引擎黄金测试覆盖(既有体系,非本方向交付) | Defender for Endpoint 门户/API 门禁;实时防护真实拦截演练(需受控样本与策略配合) |
|
|||
|
|
| 118 | 干净离线机安装冒烟 | 本方向补"真装冒烟":NSIS 静默安装到随机临时目录 + 布局/内容检查 + 卸载清理(本地实测 2 次 PASS,见 §6) | 真实用户机安装(含 %LOCALAPPDATA%\Programs 落点、开始菜单/桌面快捷方式体验);干净离线机实机安装 + 冷启动 + 升级/回滚验收,需外部验证机 |
|
|||
|
|
|
|||
|
|
硬性纪律遵守情况:未修改 server/**、apps/desktop/**、packaging/**(仅只读引用);未 commit/push/merge;未重启/占用 8003/5173。
|
|||
|
|
|
|||
|
|
## 2. 涉及文件
|
|||
|
|
|
|||
|
|
新增(本方向写范围):
|
|||
|
|
|
|||
|
|
- `scripts/nsis-install-smoke.mjs` —— NSIS 临时目录安装冒烟脚本
|
|||
|
|
- `tests/node/nsis-smoke.test.mjs` —— 可测逻辑的 node 单测
|
|||
|
|
- `docs/development/nsis-smoke.md` —— 本文档
|
|||
|
|
|
|||
|
|
只读引用:
|
|||
|
|
|
|||
|
|
- `scripts/offline-install-check.mjs` —— 复用 `sha256File` / `readDesktopConfig` / `resolveInstallerName` / `formatBytes`(round-43 CC)
|
|||
|
|
- `build/offline-check/offline-install-manifest.json` —— 期望 sha256 来源(nsis-installer 条目)
|
|||
|
|
- `apps/desktop/updater.cjs` —— `nsisInstall` 参数约定(/S 静默;/D= 安装目录须为最后参数、不带引号),本轮真装实测验证该约定可行
|
|||
|
|
- `apps/desktop/release/aps-agent-desktop-0.1.0.exe` —— 本轮实测的 NSIS 安装包(163.1 MB,sha256 `044c4a83aaf28476a1e2405a7a08185b5aff0dfab27f0299b8bcf16c8d32bf21`)
|
|||
|
|
|
|||
|
|
## 3. 脚本原理与用法
|
|||
|
|
|
|||
|
|
原理:在系统临时目录下 `fs.mkdtemp` 随机目录,用 NSIS 参数 `/S /D=<临时目录>` 静默安装(/D 是最后参数、不带引号),校验安装后布局(主 exe + resources/app.asar + resources/sidecar/aps-sidecar.exe + 卸载器),可选启动主 exe 数秒做健康冒烟,最后卸载/删除临时目录。全程不触碰真实安装位置。
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
|
|||
|
|
node scripts/nsis-install-smoke.mjs [installer.exe] [options]
|
|||
|
|
node scripts/nsis-install-smoke.mjs --installer <path> --run-uninstaller
|
|||
|
|
node scripts/nsis-install-smoke.mjs --self-test
|
|||
|
|
|
|||
|
|
| 选项 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| 位置参数 / `--installer <path>` | 显式指定安装包路径(显式优先,不再自动发现) |
|
|||
|
|
| `--expected-sha256 <hex>` | 期望 sha256(覆盖清单) |
|
|||
|
|
| `--manifest <path>` | 期望 sha256 清单(默认 `build/offline-check/offline-install-manifest.json`) |
|
|||
|
|
| `--timeout <ms>` | 安装器超时(默认 600000 = 10 min) |
|
|||
|
|
| `--run-uninstaller` | 用安装产物自带的卸载器(`Uninstall <productName>.exe /S`)卸载后再清理;默认直接删除临时目录 |
|
|||
|
|
| `--launch` / `--no-launch` | 启动主 exe 数秒健康冒烟(默认**不启动**,`--launch-seconds` 默认 8s,启动后强制 taskkill /T /F 树杀) |
|
|||
|
|
| `--temp-root <dir>` | 临时根(默认 `os.tmpdir()`);非 ASCII %TEMP% 环境建议显式 ASCII 目录 |
|
|||
|
|
| `--keep-temp-dir` | 保留临时安装目录供人工检查(打印路径,不自动删除) |
|
|||
|
|
| `--top-n <n>` | 内容清单打印条数(默认 10) |
|
|||
|
|
| `--json` | 机器可读 JSON 报告 |
|
|||
|
|
| `--self-test` | 内部逻辑冒烟(不安装、不清理) |
|
|||
|
|
|
|||
|
|
产物定位顺序:显式参数 → 清单 nsis-installer 条目 → `apps/desktop/release/<artifactName>` → `build/offline-check/<artifactName>` → release 目录下 ≥50MB 的 exe。缺产物时明确报错。
|
|||
|
|
|
|||
|
|
退出码:`0` = PASS(校验、安装、布局、清理全部通过);`1` = FAIL(sha256 不匹配 / 安装失败 / 布局无效 / 清理失败 / 非 Windows);`2` = NO_INSTALLER(找不到安装包)。
|
|||
|
|
|
|||
|
|
## 4. 安全边界与取舍
|
|||
|
|
|
|||
|
|
- **绝不写 %LOCALAPPDATA%\Programs 或系统目录**:安装目录 = `fs.mkdtemp` 于系统临时目录,`/D=` 把 NSIS 指到那里;脚本自身不写注册表、不写开始菜单/桌面快捷方式。
|
|||
|
|
- **安装器自身的副作用**:electron-builder NSIS 安装器(即便 `/S` 且 `/D=` 指向临时目录)会为本次临时安装写 per-user 卸载注册表项 + 开始菜单快捷方式(指向临时目录)。这是安装器自带逻辑,不是本脚本的行为。
|
|||
|
|
- `--run-uninstaller`:运行卸载器,删除文件并**清掉这些注册表项与快捷方式**(实测卸载后 HKCU 无 aps 卸载键、开始菜单无残留快捷方式)。
|
|||
|
|
- 默认直接删除临时目录:不执行卸载器,安装器自己的注册表项/快捷方式会残留并指向已删除路径 —— 取舍说明:CI 沙箱或不想跑卸载器的场景选默认;想彻底干净选 `--run-uninstaller`。
|
|||
|
|
- **删除前路径校验**(Windows 安全规则):`assertSafeTempDir()` 先 `path.resolve`,确认目标位于系统临时目录下、不是临时目录根、basename 以 `aps-nsis-smoke-` 开头,才允许 `fs.rmSync(recursive)`;守卫逻辑有单测覆盖。
|
|||
|
|
- **不驻留进程**:默认不启动主 exe;`--launch` 启动后强制 `taskkill /T /F` 杀进程树,并把 `--user-data-dir` 指到临时安装目录内(应用用户数据不出沙箱)。卸载/删除前还会用 `Get-CimInstance Win32_Process` 枚举可执行路径在临时目录下的残留进程并杀掉(防安装器 runAfterFinish 自动拉起残留)。
|
|||
|
|
- **卸载器异步收尾**:卸载器进程退出(exit 0)后文件删除是异步的,脚本会轮询等待目录消失(最长 25s),仍未消失才回退直接删除(重试 8 次、最长约 17s)。
|
|||
|
|
|
|||
|
|
## 5. 测试
|
|||
|
|
|
|||
|
|
- 脚本自检:`node scripts/nsis-install-smoke.mjs --self-test` → 14/14 PASS(参数解析、路径守卫、布局检测、产物定位、清单哈希、内容清单、卸载器定位)。
|
|||
|
|
- node 单测:`node --test tests/node/nsis-smoke.test.mjs` → 13/13 PASS(复用 packaging-smoke.test.mjs 的 import 风格;覆盖路径安全守卫、布局校验、sha256 确定性、清单期望哈希、产物定位、内容清单排序;全部在临时目录内,不触发真安装)。
|
|||
|
|
- 真实安装路径不在单测内(避免每轮跑 1 分钟+写盘),由 §6 本地实测覆盖。
|
|||
|
|
|
|||
|
|
## 6. 本地实测记录(2026-08-02)
|
|||
|
|
|
|||
|
|
产物:`apps/desktop/release/aps-agent-desktop-0.1.0.exe`(171,025,378 B = 163.1 MB,sha256 `044c4a83aaf28476a1e2405a7a08185b5aff0dfab27f0299b8bcf16c8d32bf21`,与 build/offline-check 清单一致)。
|
|||
|
|
|
|||
|
|
**运行 1:安装 + 布局/内容检查 + 卸载器清理(--run-uninstaller)→ PASS**
|
|||
|
|
|
|||
|
|
node scripts/nsis-install-smoke.mjs --installer apps/desktop/release/aps-agent-desktop-0.1.0.exe --run-uninstaller
|
|||
|
|
|
|||
|
|
- 校验:sha256 PASS(与清单 044c4a83…d32bf21 一致)
|
|||
|
|
- 安装:exit 0,耗时 **33.6s**(163MB → 解包 567MB,Defender 实时防护按需扫描下)
|
|||
|
|
- 布局:PASS(mainExe / resources / app.asar / sidecar / Uninstall 工业智核 APS.exe 全部存在)
|
|||
|
|
- 内容:1158 个文件 / 567.1 MB,前 10 项:
|
|||
|
|
- 215.2 MB `工业智核 APS.exe`
|
|||
|
|
- 32.9 MB `resources/sidecar/_internal/ortools/.libs/ortools.dll`
|
|||
|
|
- 24.4 MB `dxcompiler.dll`
|
|||
|
|
- 19.5 MB `resources/sidecar/_internal/numpy.libs/libscipy_openblas64_-b788215d9d47792bcba3a2e2a7114320.dll`
|
|||
|
|
- 19.4 MB `LICENSES.chromium.html`
|
|||
|
|
- 17.5 MB `resources/sidecar/aps-sidecar.exe`
|
|||
|
|
- 12.9 MB `resources/sidecar/_internal/ortools/.libs/libprotobuf.dll`
|
|||
|
|
- 12.5 MB `resources/sidecar/_internal/libprotobuf.dll`
|
|||
|
|
- 12.2 MB `resources/sidecar/_internal/ortools/.libs/libscip.dll`
|
|||
|
|
- 10.4 MB `icudtl.dat`
|
|||
|
|
- 清理:卸载器 exit 0,临时目录 removed=true
|
|||
|
|
- 机器状态复查:%LOCALAPPDATA%\Programs 下无安装;无残留 aps-nsis-smoke-* 临时目录;无残留进程;HKCU 卸载注册表无 aps 键;开始菜单无残留快捷方式
|
|||
|
|
|
|||
|
|
**运行 2:+ --launch(启动主 exe 8s 健康冒烟 + 树杀 + 卸载清理)→ PASS**
|
|||
|
|
|
|||
|
|
node scripts/nsis-install-smoke.mjs --installer apps/desktop/release/aps-agent-desktop-0.1.0.exe --run-uninstaller --launch --launch-seconds 8
|
|||
|
|
|
|||
|
|
- 安装:exit 0,耗时 **30.9s**;布局/内容与运行 1 一致(PASS)
|
|||
|
|
- 启动健康冒烟:主 exe 存活 9.1s(≥8s 窗口),进程树强制结束(killed=1),无驻留
|
|||
|
|
- 清理:卸载器 exit 0,removed=true;机器状态复查同样干净
|
|||
|
|
|
|||
|
|
**过程中发现并修复**:首次实跑时卸载器 exit 0 后目录仍被短暂占用(EBUSY,卸载器异步收尾删除文件),清理判定为 FAIL;已加"轮询等待目录消失(最长 25s)+ 直接删除重试 8 次"后复跑 PASS。EBUSY 属卸载器自身时序,非安装缺陷。
|
|||
|
|
|
|||
|
|
## 7. 矩阵达成情况与剩余风险
|
|||
|
|
|
|||
|
|
矩阵 99(Defender + 真实 APS 求解):
|
|||
|
|
- 本方向为衔接:Defender 静态扫描已在 round-43 CC 达成(4/4 clean);真实求解由黄金测试覆盖(既有体系)。
|
|||
|
|
- 剩余(外部):Defender for Endpoint 门户/API 门禁、CI 门禁;实时防护真实拦截演练(本机实时防护已开启,但真实拦截需受控样本与策略配合)。
|
|||
|
|
|
|||
|
|
矩阵 118(干净离线机安装冒烟):
|
|||
|
|
- 本方向达成:**真实 NSIS 静默安装到随机临时目录 + 布局/内容检查 + 卸载/清理** 本地 2 次 PASS;同时验证了 updater.cjs `nsisInstall` 的 `/S` + `/D=` 参数约定在真实安装包上可行(之前只做单元/校验和,未实跑)。
|
|||
|
|
- 剩余(外部):真实用户机安装(%LOCALAPPDATA%\Programs 落点、快捷方式、升级/回滚、冷启动);干净离线机(无 Python、无网络)实机安装 + 冷启动 + 升级/回滚验收,需外部验证机,可用 §4 的 portable 流程随 U 盘执行。
|
|||
|
|
|
|||
|
|
已知限制:
|
|||
|
|
- 安装器自身的 per-user 卸载注册表项与开始菜单快捷方式只有 `--run-uninstaller` 才会清掉;默认直接删除会残留指向已删路径的条目。
|
|||
|
|
- 本机非管理员,未测 perMachine(HKLM / 系统级)安装路径。
|
|||
|
|
- Defender 实时防护按需扫描会让安装耗时波动(实测 30.9s / 33.6s 两档);本次安装包静态扫描已在 CC 轮次 clean。
|
|||
|
|
- `--launch` 健康冒烟仅验证进程存活数秒(不驻留),完整渲染器/Sidecar 校验归 `scripts/smoke-packaged-desktop.mjs`。
|