dsh-relay
Cloud relay for DeepSeek Harness: expose your home dsh instance to any device with full real-time sync (out-of-tree plugin + wire-trunk architecture)
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 16, 2026
- Updated
- Aug 24, 2026
Introduction
dsh-relay
English | 中文
把家里运行的 DeepSeek Harness(dsh)完整映射到公网 —— 在任何设备、任何地点,通过一个网址获得与你家中电脑完全一致的 dsh 体验:会话实时同步、流式输出、工具卡片、审批弹窗、工作区浏览,一个不少。
手机 / 外网电脑 ──HTTPS──→ 云端中继(dsh-relay-cloud,一台 VPS)
│ 出站长连接干线(家端主动拨出)
▼
家中 dsh(保持 127.0.0.1 回环绑定,零端口暴露)
└─ dsh-relay-host 插件:进程内"隐形浏览器",
以回环身份复放全部请求
它是如何工作的(Wire-Trunk 架构)
- 家端是一个树外插件(~200 行,零构建):它作为"dsh 进程里的隐形浏览器",对本机
127.0.0.1:3080发起与真实浏览器完全相同的协议连接(POST/api/*+ 两条下行 WebSocket),并把它们原封不动复用到云端。 - 云端是纯转发面:对远端浏览器呈现与 dsh 原版逐字节一致的 wire;静态资源由家端实时上报、云端缓存——前端与宿主永远同版本,不存在协议漂移。
- 不修改 dsh 仓库任何一行代码:插件挂在
~/.dsh/profiles/web/用户补丁层,git pull、自动更新、随时卸载互不干扰。 - 安全性:dsh 本身无认证层且特权方法钉死回环——本方案恰好让请求以回环身份进入,同时把认证(配对码 → 长期 Cookie)补在云端入口;家端零入站端口,天然穿 NAT。
完整部署教程
三个角色:云端(一台公网服务器)、家端(运行 dsh 的电脑)、远端(手机/任何浏览器)。按顺序做完约 15 分钟。
前置条件
| 角色 | 要求 |
|---|---|
| 云端 | 公网 IP 的 Linux 服务器(1 核 1G 起步即可),能 SSH 登录,开放一个 TCP 端口(下文用 8443) |
| 家端 | 已能运行 dsh web(默认 127.0.0.1:3080),Node ≥ 22 |
| 远端 | 任何现代浏览器 |
第 1 步:准备凭据(家端电脑上执行)
生成两个强随机值,后面所有步骤都用它们:
# 干线令牌(家端 ↔ 云端的身份凭证,32 位)
node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"
# 记作 <TOKEN>,例如 9f86d081884c7d65...
# 配对码(远端浏览器首次访问输入,8 位)
node -e "console.log(require('crypto').randomBytes(4).toString('hex'))"
# 记作 <CODE>,例如 a1b2c3d4
⚠️ 这两个值就是整套系统的钥匙,生成后妥善保存,不要提交进任何仓库。
第 2 步:部署云端
SSH 登录你的服务器:
ssh root@<你的服务器IP>
2.1 安装 Node 22(已装可跳过;node -v 检查):
ARCH=$(uname -m); case "$ARCH" in x86_64) NARCH=x64;; aarch64) NARCH=arm64;; esac
curl -fsSL https://cdn.npmmirror.com/binaries/node/v22.14.0/node-v22.14.0-linux-$NARCH.tar.xz -o /tmp/node.tar.xz
tar -xJf /tmp/node.tar.xz -C /opt
ln -sfn /opt/node-v22.14.0-linux-$NARCH /opt/node
ln -sf /opt/node/bin/node /usr/local/bin/node
ln -sf /opt/node/bin/npm /usr/local/bin/npm
node -v # 应显示 v22.14.0
2.2 获取代码并安装依赖:
git clone https://github.com/SunNull/dsh-relay.git /opt/dsh-relay
cd /opt/dsh-relay/cloud
npm install --registry=https://registry.npmmirror.com
2.3 创建 systemd 常驻服务(把 <TOKEN>/<CODE> 换成第 1 步生成的值):
cat > /etc/systemd/system/dsh-relay.service <<EOF
[Unit]
Description=dsh-relay cloud
After=network-online.target
[Service]
Environment=DSH_RELAY_BIND=0.0.0.0
Environment=DSH_RELAY_PORT=8443
Environment=DSH_RELAY_TOKEN=<TOKEN>
Environment=DSH_RELAY_PAIRING_CODE=<CODE>
WorkingDirectory=/opt/dsh-relay/cloud
ExecStart=/usr/local/bin/node server.mjs
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now dsh-relay
systemctl status dsh-relay # 应为 active (running)
curl -s http://127.0.0.1:8443/healthz # 应返回 {"ok":true,...,"trunkReady":false}
trunkReady:false是正常的——家端还没连。
2.4 云防火墙/安全组放行 8443(TCP 入方向,源 0.0.0.0/0):
- 阿里云/腾讯云:控制台 → 实例 → 安全组/防火墙 → 添加规则 TCP 8443
- 服务器自身若开了 ufw:
ufw allow 8443/tcp
在家端电脑浏览器打开 http://<服务器IP>:8443/healthz 验证,应返回 JSON——通了。
第 3 步:安装家端插件
在家端电脑(dsh 所在机器),二选一:
方式 A:标准 bundle 安装(仓库声明 dsh.bundle,一条命令挂载):
dsh plugin --profile web add "github:SunNull/dsh-relay"
然后在 ~/.dsh/profiles/web/cordis.patch.yml 追加干线配置(bundle 层不携带凭据,用户层覆盖):
- id: dsh-relay-host
config:
relayUrl: ws://<你的服务器IP>:8443/trunk
token: <TOKEN>
方式 B:安装器一条龙(克隆仓库并自动写好配置):
git clone https://github.com/SunNull/dsh-relay.git
cd dsh-relay
node install.mjs --relay-url ws://<你的服务器IP>:8443/trunk --token <TOKEN>
安装器会:
- 把插件复制到
~/.dsh/profiles/web/plugins/dsh-relay-host.mjs - 在
~/.dsh/profiles/web/cordis.patch.yml追加托管配置块(dsh 的补丁层热生效)
两种方式都需要重启一次 dsh web(模块代码需要进程加载;之后仅改配置不再需要重启)。卸载:方式 A 用 dsh plugin --profile web remove dsh-relay-host,方式 B 用 node install.mjs --uninstall。
# 停掉现有 dsh web,重新启动,例如:
pnpm dsh web
验证干线已连——在服务器上执行:
curl -s http://127.0.0.1:8443/healthz
# "trunkReady":true 即成功;false 则检查插件配置与令牌是否一致
第 4 步:远端访问
手机(或任何设备)浏览器打开:
http://<你的服务器IP>:8443
- 首次出现配对页 → 输入
<CODE>→ 点"配对" - 自动进入完整 dsh 界面(与家中电脑一模一样:会话、模型、工作区全同步)
- 建议"添加到主屏幕",即得类原生 App 体验
多台设备重复第 4 步即可(默认上限 10 台,见环境变量)。
第 5 步(强烈推荐):远程可选工作区
dsh 默认在宿主桌面弹原生目录选择框,远程设备看不到。追加此补丁让所有人走网页内目录浏览:
编辑 ~/.dsh/profiles/web/cordis.patch.yml,在末尾追加:
- id: directory-picker
disabled: true
- insert:
- id: directory-picker-browse-host
name: '@deepseek-ai/dsh-host-directory-picker-browse'
- id: directory-picker-browse-ui
name: '@deepseek-ai/dsh-client-ui-directory-picker-browse'
保存即热生效(这是 dsh 官方为远程部署场景设计的标准换法)。之后"添加工作区"会打开网页目录浏览器,手机上可任意选择家中路径。
第 6 步(可选但强烈建议):HTTPS
明文 HTTP 有被运营商劫持/窃听风险。有一个域名即可上自动 HTTPS:
# 服务器上安装 Caddy(以官方 apt 源为例)
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install caddy
把 DNS A 记录指向服务器 IP,然后:
cp /opt/dsh-relay/cloud/Caddyfile.template /etc/caddy/Caddyfile
# 编辑文件,把 dsh.example.com 换成你的域名
nano /etc/caddy/Caddyfile
systemctl reload caddy
之后用 https://<你的域名> 访问;家端重新指向(热生效,无需重启 dsh):
cd dsh-relay
node install.mjs --relay-url wss://<你的域名>/trunk --token <TOKEN>
日常运维
| 操作 | 命令/位置 |
|---|---|
| 查看云端状态与审计 | 配对后访问 http://<服务器>:8443/__relay(JSON) |
| 健康检查 | curl http://<服务器>:8443/healthz(无需认证) |
| 云端日志 | journalctl -u dsh-relay -f |
| 踢掉所有已配对设备 | 服务器:systemctl stop dsh-relay && rm /opt/dsh-relay/cloud/relay-state.json && systemctl start dsh-relay |
| 更新云端代码 | cd /opt/dsh-relay && git pull && systemctl restart dsh-relay(各版本变化见 CHANGELOG) |
| 卸载家端插件 | 家端:node install.mjs --uninstall(再删掉仓库目录即完全清除) |
| 家端 dsh 重启后 | 无需任何操作——插件自动重连云端(断线指数退避重试) |
云端环境变量
| 变量 | 默认 | 说明 |
|---|---|---|
DSH_RELAY_PORT | 3081 | 监听端口 |
DSH_RELAY_BIND | 127.0.0.1 | 绑定地址;公网部署设 0.0.0.0 |
DSH_RELAY_TOKEN | dev-token | 家端干线令牌(与插件 config.token 一致) |
DSH_RELAY_PAIRING_CODE | 随机生成并打印 | 浏览器配对码;建议显式设置 |
DSH_RELAY_STATE | ./relay-state.json | 设备令牌 + 审计日志持久化文件 |
DSH_RELAY_MAX_TOKENS | 10 | 最多配对设备数 |
DSH_RELAY_MAX_WS | 16 | 并发浏览器 WebSocket 上限 |
DSH_RELAY_BLOCK_PRIVILEGED | 0 | 1 = 云端拦截设置/凭据等特权方法(更保守) |
DSH_RELAY_HEARTBEAT_MS | 20000 | 干线与浏览器下行连接的心跳周期(毫秒)。浏览器错过一个整周期的 pong 即判定为死连接断开(前端会自动重连并重放基线),心跳流量同时防止 NAT 空闲超时 |
验收测试(probe/)
部署完成后跑一遍,确保全链路健康:
# 1) 干线三要素(静态/RPC/WS 桥),在任意机器:
node probe/e2e-probe.mjs # 先编辑脚本顶部 BASE 为你的服务器地址
# 2) 浏览器全流程(配对→UI→WS),需 pip install playwright && playwright install chromium:
DSH_RELAY_BASE=http://<服务器>:8443 DSH_RELAY_PAIRING_CODE=<CODE> python probe/browser-acceptance-m1.py
安全须知(务必阅读)
- 配对码即钥匙:通过配对的设备 ≈ 坐在你家电脑前使用 dsh(能聊天、跑命令、看文件)。公网部署必须用强配对码 + HTTPS。
- 特权面提醒:远程默认可访问 dsh 的设置/凭据面。保守起见可设
DSH_RELAY_BLOCK_PRIVILEGED=1(远端将无法改设置/看凭据,聊天与文件功能不受影响)。 - 云端零业务落盘:
relay-state.json只存设备令牌与审计计数,不含任何会话内容。 - 令牌轮换:换
<TOKEN>需同步改云端 systemd 环境 + 家端重跑install.mjs --token。
故障排查
| 症状 | 原因与解法 |
|---|---|
| 手机打开显示"家端不在线" | 家端 dsh 没跑 / 插件没连上。服务器 curl localhost:8443/healthz 看 trunkReady;false 则查家端配置与网络 |
| 配对提示"设备数已达上限" | 之前测试占满 10 坑。按"踢掉所有设备"清理后重新配对 |
| 忘记配对码 | 服务器上执行 systemctl cat dsh-relay | grep PAIRING(显式设置过的);若启动时未设置,看日志 journalctl -u dsh-relay | grep "pairing code"(自动生成时打印过) |
| 更换配对码 | 编辑 /etc/systemd/system/dsh-relay.service 中的 DSH_RELAY_PAIRING_CODE,然后 systemctl daemon-reload && systemctl restart dsh-relay。注意:已配对的旧设备不受影响(持有长期令牌);要踢掉旧设备需按上文"踢掉所有已配对设备"清理 relay-state.json |
| 首次打开很慢(10-20 秒) | 正常:云端冷缓存逐个拉取静态资源,第二遍起快 |
| 远程"添加工作区"无反应 | 没做第 5 步补丁(原生弹窗弹在了家里屏幕上) |
| 界面空白/一直转圈 | 先强刷新/清缓存;再跑 probe 脚本定位是静态、RPC 还是 WS 环节 |
已知限制
- HTTP 响应整包缓冲(SSE 下载流除外),超大导出需等待完整传输
- dsh web 协议无版本协商(client/host 同船发布)——前端由家端上报天然同版本;dsh 大版本升级后建议全链路回归跑一遍 probe
- 家端插件模块更新需重启 dsh web(补丁配置热生效,模块代码不热替换)
适用版本
基于 DeepSeek Harness 0.1.0-rc.5(2026-08)验证。dsh 处于 developer preview,wire 变化时以 probe 验收为准。