dsh 局域网访问部署教程(Android 手机 Termux)

August 16, 2026 · View on GitHub

目标:让同一 WiFi 下的平板 / 电脑 / 其他手机,用浏览器直接打开手机上的 dsh,看到手机上的对话、能改设置、能建工作区,无需在客户端安装任何东西。

适用:dsh(DeepSeek Harness)运行在 Android 手机的 Termux 内。以下所有命令中的 <占位符> 需替换为你的实际值。


目录

  1. 总体架构
  2. 三大障碍与解法原理
  3. 安全影响评估
  4. 前置条件
  5. 部署步骤
  6. 客户端使用
  7. 验证方法(真浏览器,不是 curl)
  8. 常见问题排查
  9. 维护与升级
  10. 彻底卸载 / 回滚
  11. 关键文件速查

1. 总体架构

┌───────────── 同一 WiFi 局域网 ─────────────┐
│                                           │
│  平板/电脑/其他手机浏览器                    │
│  打开 http://<手机IP>:3088                 │
│        │                                  │
│        ▼                                  │
│  ┌─────────────────────────────┐          │
│  │ dsh-lan-proxy.js(LAN 代理) │  手机    │
│  │ 0.0.0.0:3088                │          │
│  │ ① 开关检查(~/.dsh-lan-on) │          │
│  │ ② Host/Origin 改写成回环    │          │
│  │ ③ HTML 注入 randomUUID 补丁 │          │
│  └─────────────┬───────────────┘          │
│                ▼                          │
│  ┌─────────────────────────────┐          │
│  │ dsh web(127.0.0.1:3080)   │  dsh 本体 │
│  │ 视所有流量为回环 → 全权限    │          │
│  └─────────────────────────────┘          │
│                                           │
│  控制面(可选,仅手机本地):               │
│  dsh-ctl-server.js :3098  ←  控制台 App    │
│  (局域网开关 / 重启 / 状态 / 显示 URL)     │
└───────────────────────────────────────────┘

四个端口:

端口进程监听地址作用
3080dsh web127.0.0.1dsh 本体(CLI 故意禁 --host 0.0.0.0
3088dsh-lan-proxy.js0.0.0.0局域网唯一入口,转发 + 回环改写 + 注入补丁
3090dsh-llm-proxy.js127.0.0.1LLM 代理(剥 SSE 空 id/name、429 退避重试)
3098dsh-ctl-server.js127.0.0.1控制 API(LAN 开关、重启、健康检查)

为什么需要代理而不是直接暴露 3080? 因为 dsh 是"单设备设计",有三大障碍(见下节)。代理在中间做三件事把障碍全部绕开。


2. 三大障碍与解法原理

#障碍现象根因(源码位置)解法
1客户端 loopback 门LAN 打开页面后只调 inventory 同步value:[]),workspace.list / session.list / settings.describe 一个都不调,UI 卡"正在加载工作区"dsh-client-connection/lib/client.js 约 10167 行:isLoopback: pageLocation === void 0 || isLoopbackHostname(pageLocation.hostname) —— 非回环 hostname 时 isLoopback:false,整个数据流不初始化源码补丁:强制 isLoopback: true(rebrain.sh 固化,幂等可重打)
2服务端特权围栏设置/凭据/建工作区返回 403dsh-client-connection/lib/index.jsPRIVILEGED_METHODS 特权表(settings.、credentials.、host.pickDirectory、llm.discoverModels、agentPreset.*)只对回环连接开放,判断写死为 []源码补丁:3 处 isTrustedApiRequest(request, [])isTrustedApiRequest(request, this.trustedHosts)options.authority === "loopback" ? [] : this.trustedHoststhis.trustedHosts(rebrain.sh 固化)
3🔑 crypto.randomUUID 缺失页面 console 疯狂报 WebSocket is closed before the connection is established,WS 在 23ms 内被 abort,连接永不建立dsh 客户端 JS 用 RpcId(crypto.randomUUID()) 生成 RPC ID,但 crypto.randomUUID 只在安全上下文(HTTPS 或 localhost)存在http://<局域网IP>:3088 是非安全上下文 → 抛 crypto.randomUUID is not a function → RPC 全崩 → connection abort代理层:给 HTML <head> 注入 polyfilldsh-lan-proxy.js 内置)

前两个是"门没开",第三个是"钥匙没带"——三者叠加才是"LAN 用不了 dsh"的完整真相。只测 API(curl)会假阳性:API 层 200 但 UI 层罢工,必须用真浏览器验证(见第 7 节)。

另外还有一个"信任主机"配置:start-dsh.sh 启动 dsh web 时带 --trusted-host <手机IP>(无端口 + 带端口双写),否则 LAN 流量在网关层就会被 403 拦下。


3. 安全影响评估

本节回答一个常见顾虑:这套方案会不会危害路由器 / 造成不可逆修改? 结论先行:路由器零接触,没有任何不可逆的修改,但存在一个需要知晓的"局域网可达性"风险敞口(见 3.3)。

3.1 路由器 / 网络层:完全没碰

可能担心的操作实际情况
路由器配置(DHCP/端口转发/防火墙/UPnP/DNS)一次都没改过,无需进入路由器管理界面
静态 IP / 固定 DHCP 分配不需要,IP 动态变化由控制台 App 实时展示
电脑网络设置 / hosts / 系统代理不需要改动
WiFi 本身不动

唯一的"网络动作"是手机上的 3088 端口在局域网内可访问——这是 dsh 自己监听的服务端口(监听在 0.0.0.0:3088),不是路由器端口转发,其他设备只能在 WiFi 内网连到它,外部互联网无法直接访问(除非你手动在路由器上做端口映射,本方案不需要也不建议做)。

3.2 手机 / 电脑改动清单与可逆性

手机(Termux 内)改动——全部可逆:

改了什么位置影响可逆性
dsh 源码补丁(信任门 + isLoopback)dsh 包 node_modules 内让 LAN 设备能用全功能✅ 重装 dsh 即还原;补丁前留有 .bak 备份
代理/控制脚本(lan-proxy、ctl-server 等)Termux ~/ 目录局域网转发、控制 API✅ 删文件即撤
配置文件(settings.yaml、cordis.patch.yml)dsh 配置目录换 LLM 端点、沙箱策略✅ rebrain 可重写,原配置有备份
workspace.json(可能清理过测试工作区)~/.dsh/storages/仅影响你自己创建/删除的工作区✅ 有 .bak 备份
开机自启 + 监督进程~/.termux/boot/链死后自动拉起✅ 删启动脚本即撤
控制台 App手机应用一键开关局域网pm uninstall 即卸载

电脑改动——全部可逆且不在系统目录:

改了什么位置可逆性
项目文件(bundle、测试脚本、App 源码)用户项目目录(如 <项目根目录>/dsh-shim<项目根目录>/dsh-console✅ 删除文件夹即撤
npm 包 puppeteer-core(验证用)用户级 Node 工作目录npm uninstall 即撤
临时隧道进程 / adb 端口转发(仅调试期使用)内存态✅ 已停止并释放端口

所有操作没有触碰 C 盘系统文件、注册表、系统服务(Windows 上除临时端口转发与 npm 包外无任何系统级动作)。

3.3 真正的风险敞口:局域网可达性(务必知晓)

3088 端口开着 = 局域网内任何设备都能访问手机上的 dsh。 而 dsh 是能操作手机文件系统的 AI 工具——这意味着 WiFi 内网里任何设备都可以:

  • 查看你的对话记录
  • 修改 dsh 设置
  • 指挥 dsh 执行任务(可能涉及读写手机文件)

风险分级与对策:

场景风险建议
家用 WiFi(密码不外泄)低,等同家庭内网互访 NAS可长期开启
公司/校园/公共 WiFi,同网段可能有不信任设备不要开局域网开关
WiFi 密码泄露给外人中高关闭开关 + 修改 WiFi 密码

防护手段(本方案内置):

  1. 局域网开关:控制台 App 一键"关闭局域网访问",关闭后其他设备访问 3088 立即返回 403
  2. 仅监听局域网:不做路由器端口映射,互联网不可达
  3. 不用时关闭:平板用完随手关,风险归零

3.4 一句话总结

路由器和其他设备零影响;所有修改都在手机 Termux 与电脑用户目录内、均可逆。唯一需要留意的不是"修改"而是"开关打开期间的内网可达性",不用时关闭开关即可。


4. 前置条件

要求
手机Termux 已装 dsh + Node;adb 可连(USB 或 WiFi adb,用于部署)
WiFi手机与平板/电脑在同一局域网
保活Termux 已加电池白名单(部分厂商如 vivo/小米系统杀后台较凶,不配好这条 dsh 链随时会死)+ Termux:Boot 已装并允许自启
电脑(部署用)装了 adb 的 Windows/Mac/Linux;也可直接在 Termux 内操作

5. 部署步骤

以下命令以电脑 + adb 为例。将 <项目根目录> 替换为你的 bundle 所在路径,<手机IP> 为手机当前 wlan0 IP(可在 Termux 里 ip route get 1 | grep -oE 'src [0-9.]+' 查看)。

5.1 准备 bundle

所有文件放在一个目录(下称 bundle 目录,形如 <项目根目录>/dsh-shim/dsh-brain-swap/):

文件作用
dsh-lan-proxy.jsLAN 代理(开关 + 回环改写 + polyfill 注入)
dsh-ctl-server.js控制 API 服务
start-dsh.sh启动脚本(检测 wlan0 IP → 启动 dsh web + llm-proxy + lan-proxy + ctl-server)
rebrain.sh重装大脑脚本:幂等打全部源码补丁 + 部署配置与代理 + 拉起监督链
dsh-supervise.sh监督循环(进程死 5s 自动重启)
sandbox-shim.js / settings.yaml / cordis.patch.yml / 99-dsh.sh沙箱、配置、开机自启等配套

5.2 推送到手机并注入(电脑执行)

cd <项目根目>/dsh-shim/dsh-brain-swap
for f in dsh-lan-proxy.js dsh-ctl-server.js start-dsh.sh rebrain.sh dsh-supervise.sh sandbox-shim.js settings.yaml cordis.patch.yml 99-dsh.sh; do
  adb push $f /sdcard/Download/$f
done
adb shell "run-as com.termux sh -c '
  for f in dsh-lan-proxy.js dsh-ctl-server.js start-dsh.sh rebrain.sh dsh-supervise.sh sandbox-shim.js settings.yaml cordis.patch.yml 99-dsh.sh; do
    cat /sdcard/Download/\$f > /data/data/com.termux/files/home/dsh-brain-swap/\$f
  done
  rm -f /sdcard/Download/dsh-lan-proxy.js /sdcard/Download/dsh-ctl-server.js /sdcard/Download/start-dsh.sh /sdcard/Download/rebrain.sh /sdcard/Download/dsh-supervise.sh /sdcard/Download/sandbox-shim.js /sdcard/Download/settings.yaml /sdcard/Download/cordis.patch.yml /sdcard/Download/99-dsh.sh
'"

⚠️ run-as com.termux 的 shell 与普通 shell 不同(UID 1000),直接 cp 到目标目录会静默写出 0 字节文件,必须用 cat >。这是踩过的坑。

5.3 打补丁(rebrain.sh 幂等执行)

adb shell "run-as com.termux sh -c '
  pkill -f \"[d]sh-supervise[.]sh\" 2>/dev/null
  sleep 1
  REBRAIN_NO_LAUNCH=1 sh /data/data/com.termux/files/home/dsh-brain-swap/rebrain.sh
'"

rebrain 会依次做(全部幂等,重复执行自动跳过,dsh 升级后重跑即可重新打上):

  1. 部署 sandbox-shim.js(沙箱 shim)
  2. 部署配置 settings.yaml + cordis.patch.yml
  3. 部署 dsh-lan-proxy.js / dsh-llm-proxy.js / start-dsh.sh / dsh-ctl-server.js$HOME
  4. 补丁 A:persistence 硬链 → rename
  5. 补丁 B:client-connection 信任门(3 处 []this.trustedHosts
  6. 补丁 C:client.js isLoopback: true 强制

验证补丁是否打上:

adb shell "run-as com.termux sh -c '
  F=/data/data/com.termux/files/usr/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-client-connection
  grep -c \"isTrustedApiRequest(request, this.trustedHosts)\" \$F/lib/index.js   # 期望 ≥2
  grep -c \"LAN-patch\" \$F/lib/client.js                                        # 期望 ≥1
  grep -c LOOPBACK_HOST \$HOME/dsh-lan-proxy.js                                  # 期望 ≥1
'"

5.4 启动整条链

adb shell "run-as com.termux sh -c '
  setsid sh /data/data/com.termux/files/home/dsh-brain-swap/dsh-supervise.sh \
    > /data/data/com.termux/files/home/dsh-supervise.log 2>&1 < /dev/null &
'"
sleep 40   # 冷启动等待

5.5 验证四个端口

adb shell "run-as com.termux sh -c '
  export PATH=/data/data/com.termux/files/usr/bin:\$PATH
  for p in 3080 3088 3090 3098; do
    curl -s -m 2 -o /dev/null -w \"\$p=%{http_code} \" http://127.0.0.1:\$p/
  done
  echo
  ip route get 1 | grep -oE \"src [0-9.]+\" | cut -d\" \" -f2   # 当前 wlan0 IP
'"

5.6 (可选)装控制台 App

控制台 App(原生 UI,显示实时 LAN URL + 在线状态 + 局域网开关 + 一键重启):

cd <项目根目>/dsh-console
adb push out/app-debug.apk /data/local/tmp/dsh-console.apk
adb shell pm install -r -g /data/local/tmp/dsh-console.apk
adb shell am start -n <应用包>/.MainActivity

6. 客户端使用

使用方(平板/电脑/其他手机)不需要装任何东西

  1. 看手机控制台 App 显示的大绿色地址(形如 http://<手机IP>:3088/
  2. 在浏览器地址栏输入该地址,回车
  3. 即可看到手机上的工作区、真实对话、设置、模型选择——与在手机上用完全一致

注意事项:

  • IP 会变:路由器 DHCP 可能重新分配地址,每次使用前看一眼 App 上的最新地址即可(App 实时读取 wlan0)
  • 局域网开关:在 App 上可随时"关闭局域网访问"(关闭后其他设备访问立即 403)
  • 首次加载有"内测声明"弹窗,点继续即可(只出现一次)

7. 验证方法(真浏览器,不是 curl)

⚠️ 血的教训:只测 curl / API 脚本 = 假阳性。API 层 200 不代表 UI 能用(loopback 门在客户端 JS 里,curl 不触发)。必须用真实浏览器打开页面,检查 API 调用、localStorage、截图。

电脑上推荐 puppeteer-core + Edge 无头浏览器:

// verify-lan.js —— 需先:npm install puppeteer-core
const puppeteer = require('puppeteer-core');
(async () => {
  const b = await puppeteer.launch({
    executablePath: 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
    headless: 'new',
  });
  const page = await b.newPage();
  const apis = [];
  page.on('request', r => { if (r.url().includes('/api/')) apis.push(r.url()); });
  await page.goto('http://<手机IP>:3088/', { waitUntil: 'networkidle2', timeout: 60000 });
  await new Promise(r => setTimeout(r, 8000));
  const state = await page.evaluate(() => ({
    ls: Object.keys(localStorage),
    body: document.body.innerText.slice(0, 300),
  }));
  console.log('API 调用数:', apis.length);
  console.log('关键 API:', apis.filter(u => /workspace|session|settings|credentials/.test(u)).slice(0, 10));
  console.log('localStorage:', state.ls);
  console.log('页面文本:', state.body.slice(0, 150));
  await page.screenshot({ path: 'lan-check.png' });
  await b.close();
})();

判定通过的标准

  • ✅ 页面出现手机上已有的工作区和对话文本
  • ✅ 调用了 workspace.list / session.list / settings.describe 等数据 API(≥20 个)
  • ✅ localStorage 出现 dsh.workspace.view.v5 等键
  • ✅ 截图里能看到对话内容

再手动验证设置页可打开、可新建工作区、可发消息。


8. 常见问题排查

现象原因处理
其他设备打不开 http://<IP>:3088① IP 不对(不是手机当前 IP)② 链死了(系统杀了 Termux)③ 局域网开关关了① 看 App 绿色地址 ② App 点"启动/重启 dsh" ③ App 点"开启局域网访问"
能打开但一直"正在加载工作区"polyfill 未注入(代理是旧版)确认 grep -c LOOPBACK_HOST ~/dsh-lan-proxy.js ≥1;重启代理后按 5.4 重启链
设置/凭据 403特权围栏补丁没打上跑 rebrain(5.3)并验证 grep -c 结果
页面能开但对话不显示loopback 门补丁没打上(client.js 旧版)跑 rebrain 重打补丁 C
手机本地 3080 正常、LAN 全挂LAN 代理进程死了 / 多个代理抢 3088pkill -f "[d]sh-lan-proxy[.]js",确认只剩 1 个代理进程后重启链
发消息 429 限流LLM token 全网段限流窗口(测试过猛触发)等几分钟重试,与 LAN 无关
重启手机后链没了未配 Termux:Boot 自启或电池白名单检查 Termux 电池优化白名单 + ~/.termux/boot/99-dsh.sh 存在;或手动:run-as com.termux sh -c 'setsid sh $HOME/dsh-brain-swap/dsh-supervise.sh > $HOME/dsh-supervise.log 2>&1 < /dev/null &'
改过 workspace.json 后 dsh 启动崩循环结构是 {unit, global:{workspaceIds:[...]}, tables:{workspaces:{id:data}}}删条目必须同时删 global.workspaceIds 引用补删引用或从备份恢复

9. 维护与升级

  • dsh 升级后:重跑 rebrain.sh(5.3)即可把全部源码补丁重新打上(幂等、自动跳过已打的)
  • bundle 更新:改完本地文件 → 重复 5.2 → 5.4(重启链)
  • 修改代理后只重启代理pkill -f "[d]sh-lan-proxy[.]js" → 用带 HOME/LAN_PORT/DSH_WEB 环境的命令重新拉起(注意 run-as 不自动设 HOME,必须 export HOME=/data/data/com.termux/files/home,否则 ~/.dsh-lan-on 找不到,开关判断错误)
  • pkill 自匹配陷阱pkill -f 的模式必须用括号防自匹配(如 [d]sh-lan-proxy[.]js),且不要在同一命令行里又 pkill 又 setsid 启动同名进程(外层命令字符串含路径名会自杀,报 143)

10. 彻底卸载 / 回滚

如果不想再用了,按顺序执行即可完全撤掉:

步骤操作说明
1. 关局域网控制台 App 点"关闭局域网访问",或删手机上的 ~/.dsh-lan-on其他设备立即无法访问
2. 停监督链pkill -f "[d]sh-supervise[.]sh"; pkill -f "[b]in[.]js web" 及四个进程停止所有服务
3. 撤开机自启删除 ~/.termux/boot/99-dsh.sh重启不再拉起
4. 还原 dsh 源码补丁重装 dsh 包;或用补丁前 .bak 备份恢复恢复官方原版行为
5. 删代理/脚本删除 ~/dsh-lan-proxy.js~/dsh-ctl-server.js~/start-dsh.sh~/dsh-brain-swap/清空部署产物
6. 卸载 Apppm uninstall <应用包名>移除控制台 App
7. 清电脑删除 <项目根目录>/dsh-shim<项目根目录>/dsh-consolenpm uninstall puppeteer-core清空本地产物

11. 关键文件速查

位置文件说明
手机 ~/dsh-lan-proxy.jsLAN 代理(核心交付物)
手机 ~/dsh-ctl-server.js控制 API(3098)
手机 ~/start-dsh.sh一键起链(自动检测 IP)
手机 ~/dsh-brain-swap/rebrain.sh幂等重装 + 打全部补丁
手机 ~/dsh-brain-swap/dsh-supervise.sh保活监督循环
手机 ~/.termux/boot/99-dsh.sh开机自启
手机 ~/.dsh-lan-on局域网开关标志(存在=开)
电脑<项目根目录>/dsh-shim/dsh-brain-swap/bundle 源(与手机同步)
电脑<项目根目录>/dsh-console/控制台 App 源码 + out/app-debug.apk