This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
# Cloudflare Tunnel(cloudflared)与 Fonu 的融合方案
|
||||
# Cloudflare Tunnel(cloudflared)与 Havline 的融合方案
|
||||
|
||||
> 日期:2026-09-25,v2 修订 2026-09-28 对象:Fonu 1.5.4(VERSION 1.5.4)
|
||||
> 日期:2026-09-25,v2 修订 2026-09-28 对象:Havline 1.5.4(VERSION 1.5.4)
|
||||
> 方法:先查 Cloudflare 官方文档与同类开源项目,再逐个核对本仓库现有可复用面(下文带 `文件:行` 的都是读过代码得出的);v2 逐文件核对了 `F:\WWW\cf\Tunnel-Cloudflared-linux`(Tunnel-Cloudflared-Manager 1.0.1)源码。
|
||||
> 结论速览:**值得做,但建议以「第二条出口」的形态并存,先做 locally-managed + 进程托管,不接 Cloudflare API**。v2 修订了部署位置、模式生效侧、metrics / 健康与二进制校验四处关键细节,详见 §五、§六、§十二;实施以 §十二 为准。
|
||||
|
||||
---
|
||||
|
||||
## 一、为什么值得做(对 Fonu 的战略意义)
|
||||
## 一、为什么值得做(对 Havline 的战略意义)
|
||||
|
||||
Fonu 现在的穿透链路**必须有公网服务端**:本机 `frpc` → VPS 上的 `frps` → 域名。用户得有一台带公网 IP 的云主机,还得自己放行端口、签证书、配 DNS。
|
||||
Havline 现在的穿透链路**必须有公网服务端**:本机 `frpc` → VPS 上的 `frps` → 域名。用户得有一台带公网 IP 的云主机,还得自己放行端口、签证书、配 DNS。
|
||||
|
||||
Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `cloudflared` **主动向外**建立到边缘的长连接(出站 only),公网侧不需要任何监听端口,也不需要公网 IP——顺带白拿 CF 的 WAF / DDoS 防护与自动 HTTPS。
|
||||
|
||||
对 Fonu 的用户画像(家庭/个人 NAS)来说,这正好补上一类人:**没有实体公网 IP、或不想为穿透长期租 VPS**的人。
|
||||
对 Havline 的用户画像(家庭/个人 NAS)来说,这正好补上一类人:**没有实体公网 IP、或不想为穿透长期租 VPS**的人。
|
||||
|
||||
但要说清楚它**替代不了什么**(这决定了它是「并存」而不是「替换」):
|
||||
|
||||
@@ -30,16 +30,16 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
|
||||
## 二、同类项目(检索结果)
|
||||
|
||||
| 项目 | 做什么 | 对 Fonu 的借鉴 |
|
||||
| 项目 | 做什么 | 对 Havline 的借鉴 |
|
||||
| --- | --- | --- |
|
||||
| [WisdomSky/Cloudflared-web](https://github.com/WisdomSky/Cloudflared-web)(≈451★,Vue) | Docker 镜像里打包 `cloudflared` + 简单 Web UI,用来 start/stop **remotely-managed** 隧道 | **最接近的同位产品**:证明「面板管 cloudflared 进程」这个形态成立;但它的能力止步于开关,规则仍在 CF 面板里——这正是 Fonu 可以做得更好的地方(规则同源生成) |
|
||||
| [adyanth/cloudflare-operator](https://github.com/adyanth/cloudflare-operator)(≈691★) | Kubernetes Operator,用 **CF API** 创建/管理 Tunnel 与 DNS | 演示了「API 驱动」的完整闭环;但需要 API Token + Account/Zone ID,是第二个凭据面(Fonu 第一轮不建议走,见 §六) |
|
||||
| [WisdomSky/Cloudflared-web](https://github.com/WisdomSky/Cloudflared-web)(≈451★,Vue) | Docker 镜像里打包 `cloudflared` + 简单 Web UI,用来 start/stop **remotely-managed** 隧道 | **最接近的同位产品**:证明「面板管 cloudflared 进程」这个形态成立;但它的能力止步于开关,规则仍在 CF 面板里——这正是 Havline 可以做得更好的地方(规则同源生成) |
|
||||
| [adyanth/cloudflare-operator](https://github.com/adyanth/cloudflare-operator)(≈691★) | Kubernetes Operator,用 **CF API** 创建/管理 Tunnel 与 DNS | 演示了「API 驱动」的完整闭环;但需要 API Token + Account/Zone ID,是第二个凭据面(Havline 第一轮不建议走,见 §六) |
|
||||
| [qingchencloud/cftunnel](https://github.com/qingchencloud/cftunnel)(≈338★) | 轻量隧道控制 | 佐证需求存在 |
|
||||
| [brenner-tobias/addon-cloudflared](https://github.com/brenner-tobias/addon-cloudflared) | Home Assistant 插件,locally-managed 与 remotely-managed 两种模式都支持 | HA 社区的做法可参考:**两种模式并存**,文档把差异讲透 |
|
||||
| [cloudflare/cloudflared](https://github.com/cloudflare/cloudflared) | 官方客户端(Apache-2.0) | 我们的被托管对象;GitHub Release 提供多平台二进制 |
|
||||
| 对照项:[Pangolin](https://github.com/fosrl/pangolin) / Tailscale Funnel / ngrok | 同类「免公网 IP」方案 | Tailscale Funnel 只开 HTTPS 且端口数量有限、ngrok 免费档收紧;CF Tunnel 免费档最宽松,这也是它值得优先支持的原因 |
|
||||
|
||||
`docs/feature-benchmark-2026-09-24.md` 里已经把 Pangolin 列为架构最近的同类项目;Cloudflare Tunnel 的差别在于:**它把「服务端」外包给了 Cloudflare,Fonu 只需管好本机客户端**。
|
||||
`docs/feature-benchmark-2026-09-24.md` 里已经把 Pangolin 列为架构最近的同类项目;Cloudflare Tunnel 的差别在于:**它把「服务端」外包给了 Cloudflare,Havline 只需管好本机客户端**。
|
||||
|
||||
---
|
||||
|
||||
@@ -51,23 +51,23 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
| 规则来源 | 本机 `config.yml` 的 `ingress:` | **CF 面板 / API**(本地 `config.yml` 会被**忽略**) |
|
||||
| 建隧道 | `cloudflared tunnel create <name>` | CF 面板,或 `POST /accounts/{id}/cfd_tunnel` |
|
||||
| DNS | `cloudflared tunnel route dns <tunnel> <hostname>` | CF 面板自动建 CNAME |
|
||||
| 与 Fonu 的价值取向 | ✅ **契合**:反代规则由 Fonu 同源生成(和现在生成 `frps.toml` / nginx vhost 一个路子) | ⚠️ 冲突:规则跑到 CF 面板去了,Fonu 沦为「进程开关」 |
|
||||
| 与 Havline 的价值取向 | ✅ **契合**:反代规则由 Havline 同源生成(和现在生成 `frps.toml` / nginx vhost 一个路子) | ⚠️ 冲突:规则跑到 CF 面板去了,Havline 沦为「进程开关」 |
|
||||
|
||||
官方文档确认了这个分界([配置参考](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/local-management/configuration-file)),社区也踩过坑:给 remotely-managed 隧道传 `--config` 时配置会被云端覆盖,日志里出现 `No ingress rules were defined ... cloudflared will return 503`([cloudflared#1029](https://github.com/cloudflare/cloudflared/issues/1029))。
|
||||
|
||||
**结论:第一轮只支持 locally-managed。** 若用户只想粘贴 token,提供「仅托管进程」的降级模式并**在界面上明确写清「规则由 Cloudflare 面板管理」**,不要让用户以为 Fonu 生成的规则生效了。
|
||||
**结论:第一轮只支持 locally-managed。** 若用户只想粘贴 token,提供「仅托管进程」的降级模式并**在界面上明确写清「规则由 Cloudflare 面板管理」**,不要让用户以为 Havline 生成的规则生效了。
|
||||
|
||||
> 补充(参考 §九 后修正):这个二分法不够细。参考实现证明**token 可以解码**(base64 的 JSON,含 `AccountTag` / `TunnelID` / `TunnelSecret`),用它自己生成 `creds.json` 就能按 locally-managed 跑本地 `config.yml`;再配合「云端 ↔ 本地 ingress 互相同步」,两种模式实际上是可切换的。所以 Fonu 的正确做法不是「二选一」,而是**显式区分并同步**:当前生效的是本地 `config.yml` 还是云端 ingress,以及「推送/拉取」两个动作。**v2 已把模式拆成三种并明确生效侧,见 §12.3。**
|
||||
> 补充(参考 §九 后修正):这个二分法不够细。参考实现证明**token 可以解码**(base64 的 JSON,含 `AccountTag` / `TunnelID` / `TunnelSecret`),用它自己生成 `creds.json` 就能按 locally-managed 跑本地 `config.yml`;再配合「云端 ↔ 本地 ingress 互相同步」,两种模式实际上是可切换的。所以 Havline 的正确做法不是「二选一」,而是**显式区分并同步**:当前生效的是本地 `config.yml` 还是云端 ingress,以及「推送/拉取」两个动作。**v2 已把模式拆成三种并明确生效侧,见 §12.3。**
|
||||
|
||||
### 健康探测(这点比 frp 更省事)
|
||||
|
||||
`cloudflared tunnel --metrics 127.0.0.1:<port> run --config ...` 会暴露 HTTP 端点,其中 **`/ready` 在有到边缘的活跃连接时返回 200**(CF 官方在 k8s livenessProbe 里就是这么用的)。Fonu 现在为了判断「隧道通不通」要绕 frps dashboard,还专门加过 `tunnel_known` 来区分「判不出来」和「不通」;CF Tunnel 则有明确的就绪端点,可以直接喂给现有健康链路。
|
||||
`cloudflared tunnel --metrics 127.0.0.1:<port> run --config ...` 会暴露 HTTP 端点,其中 **`/ready` 在有到边缘的活跃连接时返回 200**(CF 官方在 k8s livenessProbe 里就是这么用的)。Havline 现在为了判断「隧道通不通」要绕 frps dashboard,还专门加过 `tunnel_known` 来区分「判不出来」和「不通」;CF Tunnel 则有明确的就绪端点,可以直接喂给现有健康链路。
|
||||
|
||||
---
|
||||
|
||||
## 四、本仓库可复用的面(已核对代码)
|
||||
|
||||
| 能力 | Fonu 现有实现 | 复用方式 |
|
||||
| 能力 | Havline 现有实现 | 复用方式 |
|
||||
| --- | --- | --- |
|
||||
| 二进制下载 / 启用 | `Service.ListReleases`(`internal/frp/service.go:112`)、`ListBinaries`(`:120`)、`ActivateBinary`(`:258`);`internal/frp/runtime.go` 的 `fetchOfficialReleases` / `listBinaries` / `readActiveBinary` / `writeActiveBinary` / `versionFromBinaryPath` | cloudflared 同样发 GitHub Release,**同一套框架泛化成「可指定仓库」即可**(现在 URL 拼的是 frp 的 release 页) |
|
||||
| 客户端进程托管 | `internal/frp/manager.go`:`os.OpenFile(logPath, O_CREATE\|O_APPEND\|O_WRONLY, 0600)` + `cmd.Stdout/Stderr` 重定向 + 状态/退出原因 + `tailFile` 读日志尾 | 与 `cloudflared tunnel run` 完全同构,可直接复用(日志追加、不截断的语义也一致) |
|
||||
@@ -87,7 +87,7 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
|
||||
- 管理一份 cloudflared 二进制(复用 §四 的下载/启用框架)。
|
||||
- 管理 N 个隧道实例,每个实例 = 一份 `config.yml` + credentials + 日志文件 + 子进程。
|
||||
- `ingress` 规则**由 Fonu 生成**:从本机已有的反代规则里挑「要暴露的域名」,映射到 `http://127.0.0.1:<本地端口>`,模板与 nginx vhost 同源风格;末尾固定给 catch-all `service: http_status:404`(CF 要求最后一条兜底)。
|
||||
- `ingress` 规则**由 Havline 生成**:从本机已有的反代规则里挑「要暴露的域名」,映射到 `http://127.0.0.1:<本地端口>`,模板与 nginx vhost 同源风格;末尾固定给 catch-all `service: http_status:404`(CF 要求最后一条兜底)。
|
||||
- 健康:`--metrics` + `/ready`,写入现有健康与可用率链路;通知复用(隧道断开/恢复)。
|
||||
- 前端:与「公网服务端」同构的列表 + 详情(状态 / 日志 / 配置预览 / 下发)。
|
||||
|
||||
@@ -106,7 +106,7 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
|
||||
只生成 `config.yml` 与 DNS 指引,让用户自己跑 `cloudflared`。
|
||||
|
||||
**不建议**:不接管进程就没有状态、日志、健康、可用率,等于放弃 Fonu 的全部价值。
|
||||
**不建议**:不接管进程就没有状态、日志、健康、可用率,等于放弃 Havline 的全部价值。
|
||||
|
||||
---
|
||||
|
||||
@@ -119,7 +119,7 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
1. 新增「Cloudflare 隧道」入口:粘贴 **token**(remotely-managed)或上传 **credentials JSON + tunnel UUID**(locally-managed),凭据加密存储。
|
||||
2. 生成 `config.yml` 预览(**脱敏**,沿用 v1.2.12 的预览/下发两分支约定)并把文件写到 `<DataDir>/cloudflared/<name>/config.yml`。
|
||||
3. 界面给出 CF 侧操作指引(建隧道、CNAME、`cloudflared tunnel route dns`),**不接管进程**。
|
||||
4. 明确提示模式差异:locally-managed 才由 Fonu 生成规则。
|
||||
4. 明确提示模式差异:locally-managed 才由 Havline 生成规则。
|
||||
|
||||
### 阶段 2:托管进程与健康(核心价值)
|
||||
|
||||
@@ -146,7 +146,7 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
- TryCloudflare(快速隧道:`cloudflared tunnel --url http://localhost:PORT`)官方明确「不保证 SLA 与可用性,仅供测试与开发」,域名随机、每次重启变化——**不作为正式功能**,最多做成「临时演示」按钮。
|
||||
|
||||
**网络可用性**
|
||||
- 中国大陆访问 CF 边缘的延迟与稳定性不稳定,这对 Fonu 的用户群是**现实问题**:建议在文档与界面上对比说明「CF 适合海外访问优先的场景,国内链路以 frp + 自持 VPS 更可控」。
|
||||
- 中国大陆访问 CF 边缘的延迟与稳定性不稳定,这对 Havline 的用户群是**现实问题**:建议在文档与界面上对比说明「CF 适合海外访问优先的场景,国内链路以 frp + 自持 VPS 更可控」。
|
||||
- 出站 only 意味着 VPS 上不需要任何放行端口,但也就**读不到**「CF 侧有哪些域名指向了这条隧道」——相关校验只能靠 `cloudflared` 自身命令,不能靠 agent。
|
||||
|
||||
**实现边界**
|
||||
@@ -182,29 +182,29 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
### 9.2 值得直接借鉴的六个做法
|
||||
|
||||
**① token 可以解码成凭据,于是 token 模式也能本地管规则**
|
||||
`initialize_token_tunnel` 把 CF 隧道 token 解成 `{AccountTag, TunnelID, TunnelSecret}` 并写出 `creds.json` + `config.yml`——即「用户只给一个 token,但规则仍由面板本地生成」。这直接提升了 Fonu 方案的上限(§三 的补充已按此修正)。
|
||||
`initialize_token_tunnel` 把 CF 隧道 token 解成 `{AccountTag, TunnelID, TunnelSecret}` 并写出 `creds.json` + `config.yml`——即「用户只给一个 token,但规则仍由面板本地生成」。这直接提升了 Havline 方案的上限(§三 的补充已按此修正)。
|
||||
|
||||
**② metrics 端口不去猜,从日志里读出来**
|
||||
命名隧道启动时**故意不传 `--metrics`**,让 cloudflared 用默认端口段(20241-20245),然后从 stderr 解析 `INF starting metrics server on 127.0.0.1:65432/metrics` 拿到真实端口——天然避开端口冲突(Fonu 现在给 frps dashboard 固定端口,多实例时会撞)。
|
||||
命名隧道启动时**故意不传 `--metrics`**,让 cloudflared 用默认端口段(20241-20245),然后从 stderr 解析 `INF starting metrics server on 127.0.0.1:65432/metrics` 拿到真实端口——天然避开端口冲突(Havline 现在给 frps dashboard 固定端口,多实例时会撞)。
|
||||
|
||||
**③ `localhost` 必须归一化成 `127.0.0.1`**
|
||||
`add_tunnel_endpoint` 里把 `localhost` 换成 `127.0.0.1`,注释写明「避免 Cloudflare 将其转换为本地网络 IP」。这是 CF 侧的真实坑,Fonu 生成 `ingress` 时要照做。
|
||||
`add_tunnel_endpoint` 里把 `localhost` 换成 `127.0.0.1`,注释写明「避免 Cloudflare 将其转换为本地网络 IP」。这是 CF 侧的真实坑,Havline 生成 `ingress` 时要照做。
|
||||
|
||||
**④ 始终保留最后一条 catch-all**
|
||||
新增端点插在「数组倒数第一位之前」(`insert_pos = len - 1`),末条永远是 `service: http_status:404`。Fonu 改 `ingress` 时必须沿用这个不变式,否则配置校验不过。
|
||||
新增端点插在「数组倒数第一位之前」(`insert_pos = len - 1`),末条永远是 `service: http_status:404`。Havline 改 `ingress` 时必须沿用这个不变式,否则配置校验不过。
|
||||
|
||||
**⑤ 带宽指标:官方 metrics 没有 HTTP/2 字节数,它做了系统级兜底**
|
||||
- QUIC 下直接读 `quic_client_receive_bytes` / `quic_client_sent_bytes` / `quic_client_smoothed_rtt`。
|
||||
- HTTP/2 下官方无字节数,改用**按进程采 TCP 连接字节**:Linux 走 netlink `SOCK_DIAG_BY_FAMILY` + `/proc/<pid>/fd` 取 socket inode;Windows 走 `GetExtendedTcpTable` + `GetPerTcpConnectionEStats`(需要管理员,失败时给 `windows_estats_requires_admin` 降级原因)。
|
||||
- 并且**只统计公网远端 IP**(过滤私有/回环/链路本地/`100.64/10` CGNAT),延迟兜底用**主动 HEAD 探测** ingress 里的公网 hostname(带 `?__cf_probe=<nonce>` + no-cache)。
|
||||
> 对 Fonu 的意义:这一块可以**先不做**(我们已有规则级流量与可用率),但「降级要给出明确原因而不是空白」这个原则要学。
|
||||
> 对 Havline 的意义:这一块可以**先不做**(我们已有规则级流量与可用率),但「降级要给出明确原因而不是空白」这个原则要学。
|
||||
|
||||
**⑥ Windows 下起子进程要隐藏控制台窗口**
|
||||
`creation_flags(CREATE_NO_WINDOW)`(`0x08000000`)。Fonu 本机在 Windows 上跑,托管 `cloudflared.exe` 时必须带上,否则会闪黑窗。
|
||||
`creation_flags(CREATE_NO_WINDOW)`(`0x08000000`)。Havline 本机在 Windows 上跑,托管 `cloudflared.exe` 时必须带上,否则会闪黑窗。
|
||||
|
||||
### 9.3 与 Fonu 的差异(决定「借鉴什么、不借鉴什么」)
|
||||
### 9.3 与 Havline 的差异(决定「借鉴什么、不借鉴什么」)
|
||||
|
||||
| 维度 | cf-tunnel-manager | Fonu 该怎么做 |
|
||||
| 维度 | cf-tunnel-manager | Havline 该怎么做 |
|
||||
| --- | --- | --- |
|
||||
| 职责 | **专做** Cloudflare Tunnel 的一个独立面板 | 把 CF 作为**第二条出口**并入现有规则/健康/通知/状态页体系 |
|
||||
| 存储 | JSON 文件(`data/*.json`)+ `data/tunnels/<名>/` | 继续用 SQLite + 迁移;凭据进 `secret.Box` 加密字段 |
|
||||
@@ -212,11 +212,11 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
| 健康 | metrics 端点 scraping + 主动探测(仅前端展示) | 接入 `internal/monitor` 的 `Source`/`HealthRecorder`,落库可用率 + 复用告警与通知 |
|
||||
| 实时日志 | WebSocket 广播(专为「看着它滚」设计) | 沿用现有「读日志文件 + 日志页」模型(与 frpc 一致,无需引入 WS) |
|
||||
| 多实例 | 多隧道并行(每隧道一个子进程) | 同模;但用 SQLite 建 `tunnels` 表而不是内存 HashMap |
|
||||
| 认证 | 自带 JWT + 账号体系 | 复用 Fonu 现有会话 + API Token |
|
||||
| 认证 | 自带 JWT + 账号体系 | 复用 Havline 现有会话 + API Token |
|
||||
|
||||
### 9.4 对方案本体的修订建议
|
||||
|
||||
1. **方案 A 的第一阶段就加「凭据导入」两条路**:`token`(解码出 creds.json)与 `cert.pem + credentials JSON`(账号模式),二者最终都落到「本地 `config.yml` 由 Fonu 生成」。
|
||||
1. **方案 A 的第一阶段就加「凭据导入」两条路**:`token`(解码出 creds.json)与 `cert.pem + credentials JSON`(账号模式),二者最终都落到「本地 `config.yml` 由 Havline 生成」。
|
||||
2. **阶段 2 把「metrics 端口从日志发现」写成实现约定**,不要固定端口。
|
||||
3. **阶段 3 增加「云端 ↔ 本地同步」两个动作**(拉取 / 推送),并在界面上标明当前生效侧——这是参考项目比原方案更完整的地方。
|
||||
4. **`ingress` 生成器锁定三条不变式**:末条 catch-all、`localhost` → `127.0.0.1`、插入位置在 catch-all 之前;并补一个「生成后自检」的纯函数测试(参照本仓 `maskFRPSSecrets` 的测试写法)。
|
||||
@@ -234,7 +234,7 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
| 手段 | 具体做法 | 适用与代价 |
|
||||
| --- | --- | --- |
|
||||
| **选协议** | `--protocol quic` / `http2` | 国内 QUIC(UDP 7844)常被 QoS 掉,`http2`(TCP 7844)反而稳;这是**成本最低、收益最直接**的一项 |
|
||||
| **选地址族** | `--edge-ip-version 4` / `6` / `auto`,或环境变量 `TUNNEL_EDGE_IP_VERSION` | ⚠️ **2026.4.0 起默认值由 `4` 改成 `auto`**(官方 CHANGES 里的 Breaking Change)——`auto` 会跟着系统解析器走,国内环境若 IPv6 路由差就可能反而变慢。**建议 Fonu 显式下发 `--edge-ip-version 4`**(除非用户自己有 IPv6 好线路) |
|
||||
| **选地址族** | `--edge-ip-version 4` / `6` / `auto`,或环境变量 `TUNNEL_EDGE_IP_VERSION` | ⚠️ **2026.4.0 起默认值由 `4` 改成 `auto`**(官方 CHANGES 里的 Breaking Change)——`auto` 会跟着系统解析器走,国内环境若 IPv6 路由差就可能反而变慢。**建议 Havline 显式下发 `--edge-ip-version 4`**(除非用户自己有 IPv6 好线路) |
|
||||
| **优选边缘 IP** | 对 `region1.v2.argotunnel.com` / `region2.v2.argotunnel.com` 做延迟测速,选中最快的官方边缘 IP,写 hosts 覆盖解析 | 官方文档给出固定端点与端口:这类域名指向 CF 的 IPv4 段(`198.41.192.7`~`198.41.192.127`、`162.159.234.1`~`162.159.234.10`)与 **端口 7844(TCP/UDP)**。这是社区「Tunnel 优选 IP」的通行做法 |
|
||||
|
||||
**优选时两条不能错的约束**:
|
||||
@@ -250,19 +250,19 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
|
||||
也就是说:换域名解析 / 让访客改 hosts 都不现实,**访问侧这一段在 Tunnel 架构下就是交给 CF 的**。
|
||||
|
||||
### 对 Fonu 方案的落点
|
||||
### 对 Havline 方案的落点
|
||||
|
||||
| 阶段 | 做什么 | 依据 |
|
||||
| --- | --- | --- |
|
||||
| 阶段 2(建议直接做) | 「网络调优」三项做成设置项:`protocol`(默认 http2)、`edge-ip-version`(**默认 4**)、`ha-connections`;以命令行参数下发 | 参考实现已验证可行,且改一项就能治国内 QUIC 被打压的场景 |
|
||||
| 阶段 3(可选) | 「边缘优选」工具:并发探测官方 IPv4 段的 **TCP 7844 握手延迟** → 生成「两个 region 各 1~2 个优选 IP」的 hosts 建议;**页面展示而不是代写 hosts**(Windows 改 hosts 需管理员,Fonu 不应越权),并提供「复制这段内容」+ 生效检测 | 沿用本仓「不越权、降级要说明原因」的口径 |
|
||||
| 阶段 3(可选) | 「边缘优选」工具:并发探测官方 IPv4 段的 **TCP 7844 握手延迟** → 生成「两个 region 各 1~2 个优选 IP」的 hosts 建议;**页面展示而不是代写 hosts**(Windows 改 hosts 需管理员,Havline 不应越权),并提供「复制这段内容」+ 生效检测 | 沿用本仓「不越权、降级要说明原因」的口径 |
|
||||
| 阶段 3(可选) | 优选前后对比:用**已有的** `metrics_samples` 延迟/可用率序列展示效果,而不是另造一套测速图 | 复用 §四 的健康与指标链路 |
|
||||
|
||||
> metrics 端口发现、健康判定与网络调优默认值见 §12.5 / §12.7;本节只讨论「本机 → CF 边缘」的优选。
|
||||
|
||||
### 必须写进功能说明的三句实话
|
||||
|
||||
1. 优选只影响「本机 → CF 边缘」这一段;访客到自己那侧 CF 边缘的那段,Fonu 与用户都控制不了。
|
||||
1. 优选只影响「本机 → CF 边缘」这一段;访客到自己那侧 CF 边缘的那段,Havline 与用户都控制不了。
|
||||
2. 所以它**不是**「翻墙提速」类工具:国内访问 CF 的稳定性受链路与运营商影响,优选只能把「本机出口到边缘」这一段挑好。
|
||||
3. 官方端点会变(见上),因此把所有 IP/域名做成**可配置 + 有默认值 + 可一键恢复默认**。
|
||||
|
||||
@@ -270,13 +270,13 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
|
||||
## 十一、给用户的一句话结论(可直接对外)
|
||||
|
||||
> Fonu 现在支持两类公网出口:**frp**(自持 VPS,任意 TCP/UDP,完全自主)与 **Cloudflare 隧道**(无需公网 IP 与 VPS,HTTP 类最省事,自带 CF 防护)。
|
||||
> Havline 现在支持两类公网出口:**frp**(自持 VPS,任意 TCP/UDP,完全自主)与 **Cloudflare 隧道**(无需公网 IP 与 VPS,HTTP 类最省事,自带 CF 防护)。
|
||||
> 要裸 TCP 端口、要完全自主,用 frp;要让内网 Web 服务零端口暴露地上公网,用 Cloudflare 隧道。
|
||||
> Cloudflare 隧道还提供网络调优(协议 / 地址族 / 连接数)与**边缘 IP 优选**(仅作用于本机到 CF 边缘这一段,访客侧无法自选)。
|
||||
|
||||
---
|
||||
|
||||
## 十二、方案优化 v2(结合 Tunnel-Cloudflared-Manager 1.0.1 与 Fonu 1.5.4)
|
||||
## 十二、方案优化 v2(结合 Tunnel-Cloudflared-Manager 1.0.1 与 Havline 1.5.4)
|
||||
|
||||
> 本节是 2026-09-28 的优化版落地口径。§一~§十一 保留为背景与论证;出现冲突时以本节为准。
|
||||
|
||||
@@ -290,43 +290,43 @@ Cloudflare Tunnel 把「服务端」换成 Cloudflare 的边缘网络:本机 `
|
||||
| --- | --- | --- |
|
||||
| token 解码后的 JSON 字段是 `a` / `t` / `s`,兼容 `AccountTag` / `TunnelID` / `TunnelSecret`;使用标准 base64 并自动补 `=` | `src/services/cloudflare_api.rs` 的 `decode_tunnel_token` / `base64_decode` | token 模式也能本地生成 `creds.json`,从而按 locally-managed 跑本地规则 |
|
||||
| `cert.pem` 内含 `-----BEGIN ARGO TUNNEL TOKEN-----` 块,解出 `accountID` + `apiToken` / `serviceKey` | 同文件 `parse_cert_pem` | 只有账号模式持有云端 API 凭据,才能 push / fetch 云端 ingress |
|
||||
| `start_tunnel` 在 token 非空时优先用 `--token` 启动,即使已经生成了本地 `config.yml` | `src/services/tunnel.rs` 的 `start_tunnel` | **本地规则会被云端覆盖**;Fonu 必须显式选择「本地生效」还是「云端生效」 |
|
||||
| `start_tunnel` 在 token 非空时优先用 `--token` 启动,即使已经生成了本地 `config.yml` | `src/services/tunnel.rs` 的 `start_tunnel` | **本地规则会被云端覆盖**;Havline 必须显式选择「本地生效」还是「云端生效」 |
|
||||
| 命名隧道**不传** `--metrics`,从 stderr 解析 `starting metrics server on 127.0.0.1:<port>/metrics`;快速隧道传 `--metrics 127.0.0.1:0` | `start_tunnel` / `start_quick_tunnel` | metrics 端口必须从日志发现,不能固定,否则多实例会撞端口 |
|
||||
| 健康用 `cloudflared_tunnel_ha_connections > 0` 判定已连接;QUIC 有字节指标,HTTP/2 没有 | `start_metrics_scraper` | `/ready` 只适合做就绪判断,连接数与带宽要走 metrics;HTTP/2 带宽要明确降级 |
|
||||
| 二进制下载只校验「文件 >= 10MB」,没有 SHA-256;`get_available_mirrors` 返回的镜像 ID 与实际支持的镜像不一致 | `src/services/binary.rs` | Fonu 必须做哈希 / 版本校验,并统一镜像表 |
|
||||
| 二进制下载只校验「文件 >= 10MB」,没有 SHA-256;`get_available_mirrors` 返回的镜像 ID 与实际支持的镜像不一致 | `src/services/binary.rs` | Havline 必须做哈希 / 版本校验,并统一镜像表 |
|
||||
| ingress 三条不变式:末条固定 `http_status:404`、`localhost` 归一化为 `127.0.0.1`、新规则插在 catch-all 之前 | `add_tunnel_endpoint` / `add_tunnel_endpoint_local_only` | 生成器必须锁定这三条并加纯函数测试 |
|
||||
| Windows 子进程设置 `CREATE_NO_WINDOW` | `start_tunnel` | Fonu 在 Windows 托管 `cloudflared.exe` 时必须带,否则闪黑窗 |
|
||||
| Windows 子进程设置 `CREATE_NO_WINDOW` | `start_tunnel` | Havline 在 Windows 托管 `cloudflared.exe` 时必须带,否则闪黑窗 |
|
||||
| 快速隧道从 stderr 解析 `*.trycloudflare.com` URL 并缓存 | `start_quick_tunnel` | 只能做「临时演示」;URL 每次重启都会变 |
|
||||
| 存储是 JSON 文件 + 内存 `HashMap<String, Child>` | `src/storage/config.rs`、`TunnelProcessManager` | Fonu 用 SQLite + 迁移,多实例与并发状态更稳 |
|
||||
| 存储是 JSON 文件 + 内存 `HashMap<String, Child>` | `src/storage/config.rs`、`TunnelProcessManager` | Havline 用 SQLite + 迁移,多实例与并发状态更稳 |
|
||||
|
||||
### 12.2 部署位置:原方案最大的空白
|
||||
|
||||
原方案默认「本机跑 cloudflared」,但没有回答一个关键问题:**Fonu 是容器化部署,容器里的 `127.0.0.1` 不是宿主机。**
|
||||
原方案默认「本机跑 cloudflared」,但没有回答一个关键问题:**Havline 是容器化部署,容器里的 `127.0.0.1` 不是宿主机。**
|
||||
|
||||
Fonu 的运行时(`Dockerfile`)在同一个容器里装了主程序与 nginx,`docker-compose.yml` 只映射 `6893` / `80` / `443`。因此 v2 的默认拓扑是:
|
||||
Havline 的运行时(`Dockerfile`)在同一个容器里装了主程序与 nginx,`docker-compose.yml` 只映射 `6893` / `80` / `443`。因此 v2 的默认拓扑是:
|
||||
|
||||
> **cloudflared 跑在 Fonu 容器内,与 nginx 同命名空间;CF ingress 统一指向 `http://127.0.0.1:<nginx_http_port>`(默认 80),由 nginx 再转发到上游。**
|
||||
> **cloudflared 跑在 Havline 容器内,与 nginx 同命名空间;CF ingress 统一指向 `http://127.0.0.1:<nginx_http_port>`(默认 80),由 nginx 再转发到上游。**
|
||||
|
||||
这样做有四个直接收益:
|
||||
|
||||
1. 绕开容器网络命名空间的坑:`127.0.0.1` 在容器内确定指向 Fonu nginx,而不是宿主机或上游。
|
||||
1. 绕开容器网络命名空间的坑:`127.0.0.1` 在容器内确定指向 Havline nginx,而不是宿主机或上游。
|
||||
2. 复用现有 nginx 的安全、限流、geo、TLS、错误页与 Basic Auth,不在 CF 侧重复一套策略。
|
||||
3. 规则同源:CF 只负责「域名 → Fonu nginx」,nginx 负责「域名 → 上游」;本机反代规则仍是唯一事实来源。
|
||||
3. 规则同源:CF 只负责「域名 → Havline nginx」,nginx 负责「域名 → 上游」;本机反代规则仍是唯一事实来源。
|
||||
4. 只有 HTTP / HTTPS 规则能进 CF;TCP / UDP 规则明确排除,避免用户误解。
|
||||
|
||||
三种拓扑的取舍:
|
||||
|
||||
| 拓扑 | cloudflared 位置 | ingress 目标 | 适用 |
|
||||
| --- | --- | --- | --- |
|
||||
| **容器内(默认)** | Fonu 容器 | `http://127.0.0.1:80`(容器内 nginx) | 绝大多数部署;规则先接入本机 nginx |
|
||||
| host 网络 | Fonu 容器 `network_mode: host` | `http://127.0.0.1:80`(宿主机 nginx) | 需要直接访问宿主机端口、或已有宿主机 nginx |
|
||||
| VPS Agent(可选) | VPS 上的 `fonu-agent` 托管 | `http://127.0.0.1:<frps vhost 端口>` | 已有 VPS、只想让部分域名走 CF 的高级用户 |
|
||||
| **容器内(默认)** | Havline 容器 | `http://127.0.0.1:80`(容器内 nginx) | 绝大多数部署;规则先接入本机 nginx |
|
||||
| host 网络 | Havline 容器 `network_mode: host` | `http://127.0.0.1:80`(宿主机 nginx) | 需要直接访问宿主机端口、或已有宿主机 nginx |
|
||||
| VPS Agent(可选) | VPS 上的 `havline-agent` 托管 | `http://127.0.0.1:<frps vhost 端口>` | 已有 VPS、只想让部分域名走 CF 的高级用户 |
|
||||
|
||||
部署注意事项:
|
||||
|
||||
- cloudflared 是**出站 only**,不要为它额外映射任何入站端口。
|
||||
- 二进制、`creds.json`、`cert.pem`、`config.yml`、日志都必须落在 `/data` 持久卷下,否则容器重建即丢失。
|
||||
- 如果要暴露宿主机或内网服务,推荐先把该服务接入 Fonu 的本机 nginx 反代,再让 CF 指向 nginx;不要在 CF ingress 里直接写宿主机 IP。
|
||||
- 如果要暴露宿主机或内网服务,推荐先把该服务接入 Havline 的本机 nginx 反代,再让 CF 指向 nginx;不要在 CF ingress 里直接写宿主机 IP。
|
||||
- host 网络模式只作为显式选项,不默认开启;开启后 `127.0.0.1` 的语义会变化,UI 必须提示。
|
||||
|
||||
### 12.3 凭据与模式:显式区分「谁在生效」
|
||||
@@ -335,21 +335,21 @@ Fonu 的运行时(`Dockerfile`)在同一个容器里装了主程序与 nginx
|
||||
|
||||
| 模式 | 凭据 | 启动方式 | 规则来源 | 云端 fetch / push |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **A. token + 本地规则** | token | 解码 token → 写 `creds.json` → `tunnel --config config.yml run` | Fonu 生成的 `config.yml` | ❌(token 不含 API 凭据) |
|
||||
| **A. token + 本地规则** | token | 解码 token → 写 `creds.json` → `tunnel --config config.yml run` | Havline 生成的 `config.yml` | ❌(token 不含 API 凭据) |
|
||||
| **B. token + 云端规则** | token | `tunnel run --token <token>` | Cloudflare Dashboard / 云端 | ❌ |
|
||||
| **C. 账号 + 本地规则 + 云端同步** | `cert.pem` + credentials JSON | `tunnel --config config.yml run` | 本地,可与云端互相同步 | ✅ |
|
||||
|
||||
实施口径:
|
||||
|
||||
- 模式 A 绝不传 `--token`,只用 `--config`,否则本地规则不生效。
|
||||
- 模式 B 必须在 UI 明确写「规则由 Cloudflare 面板管理,Fonu 的本地预览不生效」,不要让用户以为 Fonu 生成的规则已经下发。
|
||||
- 模式 B 必须在 UI 明确写「规则由 Cloudflare 面板管理,Havline 的本地预览不生效」,不要让用户以为 Havline 生成的规则已经下发。
|
||||
- 模式 C 才提供「从云端拉取」「推送到云端」两个动作;token 模式不提供,只给 Dashboard 指引。
|
||||
- 所有凭据走 `secret.Box` 加密字段;预览一律掩码,下发时用原文。`creds.json` 权限 `0600`,`config.yml` 权限 `0640`,目录 `0750`。
|
||||
- 状态接口返回 `effective_side: local | cloud`,前端常驻显示,切换模式时要求二次确认。
|
||||
|
||||
### 12.4 数据模型与目录布局(Fonu 版)
|
||||
### 12.4 数据模型与目录布局(Havline 版)
|
||||
|
||||
参考实现用 JSON 文件 + 内存 HashMap,适合单进程小工具;Fonu 已有 SQLite 与迁移体系,v2 建议落成表:
|
||||
参考实现用 JSON 文件 + 内存 HashMap,适合单进程小工具;Havline 已有 SQLite 与迁移体系,v2 建议落成表:
|
||||
|
||||
`cf_tunnels`:
|
||||
|
||||
@@ -366,7 +366,7 @@ Fonu 的运行时(`Dockerfile`)在同一个容器里装了主程序与 nginx
|
||||
| `network_json` | protocol / edge-ip-version / ha-connections / proxy_mode / originRequest |
|
||||
| `status` / `last_error` | `stopped` / `starting` / `connected` / `reconnecting` / `failed` |
|
||||
| `metrics_port` | 从日志发现的端口,仅运行时缓存 |
|
||||
| `auto_start` | Fonu 启动时是否自动拉起 |
|
||||
| `auto_start` | Havline 启动时是否自动拉起 |
|
||||
|
||||
目录布局:
|
||||
|
||||
@@ -391,7 +391,7 @@ Fonu 的运行时(`Dockerfile`)在同一个容器里装了主程序与 nginx
|
||||
- 统一镜像表:`official` / `gh-proxy-org` / `gh-proxy-hk` / `gh-proxy-cdn` / `gh-proxy-edgeone` + 自定义 URL 模板;修正参考项目「镜像列表与实现不一致」的问题。
|
||||
- 下载校验:优先官方 checksums(若提供);否则至少校验文件大小、可执行性、`cloudflared --version`;镜像源与官方源使用同一份哈希。
|
||||
- 版本目录 + 原子替换 + 激活指针,复用 `internal/frp/runtime.go` 的 `readActiveBinary` / `writeActiveBinary` 思路。
|
||||
- 支持 linux amd64 / arm64、windows amd64、darwin amd64 / arm64;Fonu 侧用 `runtime.GOOS/GOARCH` 选择。
|
||||
- 支持 linux amd64 / arm64、windows amd64、darwin amd64 / arm64;Havline 侧用 `runtime.GOOS/GOARCH` 选择。
|
||||
|
||||
**进程**
|
||||
|
||||
@@ -414,7 +414,7 @@ Fonu 的运行时(`Dockerfile`)在同一个容器里装了主程序与 nginx
|
||||
- `/ready` 返回 200:cloudflared 已就绪。
|
||||
- `cloudflared_tunnel_ha_connections > 0`:已连上边缘。
|
||||
- 延迟:QUIC 用 `quic_client_smoothed_rtt`;HTTP/2 用 `cloudflared_rpc_client_latency_secs` 或主动 HEAD 探测。
|
||||
- 带宽:QUIC 直接用 `quic_client_receive_bytes` / `quic_client_sent_bytes` 差值;HTTP/2 官方没有字节指标,Fonu 第一轮明确降级为「不展示带宽」并给出原因,不做参考项目那套进程 socket 统计。
|
||||
- 带宽:QUIC 直接用 `quic_client_receive_bytes` / `quic_client_sent_bytes` 差值;HTTP/2 官方没有字节指标,Havline 第一轮明确降级为「不展示带宽」并给出原因,不做参考项目那套进程 socket 统计。
|
||||
|
||||
---
|
||||
|
||||
@@ -423,7 +423,7 @@ Fonu 的运行时(`Dockerfile`)在同一个容器里装了主程序与 nginx
|
||||
输入:
|
||||
|
||||
- 本机反代规则中启用且属于 HTTP / HTTPS 的域名;
|
||||
- Fonu nginx 的容器内入口端口(默认 `80`,可用配置覆盖);
|
||||
- Havline nginx 的容器内入口端口(默认 `80`,可用配置覆盖);
|
||||
- 用户勾选要暴露的域名集合。
|
||||
|
||||
输出:`config.yml` 的 `ingress` 数组。三条不变式必须写进生成器并用纯函数测试锁定:
|
||||
@@ -435,7 +435,7 @@ Fonu 的运行时(`Dockerfile`)在同一个容器里装了主程序与 nginx
|
||||
与 nginx 的关系:
|
||||
|
||||
```text
|
||||
访客 → Cloudflare 边缘 → cloudflared(Fonu 容器)→ 127.0.0.1:80(Fonu nginx)→ 上游
|
||||
访客 → Cloudflare 边缘 → cloudflared(Havline 容器)→ 127.0.0.1:80(Havline nginx)→ 上游
|
||||
```
|
||||
|
||||
- CF 侧不重复配置限流、geo、TLS、Basic Auth;这些仍由本机 nginx 负责。
|
||||
@@ -444,7 +444,7 @@ Fonu 的运行时(`Dockerfile`)在同一个容器里装了主程序与 nginx
|
||||
|
||||
### 12.7 网络调优(默认值与下发方式)
|
||||
|
||||
| 设置 | Fonu 默认 | 下发方式 | 说明 |
|
||||
| 设置 | Havline 默认 | 下发方式 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `transport_protocol` | `http2` | 环境变量 `TUNNEL_TRANSPORT_PROTOCOL` + `config.yml` 的 `protocol` | 国内 QUIC(UDP 7844)常被 QoS,TCP 更稳;提供 `auto` / `quic` |
|
||||
| `edge_ip_version` | `4` | 环境变量 `TUNNEL_EDGE_IP_VERSION` + `config.yml` 的 `edge-ip-version` | 2026.4.0 起官方默认改为 `auto`,国内 IPv6 路由差时反而变慢;提供 `auto` / `6` |
|
||||
@@ -477,9 +477,9 @@ Fonu 的运行时(`Dockerfile`)在同一个容器里装了主程序与 nginx
|
||||
|
||||
token 模式不提供 push / fetch,只给 Dashboard 指引;这是凭据能力的硬边界,不要用「同步失败」这类模糊提示掩盖。
|
||||
|
||||
### 12.9 与 Fonu 现有模块的集成点
|
||||
### 12.9 与 Havline 现有模块的集成点
|
||||
|
||||
| Fonu 模块 | 复用还是新增 | 具体落点 |
|
||||
| Havline 模块 | 复用还是新增 | 具体落点 |
|
||||
| --- | --- | --- |
|
||||
| 二进制下载 / 启用 | 新增 `internal/cloudflared`,参考 `internal/frp/runtime.go` | 镜像表、哈希校验、版本目录、激活指针 |
|
||||
| 进程托管 | 新增 `internal/cloudflared`,参考 `internal/frp/manager.go` | 启停、日志追加、退出原因、`tailFile` |
|
||||
@@ -532,7 +532,7 @@ token 模式不提供 push / fetch,只给 Dashboard 指引;这是凭据能
|
||||
- **容器网络**:默认拓扑是容器内 cloudflared → 容器内 nginx;不要假设 `127.0.0.1` 是宿主机。
|
||||
- **token 覆盖**:模式 B 的本地 `config.yml` 不生效;UI 必须明确,模式 A 才能本地管规则。
|
||||
- **HTTP/2 带宽**:官方 metrics 没有字节指标;第一轮明确降级并说明原因,不做进程 socket 统计。
|
||||
- **Windows**:`CREATE_NO_WINDOW` 必带;若做进程级字节统计还需要管理员权限,Fonu 第一轮不做。
|
||||
- **Windows**:`CREATE_NO_WINDOW` 必带;若做进程级字节统计还需要管理员权限,Havline 第一轮不做。
|
||||
- **CF 条款**:视频 / 大文件分发不在推荐用法内;TryCloudflare 不保证 SLA。
|
||||
- **国内链路**:CF 边缘延迟受运营商影响;优选只影响本机到边缘一段。
|
||||
- **API 凭据**:账号模式的 `cert.pem` 与可选 API Token 都是高权限凭据,必须最小化权限并加密存储。
|
||||
@@ -542,9 +542,9 @@ token 模式不提供 push / fetch,只给 Dashboard 指引;这是凭据能
|
||||
|
||||
### 12.13 结论
|
||||
|
||||
Cloudflare Tunnel 对 Fonu 是**第二条出口**,不是 frp 的替代:它补上「没有公网 IP、也不想长期租 VPS」的 HTTP 类场景。v2 的核心修订是四件事:
|
||||
Cloudflare Tunnel 对 Havline 是**第二条出口**,不是 frp 的替代:它补上「没有公网 IP、也不想长期租 VPS」的 HTTP 类场景。v2 的核心修订是四件事:
|
||||
|
||||
1. cloudflared 跑在 Fonu 容器内,ingress 统一指向容器内 nginx,绕开容器网络坑;
|
||||
1. cloudflared 跑在 Havline 容器内,ingress 统一指向容器内 nginx,绕开容器网络坑;
|
||||
2. 显式区分「本地规则生效」与「云端规则生效」,不让 token 模式悄悄覆盖本地配置;
|
||||
3. metrics 端口从日志发现,健康用 `/ready` + `ha_connections`,HTTP/2 带宽明确降级;
|
||||
4. 二进制下载补哈希 / 版本校验,ingress 生成器锁定三条不变式。
|
||||
|
||||
Reference in New Issue
Block a user