English | 简体中文
@zhangzhian/dsh-tunnel-core
开发工具(@zhangzhian/dsh-devtools)的隧道组件:host 半身(隧道生命周期、sshd 体检、
系统服务、Remote API)+ 「远程接入」设置页。
不要单独装它 —— 装清单包 @zhangzhian/dsh-devtools,它会作为依赖自动拉下来,
在插件页上表现为「开发工具」卡片里的一行(不是另一张卡片)。
2026-10:原来有一个独立的清单包
@zhangzhian/dsh-tunnel(插件页上单独一张 「远程接入」卡片)。用户反馈「远程接入没和开发工具在一起」,于是把它并进了dsh-devtools的 patch —— 同一个包只能被一个清单 insert,否则宿主模块会跑两遍。
安装
dsh plugin --profile web add @zhangzhian/dsh-devtools
提供什么
dshTunnelRemote 命名空间 ——status/startSsh/stopSsh/startWeb/stopWeb/start/stop/probe/sshd/sshdEnablePlan/runSshdEnable/recipe/settings/updateSettings/installService/uninstallService/serviceState/doctor/credential/rotateCredential/setCredential,两端类型打通。 ⚠️probe/doctor/installService/uninstallService/serviceState不在面板上:它们是排查和运维用的。能力在、有测试,只是没有界面入口。- 隧道生命周期 —— 解析
cloudflared(显式路径 →PATH→ 常见安装位置 → 按固定 版本下载)、带--protocol http2起进程、从 JSON 行的 logfile 里解析 Quick Tunnel 域名,并把每次状态变化落盘,所以 DSH 重启后面板还在。 - 两条隧道各自独立 —— SSH 隧道(
phase)和 dsh Web 隧道(webPhase)各有自己的 阶段、域名、句柄、开关和风险确认;exposeWeb是"用户想不想开"的期望态,webPhase才是"现在到底开没开"。 - 公网入口凭据 —— 隧道 B 的代理前面挡着一对账号 + 密码(
credential/rotateCredential/setCredential,落盘在access-code,升级前那种"只有一行 明文密码"的文件照样读得出来);浏览器用 cookie 记 30 天,直达链接是?token=账号:密码。随机那一份是默认(8 位账号 + 24 位密码),也可以换成用户自己 填的:校验在 host 半(validateCredential,账号和密码里都不许有冒号 —— 直达链接 靠第一个冒号切分)。 - 端口纪律 —— 客户端端口永远落在 2201–2299,而且对同一台宿主机是稳定的,所以 dsh-electron 那边只需要配一次。
- 三平台服务定义 ——
plistFor(LaunchAgent)、unitFor(systemd 用户 unit)、taskXmlFor(计划任务)都是纯函数,所以虽然这里只能跑其中一个,转义规则在每个平台 上都有单测守着。 - 零依赖二维码编码器 —— 客户端 bundle 只允许
require平台提供的 9 个种子模块, 所以qr.ts是自己写的字节模式编码器,八个掩码都对着参考实现验证过。 - 「还没开启远程登录」的引导 —— sshd 没在监听时,SSH 那一屏会给出本平台确切的
开启命令(macOS
systemsetup -setremotelogin on;Linuxsystemctl enable --now ssh/sshd/ssh.socket,按发行版与是否 socket 激活探测;WindowsAdd-WindowsCapability+Set-Service sshd+Start-Service sshd),逐条可复制。 主按钮「在终端里执行」把命令预填进 dsh 自带的终端(TerminalView.write(),不带 换行),用户在自己的终端里按回车、输密码 —— 插件不提权、不碰sshd_config, 无头服务器上照样能用。dsh 本身就是 root / 管理员时才多一颗「直接开启」。 已经在监听时这块不出现(判据是读到 SSH 横幅,不是"用户点没点过")。 - 设置面板 —— 三个页签:dsh Web 隧道在前(也是默认那一屏)、SSH 隧道、 配置(两条隧道共用的端口与 cloudflared 路径,排在最后)。页签条通栏、吸顶在 页头下面,选中项下面一条 2px 下划线。dsh Web 那条装着访问地址 + 二维码(扫一下 直接进去,链接里带着账号密码)+ 可自定义的账号密码;SSH 那条只装客户端接入信息 (域名、命令、脚本、四步说明 —— 平台按浏览器自动判断,没有切换控件,也没有二维码); 配置那条就是四个端口 / 路径字段(一份草稿 + 一个保存)。两条隧道各一张卡片(各自的 状态轮询与开关)、各一份独立的开启前风险确认。体检 / 自检 / 服务 / 诊断包不在面板上 (见上)。
要求
- Node.js 20+。 host 半只用
node:child_process、node:fs/promises、node:dgram(自检的 DNS 直查,见下)和AbortController—— 没有原生模块,所以 Linux / macOS / Windows 上行为一致。 - 一个 DSH 安装,其客户端半身提供那 9 个种子模块(
react、react/jsx-runtime、@deepseek-ai/dsh-client-ui-primitives等)。其余依赖在构建期内联进 bundle。 cloudflared—— 在PATH里、在常见安装位置,或者被下载(带 SHA-256 校验)到$DSH_HOME/dsh-tunnel/bin。- 一个在监听的 sshd,最好只允许密钥登录。没开的话面板会给出本平台的开启命令,
并在终端里预填好(见上);各系统的开法另见
@zhangzhian/dsh-devtools的 README。
English
@zhangzhian/dsh-tunnel-core
The tunnel component of Dev tools (@zhangzhian/dsh-devtools): the host half (tunnel lifecycle,
sshd inspection, system services, Remote API) plus the "Remote access" settings page.
Do not install it on its own — install the bundle package, which pulls this in as a dependency.
Install
dsh plugin --profile web add @zhangzhian/dsh-devtools
What it provides
dshTunnelRemote namespace —status/startSsh/stopSsh/startWeb/stopWeb/start/stop/probe/sshd/sshdEnablePlan/runSshdEnable/recipe/settings/updateSettings/installService/uninstallService/serviceState/doctor/credential/rotateCredential/setCredential, all typed end to end. ⚠️probe/doctor/installService/uninstallService/serviceStateare not on the panel: they are for troubleshooting and operations. The capabilities are there and tested, they just have no UI entry point.- Tunnel lifecycle — resolve
cloudflared(explicit path →PATH→ common install locations → pinned download), spawn it with--protocol http2, parse the Quick Tunnel hostname out of the JSON-lines logfile, and persist every state change so the panel survives a DSH restart. - Two independent tunnels — the SSH tunnel (
phase) and the dsh web tunnel (webPhase) each own their phase, hostname, process handle, on/off button and risk acknowledgement;exposeWebis the desired state,webPhaseis what is actually on. - Public entry credentials — one username plus a password sit in front of tunnel B's
proxy (
credential/rotateCredential/setCredential, persisted inaccess-code; the pre-upgrade file holding a single plain-text password is still readable). Browsers keep a cookie for 30 days, and the direct link carries?token=<user>:<pass>. The random pair is the default (an 8-character username and a 24-character password), and it can be replaced with one the user types: the validation lives in the host half (validateCredential, and neither the username nor the password may contain a colon — the direct link splits on the first one). - Port discipline — the client port always comes from 2201–2299, and it is stable for a given host, so dsh-electron only needs configuring once.
- Platform services —
plistFor(LaunchAgent),unitFor(systemd user unit) andtaskXmlFor(Task Scheduler) are pure functions, so their escaping rules are unit tested on every platform even though only one of them can run here. - A dependency-free QR encoder — the client bundle may only
requirethe platform's nine seed modules, soqr.tsis a self-contained byte-mode encoder validated against the reference implementation for all eight masks. - An "SSH is not enabled yet" guide — when sshd is not listening, the SSH tab prints the
exact commands for this platform (macOS
systemsetup -setremotelogin on; Linuxsystemctl enable --now ssh/sshd/ssh.socket, picked from the distro and whether socket activation is in use; WindowsAdd-WindowsCapability+Set-Service sshd+Start-Service sshd), each copyable. The primary button prefills the command into dsh's own terminal (TerminalView.write(), no trailing newline), so the user presses Enter and types the password in their own terminal — the plugin never elevates and never touchessshd_config, and it works on a headless server too. An "enable now" button only appears when dsh itself is root / administrator. The block does not appear when sshd is already listening (the criterion is reading the SSH banner, not "did the user click"). - The settings panel — three tabs: dsh web tunnel first (and it is the default view), the SSH tunnel, and config (the ports and cloudflared path both tunnels use, last). The tab bar spans the full width, is pinned under the page header, and marks the selected tab with a 2px underline. The dsh web tab holds the URL plus a QR code (scan it and you are in — the link carries the credentials) and an editable username and password; the SSH tab holds only the client recipe (hostname, command, script, four steps — the platform is auto-detected, with no switcher and no QR code); the config tab holds the four port / path fields (one draft, one save). Each tunnel keeps its own card (its own status polling and switch) and its own risk confirmation. The check-up, self-test, service and diagnostics bundle are not on the panel (see above).
Requirements
- Node.js 20+. The host half uses only
node:child_process,node:fs/promises,node:dgram(the self-check's direct DNS query) andAbortController— no native modules, so it works the same on Linux, macOS and Windows. - A DSH install whose client half provides the nine seed modules (
react,react/jsx-runtime,@deepseek-ai/dsh-client-ui-primitives, …). Anything else is bundled at build time. cloudflared— found onPATH, at a common install location, or downloaded (SHA-256 verified) into$DSH_HOME/dsh-tunnel/bin.- A listening sshd, ideally key-only. If it is off, the panel prints the per-OS commands and prefills them in a terminal (see above); the bundle package README covers the same ground.