# 103 · 客户端安装与覆盖网络互联 · 可行性评估 - 日期:2026-09-16 | 状态:📋 **规划态 / 未实施**(本轮零代码、零服务器改动;本档=设计单,尚未开跑) - 来源:工作区根 `可行性评估_客户端安装与覆盖网络互联_20260916.md`(2026-09-16 转为正式档案,正文见 附录 A) - 层级:**决策层 · 总纲**(本线其余 9 份档案的共同前置)| 关联:档案 104 · 106–112 - 触发(用户原话):「当前项目是否可以改造为支持服务部署以及客户端安装(客户端壳套服务器项目),让他们通过覆盖网络互联」 > **TL;DR** > ✅ **可行,且现有架构已给出约 80% 的形状** —— T08 集群化已经把「一台机器 = 一个可被平台拨入的执行体」定义完了, > 并已有两条现成能力正好解决「没有公网 IP」:① `soft` 隔离模式下实例**只是一个普通子进程**(不需 root / bwrap / systemd); > ② Worker 用**拨出式 SSH 反向隧道**接入 Manager —— **节点本来就不需要公网 IP**。 > 真正的缺口 4 条:**客户端运行时落点 · 节点身份(现为共享令牌)· 信任模型反转 · 分发与版本矩阵**。 ## 一、目标 判定「平台能否支持『服务部署 + 客户端安装 + 覆盖网络互联』」,并给出**可增量落地的形态与顺序**、**缺口清单**、**未验证项诚实清单**。 可判定「做完了没有」= 产出 ① 形态结论(A/B/C 三档取舍)② 缺口 4 条 ③ 若走 B 档的 6 步落地顺序(每步可单独回滚)。 ## 二、只读前置 > 执行前必须先核实的 5 条事实(**均已在本轮取证过,给出可复跑命令**): | # | 事实 | 核对方式(期望输出) | |---|---|---| | 1 | `soft` 是默认隔离档 ⇒ 实例 = 裸子进程 | `grep -n "isolationMode" src/config.ts` → `'soft' \| 'account'`,默认 `soft` | | 2 | soft 下不 bwrap / 不 setpriv / 不 systemd-run | `sed -n '660,665p' src/supervisor/orchestrator.ts` → 直接 `spawn(command,args)` | | 3 | Worker 走**拨出式**反向隧道(节点无需公网 IP) | `sed -n '147,155p' src/worker/tunnel.ts` → `ssh -O forward -R :127.0.0.1:` | | 4 | 全仓**仅一处**平台分支 | `grep -rn "process.platform" src` → 1 处(`src/supervisor/firewall.ts`) | | 5 | 节点身份现为**共享令牌 + bearer 比较**(无吊销) | 读 `src/worker/agent.ts` 头部注释(自陈「仅内网 + nft 白名单」) | ## 三、范围 - **改什么**:仅**新增**(新客户端形态 + 新 Spawner 实现 + 新节点身份机制)。**服务端现有路径不动**。 - **不动什么**:⛔ 不改官方 dsh 主程序与缓存(R2)|⛔ 不改管理面(门户 / DB / 归属 / 租约留在服务器)|⛔ 本轮不动服务器、不改代码。 - **红线约束**:R5(权限只准收窄 —— 客户端形态下不得下发平台级凭据)|R2|R1(不自动升级 dsh)。 ## 四、决策点 **已定** 1. **形态已拍板 = 单机自用**(用户原话:「考虑用户自己使用就可以了,不用考虑当作服务器 其他人访问」)⇒ 本档 §三 的 A/C 两档「多人维度」**已放弃**,三档表保留为**历史决策依据**,不再是待拍板项。 2. **地基 = `soft` 隔离档**(用户已定:「隔离档保持默认 soft」)—— 三份客户端文档同向。 **历史决策依据(本档 §三,已不再是开放问题)**:A 只做壳(覆盖网络用不上)|**B 自带实例(本档倾向)**|C 客户端当集群 Worker(需强身份 + 重划合规边界)。 **待执行会话复核(非上抛项)**:`soft` 模式在 Windows / macOS 上的实际隔离强度与可运行性 —— **本档已实测修正**:真正的硬阻塞**不在 dsh 运行时**,而在**平台自己的启动方式**(裸名 `spawn('npm')` 在 Windows 报 `ENOENT`;显式 `.cmd` 被 Node 拒 `EINVAL`;改 `cmd.exe /c` 或 `shell: true` 即通)⇒ **代价 = 一处调用方式**。 ## 五、步骤(若走 B 档;每步自带一次验证) 1. **验证 dsh 在 Windows / macOS 可运行性**(只读 + 本地装,不动服务器)→ 验证:客户端能起实例 + 实例内 bash 工具可用。 2. **客户端节点代理**(复用 agent 协议、落 `soft`)—— 先只做 `/healthz` `/launch` `/stop` `/fence` → 验证:台账式幂等(同 `operationId` 重放不产生副作用)。 3. **通道**:先用现有**拨出式 SSH 反向隧道**打通(零新依赖,当天可验)→ 验证:无公网 IP 的节点可被平台访问。 4. **身份**:每节点独立凭据 + 吊销(替换共享 bearer)→ 验证:吊销单台后该台拨入被拒,其余不受影响。 5. **强约束落码**:实例所属 == 客户端主人;**共享密钥不下发客户端**(红线级)→ 验证:断言/测试钉住,构造越权用例必须失败。 6. **分发与自动升级** + 客户端 × 平台版本兼容矩阵 → 验证:旧客户端连新平台给出可读的「需升级」而非崩。 ## 六、验收 | # | 口径 | 期望 | |---|---|---| | A | 可行性判定 | 已给出(✅ 可行 + 80% 形状 + 缺口 4 条)—— 本档已交付 | | B | 形态可选性 | 三档各有优缺、含倾向(B),且**已按用户口径收窄为单机自用** | | C | 落地可执行性 | 6 步,**每步可单独回滚**,且第 1 步不依赖服务器 | | D | 未验证项透明 | §五 五条未验证项已列全(Windows/macOS 起实例、soft 实际隔离强度、共享密钥能否按形态裁剪、打洞替换代价、版本矩阵判据) | ## 七、回滚 - **本档自身**:零改动 ⇒ 无回滚需要。 - **若按 §五 执行**:全程**只新增**(新客户端形态 + 新 Spawner 实现),**服务端现有路径不动** ⇒ **删客户端即回到现状**。 - 服务器侧待复核事实(非本档动作):控制面机规格(1.8 GB / 2 核 是 09-08 旧记录);47↔106 跨云实测 **~22 KB/s**。 ## 八、回报格式 执行会话回填:① 每一步的验证命令 + 实际输出 ② 触及的文件清单(应与 §三 一致,多出来的要说明)③ 红线遵守声明(R2 官方包零改动 / R5 未下发平台凭据)④ commit ⑤ 未完成项与卡点(附客观依据)⑥ 若推翻本档任一结论,给出取证。 ## 九、本档需收口的两处(**未处理,仅报告**) 1. **双源声明**:`dsh客户端化部署方案_20260916.md`(v3)自称「'客户端化'的**单一来源**」,而 `dsh桌面客户端_开发方案_20260916.md`(v1)与其同日并存且**不同层**(平台侧 vs 交付载体层)⇒ 属「第二个漂移源」风险。建议:v3 定平台侧、v1 定交付层,v3 补一行指针指向 v1。 2. **判定句互相矛盾**:v3 §12 附录 C 把「官方桌面版(Electron)作为载体」列为**放弃项**,而 v1 恰恰选了这条路(且其「保留壳、换内核」已实际消解该理由)⇒ 并列阅读会读成"同一件事既放弃又采纳"。建议在 v3 §12 该行加注「已被 v1 以'换内核'方式规避,判定失效」。 > ⚠️ 这两处**不在本档 lane**(两份文档已收口、只读),仅报告不代改。 --- ## 附录 A · 原始规划正文(逐字保留 · 2026-09-16) > 以下为工作区根 `可行性评估_客户端安装与覆盖网络互联_20260916.md` 的**原文全文**,仅删除其一级标题(避免与档案标题重复), > 其余**一字未改**;本档的可判定部分以上方 8 段为准。 > 日期:2026-09-16 | 状态:**调研稿**(只读取证完成,未改任何代码/服务器) > 触发:用户问「当前项目是否可以改造为支持服务部署以及客户端安装(客户端壳套服务器项目),让他们通过覆盖网络互联」 > ⚠️ **本稿落点说明**:文档库(`dsh-server-docs/`)当时被**全局执行锁**占用(持有者 `guest迁移-2307`,09-15 23:08 起)⇒ 按纪律**未写入文档库**,暂落工作区根。锁释放后应转为 `04-调整方案/-*.md` 正式档案并登记。 --- ## TL;DR **可行,而且现有架构已经给出约 80% 的形状** —— 因为「一台机器 = 一个可被平台拨入的执行体」这件事,T08 集群化已经定义完了,并且**已经有两条现成能力**正好解决"没有公网 IP": ① `soft` 隔离模式下实例**只是一个普通子进程**(不需要 root / bwrap / systemd); ② Worker 用**拨出式 SSH 反向隧道**接入 Manager —— **节点本来就不需要公网 IP**。 真正的缺口是 4 条:**客户端运行时落点**、**节点身份(现为共享令牌)**、**信任模型反转**、**分发与版本矩阵**。 --- ## 一、现成资产(可直接复用 · 均带代码坐标,已核实) | # | 资产 | 证据 | 为什么关键 | |---|---|---|---| | 1 | **`soft` 隔离模式**:实例 = 裸子进程 | `src/supervisor/orchestrator.ts:663` —— `isolationMode !== 'account'` 时直接 `spawn(command, args)`,**无 bwrap / 无 setpriv / 无 systemd-run / 不需要 root**;`src/config.ts:14,180`(`'soft'|'account'`,**默认 `soft`**) | 客户端侧不需要任何特权即可承载实例;Linux 隔离层(bwrap·scope·uid)是**可选增强**,不是前置依赖 | | 2 | **Worker agent 协议**(≈ 现成的"节点契约") | `src/worker/agent.ts` 头部 4 条纪律:**单向拨入**(不反向连 Manager、不写控制面)· **幂等键 `operationId`** · **最小接口白名单** · **self-fencing(epoch 落后即自停)**;端点:`/healthz` `/launch` `/stop` `/status` `/endpoint` `/fence` `/watchdog` `/fs/*` | 「个人电脑作为节点」所需的核心语义**已经定义并跑通过**(S0–S7 全绿 + 真跨机演练) | | 3 | **拨出式反向隧道**(解决无公网 IP) | `src/worker/tunnel.ts:152` —— `ssh -O forward -R :127.0.0.1:`;含 20 s 本地定时器自愈(T08 §16.5 ②) | 节点**主动拨出**即可被平台访问,**无需公网 IP、无需端口映射** —— 这正是覆盖网络"中继支"的等价物 | | 4 | **归属/租约分层判据** | 集群化设计 §1.3:**归属与租约只有 Manager 能写**;Worker 自有本地库 | 客户端天生**只能是数据面**,不会出现"客户端写权威状态"的脑裂 —— 与"个人电脑不可信"天然相容 | | 5 | **Spawner 接口已抽象** | `src/supervisor/spawner.ts`(`launch / stop / list`)+ `local-*` / `remote-*` 两个实现 | 新增一种"客户端节点"= **多一个 Spawner 实现**,不是改内核 | | 6 | **平台无跨平台分支** | 全仓仅 `src/supervisor/firewall.ts:42` 一处 `process.platform === 'linux'`(且仅用于 nft 联网护栏) | 除隔离与防火墙外,**代码本身跨平台** | **结论**:能移植的不是"平台",而是**实例运行时 + 节点代理**这两块;管理面(门户/DB/归属/租约)留在服务器不动。 --- ## 二、缺口(4 条,按必须先解决排序) ### 缺口 1 · 客户端运行时落点(技术,自决范围) - Linux 侧原语分布(已清点):`bwrap`(`orchestrator.ts` / `spawner.ts` / `agent.ts` / `cli.ts` / `routes/admin.ts`)· `systemd-run`(`cli.ts` / `orchestrator.ts`)· `setpriv`(`cli.ts` / `config.ts` / `orchestrator.ts` / `routes/business-plugins.ts`)· `nft`(`cli.ts` / `config.ts` / `orchestrator.ts` / `agent.ts`)· 绝对路径 `/var/lib/dshs` `/opt/dshs` `/usr/local/dsh-runtime`。 - ⇒ 客户端侧需要一套**平台无关的落点**:Node + `dsh` 可执行 + profile + 工作区目录(Windows 用 `%LOCALAPPDATA%`、macOS 用 `~/Library/Application Support`)。 - ⚠️ **未验证(须实测)**:`dsh` 自身在 Windows / macOS 上的**沙箱后端行为**。Linux 侧注释显示 dsh 有"沙箱后端探针",探针失败会 `refusing to run the command unconfined`(实例内 bash 被整体拒绝)—— Windows / macOS 上走哪条后端、是否可用,**尚无证据**。 (验收口径 L2 起:客户端上起一个实例 → `curl` 实例页 200 + 实例内跑一次 bash 工具。) ### 缺口 2 · 节点身份(现在是共享令牌,公网上不成立) - 现状自陈(`agent.ts` 头部):token 是**共享密钥 + bearer 式比较**,"仅内网 + nft 白名单";HMAC / 防重放**留待后续**。 - 个人电脑在公网 ⇒ 共享令牌**无法吊销单个节点**、无法抗重放、一台泄露等于放行全部。 - ⇒ 必须升级为**每节点独立身份**(客户端证书 / 独立密钥对)+ 准入与吊销,且**通道本身要端到端加密**。 ### 缺口 3 · 信任模型反转(最重要的一条,不是技术问题而是边界问题) - 现平台假设:**宿主机由我们掌控**(所以用 uid 隔离"用户 A 防用户 B")。 - 客户端形态下:**宿主机由用户掌控** ⇒ 原有隔离语义在"防机器主人"这一维上**直接失效**(机器主人本来就能读自己机器上的一切)。 - ⇒ 必须新增硬约束:**一台客户端上的实例只能属于该客户端的主人**;平台**不得**把别人的实例、别人的数据、或平台级凭据下发到客户端。 - ⚠️ 现状反例:平台会把**共享模型密钥**注入实例(档案 85/87 的两层密钥中的"共享"那一层)—— 下发到用户掌控的机器等于**把平台密钥交给用户**。 - ⇒ 客户端形态**必须只用"用户自己的密钥"这一层**;共享层只在服务端实例上使用。 ### 缺口 4 · 分发与版本矩阵(运维) - 客户端安装包 + 自动升级 + **客户端版本 × 平台版本兼容矩阵**(现有 `plugin-compat` / `dsh-install.ts` 是同类问题的服务端解,需要客户端版对应物)。 - 客户端一多,**故障面从"我们的机器"扩到"用户的机器"**(系统时间、磁盘、杀软、代理),排障成本上升。 --- ## 三、形态三档(**真取舍,需用户拍板**) > 判据:三档的**最终效果**对用户完全不同(数据在哪、算力归谁、谁能调度),属"业务目标与优先级",不能由技术标准裁决。 **A 档 · 客户端只做"壳"(远程访问)** - 优点:改动最小;安全模型**零变化**;不引入任何跨租户风险;沿用现有实例与全部隔离。 - 缺点:个人电脑**不成为节点**,算力仍在服务器;离线不可用;覆盖网络在这一档**没有实际用途**。 **B 档 · 客户端自带"自己的实例"(平台退化为控制面 + 中继 + 目录)** - 优点:数据与算力都在用户自己机器上,**服务器成本大降**;离线可用;能用到本机文件与本地软件;不引入跨租户风险(每位用户只碰自己的机器)。 - 缺点:`soft` 模式隔离强度**低于**现在的 `account` 模式(本机场景可接受,但需明确边界);Windows / macOS 运行时**未验证**;平台对实例的可控性下降(升级、清理、取证都要客户端配合)。 **C 档 · 客户端作为集群 Worker(平台可把实例调度到用户机器)** - 优点:算力可横向扩张且复用**已跑通的集群协议**(agent 四纪律 + 归属/租约分层已就位);对平台运营方收益最直接。 - 缺点:**信任模型反转**(别人的代码、别人的数据、平台凭据落到用户掌控的机器上);必须有节点准入 + 吊销 + 强身份;**合规与责任边界要重新界定**;Windows / macOS 的隔离层基本**要从零做**(无 bwrap / 无 systemd / 无 uid 隔离)。 **我的倾向:先做 B**(唯一一档能"增量落地 + 不引入跨租户风险",且正好吃满资产 1/3/4 的现成能力)。A 档价值有限(覆盖网络用不上);C 档应等服务端能力与合规边界都清楚后再谈。 --- ## 四、若走 B 档:落地顺序(每步可单独回滚) 1. **验证 dsh 在 Windows / macOS 的可运行性**(只读 + 本地装,不动服务器)——产出:能否起实例 + 实例内 bash 工具是否可用(L2/L4)。 2. **客户端节点代理**(复用 agent 协议,落 `soft` 模式):先只做 `/healthz` + `/launch` + `/stop` + `/fence`,台账式幂等。 3. **通道**:先用现有**拨出式 SSH 反向隧道**打通(零新依赖,当天可验),再评估换"打洞优先 + 中继兜底"。 4. **身份**:每节点独立凭据 + 吊销(替换共享 bearer)。 5. **强约束落码**:实例所属 == 客户端主人;共享密钥不下发客户端(**红线级**,须有断言/测试钉住)。 6. 分发与自动升级。 **回滚**:全程只新增(新客户端形态 + 新 Spawner 实现),**服务端现有路径不动** ⇒ 删客户端即回到现状。 --- ## 五、未验证 / 待确认(诚实清单,勿当已知) | # | 项 | 状态 | |---|---|---| | 1 | `dsh` 在 Windows / macOS 起实例 + 实例内 bash 是否可用 | ⚠️ **未验证**(本轮无对应环境) | | 2 | `soft` 模式下实例的实际隔离强度(能读到宿主什么) | ⚠️ **未验证**(本轮只读了代码分支,未实测) | | 3 | 客户端形态下"共享密钥不下发"是否已有现成开关 | ⚠️ **待查**(档案 85/87 是两层密钥,需确认能否按形态裁剪) | | 4 | 打洞优先方案与现有隧道的替换代价 | 📋 方案调研(见同日讨论结论:自托管 NetBird / headscale + 自建中继) | | 5 | 客户端 × 平台版本兼容矩阵的判据 | 📋 待设计(可仿 `plugin-compat`) | --- ## 附:本轮取证命令(可复跑) ```bash cd /d/github/dsh_shenxian sed -n '660,665p' src/supervisor/orchestrator.ts # soft 模式 = 裸 spawn grep -n "isolationMode" src/config.ts # 'soft'|'account',默认 soft sed -n '1,30p' src/worker/agent.ts # 节点四条协议纪律 sed -n '147,155p' src/worker/tunnel.ts # -R 反向转发(拨出式) grep -rn "process.platform" src # 全仓仅 1 处(firewall.ts) ``` --- ## 六、与另两份客户端文档对账(2026-09-16 08:5x 追加) > 对账对象(同为大工作区根,**均晚于本稿 8 小时**,08:06 产出): > `dsh客户端化部署方案_20260916.md`(v3 · 单机自用 · 自称「客户端化」的单一来源) > `dsh桌面客户端_开发方案_20260916.md`(v1 · 基于官方 Electron 壳 · 独立仓库) ### 6.1 结论:**不冲突,是互补;但两处需要收口** | 维度 | 本稿 | 部署方案 v3 | 桌面客户端 v1 | 判定 | |---|---|---|---|---| | 形态选择 | 三档 A/B/C,倾向 **B** | **单机自用**(用户原话已定,§12) | 单机自用为默认,域名可切多人 | ✅ **同向**(本稿的 B 即其单机自用;三档问题已被用户拍板,见 6.2) | | 地基 | 资产 1:`soft` 默认、无需 root | 明确"隔离档保持默认 soft" | 同 | ✅ 互相印证 | | 平台改动面 | 资产 6:仅 firewall 一处平台分支 | "唯一硬阻塞 = spawn 方式" | P1 同一条 | ✅ 不同维度,可并存 | | 分发与版本 | 缺口 4 | — | §3.4 上游同步 / §4.6 自动更新 / §6 打包 | ✅ 已被其覆盖 | ### 6.2 本稿需修正的两点(**如实记录,勿当原判**) 1. **缺口 1 的位置标错了**:本稿把"客户端运行时"列为缺口、并怀疑 `dsh` 在 Windows 的**沙箱后端**不可用。部署方案 v3 **已在本机 Windows 实测**:真正的硬阻塞是**平台自己的启动方式** —— 裸名 `spawn('npm')` 在 Windows 报 `ENOENT`(全局包是 `.cmd`),显式 `.cmd` 被 Node 拒(`EINVAL`),改用 `cmd.exe /c` 或 `shell: true` 即通。⇒ **阻塞点在平台调用层,不在 dsh 运行时**,且**代价很小**(一处调用方式)。 2. **三档"需用户拍板"已不再是开放问题**:用户此前已定「考虑用户自己使用就可以了,不用考虑当作服务器 其他人访问」⇒ **A/C 两档的多人维度已被放弃**。本稿第五节的三档保留为**历史决策依据**,不再是待拍板项。 ### 6.3 仅本稿覆盖、另两份未涉的:**覆盖网络**(用户最初问题里的那一项) - 两份文档的"多人形态"路线 = **内网 DNS 泛解析 + 域名**(局域网内多机访问),与"覆盖网络"是**同一需求的两种替代实现**: - 局域网 + 域名:只能在同一内网;跨公网不行;需要域名与 DNS 泛解析。 - 覆盖网络:跨公网可直连(打洞优先、中继兜底);不需要域名;但需要客户端常驻 + 节点身份。 - ⇒ **本稿与另两份文档在"能力面"上不冲突,但在"要不要跨公网互连"上是分叉**;两份文档默认不跨公网。若确定不做跨公网,本稿的缺口 2/3 可整体降级为"不适用"。 - 📄 **百台规模的逻辑预演见 `覆盖网络_百台规模推演_20260916.md`**(2026-09-16):结论 = 100 台规模下**控制面不是瓶颈,中继带宽与"大流量过网"才是**(实例首屏含 ≈10.8 MB 客户端脚本);而"单机自用"天然把大流量留在回环上 ⇒ 该形态在 100 台规模下是**正确版而非妥协版**。 ### 6.4 两处需收口的隐患(**建议进文档库时一并处理**) 1. **双源声明**:部署方案 v3 自称「'客户端化'的**单一来源**」,而桌面客户端 v1 与其**同日并存**且内容不同层(平台侧 vs 交付载体层)⇒ 属项目明令禁止的"第二个漂移源"风险。建议:**v3 定平台侧、v1 定交付层,v3 里补一行指针指向 v1**,或合并。 2. **判定句互相矛盾**:v3 §12 附录 C 把「官方桌面版(Electron)作为载体」列为**放弃项**(理由:换载体会丢掉门户能力),而 v1 **恰恰选了这条路**。v1 的"保留壳、换内核为我们的平台"已实际**消解**该理由(门户能力并未丢),但 **v3 的判定句未更新** ⇒ 并列阅读时会读成"同一件事既放弃又采纳"。建议在 v3 §12 该行加注"已被 v1 以'换内核'方式规避,判定失效"。 ### 6.5 本稿的**缺口 3 仍然有效,且是"切多人形态时的前置门禁"** - 部署方案 v3 §1.3 明确写:切多人形态**零代码**(只配域名),且**多人时隔离档仍是 `soft` ⇒ 用户之间无隔离**;桌面客户端 v1 §4.7 进一步要求"**客户端 spawn 平台时不要清理环境变量、不要白名单化**"(为透传域名)。 - 这两条与本稿缺口 3 的硬约束**正面相交**: - ① **实例只能属于机器主人** —— 单机自用下天然成立;一旦配域名多人用,**立刻不成立**(soft 档无隔离)。 - ② **平台级共享模型密钥不得随客户端分发** —— 与"环境变量原样继承"相冲。**建议改为白名单透传**,只放形态开关三项(`DSHS_BASE_DOMAIN` / `DSHS_COOKIE_DOMAIN` / `DSHS_SECURE_COOKIES`),模型密钥只走"用户自己的密钥"那一层。 - ⇒ 结论:**不是"不兼容",而是"范围收窄把风险面暂时关掉了、但开关就在配置里"**。建议把上述两条写成**切多人形态的前置检查项**(而非留在散文里)。