GPU_GUARD_MONOREPO/docs/superpowers/specs/2026-04-25-windows-installer-design.md
2026-05-21 11:20:19 +08:00

13 KiB
Raw Permalink Blame History

GPU Guard Windows 离线安装方案设计

日期2026-04-25 状态:待实施 修订v3 — 补充数据库凭证外置、config 校验、启动顺序、网络驱动器约束、CLI 版本参数、卸载进程停止

目标

将 GPU Guard 智能管理审核平台Vue 3 前端 + Midway.js 后端)打包为 单个离线 Windows 安装器 exe。 目标用户是 Windows 用户,安装后双击即可使用,无需安装 Node.js、数据库等开发环境。

约束

  • 交付形式:单个 setup.exe,离线安装,不依赖网络
  • 安装后体验:双击桌面快捷方式 → 启动后端 → 自动打开浏览器访问前端首页
  • 数据库MySQL 在云端,本地不安装
  • 本地持久化所有本地文件上传、缓存、插件、skills、agent 记忆、session 树等)统一写入用户选择的数据目录
  • 支持:安装、卸载、重装
  • 预留:未来版本更新机制(本期不实现)
  • 支持:桌面启动 + 可选开机自启

1. 安装后目录结构

[安装目录,默认 C:\Program Files\GPUGuard]
├── backend.exe              # 主程序pkg 产物,内含前端静态资源)
├── config.yaml              # 运行配置(安装器生成,用户可编辑)
├── unins000.exe             # 卸载器Inno Setup 自动生成)
└── unins000.dat
[数据目录,默认 C:\GPUGuardData安装时用户选择]
├── uploads/                 # 文件上传(原 {cwd}/dist/upload/
├── cache/                   # 文件缓存(原 {cwd}/dist/cache/
├── plugins/                 # 插件文件(原 {cwd}/dist/plugin/
├── cool.sqlite              # Cool Admin 本地 SQLite原 {cwd}/dist/cool.sqlite
├── skills/                  # Agent 技能安装目录(原 {cwd}/skills/
├── .skillhub/               # Skill 元数据和锁文件(原 {cwd}/.skillhub/
├── memory/
│   └── memory.db            # Agent 记忆 SQLite原 ~/.neta/memory/memory.db
├── sessions/                # Session 树文件(原 ~/.neta/sessions/
├── logs/                    # 运行日志(按天滚动,保留 30 天)
└── neta.lock                # 进程锁文件

核心原则:程序目录只读,数据目录可写,完全分离

2. 路径统一方案v2 新增)

2.1 现状:两套独立路径体系

路径体系 解析方式 内容
comm/path.ts process.cwd()/dist/... uploads、cache、plugin、cool.sqlite
netaclaw.dataDir ~/.neta/ agent memory SQLite、session tree
skill 系统 process.cwd()/skills/ 技能文件、.skillhub 元数据

2.2 改造:统一到 config.yaml 的 data.dir

新增一个启动早期的路径初始化模块 comm/data-dir.ts

// 优先级config.yaml data.dir > 环境变量 NETA_DATA_DIR > 默认 ./data
export function resolveDataDir(): string;

改造 comm/path.ts 中所有路径函数,使其从 resolveDataDir() 派生:

函数 改造前 改造后
pUploadPath() {cwd}/dist/upload/ {dataDir}/uploads/
pCachePath() {cwd}/dist/cache/ {dataDir}/cache/
pPluginPath() {cwd}/dist/plugin/ {dataDir}/plugins/
pSqlitePath() {cwd}/dist/cool.sqlite {dataDir}/cool.sqlite

改造 netaclaw 配置:

配置项 改造前 改造后
netaclaw.dataDir ~/.neta {dataDir}
netaclaw.skillsDir ./skills {dataDir}/skills

2.3 process.cwd() 安全处理

pkg exe 运行时 process.cwd() 取决于启动方式(快捷方式的"起始位置"、命令行当前目录等),不可靠。

解决方案:

  • Inno Setup 创建快捷方式时,设置"起始位置"为安装目录
  • comm/data-dir.ts 中不依赖 process.cwd(),而是:
    1. 读取 exe 同目录下的 config.yaml(通过 process.execPath 定位 exe 目录)
    2. config.yamldata.dir 获取数据目录绝对路径
    3. fallback如果 config.yaml 不存在,使用 {exeDir}/data/ 作为默认值

2.4 开发环境兼容

改造后的路径函数需要同时兼容开发环境(npm run dev)和打包环境(backend.exe

  • 开发环境:无 config.yamlfallback 到 {cwd}/dist/(保持现有行为)
  • 打包环境:读 config.yaml,使用用户选择的数据目录

检测方式:process.pkg 存在时为 pkg 环境。

3. 外部配置加载机制v2 新增)

3.1 安全约束:数据库凭证必须外置

云端 MySQL 的 host / username / password / database 不得硬编码进 execonfig.prod.ts 中现有硬编码凭证在实施时必须移除,改为全部从外部 config.yaml 或安全环境变量读取。

原因pkg 产物可被逆向分析,硬编码凭证会泄露。

3.2 config.yaml 完整结构

server:
  port: 8003

data:
  dir: "C:\\GPUGuardData"    # 安装器写入用户选择的数据目录绝对路径

autoOpenBrowser: true

database:
  type: mysql
  host: ""
  port: 3306
  username: ""
  password: ""
  database: ""

3.3 config.yaml 校验

程序启动时必须对 config.yaml 做 schema 校验:

  • 必填项缺失(如 data.dir
  • 类型错误(如 port 不是数字)
  • 路径无效(数据目录不存在或不可写)
  • 数据库配置不完整

校验失败时:

  1. 不启动 Midway
  2. 弹出清晰错误信息Windows MessageBox 或控制台提示)
  3. 写入启动失败日志到 {dataDir}/logs/bootstrap-error.log(若 dataDir 可用)

3.4 加载时机

bootstrap.js 启动最早期(Bootstrap.configure() 之前):

  1. 通过 process.execPath 获取 exe 所在目录
  2. 读取同目录下 config.yaml
  3. 校验 schema
  4. 解析后注入到 process.env 或全局变量
  5. Midway 的 config.prod.ts 从该全局变量读取并覆盖默认值

3.5 配置优先级

config.yaml 外部配置 > config.prod.ts 内置默认值 > config.default.ts 基础默认值

3.6 启动顺序约束

必须严格保证初始化顺序:

读取 config.yaml
  → 校验 config.yaml
  → 解析 dataDir
  → 初始化路径函数
  → 注册全局配置
  → 启动 Midway Bootstrap
  → 初始化各 service
  → onReady 自动开浏览器

任何依赖路径函数或数据库配置的模块,都不得在 config-loader 执行前初始化。

4. 安装器工具Inno Setup

选择 Inno Setup 而非 NSIS / WiX

  • 免费开源Windows 生态最成熟的安装器之一
  • 原生支持自定义安装目录、桌面快捷方式、开机自启注册表、卸载器自动生成
  • Pascal Script 可实现"选择数据目录"自定义页面
  • 产物为单个 setup.exe,离线、自包含
  • 卸载时可弹窗询问是否保留数据目录

4.1 安装流程(用户视角)

双击 setup.exe
  → 欢迎页
→ 选择安装目录(默认 C:\Program Files\GPUGuard
→ 选择数据目录(默认 C:\GPUGuardData
  → 勾选项:
      ☑ 创建桌面快捷方式
      ☐ 开机自动启动
  → 安装进度条
  → 生成 config.yaml写入数据目录路径
  → 安装完成,勾选"立即启动"
  → 启动 backend.exe → 自动打开浏览器

4.2 卸载流程

控制面板 → 卸载 GPU Guard或运行 unins000.exe
  → 停止正在运行的 backend.exe 进程(检查 neta.lock
  → 弹窗:"是否同时删除本地数据uploads、日志、agent 存储)"
      → 是:删除数据目录
      → 否:仅删除程序目录
  → 清理桌面快捷方式、开机自启注册表项
  → 卸载完成

4.3 重装

  • 安装器检测到已有安装 → 提示"覆盖安装"
  • 覆盖程序目录,数据目录保持不动
  • config.yaml 如果用户改过,保留用户版本(安装器不覆盖已存在的配置文件)

4.4 卸载前停止进程

Inno Setup 卸载器在删除文件前必须:

  • 检查 neta.lock 或直接查找 backend.exe 进程
  • 若进程仍在运行,先提示用户关闭;用户确认后可强制结束进程
  • 确保 backend.exe 已停止后再继续删除程序目录

原因Windows 下运行中的 exe 无法删除,且强删可能导致 SQLite / 缓存文件损坏。

4.5 更新预留(不实现)

  • backend.exe --version 输出当前版本号
  • config.yaml 记录数据目录路径,新版安装器可读取
  • 程序目录与数据目录分离本身就是更新的基础
  • bootstrap.js 增加最小 CLI 参数解析,只支持:
    • --version
    • --config <path>(预留)

5. 构建流水线

从源码到 setup.exe 的完整链路:

[1] 构建前端          pnpm build:frontend → packages/frontend/dist/
[2] 构建后端          npm run build → packages/backend/dist/
[3] 组装 staging      合并前端产物到 public/,扁平化 node_modules
[4] Patch + Pkg       patch generator-function exports → pkg → backend.exe
[5] 准备安装器素材    backend.exe + config.default.yaml + setup.iss
[6] Inno Setup 编译   iscc setup.iss → neta-setup-x.x.x.exe

5.1 staging 目录结构

build/pkg-stage/
├── bootstrap.js
├── dist/                   ← 后端编译产物
├── public/
│   ├── index.html          ← 前端产物(覆盖原欢迎页)
│   ├── static/             ← 前端 JS/CSS/资源
│   └── swagger/            ← 保留
├── typings/
├── package.json            ← 精简版flat pkg config
└── node_modules/           ← npm install --hoisted扁平化

5.2 自动打开浏览器

后端 onReady 钩子中,检测 autoOpenBrowser: true 且为 pkg 环境时执行:

import { exec } from 'child_process';
exec(`start http://127.0.0.1:${port}`);

5.3 开机自启

安装器在注册表写入:

HKCU\Software\Microsoft\Windows\CurrentVersion\Run
  GPUGuard = "C:\Program Files\GPUGuard\backend.exe"

卸载时清除。

5.4 已验证的 pkg 关键 patch

问题 解决方案
pnpm .pnpm 嵌套结构与 pkg 不兼容 staging 目录用 npm install --hoisted 扁平化
generator-functionmodule-sync 导出 .mjs 在 pkg 虚拟文件系统无法解析 patch 其 package.json exports 回退到 CJS
sharp / better-sqlite3 等 native modules 加入 pkg assets由 pkg 运行时自动解压

6. 运行时边界情况

6.1 端口冲突

启动时检测 8003 端口是否被占用,自动尝试 8004、8005...(项目已有 availablePort 工具函数)。 打开浏览器时使用实际绑定的端口。

6.2 重复启动防护

在数据目录写 neta.lock 文件记录 PID。启动时检查该 PID 是否存活:

  • 已在运行:不重复启动,直接打开浏览器访问已有实例
  • 未运行:正常启动

6.3 日志

写入数据目录 logs/,按天滚动,保留最近 30 天。

6.4 数据目录初始化

首次启动时自动创建所有子目录: uploads/cache/plugins/skills/.skillhub/memory/sessions/logs/

6.5 托盘图标(不做,预留)

第一版为控制台窗口,关闭窗口 = 停止服务。后续可加托盘最小化。

6.6 数据目录位置约束

安装器在选择数据目录时需要提示:

  • 推荐本地磁盘路径(如 D:\GPUGuardData
  • 不推荐网络驱动器 / NAS / 可移动 U 盘

原因:

  • SQLite 的 WAL 模式在网络文件系统上不可靠,可能导致 memory.db / cool.sqlite 损坏
  • 可移动磁盘在运行中拔出会导致缓存、session、上传文件写入失败

第一版不阻止用户选择网络驱动器,但需要明确警告。

7. 需要改动的现有代码清单

文件 改动内容
src/comm/path.ts 所有路径函数改为从 resolveDataDir() 派生,不再依赖 process.cwd()
新增 src/comm/data-dir.ts 数据目录解析config.yaml > env > fallback
新增 src/comm/config-loader.ts 启动早期读取 exe 同目录下 config.yaml
bootstrap.js Bootstrap.configure() 前调用 config-loader
src/config/config.default.ts staticFile.dirs 改为从 dataDir 读取 upload 路径
src/config/config.prod.ts 数据库连接等从外部 config.yaml 覆盖
src/modules/netaclaw/memory/sqlite_provider.ts dataDir 改为从统一配置读取
src/modules/netaclaw/session-tree/ session 存储路径改为从统一配置读取
src/modules/netaclaw/service/skill_installer.ts skills 目录改为从统一配置读取
src/modules/netaclaw/service/skill_registry.ts .skillhub/ 路径改为从统一配置读取
src/configuration.ts onReady 中加入自动打开浏览器逻辑

8. 产物体积预估

组件 大小
backend.exe含前端静态资源 ~410MB
config.default.yaml <1KB
Inno Setup 压缩后 setup.exe ~150-200MB