起因:用户 2026-09-17 明确「所有文档都要同步,都放在开发仓库 docs 对应文件夹下」。
判据:工作区根 *.md 中在仓库(git ls-files --quotepath=false)搜不到的那些。
入仓 33 份(一律复制,工作区根原件保留不动,避免引用断链):
- 04-调整方案/113–128(16 份 · 原子占号后落盘):覆盖网络 传输方案取舍 / 应用场景与待完善清单 /
插件化vs改内核 / 问题逐条推演 / 参数表与观测口径;会合中继拆分取证与改造方案;
集群化改造方案 Manager-Worker;跨节点迁移与节点自举;项目代码分层范式与迭代风险评估;
搬运与共享重建方案 guest w47→w106;方案规划方法提炼;文档无效信息审计报告;
会话接续机制复盘与修复;会话接续规范;dsh 客户端化部署方案;dsh 桌面客户端开发方案
- 交接单/archive/交接单-已完成/T09–T21(13 份 · 覆盖网络线已完成单归档)
- ops/(2 份运行态指针:接续入口 / 接续包 · 覆盖网络线)
- archive/(2 份临时与内部简报)
已排除(无需重复入仓):覆盖网络线 10 份方案正文已入档案 103–112(文件名不同)。
登记:INDEX.md §二 新增 04-113–128 共 16 行 + §四 追加 T09–T21 说明 + 机器摘要行刷新
(⛔ 未跑 docs-index-stats.py --write:该脚本会按 \r\n 归一化全文件行尾,故改为字节级单行替换);
README.md 追加 1 条入仓指针;docs-manifest.json 复跑 scripts/docs-manifest.py 刷新。
验收:工作区根 47 份 .md —— 同名已入仓 8 / 本次内容一致 29 / 已知改名映射 10 / 未入仓 0;
git status 待提交清单只含本次新增与登记 3 件(未涉 src/ 与 relay 代码面)。
288 lines
15 KiB
Markdown
288 lines
15 KiB
Markdown
# DSH 平台客户端化部署方案 —— 单机自用
|
||
|
||
- 版本:**v3**(2026-09-16;v1 规划稿 → v2 多用户 → **v3 范围收窄为单机自用**)
|
||
- 性质:**本文只做规划,不含任何代码改动**
|
||
- 定位:与 `集群化改造方案_Manager-Worker_20260914.md` 同级,是「客户端化」这件事**平台侧**的单一来源
|
||
- **分层指针(2026-09-16 收口)**:本文管**平台侧**(能不能在 Windows 跑起来、形态怎么切);**交付载体层**(Electron 壳 / 打包 / 签名 / 自动更新)以 `dsh桌面客户端_开发方案_20260916.md` 为准 —— 两份合起来才是完整的「客户端化」,⛔ 不各写一份
|
||
|
||
---
|
||
|
||
## 0. 一句话方案
|
||
|
||
把平台装到用户**自己的 Windows 电脑**上,本机浏览器打开 `http://127.0.0.1:3080`,**一个人用**。
|
||
|
||
不需要局域网、不需要域名、不需要证书、不需要开机自启(可选)。
|
||
平台**默认就监听回环地址** —— 这一项连配置都不用改。
|
||
|
||
---
|
||
|
||
## 1. 本次范围收窄的两条原则
|
||
|
||
### 1.1 使用者只有他自己
|
||
|
||
**不考虑当作服务器、不考虑别人访问。** 由此一次性消掉的东西:
|
||
|
||
| 消掉的 | 原因 |
|
||
|---|---|
|
||
| 监听 `0.0.0.0` + 防火墙放行 | 只有本机访问,回环地址即可 |
|
||
| 多账号 / 逐用户开通 | 只有一个人 |
|
||
| 子域或子路径分流 | 直接访问门户首页即可 |
|
||
| 租户隔离 | 没有"别人" |
|
||
| 开机自启(服务化) | 双击启动即可,非必需 |
|
||
|
||
### 1.2 平台机制**全部保留**,只是在客户端部署时**不启用**
|
||
|
||
**这是一条硬约束:不为客户端化去删改多租户相关的代码。**
|
||
客户端形态下这些机制**照旧存在、照旧可用**,只是没有使用场景。
|
||
|
||
> 用户的判断依据:**多租户需要域名才能跑起来,而客户端没有域名** ⇒ 客户端就用单用户形态。
|
||
|
||
因此本方案对代码的要求只有一条:**让平台能在 Windows 上把实例启起来**(见 §4)。
|
||
多租户、门户、账号体系、隔离档位、配额、集群 —— **一行都不动**。
|
||
|
||
---
|
||
|
||
### 1.3 多人形态**没有被去掉** —— "配域名即启用"
|
||
|
||
**形态开关就是一个配置项**,平台本来就支持,⛔ **不需要改任何代码**:
|
||
|
||
| 配置 | 形态 | 用户地址 |
|
||
|---|---|---|
|
||
| 域名**留空**(默认) | 单机自用 | `http://127.0.0.1:3080` |
|
||
| 配了**域名** | 多人访问 | `http://<用户名>.<域名>` |
|
||
|
||
- 开关 = `DSHS_BASE_DOMAIN`(配 `DSHS_COOKIE_DOMAIN`;有 HTTPS 时再加 `DSHS_SECURE_COOKIES`)—— 三个都是**环境变量**,见 `src/config.ts:320-321`
|
||
- 配套(用户侧):内网 DNS 泛解析 `*.<域名>` → 本机地址
|
||
- ✅ **不需要 HTTPS 也能跑**:门户域与用户子域属于**同一 site**,Cookie 的 `SameSite=Lax` 足以支撑跨子域跳转(`src/web/auth.ts:55-57` 即按"有没有 HTTPS"区分这两档)
|
||
- ⚠️ 隔离档位保持默认 `soft` ⇒ 多人访问时**用户之间无隔离**,与 §1.1 的取舍一致(且 `account` 档位在 Windows 上本来就不可用)
|
||
|
||
> **本节结论**:客户端版本**天然保留"可以当服务器"的能力** —— 启用它不需要写一行代码,只需要用户填一个域名。
|
||
|
||
> 🔒 **切多人形态的前置检查(2026-09-16 加 —— 未过则不得开这个开关)**
|
||
> 这个开关零代码,但它会**同时打开**三个被"单机自用"关掉的风险面:
|
||
> 1. **隔离档仍是 `soft` ⇒ 用户之间没有隔离**(且 `account` 档在 Windows 上本来不可用)⇒ 多人形态的**前置条件 = 先有隔离方案**;在拿到之前,只能明确限定为"**仅互信小圈子使用**",并且页面要写明这一点。
|
||
> 2. **平台级凭据不得随客户端分发**:域名配置要透传给平台,但**透传必须是白名单** —— 只放 `DSHS_BASE_DOMAIN` / `DSHS_COOKIE_DOMAIN` / `DSHS_SECURE_COOKIES` 三项,⛔ 不得把平台共享模型密钥一并继承下去;模型密钥只走"**用户自己的密钥**"那一层。
|
||
> 3. **实例必须属于机器主人**:一台机器上的实例只能归该机器使用者 —— 多人形态下需**显式**保证,不能靠"默认只有一个用户"蒙过去。
|
||
|
||
---
|
||
|
||
## 2. 范围收窄带来的变化(相对 v2)
|
||
|
||
| 维度 | v2(内网多用户) | **v3(单机自用)** |
|
||
|---|---|---|
|
||
| 监听地址 | 改 `0.0.0.0` + 防火墙 | **默认 `127.0.0.1`,零配置** |
|
||
| 账号 | 多账号 + 逐个开通 | 一个本地账号即可 |
|
||
| 入口 | 纯 IP + 子路径分流 | 直接 `http://127.0.0.1:3080` |
|
||
| 隔离 | 不需要 | **不需要**(没有"别人") |
|
||
| 服务化 | 开机自启(必需) | 可选 |
|
||
| 风险「用户互读文件」 | 🔴 须书面告知客户 | **不存在** |
|
||
| 待改代码 | 配置 + 打包 + spawn 适配 | **只剩 spawn 适配 + 打包** |
|
||
|
||
---
|
||
|
||
## 3. 现成能力盘点(**不需要动的东西**)
|
||
|
||
| 能力 | 现状 | 依据 |
|
||
|---|---|---|
|
||
| 监听回环 | **默认就是 `127.0.0.1`** | `config.ts:162` / `cli.ts:32` |
|
||
| 数据根 | 默认 `~/.dshs`(**非硬编码**) | `config.ts:257` |
|
||
| 数据库 | 默认 SQLite 单文件 | `config.ts:271` |
|
||
| 免 HTTPS | `--secure-cookies` 默认关 | `config.ts:281` |
|
||
| 文件属主处理 | `chown/chmod` 块条件是 `process.getuid() === 0` | **实测 Windows 上 `typeof process.getuid === 'undefined'`** ⇒ 整块自动跳过(`local-user-fs.ts:40`) |
|
||
| 端口守卫 | 默认 false | `config.ts:323` |
|
||
| 单机自检 | `dshs doctor` | 装机与排障直接用 |
|
||
| 版本巡检 | `runtime-baseline.cjs`(Node 写) | 跨平台 |
|
||
|
||
---
|
||
|
||
## 4. 硬阻塞点(**已在本机 Windows 实测**)
|
||
|
||
### 4.1 唯一的硬阻塞:平台启动实例的方式在 Windows 上不成立
|
||
|
||
平台用 `spawn(command, args)` 启动实例,**不带 shell**(`orchestrator.ts:663-665`)。
|
||
Windows 上 npm 全局包是 `.cmd` 垫片,于是:
|
||
|
||
| 尝试 | 本机实测结果 | 判定 |
|
||
|---|---|---|
|
||
| `spawn('npm', ['--version'])`(裸名,=平台当前做法) | `ERROR(event): ENOENT` | ❌ 起不来 |
|
||
| `spawn('…\\npm.cmd', ['--version'])`(显式 .cmd) | `THROW(sync): EINVAL` | ❌ Node 安全限制 |
|
||
| `spawn('cmd.exe', ['/c', 'npm', '--version'])` | `OK exit=0`,输出 `10.9.7` | ✅ 可用 |
|
||
| `spawn(p, { shell: true })` | `OK exit=0` | ✅ 可用 |
|
||
|
||
> 复现(Windows + Node 22):
|
||
> `node -e "require('child_process').spawn('npm',['--version'],{stdio:'inherit'}).on('error',e=>console.log(e.code))"` → 打印 `ENOENT`
|
||
|
||
**结论**:**必须**让平台在 Windows 上以 `cmd.exe /c` 或 `shell: true` 启动实例。这是全案唯一必须改代码的地方。
|
||
|
||
### 4.2 附带的小点
|
||
|
||
| # | 点 | 判定 |
|
||
|---|---|---|
|
||
| A | 播种工作区时无条件 `chownSync`(`orchestrator.ts:503`) | 🟢 非阻塞:有 `try/catch` 兜底,只会刷一行失败日志 |
|
||
| B | `dshs doctor` 会探测 cgroup 等 Linux 项 | 🟢 非阻塞:只是自检输出不准 |
|
||
|
||
---
|
||
|
||
## 5. 改造清单
|
||
|
||
### 5.1 必做
|
||
|
||
| # | 事项 | 量级 | 说明 |
|
||
|---|---|---|---|
|
||
| C1 | **Windows 子进程启动适配** | 小 | 实例启动处对 `win32` 走 `cmd.exe /c`;建议做成可配置 |
|
||
| C2 | 安装包 / 一键安装脚本 | 中 | 装 Node → 装平台与 dsh → 建目录 → 初始化本地账号 → 建启动快捷方式 → 自检 |
|
||
|
||
### 5.2 该做
|
||
|
||
| # | 事项 | 说明 |
|
||
|---|---|---|
|
||
| C3 | 启动器 | 双击启动 + 自动打开浏览器;退出时干净收尾(别留孤儿进程) |
|
||
| C4 | 容量参数按单机重算 | 现有默认按 2C2G 多用户宿主定的;Windows 无 cgroup,需靠并发上限约束 |
|
||
| C5 | 备份与清理的 Windows 实现 | 现有 `.sh` 那批要换;Node 写的那批可直接用 |
|
||
|
||
### 5.3 可选
|
||
|
||
| # | 事项 |
|
||
|---|---|
|
||
| C6 | 开机自启(单机自用非必需) |
|
||
| C7 | 顺手修 §4.2 的两处小点 |
|
||
| C8 | 免登录形态(本机单人,可考虑省掉登录步骤) |
|
||
|
||
> ⛔ **不在清单里 = 不做**:多租户、门户、账号体系、隔离档位、配额、集群 —— **一律不改**(§1.2)。
|
||
|
||
---
|
||
|
||
## 6. 落地步骤
|
||
|
||
| 步 | 动作 | 验证方式 |
|
||
|---|---|---|
|
||
| S1 | 装 Node.js(22+) | `node -v` 有输出 |
|
||
| S2 | 装平台 + 官方 dsh | `dshs --help` 与 `dsh --version` 都有输出 |
|
||
| S3 | 初始化本地账号 | 能登录门户 |
|
||
| S4 | 第一次启动 | 浏览器打开 `http://127.0.0.1:3080` 出现门户 |
|
||
| S5 | 打开实例(**这一步验证 C1**) | 实例能起来并进入对话界面 |
|
||
| S6 | 建启动快捷方式 | 双击即可启动并自动开浏览器 |
|
||
| S7 | 断网验证 | 断开公网(保留模型 API 通路)后一切正常 |
|
||
|
||
---
|
||
|
||
## 7. 验收标准
|
||
|
||
| # | 验收项 | 判据 |
|
||
|---|---|---|
|
||
| V1 | 本机可用 | `http://127.0.0.1:3080` 门户正常 |
|
||
| V2 | 实例可用 | 能打开实例、能正常对话、能改工作区文件 |
|
||
| V3 | 断网可用 | 断开公网(保留模型 API 通路)后,登录/实例/改文件全部正常 |
|
||
| V4 | 一键启动 | 双击快捷方式即可启动,无需命令行 |
|
||
| V5 | 数据可恢复 | 按备份流程恢复后,历史会话与工作区文件完整 |
|
||
| V6 | 无残留外部依赖 | 全程不装 Linux 环境、不装容器运行时 |
|
||
| V7 | 不对局域网暴露 | 从另一台机器访问本机端口**不通**(默认回环即满足) |
|
||
|
||
---
|
||
|
||
## 8. 风险
|
||
|
||
| # | 风险 | 等级 | 说明与应对 |
|
||
|---|---|---|---|
|
||
| R1 | 单实例无内存上限 | 🟡 中 | Windows 无 cgroup,失控实例可能拖慢整机;靠并发上限与人工干预约束 |
|
||
| R2 | 与现网形态不同 | 🟡 中 | 出问题不能直接对照现网排查,需另建排障知识 |
|
||
| R3 | 模型 API 需出网 | 🟡 中 | 需确认用户网络出网策略;必要时配代理 |
|
||
| R4 | Windows 上实例的沙箱强度低于 Linux | 🟡 中 | dsh 本体的 Windows 沙箱官方标注为"部分强制";单机自用可接受,需知悉 |
|
||
|
||
> ✅ 相对 v2 **消失**的风险:用户之间互读文件、无 HTTPS、局域网暴露。
|
||
|
||
---
|
||
|
||
## 9. 回滚
|
||
|
||
| 层 | 回滚 |
|
||
|---|---|
|
||
| C1(代码) | 按平台分支判断,Linux 侧行为不变 ⇒ **现网零影响** |
|
||
| 安装 | 卸载脚本 + 删除数据目录(安装时记录路径) |
|
||
| 整体 | 客户端化**完全不接触现网**(47/106 形态不动) |
|
||
|
||
---
|
||
|
||
## 10. 附录 A · 证据与复核命令
|
||
|
||
| 结论 | 复核命令 / 来源 |
|
||
|---|---|
|
||
| 默认监听回环 | `grep -n "DEFAULT_HOST" src/config.ts` |
|
||
| 启动实例不带 shell | `sed -n '663,665p' src/supervisor/orchestrator.ts` |
|
||
| chown 块在 Windows 自动跳过 | `sed -n '40,47p' src/fs/local-user-fs.ts` |
|
||
| seed 的 chown 无条件调用 | `sed -n '502,505p' src/supervisor/orchestrator.ts` |
|
||
| portGuard 默认关 | `grep -n "DSHS_PORT_GUARD" src/config.ts` |
|
||
| Windows spawn 行为 | §4.1 四行实测(本机 Windows + Node 22.22.2) |
|
||
| 无域名时有降级路径 | `sed -n '58,62p' src/web/routes/dsh.ts` |
|
||
| dsh 本体支持 Windows | 官方 `.agents/notes/**` 三篇 |
|
||
|
||
## 11. 附录 B · 保留但不启用(**给以后接手的人**)
|
||
|
||
这些机制在客户端形态下**存在但不使用**,不属于缺陷、不需要清理:
|
||
|
||
| 机制 | 客户端下的状态 | 事实备注 |
|
||
|---|---|---|
|
||
| 多租户 / 多账号 | 存在,只建一个账号 | — |
|
||
| 子域分流 | 不启用(`baseDomain` 留空) | 平台**内置了降级路径**:`baseDomain` 为空时入口自动回落到子路径 `/u/<id>/dsh/`(`src/web/routes/dsh.ts:58-62`)。即"没有域名跑不起来"在代码层面已有兜底;客户端单人场景**不需要依赖它** |
|
||
| 租户隔离(`account` 档位) | 不启用(默认 `soft`) | — |
|
||
| 端口守卫(nft) | 不启用(默认关) | 仅在 Linux+root 下可用 |
|
||
| 集群(Manager/Worker) | 不启用(`local` 模式) | — |
|
||
| 开机自启 | 可选 | — |
|
||
|
||
## 12. 附录 C · 被放弃的选项(记录决策依据)
|
||
|
||
| 曾考虑 | 放弃原因 |
|
||
|---|---|
|
||
| **内网多用户形态**(v2) | 用户明确「考虑用户自己使用就可以了,不用考虑当作服务器 其他人访问」 |
|
||
| 官方桌面版(Electron)作为载体 | ~~与"机制都保留"冲突 —— 换载体会丢掉我们的门户能力~~ ⚠️ **该判定已于 2026-09-16 失效**:`dsh桌面客户端_开发方案_20260916.md` 用「**保留官方壳、换内核为我们的平台进程**」规避了这条 —— 门户能力不丢。本行仅作历史记录 |
|
||
| Linux 宿主 / WSL2 / 容器 | 需额外环境;单机自用无必要 |
|
||
| 域名 + 证书形态 | 客户端无域名,且单人自用不需要 |
|
||
|
||
> 若范围再次变化(例如要回到内网多人),v2 的对比表与结论仍然有效,可在本文基础上恢复。
|
||
|
||
---
|
||
|
||
## 13. 附录 D · 官方桌面客户端:能否沿用(调研结论)
|
||
|
||
**问题**:能否沿用官方 Electron 桌面版,把这个项目装进去?
|
||
|
||
**结论**:❌ **直接装不进去**(三条硬理由);✅ 但它的**形态与打包链可以沿用**。
|
||
|
||
### 13.1 官方桌面版是什么
|
||
|
||
| 项 | 事实 |
|
||
|---|---|
|
||
| 组成 | `apps/desktop`(Electron 壳)+ `apps/desktop-host`(**私有 Node host 进程**)+ `resources/dsh`(内置运行时) |
|
||
| 网络 | **不开监听端口**;用分帧字节管道承载 Fetch 请求与流式响应 |
|
||
| profile | Electron **独占** `$DSH_HOME/profiles/desktop`;CLI 不能启动或修改它 |
|
||
| 插件 | profile 的 `dependencies` **只放外部插件**;有独立插件管理窗口(增删改查) |
|
||
| 数据 | 与 CLI **共享** `$DSH_HOME` 下的 会话 / 设置 / 凭据 / 工作区 / 存储 |
|
||
|
||
### 13.2 装不进去的三条硬理由
|
||
|
||
1. **载体只接受 dsh 插件,我们的平台不是插件。** 平台是独立的 HTTP 服务(路由 + 数据库 + 子进程编排),没有"作为 profile 插件被加载"的形态。
|
||
2. **桌面版没有 web server,而我们的插件是平台的前端。** 官方原文:「Desktop does not provide a `webServer`」,官方自己的「Open In...」插件就因此被禁用。我们的 `business-plugins` 数据全部来自平台 API(`/api/plugins/mine`、`/api/dsh/status`、`/api/skills/mine`、`/api/me/keys`…,见 `poc/business-plugins/lib/client.js`)—— 装进去就是空壳。
|
||
3. **两套编排者会争同一份 `$DSH_HOME`。** 桌面版自己就在跑 dsh;平台还要为每个用户再起 dsh 实例。
|
||
|
||
### 13.3 可以沿用的部分
|
||
|
||
| 可沿用 | 说明 |
|
||
|---|---|
|
||
| **形态** | Electron 桌面应用:双击打开、不暴露端口 |
|
||
| **打包链** | 官方 `apps/desktop/scripts/` 的 electron-builder + NSIS + Windows 签名 + 冒烟脚本,可作参照 |
|
||
| **数据互通** | 官方桌面版与 CLI 共享 `$DSH_HOME` 的会话/设置/凭据 —— 我们的壳若指向同一 `$DSH_HOME`,用户数据可互通 |
|
||
|
||
**做法**:自建一个**薄 Electron 壳**(不复用官方壳代码)——
|
||
主进程启动平台 → 等 `127.0.0.1:3080` 就绪 → 窗口加载它 → 退出时收干净子进程。
|
||
用户看到的是桌面应用,里面是**机制完整保留**的平台。
|
||
|
||
### 13.4 三档形态与投入
|
||
|
||
| 档 | 形态 | 投入 | 体验 |
|
||
|---|---|---|---|
|
||
| 1 | 快捷方式 + 浏览器(v3 现有 C3) | 最小 | 中 |
|
||
| 2 | 薄 Electron 壳 + 借用官方打包链 | 中(壳小,难在跑通打包链) | 好 |
|
||
| 3 | Fork 官方桌面版源码改造 | 大 | 最好但最脆 |
|
||
|
||
> ⛔ **不建议第 3 档**:官方桌面版的整个设计(不开端口、独占 profile、只装插件)与"承载一个平台服务"方向相反,改造量大于重写。
|