10 KiB
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,sha256044c4a83aaf28476a1e2405a7a08185b5aff0dfab27f0299b8bcf16c8d32bf21)
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
- 215.2 MB
- 清理:卸载器 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。