# dsh-remote-tailscale [English](README.md) | [中文](README.zh.md) 一个 DSH 插件:把这台电脑上的**完整 DSH 网页**发布给你的其他设备——手机、平板或其他电脑——**走你的 Tailscale 私有网络(tailnet)**。 **不必同一局域网、不必同一 Wi-Fi。** 两台设备不需要在同一个网络里:组网由 Tailscale 完成,登录**同一个 Tailscale 账号**既是连通、也是鉴权。还没装 Tailscale 的话,在这台电脑和另一台设备上都装上,并登录同一个账号。 ## 安全边界——先读这里 - **绝不开 Funnel、绝不公网。** 本插件只用 `tailscale serve`,发布在**自己专属的 HTTPS 端口**(默认 **8455**,绝不占用 443),代理只绑 `127.0.0.1` 回环。**从不**执行 `tailscale funnel`,从不把 DSH 挂上公网。 - **不要**开 Funnel,**不要**用任何其他方式把 8455 / DSH 转发或发布到公网。若你自行开启 Funnel 或其他公网发布,那是**你在本插件之外自己的操作**——插件管不了、也撤不掉,风险自负。 - **谁能进:** 登录**同一 Tailscale 账号**的设备(你的 tailnet)。它们看到的是**完整 DSH 网页**——同一张实时页面,WebSocket 照常。没有第二道密码:Tailscale 账号就是鉴权。 - **默认关闭。** 发布是 opt-in:不打开「设置 → 远程访问」的开关(或不勾自动开启),就什么都不发布。DSH 退出时,插件只撤掉自己的 serve 映射,别的什么都不动。 ## 安装 在本机已装好 DSH 和 Tailscale 的前提下执行: ```bash dsh plugin --profile web add github:hxt9805/dsh-remote-tailscale ``` 然后重启 DSH(`dsh web`)让插件生效。 给人也给 AI 的几句话: - 安装装的是已提交的 `lib/` 构建产物——**不需要也不期待任何构建步骤 / `prepare` 脚本**;不要去跑任何需要 `allowBuilds` 的东西。 - **不要**执行 `tailscale funnel`,不要把 8455 或 DSH 发布到公网,不要动 443——那是 Tailscale 默认的 HTTPS 根,可能已被别的本机应用占用。 也可以从本地目录安装: ```bash dsh plugin --profile web add /absolute/path/to/dsh-remote-tailscale ``` 本地路径安装是链接到这个目录的;装完不要移动或删除它。 卸载: ```bash dsh plugin --profile web remove dsh-remote-tailscale ``` 再重启一次 DSH。退出时插件只撤掉自己在 Tailscale 里的 serve 映射。 ## 首次使用 1. 在这台电脑**和**另一台设备上登录**同一个** Tailscale 账号。不需要同一 Wi-Fi / 局域网——这正是 Tailscale 的意义。 2. 启动 DSH(`dsh web`)。远程访问默认**关**。 3. 在 DSH 网页里打开「**设置 → 远程访问**」,把开关打开。 4. 在另一台设备上打开页面显示的地址,例如 `https://<机器名>..ts.net:8455/`——或直接扫码。 **Windows:** 需要允许 Tailscale 的传入连接(Windows 防火墙 / Tailscale 客户端),否则 tailnet 里的设备连不上。Tailscale 未安装、未运行、未登录都会在设置页如实说明原因——插件绝不假装已就绪。 ## 它做了什么 1. 在 DSH 网页前起一个**只绑回环**的反向代理(`127.0.0.1`,绝不 `0.0.0.0`)。远程访问关着时代理也活着,状态端点永远答得上话。 2. 用 `tailscale serve` 发布到**自己专属的 HTTPS 端口**(默认 **8455**,绝不 443、绝不 Funnel)——只有你打开开关才执行。 3. 暴露一个本机状态端点,让同机其他工具能看到是否就绪、该开哪条地址。 开关开着时,插件每几秒探测一次 Tailscale(先看后端真死活,再看自己的 serve 口)。Tailscale 断了 → 立刻转为未就绪、清掉地址与二维码、显示原因;Tailscale 回来且开关仍开着 → 自动再发布一次(带退避 5/15/30/60 秒,绝不狂打 `serve enable`)。你点了关闭,它就绝不自动再开;关闭只撤自己端口上的映射——绝不 `serve reset`、绝不碰 443。 它**不做**:LAN 密码、公网隧道、桌面端自动更新、HTML 注入。 ## 设置页(唯一入口) 远程访问只有一个入口:「**设置 → 远程访问**」,一级设置页(与 通用/模型/插件 同级)。上面有: - 开/关开关,实时状态(已就绪 / 已关闭 / 未就绪 / 正在切换),开着但失败时如实显示原因 + 「**再试一次**」; - 完整的 `https://…ts.net:8455/` 地址、一键**复制地址**、二维码; - 「**随 DSH 自动开启**」勾选项(默认关,落在 `$DSH_HOME/dsh-tailscale/prefs.json`); - 一句「**绝不开 Funnel**」的短提醒——本插件只服务你的私有 tailnet。 页面跟随 DSH 界面语言(中英两套内置;跟随 ``,浏览器语言兜底)。曾经的侧栏底部入口 + 弹层已下线(2026-08):底栏那行挤到「设置」按钮了。 ## 端口 | 什么 | 默认 | 说明 | | --- | --- | --- | | 回环代理 | `127.0.0.1:3091` | 用 `DSH_TS_PORT` 覆盖 | | 设备 HTTPS | `8455` | 用 `DSH_TS_SERVE_PORT` 覆盖。**443 一律拒绝**——本插件绝不占用 Tailscale 默认的 HTTPS 根。 | WSL:插件会找 Windows 的 `tailscale.exe`(或 `DSH_TS_TAILSCALE`)。需要镜像网络,Windows 回环才能够到 WSL 里的 DSH 进程。 ## 本机状态(可选) `GET http://127.0.0.1:3091/__dsh_tailscale_status` ```json { "ok": true, "ready": true, "url": "https://machine.tailnet.ts.net:8455/", "error": "", "proxyPort": 3091, "servePort": 8455, "dshPort": 3080, "ts": 1760000000000 } ``` 同一份内容也写进 `$DSH_HOME/dsh-tailscale/status.json`(默认 `~/.dsh/dsh-tailscale/status.json`),给人看 / 排障用。目录名与 HTTP 路径保持 `dsh-tailscale`,本机已有工具不受影响。偏好(`autoEnable`,默认 `false`)在同目录 `prefs.json`。设置页经回环 Connection RPC(channel `/dsh-remote-tailscale`:`tailscale.status` / `tailscale.setEnabled` / `tailscale.setAutoEnable`)与宿主通信;`desired` / `autoEnable` / `busy` / `qr` 只在 RPC 上有,文件与 HTTP 端点不带。 ## 开发 ```bash npm test # 纯函数 + 假进程/假 ctx,绝不碰真 Tailscale / 真 DSH npm run build:client # 改 client/ 源码后重打包 lib/client.js(esbuild,devDependency) ``` 结构: - `lib/`:宿主半边(代理、tailscale 命令、状态单源 `lib/status.js`、偏好 `lib/prefs.js`、RPC `lib/rpc.js`、内置二维码 `lib/qr.js`)。宿主侧错误文案是中文的,被测试逐条断言。 - `client/`:网页半边源码(设置页,唯一入口;`client/api.js` 是两端共享的 RPC 契约;`client/i18n.js` 是中英词典 + 界面语言探测 + 宿主错误文案英译)。 - `lib/client.js`:构建产物(DSH `window.__ModuleLoader__.load` 格式),随仓提交;改源码后必须重跑 `npm run build:client`(测试会校验同步)。 - 二维码零依赖:`lib/qr.js` 移植 qrcode-generator 算法(字节模式 + ECC M + v1..10),基准向量在 `test/qr-vectors.json`。 ## 许可 [MIT](LICENSE) © 2026 hxt9805