# DSH Auth Tunnel [English](README.md) | 中文 无需修改 `deepseek-harness`,即可通过带共享密码保护的 Cloudflare Tunnel 公网访问 DeepSeek Harness Web GUI。 ## 使用 ### 前置条件 - `dsh` CLI 和 pnpm 已加入 `PATH`;Web profile 不存在时,插件命令会自动创建。 - `cloudflared` 已加入 `PATH`,或在插件中配置其绝对 `executable` 路径。 - 一个以 DSH 凭据保存的长随机共享密码。 ### 安装 从 npm 安装最新发布的预览版 bundle: ```sh dsh plugin --profile web add dsh-auth-tunnel@next ``` 也可以从 Git 安装当前源码: ```sh dsh plugin --profile web add github:ai-eks/dsh-auth-tunnel ``` 当前源码分支适配 DeepSeek Harness `0.1.5-rc.1`,同时支持 `0.1.2-rc.1` 和 `0.1.3-alpha.2`。Harness `0.1.1-rc.2` 及更早版本必须固定安装兼容的包版本、不可变 tag 或 revision: ```sh dsh plugin --profile web add dsh-auth-tunnel@0.1.1-rc.2.1 # Harness 0.1.1-rc.2 dsh plugin --profile web add 'github:ai-eks/dsh-auth-tunnel#v0.1.0-rc.8' # Harness rc.8 dsh plugin --profile web add 'github:ai-eks/dsh-auth-tunnel#b4baea7c47f5c245da789d3553d41938df89b311' # Harness rc.7 dsh plugin --profile web add 'github:ai-eks/dsh-auth-tunnel#v0.1.0-rc.6' # Harness rc.6 ``` Git 安装通过 `prepare` 构建检出的源码。pnpm 10 及以上版本可能先要求允许该构建;按照 `dsh` 打印的 profile `pnpm-workspace.yaml` 路径和准确包名配置后,重新执行命令。 使用本地 checkout 时,先构建再添加路径: ```sh cd /path/to/dsh-auth-tunnel pnpm install dsh plugin --profile web add . ``` 该 bundle 会以 quick 模式插入并启用 `auth-tunnel` 行,同时把 Host 原生目录选择器替换为应用内浏览器选择器。不需要修改 `deepseek-harness` 源码,也不需要额外添加 profile 行。 ### Quick 模式 Quick 是默认模式。把共享密码写入 `$DSH_HOME/.credentials.yaml`(`$DSH_HOME` 默认为 `~/.dsh`): ```yaml DSH_WEB_PASSWORD: 'replace-with-a-long-random-password' ``` 启动 Web profile: ```sh dsh web ``` 在配置 `DSH_WEB_PASSWORD` 前启动不再导致 Web profile 失败。插件会保持挂载并显示错误状态;添加该凭据后,隧道会自动启动。 隧道就绪后,终端会打印: ```text cloudflare tunnel: https://.trycloudflare.com ``` 打开这个 URL,在登录页输入 `DSH_WEB_PASSWORD` 对应的密码。只分享 URL,不要分享密码。启用的行也会显示在 Web Settings → Plugins 中。 ### Web 设置 保持 Loader 的 `auth-tunnel` 行启用后,打开 **Settings → Plugins → 插件配置 → Auth Tunnel** 即可编辑全部配置。页面中的 **启用公网隧道** 开关保存后会立即启动或停止密码门和 `cloudflared`,并保留这张设置卡片。页面同时显示应用中、运行中、已停止或失败状态以及当前公网 URL。 **允许远程页面修改设置** 默认开启。共享访问密码是管理员凭据:通过密码登录的公网页面无需本地设置,即可读取和保存 Auth Tunnel 卡片及语言偏好。如果不希望已认证公网页面管理隧道本身,可关闭该开关;之后重新开启必须使用本机页面或设置文件。这些写入走插件自有的鉴权接口,因此兼容未修改的 DeepSeek Harness `0.1.2-rc.1`。该开关与核心 Host 配置面是两道独立的围栏:gate 把 `settings.*`、`credentials.*` 与 `llm.*` 对每个已认证公网页面直接代理到 Host,但会拒绝核心 settings API 对 `auth-tunnel` namespace 的写入,这类写入必须经过有围栏的插件接口;bundle 的立即启动客户端会在设置 scope 判断浏览器类型前发布这条已认证路径。公网 GUI 因此与本机 GUI 保持完整的配置面一致性——响应只返回脱敏值,密钥只随写入载荷单向流出。同一时刻只接受一个远程写入;前一项配置仍在应用时,新的写入会返回冲突,页面重新读取后即可重试。远程页面不能保存会分配全新随机 Quick URL 的变更(切换到 Quick,或修改 Quick 的 Gate 端口或可执行文件);请在本机页面完成这类修改,以便获取新地址。从远程页面关闭该开关时,本次保存会完整返回后再关闭访问。 页面通过独立的 **更新密码** 按钮写入当前已保存的 `passwordRef` 凭据,访问密码与配置不会放在同一次提交里。Token 模式可直接粘贴 Tunnel Token,它会随 **保存配置** 单向写入 `tokenRef` 指向的凭据;默认引用为 `DSH_TUNNEL_TOKEN`。两种密钥输入成功后都会立即清空,Host 和页面都不会回传或展示明文。若要更换 `passwordRef`,请先创建目标凭据并保存引用,再单独更新密码。 | 关联配置 | Quick | Token | |---|---|---| | 访问密码 | 必需;两种模式共用,通过只写按钮单独更新 | 必需;两种模式共用,通过只写按钮单独更新 | | Tunnel Token | 不需要 | 必需;可在页面直接粘贴,保存到 `tokenRef` 指向的凭据 | | 公网主机名 | 不需要;自动获得临时 `trycloudflare.com` 地址 | 必需;填写 Cloudflare 已绑定域名 | | Gate 端口 | 建议 `0`,自动分配 | 必须固定为 `1–65535`,并与 ingress 一致 | 页面保存的配置会自动应用,无需重启 DeepSeek Harness。`passwordRef` 和 `sessionTtlHours` 原地更新;`mode`、`tokenRef`、`gatePort` 或 `executable` 等隧道级变更会先启动候选实例,再替换插件自己的密码门或 `cloudflared`。候选实例启动失败时,页面会显示错误并保留旧隧道;切换成功时公网页面可能短暂断开,请打开新显示的地址,重新读取后再重试失败操作。切换回 Quick 模式时会保留 Token 模式字段,方便之后切回;Quick 模式会忽略这些字段。日常启停应使用页面开关;设置 Loader `disabled: true` 会卸载 Host 的 `auth-tunnel` 设置命名空间和卡片本身。 ### 命名隧道模式 公网域名需要保持稳定时使用 token 模式。在 Cloudflare 创建命名隧道,绑定 `gui.example.com` 之类的域名,并让 dashboard ingress 指向固定 loopback 密码门,例如 `http://127.0.0.1:7677`。 可以在 Web 设置卡片直接粘贴 Tunnel Token。也可以预先把两个凭据写入 `$DSH_HOME/.credentials.yaml`: ```yaml DSH_WEB_PASSWORD: 'replace-with-a-long-random-password' DSH_TUNNEL_TOKEN: 'eyJhIjo...' ``` 在 `$DSH_HOME/profiles/web/cordis.patch.yml` 中覆盖 bundle 行: ```yaml - id: auth-tunnel disabled: false config: enabled: true mode: token tokenRef: DSH_TUNNEL_TOKEN publicHostname: gui.example.com gatePort: 7677 ``` `publicHostname` 只能填写 DNS 主机名,不能带 `https://`、端口或路径。配置和 Tunnel Token 都可以在上述 Web 设置卡片中完成并立即应用;Token 只进入凭据服务,不会写入 settings 或回显。修改 `gatePort` 后,仍需确保 Cloudflare Dashboard ingress 指向相同端口。 ### 配置参考 | 键 | 类型 | 默认值 | 作用 | |---|---|---|---| | `enabled` | boolean | `true` | 是否运行密码门和 `cloudflared`;页面保存 `false` 后立即停止公网访问但保留设置页面。 | | `allowRemoteSettings` | boolean | `true` | 是否允许已认证公网页面更新 Auth Tunnel 配置、只写访问密码和语言偏好。 | | `passwordRef` | string(credential-ref) | `DSH_WEB_PASSWORD` | 解析共享访问密码的凭据引用;未配置时插件保持挂载,添加凭据后自动启动。 | | `sessionTtlHours` | number ≥ 0.01 | `720` | Cookie 有效期,单位为小时,默认 30 天。 | | `mode` | `quick` \| `token` | `quick` | 临时 quick 隧道或命名 token 隧道。 | | `tokenRef` | string(credential-ref) | `DSH_TUNNEL_TOKEN` | Tunnel Token 凭据引用;仅 token 模式。 | | `publicHostname` | DNS hostname | — | 不带 scheme、端口或路径的命名隧道主机名;仅 token 模式。 | | `gatePort` | integer 0…65535 | `0` | loopback 密码门端口;token 模式要求固定的非零值。 | | `executable` | string | `cloudflared` | `cloudflared` 的 PATH 名称或绝对路径。 | | `startupTimeoutMs` | integer ≥ 1 | `15000` | 激活等待隧道就绪的最长时间。 | ## 已知限制 - **共享密码、单管理员信任**:每个密码持有者都被视为管理员,默认可访问完整 Web GUI、Auth Tunnel 卡片、只写密码输入和语言偏好。关闭 `allowRemoteSettings` 会移除这些插件自有控件,但不会限制核心 Host 配置面(settings、credentials、LLM 目录),该配置面仍对每个已认证公网页面直接代理到 Host。响应已脱敏,密钥只随写入载荷单向传输。当前没有速率限制、锁定、按用户会话或服务端吊销表。请勿将该密码分享给低信任访客;更严肃的部署应使用 Cloudflare Access 或其他身份感知代理。轮换密码会使所有会话失效。 - **单隧道、无自动重启**:`cloudflared` 意外退出时会记录并显示错误,但不会自动重启;在页面关闭再开启隧道即可恢复。 - **Quick URL 每次启动都会变化**:需要固定 URL 时应使用 token 模式和自有域名。 - **保留本机 DSH 认证**:共享密码保护隧道路径;直接访问原始 Web GUI 时,使用 `dsh web` 输出的带 Token 地址及 DSH 浏览器会话。 - **子进程环境最小化**:只继承 `PATH`、`HOME` 和 `TMPDIR`;公司代理应在插件之外为 `cloudflared` 配置。 - **Loopback HTTP 是明文**:密码门和上游 WebServer 通过同主机 loopback HTTP 通信;TLS 在 Cloudflare 终结。 - **每次启动只有一种目录选择器交互**:启用 bundle 后,本机客户端也使用应用内浏览器选择器,因为 Web 应用不能按连接分别选择原生和浏览器选择器。 ## 工作原理 ```text public client → Cloudflare edge (TLS) → cloudflared (this host) → password gate, loopback only → existing loopback WebServer ``` ### 密码门与代理 插件依赖 `webServer`、`credentials`、`settings` 及 Host `connection` 服务。它启动一个自己的 loopback `node:http` 密码门,解析配置的密码引用,再让 `cloudflared` 指向这道门。原始 WebServer 以及其他插件贡献的所有路由都原样保留在门后。 未认证的浏览器导航会重定向到 `/dsh-auth-tunnel/login`;其他未认证请求返回精简的 401。登录成功后签发 `HttpOnly; SameSite=Strict` 的 `dsh_auth_tunnel` Cookie,使用从密码派生的 HMAC 密钥签名。已认证导航还会刷新可读的 `dsh_auth_tunnel_surface=1` 标记,它只负责让客户端在设置插件启动前识别 tunnel 路径,不授予任何访问权限;Gate 仍会在每个请求上校验 HttpOnly Cookie。每次请求都会重新解析凭据,因此轮换密码会立即使已有会话失效。`GET` 或 `POST /dsh-auth-tunnel/logout` 会清除两个 Cookie。 密码门把登录请求体限制为 16 KiB,并代理已认证的 HTTP 与 WebSocket 流量。它把 `Host` 和匹配当前主机的浏览器 `Origin` 改写为 loopback 上游地址,让 WebServer 的 DNS-rebinding 与同源检查继续看到可信地址;外来或不透明 Origin 保持不变。HTTP 两段代理都会删除逐跳头并按连接重新生成,升级握手则保留协议需要的字段。客户端断开时,对应的上游请求也会取消。 校验公网密码 Cookie 后,Gate 通过 Host connection 的 Token 兑换接口取得私有 DSH 浏览器 Cookie,并在上游 HTTP 和 WebSocket 请求中使用。DSH 启动 Token 和浏览器 Cookie 仅保留在服务端;公网响应会过滤同名的上游 `Set-Cookie`。DSH Cookie 过期时独立续期,不要求仍有效的公网会话重新登录。 唯一不需要认证的上游应用路由是只读的 `GET`/`HEAD /manifest.webmanifest`。除非页面明确要求带凭据获取 manifest,否则浏览器不会为这类请求携带凭据;该文件只包含公开的应用元数据。 安装或升级客户端插件后需要刷新页面,以便在设置 scope 初始化前识别隧道。重新加载 Host connection 服务也会重启依赖它的隧道插件;Quick 模式可能获得新地址。 ### 目录选择器 bundle 会禁用启动时选择的原生目录选择器,并挂载应用内目录浏览器。公网 `host.pickDirectory` 无法操作 Host 显示器上的系统弹窗,否则会一直等待到 Cloudflare 返回 524。浏览器选择器无需按接口打补丁,即可同时服务本机和公网客户端。 ### 隧道生命周期 - **quick** 执行 `cloudflared tunnel --url http://127.0.0.1:`,并从子进程输出读取生成的 `*.trycloudflare.com` URL。 - **token** 通过子进程环境变量 `TUNNEL_TOKEN` 传递 Tunnel Token,执行 `cloudflared tunnel run`,并等待连接注册标记。token 不会出现在 argv 中。 通常只有密码门开始监听且隧道报告就绪后,初次插件激活才会完成。访问密码凭据未配置是可恢复的例外:插件会保持挂载,状态显示错误且 `running: false`,不启动密码门或 `cloudflared`,不发布公网 URL;配置该凭据后会自动重试。如果初次激活时访问密码已配置,模式或 Token 凭据无效、密码门端口被占用、可执行文件缺失、子进程提前退出或等待超时,都会在公布公网 URL 前让初次加载失败。如果这些故障在初始缺少访问密码后的异步重试中才被发现,已挂载的插件会改为通过设置页错误状态报告。运行期间的配置变更会串行合并;需要重建时先启动新资源,成功后再替换旧资源,失败则保留旧隧道并通过设置页状态接口报告。拆卸或页面关闭时会关闭密码门,向 `cloudflared` 发送 `SIGTERM`,必要时在 2000 ms 后升级为 `SIGKILL`,并移除 shell 与提示词贡献。 ## 模型体验 隧道就绪后,插件通过可选的 shell-env 服务发布 `DSH_PUBLIC_URL`,并通过可选的 system-prompt 服务添加 `app:public-access` 提示段。没有这行插件时,两项贡献都不存在。 提示段渲染为: ```markdown This instance is also reachable from the public internet at through a Cloudflare Tunnel, protected by the instance's shared access password. Share that URL — never the password — when the user asks to open this GUI from another device or network. All sessions, tools, and files still run on this host. ``` 该提示段在隧道进程存活期间保持静态,不会使跨轮 KV cache 失效。