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

356 lines
13 KiB
Markdown
Raw Permalink 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.

# 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`
```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.yaml``data.dir` 获取数据目录绝对路径
3. fallback如果 `config.yaml` 不存在,使用 `{exeDir}/data/` 作为默认值
### 2.4 开发环境兼容
改造后的路径函数需要同时兼容开发环境(`npm run dev`)和打包环境(`backend.exe`
- 开发环境:无 `config.yaml`fallback 到 `{cwd}/dist/`(保持现有行为)
- 打包环境:读 `config.yaml`,使用用户选择的数据目录
检测方式:`process.pkg` 存在时为 pkg 环境。
## 3. 外部配置加载机制v2 新增)
### 3.1 安全约束:数据库凭证必须外置
云端 MySQL 的 host / username / password / database **不得硬编码进 exe**
`config.prod.ts` 中现有硬编码凭证在实施时必须移除,改为全部从外部 `config.yaml` 或安全环境变量读取。
原因pkg 产物可被逆向分析,硬编码凭证会泄露。
### 3.2 config.yaml 完整结构
```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 环境时执行:
```ts
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-function``module-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 |