Files
dsh_shenxian/dsh-server-docs/04-调整方案/127-dsh客户端化部署方案.md
T
admin 04776af4b1 docs: 工作区根项目文档批量入仓(04-调整方案 113–128 / 交接单 T09–T21 / ops / archive)
起因:用户 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 代码面)。
2026-09-17 18:24:19 +08:00

15 KiB
Raw Blame History

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、只装插件)与"承载一个平台服务"方向相反,改造量大于重写。