远程协作 · dsh-remote-access
把这台机器上 DSH Harness Desktop 的 Web GUI(同一批会话、同一个工作区)通过隧道暴露到公网, 在另一台机器上用浏览器打开一条带令牌的链接即可操作,不需要公网 IP、不需要端口转发、不需要改动路由器。
另一台机器的浏览器 ──https──▶ Cloudflare 边缘 ──隧道──▶ 本机 cloudflared ──▶ 127.0.0.1:<Desktop 端口>
(DSH Desktop 的 Host 进程)
默认通道是 Cloudflare 快速隧道(cloudflare-quick):免登录、免费、随机域名,每次重启域名都会变。
需要固定域名时用 cloudflare-named(要自己的 Cloudflare 账号 + tunnel token),也可以接 ssh / 自定义 command 通道。
网络互通的场景还可以完全不开隧道,只用 ssh -L(见 §3.3)。
⚠️ 分享链接等同于这台机器的控制权。 链接里带着本进程的 launch token,谁拿到谁就能操作这台机器上的 DSH(跑命令、改文件、读会话)。不要贴到群里、不要提交进 git。不用的时候点「停止」。
源码仓库:https://github.com/zhexuany/dsh-remote-access(问题反馈走 GitHub Issues)。
0. 快速开始(3 步)
插件装进 profile 后(见 §2),运行中的 Desktop 会热加载它,不用重启应用:
- 打开 设置 → 远程协作 → 点 启动;或者命令行:
./bin/dsh-remote-access start --home ~/.dsh - 等链接出现 → 点 复制 → 在另一台机器的浏览器里打开。 那里之后 30 天免令牌(cookie 与域名绑定,域名一换就得重新点新链接)。
- 不用时点 停止(旧域名立刻失效)。
不需要再在 Desktop 里找「允许在浏览器中打开」这类开关。DSH Desktop
0.2.1-rc.3已经把它删了 (Host 上不再有webServer.setBrowserAccess(),desktop-preferences.json里的browserAccess也没人读)。 现在普通浏览器能不能进来只由两件事决定:隧道域名在connection.trustedHosts里(插件在隧道就绪时 自动发布、停止时移除),以及链接里带着本进程的 launch token(GET /?token=…换成 30 天 cookie)。 这两件事插件都替你做了,所以「链接复制出去就能打开」就是这个版本的正常状态。
链接同时落在 <DSH_HOME>/remote-access/link.txt,./bin/dsh-remote-access link 也直接打印它
(同一份输出里还有 本机链接,给 ssh -L 用)。
1. 前置条件
- macOS,DSH Desktop 已安装(默认
/Applications/DeepSeek Harness.app)。 - DSH Desktop
0.2.1-rc.2及以上(0.2.1-rc.3实测)。不再需要任何 Desktop 侧开关:0.2.1-rc.3移除了 「允许在浏览器中打开」(内部名browserAccess)和webServer.setBrowserAccess(),跨机访问改由connection.trustedHosts(信任隧道域名)+ 链接里的令牌负责,插件已经把这两件事包了。 如果你是从旧插件升级上来的,面板/CLI 里可能还留着一行browser-access switch failed: …—— 那是旧 Host 进程缓存的历史错误,重启一次 Desktop 就没了(新版本不再记这个错)。 - 网络允许出站
443/TCP。如果本机网络屏蔽 UDP/QUIC,保持默认的protocol: http2; 能走 UDP 时也可以改成quic/auto。(只走ssh -L的话连这条也不需要。)
2. 安装
2.1 GitHub 插件安装(推荐)
先通过官方 Desktop 的 Manage dsh Command… 安装随应用分发的 dsh 命令,然后完全退出 Desktop:
dsh plugin --profile desktop add git+https://github.com/zhexuany/dsh-remote-access.git
插件需要 cloudflared。可以任选一种方式提供:
brew install cloudflared
# 或把二进制放到 <DSH_HOME>/remote-access/bin/cloudflared
重新打开 Desktop,在 设置 → 远程协作 里启动隧道。共享安装默认 autoStart: false,不会自动开公网隧道。
通过 GitHub 安装时,插件源码在 profile 的
node_modules里;不要再用下面的bin/install复制一份。
2.2 本地安装器(开发/离线)
cd /path/to/dsh-remote-access
./bin/install --dry-run # 先看它会做什么,不写任何东西
./bin/install # 装进 ~/.dsh/profiles/desktop(默认复制一份快照)
(bin/install 会自动用 DSH Desktop 自带的 Node 运行 bin/install.mjs,没有系统 node 也能用。
写 ~/.dsh 在 workspace 之外,第一次执行需要授权一次全盘访问。)
安装做了五件事:
| # | 动作 | 位置 |
|---|---|---|
| 1 | 把插件目录复制(或软链)进 profile | <profile>/node_modules/dsh-remote-access |
| 2 | dependencies["dsh-remote-access"] = file:<源目录>(默认 --copy)或 link:<源目录>(--link),并把插件名加入 dsh.profile.bundles |
<profile>/package.json(先备份成 package.json.bak-<时间戳>) |
| 3 | 放一份 cloudflared | <DSH_HOME>/remote-access/bin/cloudflared |
| 4 | (可选,--enable-browser-access)把 browserAccess 置 true——只有 0.2.1-rc.2 及更旧的 Host 会读它,0.2.1-rc.3 已无此开关 |
<DSH_HOME>/desktop-preferences.json |
| 5 | 写一段受管的「mux 心跳覆盖」进 profile 补丁(把 Remote-stream 的 2s ping 放宽到 15s,见 §5.1) | <profile>/cordis.patch.yml(先备份成 cordis.patch.yml.bak-<时间戳>) |
常用选项:
--home <dir> DSH_HOME(默认 ~/.dsh)
--profile <name> profile 名(默认 desktop)
--copy | --link 复制(默认,快照)或软链(改代码即生效,适合开发)
--enable-browser-access 顺手在 desktop-preferences.json 里置 browserAccess: true(0.2.1-rc.3 已无人读取,只为旧版本兼容)
--no-cloudflared 不放置 cloudflared(用 PATH 里的或 Homebrew 的)
--heartbeat-ms <ms> 写进 profile 补丁的 mux 心跳间隔(默认 15000;宿主默认 2000 对丢包的公网路径太紧)
--no-heartbeat 不写(或删掉)那段受管心跳覆盖
--uninstall 卸载(只删自己那一条依赖/一个软链,并删掉受管心跳覆盖)
--dry-run / --json
装完怎么生效(2026-10-04 实测):
- 运行中的 Desktop 会热加载新装好的 bundle:写进
dsh.profile.bundles后大约 2 秒,Host 就把插件装上了 (日志里直接出现state dir …/starting cloudflare-quick …),不用重启。 - 网页界面需要刷新一次才会出现 设置 → 远程协作 面板(面板来自客户端的
client.js,页面已经加载过就不会再拿新 bundle)。 - 隧道就绪时插件把域名推进
connection.trustedHosts(内存里的围栏,不改写 Desktop 配置),停止时移除; 这一步在任何版本的 Host 上都是热生效的。只有改端口(port)才会触发 「应用访问设置并重启 Host?」并中断正在运行的任务。 --copy(默认)装的是快照:改完源码要重跑./bin/install;想边改边生效就用./bin/install --link。 替换已经装载过的插件代码要重启一次 DSH Desktop——Host 不会重新 import 已经加载过的模块。
3. 使用
3.1 GUI:设置 → 远程协作
面板显示:状态灯(未启动/启动中/已就绪/失败)、本地地址、隧道方式、隧道地址、连接链接(复制 / 打开)、
启动 / 停止 / 重启、「详情」(状态目录、cloudflared 路径、trustedHosts、访问方式 / 探测结果 / Desktop 开关)、日志。
- 首次使用:点 启动 → 等 5–20 秒出现链接 → 复制 → 到另一台机器浏览器打开 → 该浏览器之后 30 天内免令牌。
- 面板另有一块 本机链接:配合
ssh -L用(见 §3.3),复制按钮一键拿走。 - 面板里有一段常驻说明:这个版本的 Desktop 没有「允许在浏览器中打开」开关,分享链接本身就够用。 它不会给你一个点了也没用的按钮。
- 每次拉状态时插件会顺手探一次本机端口(带
sec-fetch-site: same-origin的GET /):401= 健康 (Host 只认带令牌的请求)、200/303= 端口直接放行、403= 连隧道域名都被拒。出现403时面板顶部 会给出红色提示,让你先看trustedHosts、重启一次隧道再分享;探测结果在「详情」里(access)。
3.2 CLI
./bin/dsh-remote-access status # 状态、公网地址、分享链接、本机链接
./bin/dsh-remote-access link # 只打印链接(方便 pbcopy)
./bin/dsh-remote-access link | pbcopy
./bin/dsh-remote-access start|stop|restart
./bin/dsh-remote-access logs # 最近 200 行(含 cloudflared 自己的输出)
./bin/dsh-remote-access open # 用本机浏览器打开链接
./bin/dsh-remote-access browser-access # 兼容入口:说明这个版本的 Host 没有开关、链接已可直接打开
./bin/dsh-remote-access browser-access --enable # 同上;--enable/--disable 仍然接受,但不再改变任何东西
./bin/dsh-remote-access status --json # 完整状态 JSON
CLI 通过回环控制套接字(<DSH_HOME>/remote-access/control.json,0600,带进程内令牌)操作正在运行的 Host;
Host 不在时退回读取落盘的 state.json / link.txt。
browser-access 保留为兼容入口:0.2.1-rc.3 的 Host 没有开关可翻,所以它只打印「链接已可直接打开」的说明并返回 0
(以前它必须连着 Host,因为那时真的要改 Host 内存里的状态)。
3.3 不想暴露到公网:走 ssh -L
只要两台机器网络互通(同一局域网,或者你能 ssh 到本机),可以完全不开隧道:
# 在另一台机器上执行(8080 换成那台机器上任意空闲端口)
ssh -N -L 8080:127.0.0.1:61733 user@192.168.1.10
然后在那台机器的浏览器里打开面板给出的 本机链接(http://127.0.0.1:<Desktop 端口>/?token=…,
端口看面板「本地地址」)。它不经过 Cloudflare,流量只在 SSH 里。
为什么可行:转发到 Host 的请求是 Host: 127.0.0.1:8080,Origin 与它一致,回环地址本来就在信任围栏里
(connection.trustedHosts 的豁免),而 0.2.1-rc.3 没有别的门槛了——带令牌的链接直接就能打开。
注意 cookie 是按 host:port 绑定签名的:换了 -L 端口就要重新复制一次本机链接;-L 端口的 cookie
和隧道域名的 cookie 互不通用。
4. 安全模型
两层,缺一不可——隧道域名本身不是凭证:
- API 信任围栏
connection.trustedHosts:Host 头不在白名单 →403。Desktop 每次启动都会重写desktop-next.runtime.patch.json(里面只有回环 + 局域网 IP),所以插件在隧道就绪时把域名推进 内存里的 API 围栏和 Web 运行时围栏;停止时两处都移除;不改写 Desktop 的配置文件。 这一步同时保护 DSH 自带的 Session 文件预览器、/api/file和侧边栏的/sidebar/file、/sidebar/html路由。 - 令牌 → Cookie:
GET /?token=<进程令牌>由 Desktop 换成HttpOnly; SameSite=Strict的签名 cookie, cookie 名与签名绑定域名。所以隧道域名一变,旧 cookie 自动失效,必须重新打开新链接。
老版本还有第三层:Desktop 的「允许在浏览器中打开」开关,关闭时非 renderer 请求一律
403 Browser access is disabled。0.2.1-rc.3把这层删掉了(Host 上已经没有setBrowserAccess(), 这个 403 也不会再出现),所以插件不再去翻它,也不再把它当成一条错误记下来。
通过 DSH 文件树打开文件时,客户端使用 Session-scoped 资源地址和现有侧边栏预览器;不会把本机目录挂成公网静态目录,
也不会依赖远程浏览器的 file:// 访问。直接请求未认证的文件路由仍返回 401,不受隧道域名本身授权。
插件不提供任何绕过上面两层的开关:能让浏览器进来的只有「域名在围栏里」和「链接带令牌」, 前者由插件在隧道就绪时自动发布、停止时收回,后者由进程内的 launch token 决定。
另外,插件自己的接口(/remote-access/status|start|stop|restart|link|logs|browser-access)同样先过这两层:
没有 cookie → 401,Host 不信任 → 403。也就是说,只知道域名的人既看不到 GUI,也控制不了这条隧道。
5. 配置
改本仓库 cordis.patch.yml 里 - id: dsh-remote-access 那一行的 config:,然后重启 Desktop。
(也可以在 profile 的 cordis.patch.yml 里用同一 id 覆盖:patch 的 config 是整体替换,不做深合并。)
| 键 | 默认 | 说明 |
|---|---|---|
enabled |
true |
关掉后插件不注册任何路由 |
autoStart |
false |
共享安装默认不自动开公网隧道;设为 true 后 Host 启动即开。面板点一次「停止」会记住 desired: stopped |
provider |
cloudflare-quick |
cloudflare-quick / cloudflare-named / ssh / command |
protocol |
http2 |
http2 / quic / auto(本机必须 http2,UDP 被屏蔽) |
cloudflaredPath |
自动探测 | 指定二进制;顺序:本项 → <DSH_HOME>/remote-access/bin/cloudflared → 包内 vendor → Homebrew → PATH |
hostname |
— | cloudflare-named 用,例 dsh.example.com |
tunnelToken |
— | cloudflare-named 用(cloudflared tunnel run --token) |
publicUrl |
— | 手动指定公网地址(不自动探测时用) |
sshDestination / sshRemotePort |
— | ssh -R 反向前向通道 |
command / urlPattern |
— | 自定义通道:命令数组 + 从输出里提取公网地址的正则 |
urlPattern |
quick 隧道内置 | 覆盖默认的 URL 探测正则 |
extraTrustedHosts |
[] |
额外要塞进围栏的域名 |
maxRestarts |
5 |
隧道异常退出后的重启次数(退避 1s→2s→…→30s 封顶) |
startTimeoutMs |
60000 |
等待隧道就绪的上限 |
portWaitMs |
内建 | 等待 Desktop 端口开始监听的上限 |
logLines |
200 |
内存日志环形缓冲行数 |
keepAliveMs |
65000 |
把 Host 的 node:http keepAliveTimeout 抬到多少毫秒(0 = 不动)。见 §5.1 |
proxyKeepAliveMs |
keepAliveMs - 5000 |
传给 cloudflared 的 --proxy-keepalive-timeout;必须短于 origin keep-alive,避免代理池复用已被 Node 回收的连接 |
tunnelLogLevel |
info |
传给 cloudflared 的 --loglevel(debug 能看清边缘连接的重连细节) |
tunnelLogBytes |
1000000 |
日志超过这个字节数就滚成 tunnel.log.1 |
home |
自动探测 | 强制指定 DSH_HOME |
enabled / autoStart 只影响隧道本身;临时开关不走配置(它是运行中的一次性动作,故意不落盘)。
5.1 稳定性:为什么页面会显示「重新连接中」
浏览器不是轮询,而是靠一条 WebSocket(/api/remote.mux)跟 Host 通信。Host 每 2 秒 ping 一次,
连续两次没收到 pong 就 terminate() 掉这条 socket(MAX_MISSED_HEARTBEATS = 2),前端于是进入
重新连接中 并按 0.5s→10s 退避重连。任务都在 Host 上跑,断开只是界面闪一下,不会丢任务。
实测(2026-10-04,本机 → Cloudflare 快速隧道):socket 回得及时时能撑满 300s (149/149 ping 全部答复、间隔精确 2.00s、无 FIN/close);回环臂同样 300s 满勤。也就是说链路本身不总是坏的—— 真正的杀手是「pong 迟到」:浏览器标签页被节流、VPN 切换路由、边缘连接被回收,两次 tick 就够杀。
于是插件做了三件事降低概率(前两项要重启一次 DSH Desktop 才生效):
- 放宽心跳:安装器往 profile 补丁写一段受管的
typert-gateway覆盖 (websocketHeartbeatIntervalMs: 15000)。间隔 15s 后,要连丢两次包得跨 30s 才致命。 - 让代理池先过期:cloudflared 默认会把 origin 连接保留约 90s,而 Host origin keep-alive 是 65s;插件默认传
--proxy-keepalive-timeout 60s,确保 cloudflared 不会在 Node 回收连接后继续复用它。 - 抬高 origin 的 keep-alive:插件把
webServer.server.keepAliveTimeout抬到keepAliveMs(默认 65s),并把headersTimeout提到至少 70s。 - 日志落盘:cloudflared 输出与插件日志都追加进
tunnel.log(超限滚成.1),这样「边缘连接有没有在重连」才有证据可查,而不是只有一个 200 行的内存环。
6. 运行时文件
<DSH_HOME>/remote-access/
├── state.json # desired / state / url / pid / target / provider / attempts(stopped 时不自动启动)
├── link.txt # 纯文本分享链接(公网隧道那条)
├── control.json # 控制套接字端口 + 令牌(0600)
├── tunnel.log # 隧道输出 + 插件日志(0600;超过 1MB 滚成 tunnel.log.1)
└── bin/cloudflared # 安装时放的二进制
日志同时进内存环(面板 / logs 可见)和 tunnel.log(面板「详情」里会显示它的路径)。
7. 排查
| 现象 | 原因 / 处理 |
|---|---|
| 面板顶部红字「探测被 403 拒绝了」 | 隧道域名没在 connection.trustedHosts 里(围栏没发布或已被清掉)。点一次 重启,再看「详情」里的探测结果;健康时 access.state 是 token-required |
403 host not trusted |
隧道域名没进围栏。看面板「详情」里的 trustedHosts,或重启一次隧道(停止→启动) |
链接打开 401 dsh web authentication required |
用了旧的链接/书签。每次开隧道域名都会变,重新复制最新链接 |
| 刚看到域名,本机 curl 解析不了 | 新域名的 NXDOMAIN 被本机 DNS 缓存了(公网 DNS 正常,实测 1.1.1.1/8.8.8.8 → 104.16.230.132)。等 1 分钟或换 DNS;curl --resolve <域名>:443:104.16.230.132 可绕过验证 |
| 一直「启动中」 | logs 里若是 failed to dial to edge with quic: timeout,说明 UDP 被屏蔽 → 用 protocol: http2 |
页面反复显示 重新连接中 / connected reconnecting |
Host 2 秒一次心跳、连续两次没答复就杀 socket(见 §5.1)。先看 tunnel.log:Unable to reach the origin service … EOF = origin keep-alive 太短;边缘重连另有 Registered tunnel connection/Retrying。安装器写的心跳覆盖(15s)和 keepAliveMs 都要重启一次 Desktop 才生效。回得及时的客户端实测能撑满 300s,所以偶发是正常的 |
| 端口不对 | Desktop 的端口来自 desktop-preferences.json 的 port(0 = 随机);插件直接读 Host 正在监听的端口,通常不用管 |
| 想彻底关掉自动开隧道 | 默认就是关的;如果 profile 覆盖成了 true,面板点一次「停止」(记住 desired: stopped),或改回 false 后重启 Desktop |
| ssh -L 打开了却 401 | 本机链接是按 host:port 签发的。换 -L 端口后要用面板/CLI 重新复制一次本机链接 |
| 改了插件源码但界面没变化 | ./bin/install 重装:面板(client.js)会被 Host 的 HMR 在约 0.5 秒内推到已打开的页面(刷新即最新);Host 侧改动(新的 HTTP action、新的 webServer API)必须重启 DSH Desktop,因为 Host 不会重载已 import 的模块 |
面板/CLI 还显示 browser-access switch failed: … |
正在跑的 Host 还是旧版插件,那条错误是旧进程缓存下来的。重启一次 DSH Desktop;新版本不再记这个错,/remote-access/status 里也不再有 supportsBrowserSwitch |
| 设置里的「远程协作」整块空白 | 已修(2026-10-04):button() 曾在渲染中途调用 useState,按钮数量随数据变化 → React 报 Rendered more hooks than during the previous render. 并卸载整个 slot。现在 button() 无 hook,test/client-react.mjs 用真 React 守住这条不变量 |
8. 验证记录(2026-10-04 实测)
test/service-unit.mjs(Host 侧单测,无 Host 进程、无网络):本机链接按端口签发、无端口时为null; 访问探测把401/200/303/403/连不上分别归成token-required/open/open/refused/unknown并记下checkedAt;refused时只写一条 warn 日志、不把错误挂到status.error上(旧版本的lastError会一直显示);browser-access这个旧入口返回unsupported: true+ 原因,且一次都不碰webServer.setBrowserAccess();link/logs/ 未知 action 的行为;keepAliveMs真的写到webServer.server.keepAliveTimeout/headersTimeout(0 = 不动、没有 server 句柄时报原因)、隧道日志文件同时收到 cloudflared 原始行与插件日志。PASS。test/installer-unit.mjs:临时DSH_HOME+ 假 profile,跑真安装器,断言 bundle 进了node_modules与dsh.profile.bundles、 清单先备份、受管心跳块写进cordis.patch.yml且手写的preference: zh不被破坏、重复安装字节不变(mux heartbeat already 15000ms)、--heartbeat-ms 5000会替换、--no-heartbeat只删自己那段、--dry-run不写任何东西、--uninstall只回收自己的痕迹。PASS。test/client-smoke.mjs:无头加载client.js,装假__ModuleLoader__/DOM,验证「不 import Harness Client 包、不用 iframe、只 requirereact、 注册settings.section/ id 与包名一致、能渲染、请求打到<baseURI>/remote-access/status、链接进复制框、本机链接进复制框、 详情可展开」,并且面板里没有任何会 POSTbrowser-access的控件、常驻一段「这版没有开关」的说明;access.state变成refused时出现trustedHosts提示、回到token-required后提示自己消失。离线与走真隧道两种模式都 PASS。 (test/run-tests.sh一次跑完三个套件。)test/client-react.mjs:用真的react+react-dom+jsdom渲染面板(不是自制 mini-React),断言 React 没有报 hook 顺序错误、 没有console.error、首帧在数据到达前就能渲染、三种状态(ready/refused/unknown)都渲染正确、链接进<input value>。 依赖装在仓库外(../.scratch/uitest,pnpm add react@18.3.1 react-dom@18.3.1 jsdom);找不到依赖时套件打印 skipped(退出码 2),不阻塞前两个套件。 这个套件就是「远程协作空白」那个 bug 的复现与防回归网。三种模式都 PASS。- 端到端(真隧道):无 cookie
401;伪造 Host403;GET /?token=…→303+Set-Cookie(HttpOnly; SameSite=Strict,30 天); 带 cookieGET /→200、35000 字节、<title>DeepSeek Harness</title>;/remote-access/status→200;跨站Origin→403;未知路由 →404。 - 生命周期:
stop→ cloudflared 退出、旧域名530、围栏清空、state: idle;start→ 新域名 + 新链接可用。 - 装进真实 profile(
~/.dsh/profiles/desktop):安装脚本成功(备份package.json.bak-20261004-165242、cloudflared 落在~/.dsh/remote-access/bin/), 随后运行中的 Desktop 直接热加载了插件(约 2 秒后日志出现state dir ~/.dsh/remote-access+tunnel URL: https://…),./bin/dsh-remote-access status --home ~/.dsh报state: ready、target: http://127.0.0.1:52075、access: token-link / token-required / gate absent。此时无令牌请求是401、带?token=…的链接直接200:0.2.1-rc.3里只剩trustedHosts+ 令牌两层,403 Browser access is disabled那一层已经不存在了。 - 复现:
# 用插件自己的 Host 跑一个不影响桌面的实例
DSH_HOME=<临时目录> ELECTRON_RUN_AS_NODE=1 "/Applications/DeepSeek Harness.app/Contents/MacOS/DeepSeek Harness" \
--expose-internals "/Applications/DeepSeek Harness.app/Contents/Resources/app.asar/dsh/node_modules/@deepseek-ai/dsh/lib/bin.js" \
--profile web --no-open --port 18790
./test/run-tests.sh # 全部离线测试
./test/client-smoke.mjs --live https://<隧道域名> --cookie "dsh-auth-…=…"
9. 已知限制
- 目标只能是本机回环上的 DSH Host(不做「转发到另一台机器」)。
cloudflare-quick已实测;ssh/command通道代码路径在,但还没有在真机上验证。- 快速隧道域名每次重启都变(Cloudflare 免费行为的限制);固定域名请用
cloudflare-named。 - 单实例:面板与 CLI 操作的都是同一个隧道(同一个 Host 进程)。
- 访问模型跟着 Host 走:
0.2.1-rc.3没有 Desktop 侧开关可翻,跨机访问完全依赖trustedHosts+ 链接里的令牌; 插件不提供(也不再假装提供)第三层开关。 - 客户端面板用的是自带的朴素 React 组件(不依赖 Harness 内部包),样式跟随
--dsw-alias-*主题变量, 深色/浅色都能用,但视觉细节不会跟 Desktop 自带设置页 100% 一致。 - 「重新连接中」只能被降低概率,不能被消灭:公网隧道本身会抖,插件做的是放宽心跳 + 抬高 origin keep-alive, 让一次迟到不再等于断线(见 §5.1)。
English summary
dsh-remote-access is a DSH bundle + web client plugin that publishes this Desktop's Harness Web GUI
(same sessions) to the internet through a tunnel (Cloudflare quick tunnel by default) and hands you a
tokenised link to open from another machine's browser — or, if the two machines can reach each other,
over plain ssh -N -L 8080:127.0.0.1:<port> <you>@<this-mac> with the panel's loopback link and no
tunnel at all. There is no Desktop-side gate to open any more: DSH Desktop 0.2.1-rc.3 removed the
"allow opening in a browser" switch and webServer.setBrowserAccess(), so an ordinary browser gets in
through exactly two things — the tunnel hostname published into connection.trustedHosts while the tunnel
is up, and the launch token carried by the link (GET /?token=… → 30-day cookie). Use
Settings → Remote access, or ./bin/dsh-remote-access start|link|stop --home ~/.dsh.
The link is full control of this machine — treat it as a secret.
Stability: the browser talks to the Host over one WebSocket that is pinged every 2s and killed after
two unanswered pings, so a lossy WAN path shows up as Reconnecting. The installer therefore writes a
managed typert-gateway override (websocketHeartbeatIntervalMs: 15000) into the profile patch
(--no-heartbeat to opt out), the plugin raises the origin keepAliveTimeout (60s+ to stop cloudflared's
origin EOF errors), and every tunnel line is appended to <DSH_HOME>/remote-access/tunnel.log with
rotation. The first two need one DSH Desktop restart.
No comments yet. Be the first to write one.