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 所在机器),两种安装方式。要管理面板 UI 请用方式 A;方式 B 功能等效但侧栏不出现面板入口(见其说明)。
方式 A:标准 bundle 安装(仓库声明 dsh.bundle,一条命令挂载;管理面板 UI 依赖此方式的 dsh.client 发现机制):
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>
安装器会:
- 把插件的三个文件(
index/admin/client)平铺复制到~/.dsh/profiles/web/plugins/(index.mjs以相对路径导入./admin.mjs,三者必须同目录) - 在
~/.dsh/profiles/web/cordis.patch.yml追加托管配置块(dsh 的补丁层热生效)
方式 B 说明:中继与管理路由完整可用,但 dsh web 侧栏不会出现「中继管理」入口——client 面板要走 bundle 的
dsh.client发现机制(方式 A)。需要面板 UI 请改装方式 A。
两种方式都需要重启一次 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>
管理面板
dsh web 侧栏的 ⚙ 中继管理 入口打开内置管理面板——本机直连和经中继配对的远程浏览器都能用,也就是说躺在沙发上用手机就能管。
四个区域:
- 状态卡:干线在线状态、桥接下行数、缓存条数、云端运行时长
- 设备表:每台已配对设备的备注/配对时间/最后活跃,逐台剔除(立即吊销其 Cookie 并断开活动连接),或一键"全部剔除"
- 配对码表:多枚带备注的配对码——新增、点击显示码值、启用/停用、换值、删除
- 审计:云端操作尾部记录(配对成败、剔除、码管理等)
安全模型一句话:管理权跟随 dsh web 的可达性——谁能打开你的 dsh web,谁就能管理中继;手机丢了,从家里电脑或任何其他已配对设备上把它剔除即可。
升级要求:面板功能需要云端与家端插件同时更新到 0.2.0+(家端重跑
dsh plugin --profile web add "github:SunNull/dsh-relay"或node install.mjs,随后重启 dsh web)。
日常运维
| 操作 | 命令/位置 |
|---|---|
| 查看云端状态与审计 | 配对后访问 http://<服务器>:8443/__relay(JSON) |
| 健康检查 | curl http://<服务器>:8443/healthz(无需认证) |
| 云端日志 | journalctl -u dsh-relay -f |
| 踢掉某台/所有已配对设备 | dsh web 侧栏「中继管理」面板直接剔除(无需 SSH;见上文管理面板) |
| 更新云端代码 | 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 坑。在「中继管理」面板"全部剔除"后重新配对 |
| 忘记配对码 | 在 dsh web「中继管理」面板中点击码值即显示;0.2.0 之前部署的实例可读云端 relay-state.json(或 systemd 单元/启动日志中的旧值) |
| 更换配对码 | 在面板中对目标码换值(或删除后新建)。注意:改 systemd 里的 DSH_RELAY_PAIRING_CODE 再重启不会轮换已有配对码——状态文件存在时环境变量只在首次播种配对码表,旧码继续有效;要作废某码请用面板的换值/停用/删除 |
| 首次打开很慢(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 验收为准。
No comments yet. Be the first to write one.