dsh-cftunnel
DeepSeek Harness 的 bundle 插件:通过 Cloudflare Tunnel 把本地 web GUI(http://127.0.0.1:3080)暴露到公网。
装好插件,打开 Settings,填 Cloudflare 信息,点连接。 cloudflared 由插件自动下载管理——不用手动安装、不用包管理器、不用重启。
快速上手
1. 安装——一条命令:
``sh dsh plugin --profile web add git+https://github.com/weicy98/dsh-cftunnel ``
2. 重启 dsh web,打开 Settings → Cloudflare Tunnel。 3. 选模式、填信息、点连接。 复制带 token 的访问链接分享出去。
> 首次连接会下载一次 cloudflared(约 40MB),慢网络可能要几分钟——面板里有进度条显示下载进度;
模式
按你手头有什么选:
| 模式 | 输入 | URL | 适合场景 |
|---|---|---|---|
| 快速隧道 | 无 | 随机 https://<名字>.trycloudflare.com | 想立刻要个链接,不想配 Cloudflare 账号 |
| 隧道 Token | 隧道 token + 域名 | https://<域名> | 已经在 Zero Trust 仪表盘建好了隧道 |
| API Token | API token + 隧道名 + 域名 | https://<域名> | 让插件自动建隧道、DNS,并可顺带开邮箱验证 |
端到端流程
1. Settings 面板里选模式(快速隧道 / 隧道 Token / API Token),点 连接。 2. 连接时插件先自动准备 cloudflared——$DSH_HOME/cloudflared/cloudflared 不存在就从 GitHub 官方 release 下载对应平台二进制并解压缓存。 3. 启动隧道: - 快速隧道:cloudflared tunnel --url http://127.0.0.1:<proxyPort> → 随机 https://<名字>.trycloudflare.com。 - 隧道 Token:cloudflared tunnel run,用 Zero Trust 仪表盘生成的 token(路由/ingress 在仪表盘配置)。 - API Token:通过 Cloudflare HTTP API 自动创建隧道 + 配置 ingress + 建 DNS CNAME,再用返回的 token 运行(不需要 cert.pem,不需要本地 config.yml)。 4. 所有隧道流量经本地反向代理(127.0.0.1:<proxyPort>):代理把 Host 改写为 loopback、删除 Origin,harness 信任围栏全放行(含特权方法)。代理前有访问 token 校验(默认开启)和可选的 Cloudflare Access 邮箱 OTP 两道门。 5. 秘密(API token、隧道 token、代理访问 token)存凭据存储(~/.dsh/.credentials.yaml);非秘密偏好(模式、域名、隧道名、账户/Zone ID、邮箱验证设置)存 settings 命名空间(~/.dsh/settings.yaml),下次打开自动预填。
安装
在本目录下:
dsh plugin --profile web add file:.或从 git / npm 源安装:
dsh plugin --profile web add git+https://github.com/weicy98/dsh-cftunnel
dsh plugin --profile web add dsh-cftunneldsh plugin 在 profile 目录执行 pnpm 并同步 dsh.profile.bundles——本包声明了 dsh.bundle.patch,所以会自动加进 bundle 栈。重启 dsh web,打开 Settings → Cloudflare Tunnel。
> 改代码后重新安装:先 dsh plugin --profile web remove dsh-cftunnel 再 add file:.(pnpm 把 file: 依赖视为不可变,remove + add 是最可靠的刷新方式)。
配置
部署级选项写在 profile 自己的 cordis.patch.yml(~/.dsh/profiles/web/cordis.patch.yml)或 --patch 覆盖层;其余都是 Settings 面板字段,持久化在 settings 命名空间。
- id: cloudflare-tunnel
config:
cloudflaredPath: 'cloudflared' # 显式二进制路径(默认:自动下载到 $DSH_HOME/cloudflared)
port: 3080 # 要代理的本地 harness 端口
downloadMirror: '' # 可选 ghproxy 风格镜像前缀,如 'https://ghproxy.example/'
proxyPort: 3091 # 反向代理的 loopback 端口
proxyAuth: 'token' # 'token'(默认)| 'none'
proxyToken: '' # 可选固定访问 token;留空 = 自动生成并持久化
accessPolicy: 'none' # 'none' | 'emailOtp'
accessEmailDomains: [] # 如 ['yourcompany.com'] — 限制邮箱 OTP 发送域
accessAccountId: '' # 可选显式 Cloudflare 账户 ID
accessZoneId: '' # 可选显式 Cloudflare Zone ID说明:
downloadMirror控制一次性下载方式。留空(默认)走 GitHub API → CDN——权威但在部分网络较慢(实测约 80 KB/s,40MB 约 8 分钟)。配镜像前缀可加速;镜像属于第三方,需信任其不篡改二进制。accessAccountId/accessZoneId也可以在设置面板里填,填了就不需要 API token 的Account Settings:Read/Zone:Read权限。proxyAuth: 'none'关闭访问 token 校验(UI 关闭前会二次确认)。
安全模型(重要)
所有隧道流量经本地反向代理(127.0.0.1:<proxyPort>),代理把请求改写为 loopback(Host → 127.0.0.1:<port>、删除 Origin),因此 harness 信任围栏完全放行——包括特权方法,远程访客获得与本机一致的完整体验。
代理只绑定 127.0.0.1,唯一入口是 cloudflared(Cloudflare edge)。它前面有两道独立门:
- 访问 token(默认开启,
proxyAuth: 'token'):每个请求必须带 token——首次以?token=…带一次(代理随后种下HttpOnly; Securecookie),之后靠 cookie 自动携带。面板会显示可分享的带 token 链接,开关实时生效。 - Cloudflare Access 邮箱 OTP(可选,
accessPolicy: 'emailOtp'):edge 层邮箱验证,请求到代理前就被拦截。
建议:公网暴露时两道门都开。两道都关意味着任何拿到 URL 的人都能完全控制 harness(设置、凭据、目录选择器)——只在可信网络才可接受。
本机使用(http://127.0.0.1:3080)不经过代理,完全不受影响。
邮箱验证(Email OTP)
把 accessPolicy 设为 'emailOtp'(或在面板里勾选)即可用 Cloudflare Access 一次性 PIN 拦住隧道。连接时插件用同一个 API token 创建绑定该域名的 self-hosted Access 应用和 allow 策略:
- 不配白名单:任何访客输入邮箱 → 收 6 位 PIN → 输入正确才放行。
- 白名单填完整邮箱(
user@example.com):精确邮箱规则(Access 里的 "Emails")。 - 白名单填域名(
yourcompany.com):邮箱域名规则(Access 里的 "Emails ending in")。
要求:
- 仅 API Token 模式(quick / token 隧道的域名不由本插件程序化管理,无法自动挂策略)。
- API token 额外需要
Access: Organizations, Identity Providers, and Groups:Edit;未配accessAccountId时还需要Account Settings:Read来解析账户 ID。
API token 权限清单
API Token 模式需要(面板里也有提示):
| 资源 | 权限 | 用途 |
|---|---|---|
| Account | Cloudflare Tunnel — Write | 建隧道、配 ingress |
| Zone | DNS — Edit | 建 CNAME 记录 |
| Account | Account Settings — Read | 解析账户 ID(填了 Account ID 可省) |
| Zone | Zone — Read | 解析 Zone ID(填了 Zone ID 可省) |
| Account | Access: Organizations, Identity Providers, and Groups — Write | 仅邮箱验证需要 |
前置条件
- 带 web 界面的 profile(
dsh web/--profile web)。 - 隧道 Token 模式:Zero Trust 仪表盘生成的隧道 token,且公共域名路由到
http://127.0.0.1:3091(loopback 代理端口——路由到3080会绕过访问 token 门)。 - API Token 模式:具备上述权限的 API token,且域名的 DNS zone 在该账户上。
- 首次连接需要能访问
github.com(只用于下载cloudflared,之后缓存)。
开发
pnpm install # dsh-settings(dev/peer)、schemastery
pnpm test # node --test(cfapi、cloudflared、主机生命周期、proxy 四套)架构
| 关注点 | 位置 | 机制 |
|---|---|---|
准备 cloudflared | lib/cloudflared.js | GitHub release 下载 + tar 解压 + 缓存到 $DSH_HOME/cloudflared |
| 运行/管理隧道 | lib/index.js | node:child_process spawn + ctx.effect 清理 |
| Cloudflare API | lib/cfapi.js | cfd_tunnel 创建 + configurations ingress + dns_records CNAME + Access 应用/策略 |
| Loopback 代理 | lib/proxy.js | Host 改写 loopback + 删 Origin + 访问 token 门(cookie / ?token=)+ WS 转发——零依赖(node:http + node:net,不再依赖 http-proxy / DEP0060) |
| 依赖 | package.json | schemastery 作为叶子依赖;宿主 seam(dsh-settings)放 peerDependencies;engines.node >= 20.3 |
| 秘密 | lib/index.js | ctx.credentials(CLOUDFLARE_API_TOKEN、CLOUDFLARE_TUNNEL_TOKEN、CLOUDFLARE_TUNNEL_PROXY_TOKEN) |
| 偏好 | lib/index.js | ctx.settings 命名空间 cloudflare-tunnel |
| Client→Host RPC | 两端 | ctx.connection.rpc.handle('/cloudflare-tunnel', …) / ctx.get('connection').rpc.call(…) |
| 设置 UI | lib/client.js | ctx.slots.inject('settings.section', …) + locale 字典 |
| Bundle 接线 | cordis.patch.yml + package.json dsh | 单行 insert,自动加入 dsh.profile.bundles |
/cloudflare-tunnel 通道的端点:status、start、stop、setProxyAuth。start 接受 AbortSignal,UI 取消会中止正在进行的下载与 API 配置。
已知限制
- 仅 web profile(node 半等待
connection、credentials、settings、webServer;headless/TUI 下保持休眠)。 - 进程内有效:运行中的隧道不跨 harness 重启,但缓存的二进制、存储的秘密与偏好会保留;隧道、ingress、DNS 记录持久化在 Cloudflare 侧。
- API token 只用于 Cloudflare API 配置调用(隧道/ingress/DNS/Access);运行中的
cloudflared用隧道 token 认证,不用 API token。