远程隧道管家 Linjiangxian0203/dsh-remote-tunnel
DeepSeek Harnessv0.2.1dsh 远程主机隧道管理器:远程端口分配 + 注册表 + 抗断 SSH 隧道
36.8/100
综合分
6
GitHub 星标
289
npm 周下载
—/5
用户评分
数据截至 2026/10/18 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-remote-tunnelnpm 源 · dsh-remote-tunnel 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · market
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 17 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-remote-tunnel @ 0.2.1
✓Node 引擎要求 >=22.19 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/31 07:32:07
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 28 处,展示前 28 条)
- 发起网络请求src/client.js:48
if (typeof fetch === "function") fetch(query, { headers: { accept: "application/json" } }).catch(function () {}); - 发起网络请求src/client.js:57
var response = await fetch(ROUTE + action, { headers: { accept: "application/json" } }); - 执行外部命令来自配置/变量src/manager.js:888
const child = spawn(launcher[0], launcher[1], { stdio: "ignore", windowsHide: true }); - 读取本地文件src/web.js:9
return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version ?? null; - 写入本地文件src/config.js:76
mkdirSync(home, { recursive: true }); - 读取本地文件src/config.js:77
if (!existsSync(path)) { - 写入本地文件src/config.js:78
writeFileSync(path, "# dsh-remote-tunnel config — see README.md for every option.\n" + yaml.dump(structuredClone(DEFAULT_CONFIG), { lineWidth: 100 }), - 读取本地文件src/config.js:82
user = yaml.load(readFileSync(path, "utf8")) ?? {}; - 写入本地文件src/config.js:176
mkdirSync(dirname(path), { recursive: true }); - 写入本地文件src/config.js:177
writeFileSync(path, "# dsh-remote-tunnel config — see README.md for every option.\n" + yaml.dump(config, { lineWidth: 100 }), "utf8"); - 读取本地文件src/ssh-config.js:159
if (!existsSync(path)) return { hosts: [], hashed: 0, revoked: 0, path, exists: false }; - 读取本地文件src/ssh-config.js:160
return { ...parseKnownHosts(readFileSync(path, "utf8")), path, exists: true }; - 读取本地文件src/ssh-config.js:166
if (!existsSync(path)) return { aliases: [], wildcard: {}, includes: [], path }; - 读取本地文件src/ssh-config.js:167
return { ...parseSshConfig(readFileSync(path, "utf8")), path }; - 执行外部命令来自配置/变量src/ssh.js:40
const child = spawn(sshCmd[0], [...sshCmd.slice(1), ...commonSshArgs(cfg, { clearForwardings }), ...sshTargetArgs(hostDef), ...args], { - 读取本地文件src/remote/bootstrap.js:19
return readFileSync(BOOTSTRAP_SCRIPT_URL, "utf8"); - 发起网络请求src/local/ports.js:59
const request = http.request( - 执行外部命令来自配置/变量src/local/ports.js:79
execFile(file, args, { timeout: timeoutMs, windowsHide: true }, (error, stdout) => { - 读取本地文件src/local/state.js:17
if (!existsSync(path)) return undefined; - 读取本地文件src/local/state.js:19
return JSON.parse(readFileSync(path, "utf8")); - 写入本地文件src/local/state.js:26
mkdirSync(stateDir(home), { recursive: true }); - 写入本地文件src/local/state.js:27
writeFileSync(stateFile(home, alias), JSON.stringify({ ...data, updatedAt: new Date().toISOString() }, null, 2), "utf8"); - 删除本地文件src/local/state.js:31
rmSync(stateFile(home, alias), { force: true }); - 写入本地文件src/local/state.js:35
mkdirSync(stateDir(home), { recursive: true }); - 读取本地文件src/local/state.js:37
for (const name of readdirSync(stateDir(home))) { - 读取本地文件src/local/state.js:40
out.push(JSON.parse(readFileSync(join(stateDir(home), name), "utf8"))); - 写入本地文件src/local/tunnel.js:30
mkdirSync(dirname(this.logPath), { recursive: true }); - 写入本地文件src/local/tunnel.js:31
appendFileSync(this.logPath, `${new Date().toISOString()} ${line}\n`, "utf8");
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-cmdline@deepseek-ai/schemastery给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 远程端口分配
- 注册表管理
- 抗断SSH隧道
最近更新
在 GitHub 查看全部 →v0.2.12026-10-03
中文 | English
中文
体验优化
· 新增右侧栏「远程连接」面板(指南页图标为显示器):新建空会话也能打开远端 dsh web —— 补上 0.2.0 遗留的唯一缺口(命令卡片要有对话历史,状态条在 hero 布局里不渲染)
· 面板标题旁显示当前插件版本(桌面端「插件」页对任何插件都不显示版本号)
· 面板内管理隧道:启动 / 断开(两步确认)/ 在侧栏打开 / 在浏览器打开 / 刷新,并且跟随所选主机
· 自动发现 ~/.ssh/known_hosts 里连接过的主机,一键「添加」写进插件配置;~/.ssh 全程只读,被哈希的条目只报数量
· 面板里可「+ 手动添加主机」原地填表(别名/主机/端口/用户/workspace)建主机;一台主机都没有时给出三步引导(添加 → 启动隧道 → 在侧栏打开),不用命令行也能上手
其他变更
· 面板内可增删插件配置里的主机(两步确认);~/.ssh/config 条目只读,但可以「隐藏」;发现的主机可以「忽略」,「已隐藏」区一键恢复(只写插件配置,不动 ~/.ssh)
· 命令行 hosts add / hosts rm 与面板走同一套校验;hosts 列表也会列出发现的主机
· 路由失败按语义返回:400(参数)/ 409(已存在)/ 404(未知主机)
---
English
Improvements
· New right sidebar "remote connections" panel (a monitor glyph in the guide): open the remote dsh web even in a brand-new session — the last gap left by 0.2.0 (the command card needs conversation history, and the status strip is not rendered in the hero layout)
· The pane shows the live plugin version beside its title (the desktop app's Plugins page renders no version for any plugin)
· Manage the tunnel from the pane: start / disconnect (two-step) / open in sidebar / open in browser / refresh, following the selected host
· Hosts from ~/.ssh/known_hosts are discovered automatically and can be added with one click; ~/.ssh stays read-only and hashed entries are reported as a count
· + 手动添加主机 opens an inline form (alias / host / port / user / workspace), and a pane with no host at all shows a three-step guide — no CLI needed to get started
Chores
· Add or remove the plugin's own host definitions from the pane (two-step); ~/.ssh/config entries stay read-only but can be hidden, and a discovered candidate can be ignored — the 已隐藏 section restores either (the plugin's own config only, never ~/.ssh)
· CLI hosts add / hosts rm share the panel's validation, and hosts also lists discovered hosts
· Route failures answer with their meaning: 400 (input) / 409 (exists) / 404 (unknown host)
v0.2.02026-10-02
中文 | English
中文
新增
· 桌面端面板:远程 dsh web 可直接开在 DSH 桌面应用右侧栏的「浏览器」标签里(ctx.sidebarRight.openTab("browser", …));浏览器半是手写的 bundle(src/client.js,window.__ModuleLoader__.load 格式),不需要构建步骤 —— npm、GitHub、本地目录三种安装方式拿到的是同一个文件
· 两种打开方式:「在侧栏打开」与「在浏览器打开」(宿主直接调用与 CLI 相同的打开器);openIn(ask | browser | panel)决定默认值
· 命令卡片:每条 /remote … 命令都会渲染一张卡片,包含命令输出、隧道状态与操作按钮 —— 在侧栏打开 / 在浏览器打开 / 启动隧道(up)/ 断开连接(down,两步确认)/ 刷新。客户端只对第一方命令做本地化,因此命令行与卡片都提供中英文案
· 输入框常驻状态条(dock,默认开启):一行显示隧道状态与同样的操作,不必翻历史找卡片
· 宿主路由 GET /remote-tunnel/{status,open,up,down}:每个请求都过 ctx.connection.admit()(与 /api 通道同一套 Host/Origin 栅栏 + 浏览器会话 cookie 校验),未通过返回 401/403 —— 因为这些路由会下发携带一次性 launch token 的 URL;确实无法携带 cookie 的载体可用 auth: false 显式关闭
· 本地化:locale/en.json + locale/zh.json(meta.title / meta.description),插件卡片跟随界面语言(中文界面显示「远程隧道 (dsh-remote-tunnel)」)
· row 配置(home / openIn / autoOpen / dock / auth):可在「设置 → 插件」编辑,默认值在代码里兜底
问题修复
· 模式判定不再依赖邻近行的 webStartup 服务,改用 profileContext.name(web/desktop):修掉一个启动竞态 —— 判错会让插件走 CLI 分支,program.help() → appExit(0) 把整个桌面端 / dsh web 宿主进程退掉
· Config schema 不能用 .default(...):在 dsh 0.2.0-rc.2 上会让合并后的 row 配置变成未解析的 schema 对象({}),配置值静默丢失;现在只写纯类型,默认值放在 readSettings() 里
· peer 范围放宽为 ^0.1.2-alpha.3 || ^0.1.7-rc.1 || ^0.2.0-rc.2,一个包同时服务两条运行时线
其他变更
· service 模式只创建一个 TunnelManager,由 /remote 命令与 HTTP 路由共享
已知行为(外壳规则,不是故障)
· 纯新会话里看不到命令卡片:会话视图在没有模型历史时显示欢迎页,命令记录不属于模型历史;发出第一轮对话后,之前的卡片会一起出现(命令早已执行成功)
· 状态条只在"会话已有内容"时渲染:它位于输入框的 dock 座位,而全新会话用的是居中(hero)布局,该座位不渲染 —— 0.2.1 会增加侧栏「远程主机」面板,任何会话状态都能点到
---
English
Added
· Desktop panel: the remote dsh web opens inside the DSH desktop app's right sidebar Browser tab (ctx.sidebarRight.openTab("browser", …)). The browser half is a hand-written bundle (src/client.js in the window.__ModuleLoader__.load format) that needs no build step — npm, GitHub and local-directory installs ship the same file
· Two ways to open: "Open in sidebar" and "Open in browser" (the host runs the same opener the CLI uses); openIn (ask | browser | panel) chooses the default
· Command card: every /remote …
v0.1.122026-09-14
中文 | English
中文
问题修复
· 修正 0.1.11 引入的本地隧道校验判据:当时要求一次性 token URL 返回 200,但 dsh 对有效 token 的应答是 303(种下浏览器 cookie 并重定向到干净 URL),已在同一会话中持有 cookie 时才是 200。要求"恰好 200"导致每一个有效本地端口都被判为"不是我们的 dsh":up 依次试完 5 个候选端口后报错 local port N did not serve our dsh (HTTP 303)。现在 2xx/3xx 都算"隧道通到我们自己的 dsh",只有 401/403(别的实例拒绝了我们的 token)才判定该候选端口不合格
· 已在真实服务器 + dsh 0.1.5-rc.2 上验证:token URL 经隧道返回 303,浏览器跟随重定向后拿到完整 UI(HTTP 200,约 27 KB)
---
English
Bug Fixes
· Corrects the local-tunnel criterion added in 0.1.11: it required HTTP 200 from the one-time token URL, but dsh answers a valid token with 303 (minting the browser cookie and redirecting to the clean URL) and only returns 200 when the cookie is already present. Demanding exactly 200 rejected every valid local port — up tried all five candidates and failed with local port N did not serve our dsh (HTTP 303). Any 2xx/3xx now means "this tunnel reaches our own dsh"; only 401/403 (another instance rejecting our token) disqualifies a candidate
· Verified on a real server running dsh 0.1.5-rc.2: the token URL answers 303 through the tunnel, and following the redirect lands on the full UI (HTTP 200, ~27 KB)
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成dsh-remote-tunnel
Awesome DSH Plugin
Remote Host Tunnel Manager:把「本地浏览器 → 远程 Linux 服务器上的 dsh web」这条链路自动化——远程端口分配与登记、systemd 守护、SSH 隧道保活、本地 URL 输出、全生命周期管理,并面向多人共用同一台服务器的场景。
- 会话与文件都在服务器上(远程 dsh web 的工作区 = 服务器目录),本地只开一条隧道
- 每个使用者自动分到独立的远程端口,分配前在服务器上双重检查(真实占用 + 登记表),并发也安全
- 每次分配都在服务器上的登记表留档(/etc/dsh-ports.tsv 或按权限自动降级),可随时 audit 对照审查
- 隧道断线自动重连(进程退出后按退避重新拉起),心跳定期刷新登记表
- 本地端口被占自动顺延,并报告占用者进程
如果你只是使用者(不开发)
1. 安装(npm 发布版)
dsh plugin --profile remote add dsh-remote-tunnel
想在 web UI 里也看到它(「设置 → 插件」)并用 /remote 斜杠命令?
展开完整文档(README 剩余 1 节)
dsh-remote-tunnel Awesome DSH Plugin Remote Host Tunnel Manager:把「本地浏览器 → 远程 Linux 服务器上的 dsh web」这条链路自动化——远程端口分配与登记、systemd 守护、SSH 隧道保活、本地 URL 输出、全生命周期管理,并面向多人共用同一台服务器的场景。 - 会话与文件都在服务器上(远程 dsh web 的工作区 = 服务器目录),本地只开一条隧道 - 每个使用者自动分到独立的远程端口,分配前在服务器上双重检查(真实占用 + 登记表),并发也安全 - 每次分配都在服务器上的登记表留档(/etc/dsh-ports.tsv 或按权限自动降级),可随时 audit 对照审查 - 隧道断线自动重连(进程退出后按退避重新拉起),心跳定期刷新登记表 - 本地端口被占自动顺延,并报告占用者进程 如果你只是使用者(不开发) 1. 安装(npm 发布版) dsh plugin --profile remote add dsh-remote-tunnel 想在 web UI 里也看到它(「设置 → 插件」)并用 /remote 斜杠命令? 再装进 web profile,并重启 dsh web: dsh plugin --profile web add dsh-remote-tunnel ⚠️ 多人共用同一台服务器?先做一次性注册(需要 root,见下面「多用户共享服务器」一节): 不注册也能用,但会退化成每人一份私有登记表 —— audit 只看得到自己占的端口 2. 确认你的服务器能被识别(~/.ssh/config 里的 Host 别名自动发现) dsh --profile remote hosts 没有?手动定义一台: dsh --profile remote hosts add lab --host 192.0.2.10 --user alice --workspace /home/alice/project 3. 第一次先体检,缺什么它逐项告诉你(密钥/Node/dsh/登记表/systemd) dsh --profile remote check lab 3.5 体检说 Node/dsh 缺失?按你登录的账号一键补齐(幂等;师兄师姐各自跑一次) dsh --profile remote bootstrap lab --upgrade 则强制把远程 dsh 更新到最新版 4. 起隧道,浏览器自动打开服务器上的 dsh web dsh --profile remote up lab --open 日常:status 看状态 / down 收尾 / logs 看远端日志 / audit 审查端口登记 dsh --profile remote down lab 远程服务器需要:Node ≥ 22.19、dsh、systemd、免密钥 ssh 登录。每个账号(包括实验室里其他师兄师姐各自的账号)在自己电脑上跑一次初始化即可,幂等: dsh --profile remote bootstrap lab(同一脚本亦可手动:ssh <host> 'sh -s' < scripts/bootstrap-remote.sh)。其余参数在 $DSH_HOME/remote-tunnel/config.yaml,不改就能用。 remote CLI profile 是插件的主界面。装进 web profile 才会让插件出现在「设置 → 插件」里、并启用聊天中的 /remote 斜杠命令——装完记得重启一次 dsh web。 要求 - 本地:Windows/macOS/Linux,自带 OpenSSH 客户端(Windows 10+ 已内置),Node ≥ 22.19 - 远程:Linux,Node ≥ 22.19 + dsh(用 dsh --profile remote bootstrap <host> 或 scripts/bootstrap-remote.sh 安装,每个账号各自装一次),systemd(用户级即可,无需 root) - 推荐:远程已配置 SSH 免密钥登录(ssh <别名> 直接能进,不弹密码) 安装 1. 安装到专用 CLI profile(推荐;首次会自动初始化 remote profile) cd <插件源码目录> # 或 npm 包名 dsh-remote-tunnel dsh plugin --profile remote add . 2. 装到 web profile,让插件在 web UI 的「设置 → 插件」里可见、并启用聊天里的 /remote 斜杠命令;装完重启 dsh web dsh plugin --profile web add . 快速上手 看看有哪些主机(~/.ssh/config 里的 Host 别名会被自动发现) dsh --profile remote hosts 也可以手动定义一台主机(不含 ~/.ssh/config 时) dsh --profile remote hosts add lab --host 192.0.2.10 --user alice --workspace /home/alice/project 就绪诊断:密钥/Node/dsh/登记表/systemd 逐项检查 dsh --profile remote check lab 一键:分配远程端口 → 登记 → 写 systemd 单元并启动远程 dsh web → 本地起隧道 dsh --profile remote up lab --open 输出示例: allocated remote port 3081 (range 3080-3119, registered for alice) ✓ tunnel up — http://127.0.0.1:3083 (remote lab:3081) stop: dsh --profile remote down lab (or Ctrl+C) 查询/停止/审查 dsh --profile remote status lab dsh --profile remote logs lab # 远程 dsh web 日志(journalctl) dsh --profile remote audit lab # 登记表 vs 真实占用 dsh --profile remote down lab # 停隧道 + 登记表 released + 停服务 + 核实端口已释放 打开本地 URL 后,登录到的是服务器上的 dsh web:能对话、能读写服务器文件。API key 在远程 web 的「设置 → 模型」里配置(写入服务器 ~/.dsh/.credentials.yaml,本插件与隧道不触碰凭据)。 桌面端面板(DSH 桌面应用) 装进桌面 profile 后,远程的 dsh web 可以直接开在右侧栏「浏览器」面板里,不必跳系统浏览器。插件在桌面端提供四个入口: | 入口 | 位置 | 用途 | | 远程连接面板 | 右侧栏 → 指南页 → 远程连接 | 主机列表、隧道状态与全部动作 —— 新建空会话里也能用的入口 | | 命令卡片 | 对话里发 /remote … | 命令结果 + 隧道状态 + 「在浏览器打开」「在侧栏打开」「启动隧道 / up」「断开连接 / down」 | | 状态条 | 输入框上方 | 常驻显示隧道状态与一键操作(可用 dock: false 整条隐藏) | | 插件页 | 左侧「插件」 | 开关插件;标题与描述跟随界面语言(中文界面显示「远程隧道 (dsh-remote-tunnel)」) | 侧栏「远程连接」面板 侧栏的标签条与指南页在任何会话状态下都会渲染,所以这个面板是唯一不受对话历史影响的入口 ——命令卡片要有对话内容才出现,状态条在新建会话的 hero 布局里不渲染。 1. 先展开右侧栏(收起点会话头部右上角的按钮); 2. 指南页里点「远程连接」胶囊(图标是显示器;或标签条上的 +); 3. 面板里管理所选主机与它的隧道: | 面板内容 | 作用 | | 启动隧道 / up · 断开连接 / down | 起隧道;断开为两步确认(停隧道 + 停远端单元 + 释放端口登记) | | 在侧栏打开 · 在浏览器打开 | 把远端 dsh web 开进侧栏「浏览器」标签,或用系统浏览器打开 | | 刷新 / refresh | 重新读取 /remote-tunnel/status(面板本身每 15 秒也会自动刷新) | | vX.Y.Z(标题旁) | 宿主半上报的当前版本 —— 桌面端「插件」页对任何插件都不显示版本号 | | 已配置主机 / managed hosts | ~/.ssh/config 条目(只读、已标注、可隐藏)与插件自己 config.yaml 里的条目(可两步删除)。「+ 手动添加主机」原地展开表单(别名 / 主机 / 端口 / 用户 / workspace)—— 不用命令行也能建主机 | | 第一台主机从哪来 | 一台都没有时,面板给出三步引导:① 添加(填表,或点「发现的主机」里的添加)→ ② 启动隧道 / up → ③ 在侧栏打开;并写明唯一的前置条件:dsh --profile remote bootstrap <别名> | | 发现的主机 / discovered in ~/.ssh | ~/.ssh/known_hosts 里的明文条目 —— 本机连接过的主机。点「添加」写进插件配置,点「忽略」把它挪开,该区右上角「刷新」可随时重扫(另有 15 秒自动轮询);~/.ssh 本身绝不会被写入 | | 已隐藏 / hidden in this pane | 你隐藏过的东西,每条一个「恢复」—— 隐藏只是显示偏好,永远不是删除 | 被哈希的 known_hosts 条目(很多系统默认 HashKnownHosts yes)无法反解成主机名,面板只报告跳过条数,不做猜测。 非默认端口的候选在 known_hosts 里写作 host:port,那是显示文本而不是合法别名 —— 点「添加」时存下的是安全别名(如 host-port)加真实端口。 「隐藏」只写插件自己的 config.yaml(hiddenHosts),不动别的:命令行 hosts 依旧列出全部主机, 所以面板里隐藏过的条目不会丢。 面板属于客户端半。改动 src/client.js 会改变 bundle 的 revision,而运行中的宿主拒绝提供过期 revision ——更新插件后请重启桌面应用。 安装到桌面端 推荐走 GUI(和其它插件一样): 「插件」页 →「添加插件」→ 输入 dsh-remote-tunnel → 安装 → 重启桌面应用 命令行等价写法: dsh plugin --profile desktop add dsh-remote-tunnel 桌面应用只在自己启动时读取 profile,所以装完必须重启应用(窗口关掉后还要退出托盘进程)。 两种打开方式 - 在侧栏打开(推荐):远程 dsh web 显示在右侧栏「浏览器」面板里,鉴权(?token= → cookie)由插件自动完成,不会再出现 dsh web authentication required; - 在浏览器打开:用系统默认浏览器打开同一条本地 URL,适合希望页面独占窗口、或需要浏览器扩展的场景。 openIn 可设置默认偏好(ask / browser / panel)。 桌面端配置(设置 → 插件) | 字段 | 默认 | 说明 | | home | $DSH_HOME/remote-tunnel | 隧道状态、日志与 config.yaml 所在目录 | | openIn | ask | 打开方式偏好 | | autoOpen | false | 启动桌面应用时自动把远程 dsh web 开进侧栏 | | dock | true | 是否显示输入框上方的常驻状态条 | 也可以直接改 profile 补丁层($DSH_HOME/profiles/desktop/cordis.patch.yml)里 remote-tunnel 那一行的 config,效果相同。 已知的外壳行为(不是故障) - 纯新会话里看不到命令卡片:会话视图在没有对话内容时显示欢迎页,而命令记录不属于模型历史;发出第一轮对话后,之前所有 /remote 卡片会一起出现(命令其实早已执行成功); - 状态条只在"会话已有内容"时出现:它挂在输入框的 dock 座位,而全新会话用的是居中(hero)布局,该座位不渲染 —— 0.2.1 起用侧栏「远程连接」面板补齐:任何会话状态都能点到; - 斜杠命令的描述不会被客户端本地化(只有第一方命令有本地化副本),因此插件里的命令描述写成中英双语。 命令一览 hosts / hosts add <别名> --host H [--port 22] [--user U] [--workspace DIR] / hosts rm <别名> check <host> 就绪诊断(可作 CI 探针,非零退出码 = 有问题) bootstrap <host> [--upgrade] 按登录账号补齐远程环境:Node/dsh(~/.npm-global)/~/.dsh/linger(幂等) provision <host> [--port N] 只做远程侧:分配端口 + systemd 单元 + 启动 + 登记(不起隧道) up <host> [--port N] [--local-port N] [--open] [--heartbeat 秒] down [host] [--keep-service] 停隧道 + released + 停单元 + 核实端口释放 status [host] [--json] list logs <host> [--lines N] [--follow] [--local] audit <host> [--json] [--release <port>] [--clean-stale] open [host] config show / config path 工作原理 1. 远程端口分配(原子):一条远程脚本在 flock 锁内完成——读登记表的 in-use 集合 + 对区间内每个端口做真实 bind 探测 → 取第一个「两者都空闲」的端口 → 追加 TSV 行 → 回显端口。多账号并发分配互不冲突。 2. 远程守护:写入 systemd 单元并 enable --now。有密码 sudo 时用系统级单元(/etc/systemd/system/dsh-web-<user>.service,与任务书模板一致);没有 sudo 时自动改用用户级单元(~/.config/systemd/user/dsh-web.service)+ loginctl enable-linger,完全不需要 root。服务器重启自动拉起,崩溃自动重启。 3. TOCTOU 兜底:若 dsh 启动时端口被抢(EADDRINUSE 出现在单元日志),自动把该端口加入排除集,顺延下一个空闲端口重试(默认最多 5 轮)。 4. 本地隧道:ssh -N -L 127.0.0.1:<本地>:127.0.0.1:<远程> <别名>,本地端口先检查占用(被占自动顺延,并用 netstat+tasklist 报出占用者);ssh 进程退出后按退避序列(1s→2s→4s→8s→15s→30s 封顶)自动重连,永不断线(可配 maxAttempts)。隧道明确不传 ClearAllForwardings(Windows OpenSSH 会把它连同命令行 -L 一起清掉);exec 会话仍会清掉 config 里的转发。 5. 心跳:隧道存活期间每 heartbeatSeconds(默认 120 秒)在锁内原位刷新登记表 last_heartbeat。 6. 释放:down(或 up 的 Ctrl+C)按序:停隧道 → 删除本地状态 → 登记表 released → 停远端单元并 disable(禁用,避免服务器重启后自己回来占住已释放的端口)→ 核实端口真的释放。up/provision 会重新 enable;--keep-service 则完全不动远端单元。另一个进程里的 up 监督器检测到状态文件被删除后自动停止重连,不会「诈尸」。硬关终端(不按 Ctrl+C)则远端服务照跑、登记表仍是 in-use——这是真实占用,不是泄漏:下次 up 会自动清理残留的本地状态并复用同一个已登记端口,不会越攒越多。 配置 $DSH_HOME/remote-tunnel/config.yaml(dsh --profile remote config path 查看路径): hosts: lab: # 手动定义的主机(与 ~/.ssh/config 的别名合并,二者同名时这里优先) host: 192.0.2.10 port: 22 user: alice workspace: /home/alice/project remotePortRange: [3080, 3119] # 可选,按主机覆盖 defaults: remotePortRange: [3080, 3119] # 远程 dsh 端口区间(先查占用再分配) localPortRange: [3081, 3140] # 本地隧道端口区间 registry: path: /etc/dsh-ports.tsv lockPath: /etc/dsh-ports.lock sudo: auto # auto | always | never fallbackPath: .dsh-ports.tsv # 共享登记表不可写时,降级到远程家目录(相对路径) unit: prefix: dsh-web- restartSec: 5 type: auto # auto | system | user heartbeatSeconds: 120 # 0 = 关闭心跳 remoteWaitSeconds: 60 # 等远程端口就绪 localWaitSeconds: 15 # 等本地 URL 可访问 reconnect: delaysMs: [1000, 2000, 4000, 8000, 15000, 30000] maxAttempts: 0 # 0 = 永不放弃 allocateRetries: 5 ssh: connectTimeout: 0 # 0 = 不传 -o ConnectTimeout(见排错表) extraArgs: [] 多用户共享服务器(实验室场景:先做这一步) 多人用同一台服务器时,建议第一次先用 root 做一次性注册——否则插件会自动降级为"每人一份私有登记表", 端口分配只能靠真实占用探测避让(仍然不会连错、不会永久撞车,但 audit 看不到别人占了哪些端口)。 注册一次(需要 root,之后永不再做)——建共享登记表 + 共享组: sudo groupadd -f dshports sudo install -m 0664 -o root -g dshports /dev/null /etc/dsh-ports.tsv sudo install -m 0664 -o root -g dshports /dev/null /etc/dsh-ports.tsv.lock sudo usermod -aG dshports <用户名1> <用户名2> ... # 需要用到插件的每个账号 两个文件都必须提前建好:它们位于仅 root 可写的目录里,成员自己无法创建锁文件,而所有登记操作都要先拿这把锁。 只有这两个文件带组写权限(0664),所在目录保持仅 root 即可——更新登记表时插件用用户级 mktemp 中转、原地改写, 既不触碰目录,也不改变文件的属主/组。 每个成员:① 重新 SSH 登录一次让组生效;② 首次准备环境 dsh --profile remote bootstrap <host>; ③ dsh --profile remote check <host> 应显示 registry: /etc/dsh-ports.tsv (shared-direct)——无需改任何配置, 插件会自动从私有登记表切到共享表。 之后每个人 up 的分配都在服务器上被 flock 串行化并写入同一张表:分到的远程端口必定互不相同, audit 能直接看出"谁占了哪个端口、有无 stale/冲突"。无主行定期清理:dsh --profile remote audit <host> --clean-stale。 | 服务器环境 | 登记表 | 服务守护 | | 管理员按上面建了 dshports 组(推荐) | /etc/dsh-ports.tsv(组 0664,免 sudo) | 用户级单元 + linger | | 成员都有 passwordless sudo | /etc/dsh-ports.tsv(sudo 写入,0644) | 系统级单元,一人一个端口 | | 什么都没配 | 自动降级 ~/.dsh-ports.tsv(只含本人记录;check 会提示找管理员) | 用户级单元 + linger | 共享登记表全员可读(0644/0664),里面只有端口、账号、工作区、来源、时间戳和状态——不含任何密码、密钥或 token; 写入由 flock 串行化,非本组成员无法篡改。 每个账号各自准备好自己的环境(一台服务器 N 个用户 = 各自跑一次,幂等): dsh --profile remote bootstrap <host> # 在自己电脑上,以自己账号 ssh 登录后执行 这一步把 Node/dsh(装进该账号自己的 ~/.npm-global)/~/.dsh/linger 全部补齐——它不碰别的账号的任何东西,会话历史也按账号彼此独立。 两个用户各自 up → 自动分到不同远程端口;audit 能看出谁占哪个端口、有无 stale/冲突。 远程账号初始化(bootstrap,按登录账号) dsh --profile remote bootstrap <host> 为 ssh 别名登录的那个账号补齐环境:Node ≥ 22.19(缺失时尽量装)、dsh(装进该账号 ~/.npm-global)、~/.dsh、systemd linger、npm-global 的 PATH 条目——幂等,新账号跑一次即可。--upgrade 则会强制把 dsh 更新到最新版。 dsh --profile remote bootstrap lab # 准备好当前账号 dsh --profile remote bootstrap lab --upgrade # 更新远程 dsh 到最新版 同一脚本也随包提供,可手动执行(和插件行为完全一致): ssh <host> 'sh -s' < scripts/bootstrap-remote.sh 常见排错 | 症状 | 原因与处理 | | dsh not found / node not found(check 或 up 报错) | 该 ssh 登录账号还没装 Node/dsh——插件为每个账号分别工作。dsh --profile remote bootstrap <host> 按当前账号补齐(幂等,--upgrade 可更新);已装但 ssh 通道 PATH 看不到时,插件也会自动探测 ~/.npm-global/bin/dsh。 | | Error: listen EADDRINUSE ... 127.0.0.1:3080 | 有人(或你上一个实例)占了该端口。本插件分配前双重检查,up 时若仍发生(TOCTOU)会自动顺延;手工起 dsh 才会看到这个报错。 | | Could not resolve hostname <别名> | 别名不在 ~/.ssh/config 里,且没在插件配置里定义。hosts add 或写入 ssh config 后重试。 | | Connection refused / remote port forwarding failed | 远端 dsh web 没起或端口不对。check <host> 看「web port listening」;logs <host> 看远端日志;ss -tln \| grep <port> 在服务器上核实。 | | channel_setup_fwd_listener_tcpip: cannot listen to port | 本地端口已被占(常见:两个 dsh web 实例)。本插件会自动顺延,并输出占用者进程名;也可 --local-port 手动指定。 | | Permission denied (publickey) / sudo: a password is required | 密钥没配好 / 没有 NOPASSWD sudo。前者 ssh-copy-id;后者见上表,无 sudo 也能用(用户级单元 + 兜底登记表)。 | | Could not create directory '/home/xxx/.ssh' + host key 提示 | 首次连接需接受主机指纹,插件默认 accept-new(TOFU),已在自动处理。 | | 断网后隧道没恢复 | 默认无限重连,status 看 ssh pid 是否 alive;logs <host> --local 看重连日志。若设了 reconnect.maxAttempts,达到上限会停止。 | | 隧道进程一直活着,但本地 URL 始终 not reachable(端口不通) | Windows OpenSSH 8.1 会把 -o ClearAllForwardings=yes 连同命令行自己的 -L 一起清掉,导致隧道只连接、不转发。已于 0.1.1 修复:隧道不再传该选项(exec 会话仍保留)。 | | 打开隧道 URL 只显示 dsh web authentication required; reopen the URL printed by dsh web. | dsh web ≥ 0.1.2-rc 用启动时打印的一次性 token URL 鉴权。up 现在会打印改写成本地端口的带 token 地址(auth: 行)。若已过期(服务重启过),把 logs <host> 里的 dsh web: http://…?token=… 整行复制到浏览器地址栏。 | | 登记表读不到(/etc/dsh-ports.tsv missing) | 首次分配时自动创建(需写入权限);无权限时自动降级到 ~/.dsh-ports.tsv,check 会给出管理员初始化命令。 | | 每条 ssh 命令都慢 ~N 秒 | 部分服务器上给 ssh 传 ConnectTimeout 会让每条连接都等满超时(即使秒连)。默认已不传该参数(ssh.connectTimeout: 0);需要时再显式打开。 | | Bad owner or permissions on .../.ssh/config(所有远程操作全挂) | 你的 ~/.ssh/config 里被别的工具(如 AtomGit DevEnv、conda 环境)注入了 Include,而那个被包含的文件权限过宽(带 Everyone:(F)),OpenSSH 直接拒绝加载整份配置。修复:icacls "<被包含的文件>" /inheritance:r /grant:r "$env:USERDOMAIN\$env:USERNAME:F" /grant:r "NT AUTHORITY\SYSTEM:F"(目录同样处理)。另一种成因是 HOME 被工具改指到别处,使 ssh 读了另一个目录下的 config——echo $env:HOME 确认。 | | audit 显示某些 in-use 行是 STALE,端口总被"占用" | 那是会话被杀/硬关终端后没来得及 down 留下的历史行(分配时会当作占用,避免撞车)。清理:dsh --profile remote audit <host> --clean-stale。 | 开发与测试 npm install # 插件自身依赖(package-lock.json 已入库,构建可复现) npm test # 单元测试 + 假 ssh shim 集成测试(无需真实服务器) 集成测试用一个仿真的 ssh(把远程命令解释到临时「服务器」上,隧道真实转发 TCP),覆盖:分配/登记/释放、并发多人分配、TOCTOU 顺延、本地端口冲突顺延、断线自动重连、跨进程 down 取消、audit stale/orphan/clean。 安全说明 - 隧道与远程 dsh 一律只绑 127.0.0.1(dsh 本身禁止 --host 0.0.0.0) - 插件不保存、不传输任何密码/密钥/API key;SSH 全走现有密钥(BatchMode,拒绝密码提示挂起) - 登记表不记录任何敏感信息(见 docs/registry-format.md · English) - 远程脚本仅在 flock 锁内追加/改写登记表与 systemd 单元,不执行其他写入 - 桌面/网页端的 /remote-tunnel/* 路由走运行时自己的准入检查(ctx.connection.admit():与 /api 同一套 Host/Origin 栅栏 + 浏览器会话 cookie 校验),未通过返回 401/403 —— 因为这类路由会下发携带一次性 launch token 的 URL;确实无法携带 cookie 的载体可用 auth: false 显式关闭(不推荐) 非目标 - 不实现 SSH/SFTP/远程挂载:方案本质是「在服务器上跑 dsh」,隧道只把 HTTP 引回本地 - 不做新 TUI:CLI 子命令 + web 的 /remote 斜杠命令
预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
Linjiangxian0203/dsh-remote-tunnel
dsh 远程主机隧道管理器:远程端口分配 + 注册表 + 抗断 SSH 隧道
相关插件 · 市场 / 管理
插件精选列表awesome-dsh-plugin/awesome-dsh-plugin
DeepSeek Harness(dsh)插件精选列表
★ 22,025未汉化DSH
73.6
新词雷达zhuyansen/new-term-radar
new-term-radar新词雷达
★ 2,049已汉化MCP
72.8
插件生态导航0xsline/awesome-deepseek-harness
安装 贡献指南 DeepSeek Docs 公开插件目录 Issues
★ 1,188已汉化DSH
72.6
技能注册中心iflytek/skillhub
企业级开源智能体技能注册中心——发布、发现并管理组织内可复用的技能包。
★ 5,358已汉化DSH
71.9
插件市场目录imsai-sh/awesome-deepseek-harness-plugins
面向 DeepSeek Harnessdsh生态的社区插件目录,共收录 13938 个插件含 PR 收录与…
★ 350已汉化DSH
71.8
插件精选清单Anil-matcha/awesome-dsh-plugin
DeepSeek Harnessdsh精选指南——DeepSeek…
★ 1,013已汉化DSH
71.7
这是你的仓库?认领条目