从 FRP 翻车到 Cloudflared 固定域名:DSH 外网访问完整实战教程

其他杂项45字数 3549阅读11分49秒阅读模式

LocalSync2026-08-24

从 FRP 翻车到 Cloudflared 固定域名:DSH 外网访问完整实战教程

起因:想把本机的 DSH(DeepSeek Harness)带出家门,用手机 4G 随时看会话、发消息。中间踩了一路坑:FRP 跨洋太慢、随机隧道域名天天变、证书报错、下载卡死……最后用 Cloudflared 命名隧道 + 固定域名 +「秘密路径免密」一步到位。本文记录完整起因、方案演进、踩坑与最终解法。


一、需求与背景

  • 本机运行 DSH Desktop,Web 界面监听本机回环地址(默认 43120 端口),只有本机浏览器能访问;
  • 想在外网(手机 4G / 公司网络)打开 同一个界面,看到和电脑完全一致的会话列表、实时消息;
  • 两个硬要求:
    1. 地址固定,不想每次变来变去;
    2. 根路径要密码,但想留一条「只有我知道的秘密路径」可以免密直达(个人自用场景)。

二、方案一:自建 FRP 中转(翻车)

做了什么

  • 台 VPS 上起 FRP 服务端(frps),绑定 vhostHTTPPort;
  • 本机跑 frpc 客户端,把 DSH 端口穿透出去;
  • 域名解析到 VPS,nginx 做反代 + Basic Auth + 秘密前缀路由。

踩的坑(按时间线)

  1. 端口 43120 会不会变?
    查源码确认:DSH 的 Web 端口是持久化配置(默认 43120),重启不变,只有被占用才换端口。→ frpc 的 localPort 写死没问题。

  2. 页面白屏(静态资源 404)
    SPA 的资源(/assets/、/plugins/)是绝对路径,秘密路径前缀一提就 404。→ 被迫改为全路径透传 + HTTP Basic Auth。

  3. /api 全部 403(browser-trust fence)
    DSH 有「浏览器信任围栏」:dynamicCordisRunner 等敏感端点即使配了 trusted-host 也强制只认 loopback。前端加载插件时调这些 API 直接 403 → 页面卡在 Loading / Failed to load plugins。
    试过:加 connection.trustedHosts 白名单(对普通端点有效,但对 loopback-only 端点无效);nginx 伪装 Host/Origin 为 127.0.0.1(对部分端点有效,但导致浏览器端 Origin 与页面地址不一致,恢复页)。

  4. WebSocket 长连接被掐
    代理用 HTTP/1.0 + 清空 Connection 头时,前端「connection lost, retry #N」刷屏。→ 换 HTTP/1.1 + Upgrade 支持后解决。

  5. FRP 跨洋线路不稳定
    手机 → VPS(境外)→ 国内电脑,跨洋 TCP 频繁重连(日志里大量 client exit / login 循环),加载慢、会话列表经常拉不出来。→ 这是 FRP 方案的根本死穴,物理距离决定论,优化只能缓解不能根治。

结论

FRP 自建不是不能用,但对「人在国内 + 服务器在境外 + 手机访问 DSH 这种实时界面」的场景,慢、不稳、配置复杂。弃用。

三、方案二:Cloudflared 命名隧道(最终方案)

为什么它快

  • 手机 → 就近的 Cloudflare 边缘节点(全球节点,国内也有)→ 骨干网 → 本机 cloudflared,不再跨洋绕回
  • 相比 FRP 直连 VPS,路径短、线路稳。

最终架构

手机浏览器
  │  https://你的域名/            ← 根路径:要求 8 位访问密码
  │  https://你的域名/秘密路径/    ← 秘密路径:免密直达(自定义 patch)
  ▼
Cloudflare 边缘(DNS: CNAME → 隧道,橙云代理)
  ▼
命名隧道(cloudflared,随系统开机自启)
  ▼
本机 dsh-pocket 代理(默认 3081,负责 Host/Origin 改写 + 密码校验)
  ▼
本机 DSH Web(127.0.0.1:43120)

关键步骤

  1. 用 Cloudflare API 创建命名隧道,拿到隧道 Token(cloudflared tunnel run --token … 即可跑,无需本地凭据文件);
  2. DNS 把域名改成 CNAME → 隧道ID.cfargotunnel.com,开橙云代理(不能是灰云裸 A 记录);
  3. 本机 cloudflared 指向 dsh-pocket 代理cloudflared tunnel run --token … --url http://127.0.0.1:3081
  4. 创建系统计划任务,开机自启。

踩坑补充(方案二阶段)

  1. 证书报错 ERR_CERT_COMMON_NAME_INVALID
    Cloudflare 边缘证书本身合法(SAN 含泛域名),但手机 DNS 缓存旧记录(旧 FRP 的 A 记录),连到旧服务器 → 报证书错。解决:清 DNS 缓存 / 等运营商缓存过期。

  2. cloudflared 下载卡死(“已等待 67 秒…”)
    下载源 GitHub releases 国内慢。解决:直接把已装插件 bundle 里的 cloudflared.exe(约 54MB)复制到 $DSH_HOME/dsh-pocket/bin/cloudflared-windows-amd64.exe,插件启动时检测到就直接用,不再下载。

  3. 临时隧道域名天天变
    *.trycloudflare.com 是 quick tunnel,每次重启都换。→ 正是因为要固定域名,才用命名隧道 + 自定义域名。

四、「秘密路径免密」的实现(核心技巧)

需求

  • 根路径 https://你的域名/要密码
  • 秘密路径 https://你的域名/一段随机路径/免密直达

原理

dsh-pocket 代理(proxy.mjs)的认证逻辑是:

function authCheck(req, token) {
  if (!token) return true;                      // 没配密码 → 直接放行
  const cookieTok = parseCookies(req.headers.cookie)['dsh_pocket_token'];
  if (cookieTok === token) return true;         // cookie 带正确 token → 放行
  const qTok = new URL(req.url, 'http://x').searchParams.get('token');
  return qTok === token;                        // URL ?token= 正确 → 放行
}

即:只要浏览器带了一个有效的会话 Cookie,就放行所有请求(含 SPA 的资源 / API)

补丁做法(在代理主处理块之前插入)

// 秘密路径免密入口
if (req.url.startsWith('/你的秘密路径/') || req.url === '/你的秘密路径') {
  // 1) 剥前缀:秘密路径 → 根路径(DSH SPA 只认根路径)
  req.url = req.url === 前缀 ? '/' : req.url.slice(前缀长度) || '/';
  // 2) 若浏览器还没有有效 token cookie,301 到目标并种下 cookie(等价自动登录)
  if (cookie['dsh_pocket_token'] !== theToken) {
    res.writeHead(302, {
      location: req.url,
      'set-cookie': 'dsh_pocket_token=' + theToken + '; HttpOnly; SameSite=Lax; Path=/',
    });
    res.end();
    return;
  }
}

效果

  • 第一次打开秘密路径 → 302 跳转到根路径并种 cookie → 浏览器自动带上 cookie → 之后所有请求(页面/资源/API)都免密
  • 直接打开根路径(无 cookie)→ 走正常认证 → 仍要求输入密码
  • 浏览器关闭后 cookie 失效(HttpOnly 会话 cookie)→ 下次再访问秘密路径又自动「登录」一次。

注意:这是「路径即钥匙」的轻量门禁,只适合个人单用户、域名不公开的场景。生产级多用户请用正式认证(如 Basic Auth / Cloudflare Access)。

五、最终效果与维护

  • 固定域名 + 命名隧道:地址永不变化
  • 根路径密码:固定 8 位数字(写在 $DSH_HOME/dsh-pocket/token-lan,不点「刷新」就不会变);
  • 秘密路径:免密直达,方便日常手机快速进入;
  • 开机自启:cloudflared 命名隧道注册为系统计划任务,DSH 重启自动恢复;
  • 升级提醒:dsh-pocket 插件升级会还原 proxy.mjs 的补丁,升级后需重新打补丁(先备份原文件 proxy.mjs.bak)。

六、心得

  1. 先想清楚「物理链路」再动手:自建 VPS 跨洋,路由再优化也快不过 Cloudflare 边缘就近;
  2. DSH 的安全设计很严格:browser-trust fence 对 loopback-only 端点不会因为配了信任域名就放松,别在对外代理层硬扛;
  3. 复用现成生态:dsh-pocket 这类插件把「代理改写 + 隧道 + 密码」全包了,比自己手搓 frp+nginx 干净得多;
  4. 补丁要留备份 + 记录:改 node_modules 里的源码,一定留 .bak,升级后能快速恢复。

 
  • 本文由 asdfasd 发表于 2026-08-2419:38:42
  • 转载请务必保留本文链接:http://wp.fangfa.me/other-note/3ffd13369950.html