Files
Havline/docs/project-analysis-2026-10-03.md
T
mrlee 2a0740a60f
CI / verify (push) Canceled after 0s
Docker Release / build-and-push (amd64, linux/amd64) (push) Canceled after 0s
Docker Release / build-and-push (arm64, linux/arm64) (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
Docker Release / create-manifest (push) Canceled after 0s
chore(release): 发布 v1.9.2
- 「公网服务端」新增 Nginx 启停控制(Agent 0.15.2,停止带二次确认)
- frps 启动失败不再误报成功,并增强启动诊断
- frps vhost 端口禁止 80/443,避免与 VPS 上 Nginx 冲突
- 旧服务端配置的 tcp_mux / login_fail_exit 默认值补齐
- 新增安全审查、页面布局方案与场景操作文档
2026-10-07 19:26:02 +08:00

398 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Havline 项目结构与实现分析(v1.9.0)
> 分析日期:2026-10-03
> 分析对象:`F:\Fonu-main\havline`
> 版本来源:根目录 `VERSION`,当前为 `1.9.0`
> 分析方式:重新扫描工作区目录,阅读启动入口、应用装配、路由、配置、Docker 构建、Compose 编排、前端路由、数据库迁移和现有文档。本文是当前目录的结构快照,不替代带日期的历史方案文档。
## 一、结论摘要
Havline 是面向飞牛 fnOS 和家庭 NAS 的公网访问管理控制台。项目以 Go 单体应用作为本地控制面,以 Vue 3 管理后台作为操作界面,以 SQLite 和 `/data` 数据目录保存状态,并通过 Nginx、FRP、Cloudflare Tunnel、DDNS 和 ACME 证书把内网服务发布到公网。
当前版本已经从单一的本机反向代理工具扩展为多角色运维系统:
1. 本机 Nginx 反向代理与配置版本管理;
2. 多 DNS 服务商 DDNS 与记录同步;
3. ACME 证书申请、续期、导入和下载;
4. FRP 多服务端、代理规则、运行状态、流量和诊断;
5. 公网 `havline-agent` 对 VPS 上的 frps、Nginx、证书和路由进行远程管理;
6. Cloudflare Tunnel 的本地托管、路由同步、DNS、健康和漂移管理;
7. 日志、指标、通知、备份恢复、服务发现和一键升级。
从工程形态看,项目的核心不是简单 CRUD,而是「控制面 + 外部进程编排 + 远程节点管理 + 配置生成与回滚 + 周期任务」的组合系统。复杂度最高的区域仍然是 FRP/Agent;Cloudflare Tunnel 已成为第二个需要持续关注的外部系统集成边界。
## 二、目录边界与工作区现状
外层 `F:\Fonu-main` 是工作区容器,包含多个交付形态和临时产物;当前主源码目录是 `havline`,而不是主动文件路径中显示的历史 `Fonu-1.0.2` 目录。
```text
F:\Fonu-main
├─ havline/ 当前主源码与 Git 项目内容
│ ├─ cmd/ Go 程序入口
│ ├─ internal/ 后端领域模块
│ ├─ migrations/ SQLite 数据库迁移 001–036
│ ├─ web/ Vue 3 + TypeScript 前端
│ ├─ docs/ 当前项目文档、方案和审查记录
│ ├─ scripts/ 构建、安装、升级脚本
│ ├─ Dockerfile 多阶段 Docker 构建
│ ├─ docker-compose*.yml 三种 Compose 部署形态
│ ├─ VERSION 版本单一来源,当前 1.9.0
│ └─ CHANGELOG.md 版本变更记录
├─ fnos/ fnOS 打包交付物
├─ fnos-native/ fnOS 原生应用工程及其 app-src 镜像
├─ havline-docs/ 独立 Nuxt 文档站点
├─ docs/ 外层临时资料和 FRP 迁移资料
└─ .tmp-* / 日志 / 数据库 本地构建、测试和运行产物
```
`havline` 目录内当前可见的结构规模:
| 区域 | 当前观察 |
| --- | --- |
| Go 后端 | `internal` 下约 281 个 Go 文件,包含业务代码和测试代码 |
| 数据库迁移 | 36 个 SQL 迁移,从 `001_init.sql` 到 `036_cf_health.sql` |
| 前端页面 | 14 个 Vue 页面,覆盖登录、仪表盘、代理、FRP、Cloudflare、日志和设置 |
| 前端技术 | Vue 3.5、TypeScript、Vite、Naive UI、ECharts、CodeMirror、Vitest |
| 后端技术 | Go 1.23、标准库 HTTP、SQLite、ACME、SSH、多个 DNS/Cloudflare SDK |
| 交付架构 | Docker bridge、Docker host、Git URL 构建、Linux 原生部署、fnOS 应用打包 |
外层还存在 `.tmp-guide-data`、`.tmp-native`、`.tmp-push`、`.data` 和日志等运行或验证产物。它们适合排查本地环境,不应被当成源码、正式数据迁移或发布文档的来源。
## 三、总体架构
```mermaid
flowchart LR
Browser[浏览器] -->|HTTP :6893| App[Havline 控制面]
App --> DB[(SQLite /data/havline.db)]
App --> FS[/data 数据目录]
App --> Nginx[Nginx 配置与进程]
Nginx --> NAS[NAS 内网服务]
App --> DNS[DNS 服务商]
App --> ACME[ACME CA]
App --> Scheduler[后台调度器]
App --> FRPC[本机 frpc]
FRPC --> FRPS[公网 VPS frps]
App --> Agent[公网 havline-agent]
Agent --> VPSNginx[VPS Nginx]
App --> Cloudflared[本机 cloudflared]
Cloudflared --> Cloudflare[Cloudflare Edge]
App --> Updater[受限 updater sidecar]
```
### 3.1 运行角色
| 角色 | 位置 | 主要职责 |
| --- | --- | --- |
| `havline` | NAS、Docker 或 Linux 主机 | 管理后台、本机 Nginx、DDNS、证书、FRP、Cloudflare、调度器和数据 |
| `havline-agent` | 公网 VPS | 远程管理 VPS Nginx、frps、证书、路由和诊断 |
| Nginx | NAS 与 VPS 两端 | 提供本机反代、HTTPS 终止和公网入口 |
| frpc / frps | NAS / VPS | 在没有公网入站条件时建立 FRP 隧道 |
| cloudflared | NAS | 将选定的服务通过 Cloudflare Tunnel 发布 |
| updater | Docker sidecar | 持有 Docker Socket,通过 Unix Socket 接收受限升级请求 |
| SQLite | `/data/havline.db` | 保存配置、账号、运行状态、健康事件和历史数据 |
### 3.2 典型访问链路
本机反代链路:
```text
公网客户端 → NAS Nginx → NAS 内网服务
```
FRP 链路:
```text
公网客户端 → VPS DNS/Nginx → frps → frpc → NAS Nginx → 内网服务
```
Cloudflare 链路:
```text
公网客户端 → Cloudflare Edge → cloudflared → NAS 内网服务
```
管理链路:
```text
浏览器 → Havline HTTP API → 领域 Service → SQLite / 文件系统 / 外部进程 / 远程 Agent
```
## 四、启动与依赖装配
主入口是 `cmd/havline/main.go`,启动过程较集中:
1. `config.Load()` 读取监听地址、数据目录、Nginx、frpc 和 updater 配置;
2. `app.ResolveMigrationsDir()` 从当前目录及上级候选目录寻找迁移文件;
3. 通过 `go:embed` 将 `web/dist` 嵌入主程序;
4. `app.New()` 创建数据目录和日志目录;
5. 解析会话密钥;
6. 打开 SQLite,按序执行迁移;
7. 创建加密盒、管理员服务、各领域 Store/Service;
8. 注册 API 路由和静态 SPA;
9. 启动 FRP Agent 隧道、Cloudflare 隧道、定时调度、流量采集;
10. 初始化 Nginx 配置,恢复 FRP 运行状态并执行自动启动;
11. 监听 `SIGINT` / `SIGTERM`,按超时上下文关闭 HTTP、Nginx、FRP 和数据库。
`internal/app/app.go` 是整个后端的组合根。它的优点是依赖关系可直接追踪;风险是任何新模块都需要修改装配代码,长期可能继续增大启动函数的耦合度。
## 五、后端模块职责
### 5.1 API 层
`internal/api/router.go` 集中注册 HTTP API,并通过 `routeSpec` 标记公开路由和受保护路由。当前公开接口主要包括版本、登录状态、登录、登出和可选的公开状态页;业务接口默认经过 Session Middleware 和 `RequireAuth`。
API 领域覆盖:
- `/api/proxies`:本机反向代理、日志流、客户端和 Nginx 规则;
- `/api/ddns`:DDNS 任务、测试和立即同步;
- `/api/certificates`:证书列表、申请、导入、续期和下载;
- `/api/frpmulti`:FRP 服务端、代理、运行时、Agent、诊断、指标和路由;
- `/api/cloudflare`:隧道、路由、DNS、健康、漂移、二进制和设置;
- `/api/backup`:导出、归档、下载、恢复和删除;
- `/api/logs`、`/api/metrics`:日志流、分页日志和指标历史;
- `/api/system/update`:升级状态和执行升级;
- `/api/settings`:系统设置、通知、Token 和中国 IP 段。
路由注册集中有利于鉴权覆盖测试,但文件本身已经较长。新增 API 时应同时补充处理器测试、鉴权覆盖和前端 API 类型,避免只注册后端路径而遗漏客户端契约。
### 5.2 领域模块
| 包 | 主要职责 |
| --- | --- |
| `config` | 环境变量、数据目录、默认端口、会话密钥解析 |
| `db` | SQLite 打开、迁移和数据库基础操作 |
| `auth` | 管理员、会话、密码和 API Token |
| `secret` | 敏感配置的加密存储 |
| `proxy` / `service` | 代理规则、出口、端口校验和业务编排 |
| `nginx` / `nginxtmpl` | Nginx 配置生成、语法检查、启动、重载、版本和回滚 |
| `ddns` | Cloudflare、DNSPod、阿里云、腾讯云、火山引擎等 DNS 服务商 |
| `acme` / `certificate` | ACME DNS-01、证书任务、导入、下载和元数据 |
| `frp` | 多服务端 FRP、frpc 进程、frps 配置、Agent、健康、漂移和流量 |
| `agent` | 公网 Agent HTTP 服务、路由、版本、传输和防火墙 |
| `cloudflared` | Cloudflare API、隧道进程、ingress、DNS、健康和漂移 |
| `monitor` / `metrics` | 周期巡检、健康事件和指标历史 |
| `scheduler` | DDNS、ACME、中国 CIDR、巡检和自动备份等周期任务 |
| `backup` | 数据导出、归档、恢复和自动备份保留策略 |
| `notify` | 邮件、Webhook、Telegram、钉钉、飞书和企业微信 |
| `logstore` / `traffic` | 应用日志、Nginx 日志、访问解析、流量和连接统计 |
| `discovery` | NAS 常见服务发现与候选配置 |
| `updater` | 主程序与升级 sidecar 之间的 Unix Socket 协议 |
### 5.3 外部进程与文件系统边界
Havline 不只读写数据库,还会生成和维护以下运行资源:
- `/data/nginx/nginx.conf`、规则目录、自定义配置和配置备份;
- `/data/certs` 证书和私钥;
- `/data/frp/frpc.toml`、PID 文件和运行日志;
- Cloudflare Tunnel 的二进制、配置和凭据;
- `/data/logs` 应用、Nginx 和 FRP 日志;
- `/data/ssh_known_hosts`,用于 SSH 主机密钥 TOFU 校验;
- `/data/session_secret`,用于未显式配置时持久化会话密钥。
这意味着备份、升级和恢复不能只关注 SQLite。完整实例恢复至少需要数据库、证书、Nginx 自定义配置、FRP/Cloudflare 运行文件和会话密钥保持一致。
## 六、数据与安全模型
### 6.1 数据持久化
Compose 默认把命名卷 `havline-data` 挂载到容器 `/data`,也可通过 `HAVLINE_DATA_PATH` 改为绑定目录。主数据库为 `/data/havline.db`,迁移已覆盖:
- 管理员、会话和系统设置;
- 本机代理及多 Host、端口、安全、流量和出口;
- DDNS 任务、域名记录和自定义公网 IP;
- 证书、ACME CA 和证书域名;
- FRP 服务端、选项、代理、运行状态、诊断、Agent 和健康;
- API Token、路由健康、指标采样;
- Cloudflare Tunnel、全局设置、API Token、Account ID 和健康事件。
截至当前目录,迁移编号到 `036_cf_health.sql`,说明 Cloudflare 健康事件已经进入数据库持久化边界。
### 6.2 会话密钥
`Config.ResolveSessionSecret()` 的实际优先级是:
1. 显式环境变量 `HAVLINE_SESSION_SECRET`;
2. 数据目录中的 `session_secret`;
3. 不存在或明显损坏时生成 32 字节随机值,以 64 个十六进制字符写入并设置为 `0600`。
该密钥同时用于会话签名和敏感凭据加密。已有数据后更换密钥会导致已保存的 DDNS、FRP、通知和 Cloudflare 等凭据无法解密,因此部署文档必须把密钥持久化和迁移作为数据恢复的一部分。
### 6.3 鉴权边界
管理 API 默认通过会话中间件和认证检查;API Token 用于自动化访问。公开状态页是唯一明确设计为匿名读取的业务页面,代码中还保留按 IP 限流和访问码控制的语义。远程 Agent 使用独立 Bearer Token、SSH 安装/诊断和可选的传输层能力。
主要安全关注点:
- updater sidecar 挂载 Docker Socket,必须限制其 Unix Socket 调用面和宿主机项目目录;
- Agent 若监听非回环地址,必须配合 Token、TLS/隧道和防火墙策略;
- 证书私钥、DNS 凭据、FRP Token 和 Cloudflare Token 需要同时保护数据库、数据卷和备份归档;
- 状态页和日志接口不能意外泄露内部域名、凭据或运行细节;
- Compose 文件中的 Git 构建地址、更新仓库和项目目录必须与实际发布源保持一致。
## 七、前端结构
前端入口通过 Vue Router 将页面分为公开区域和登录后的 AppLayout 区域:
```text
/login 登录
/status 公开状态页
/dashboard 仪表盘
/ddns DDNS
/certificates 证书管理
/proxies 本机反向代理
/frp 内网穿透
/frp-agent 公网 Agent
/cloudflare Cloudflare 隧道列表
/cloudflare/:id/config 隧道独立配置页
/public-proxy 公网反代
/logs 日志中心
/settings 系统设置
/about 关于项目
```
前端职责主要集中在页面组件、API Client、类型定义、状态/格式化工具和布局组件。技术栈包含:
- Vue 3.5 与 Vue Router 4;
- TypeScript 5.7 与 `vue-tsc`;
- Vite 6;
- Naive UI;
- ECharts 与 `vue-echarts`;
- CodeMirror 6,用于配置编辑;
- SortableJS,用于规则排序;
- Vitest,用于单元测试。
需要持续控制的维护成本是页面组件体量。代理、设置、Agent 和 Cloudflare 页面承载了较多状态、弹窗、表单、配置编辑和运行操作,后续新增功能应优先抽取可复用的表单、状态卡片、日志流和配置编辑子组件,避免继续扩大单文件复杂度。
## 八、部署与构建
### 8.1 Dockerfile
当前 Dockerfile 是多阶段构建:
1. 从 `VERSION` 读取版本;
2. 使用 Node 镜像构建前端,并将版本注入 `VITE_APP_VERSION`;
3. 使用 Go 镜像下载依赖并交叉编译 amd64/arm64 的 `havline-agent`;
4. 将前端 `dist` 复制到 `cmd/havline/web/dist`,由 Go `embed` 打入主程序;
5. 编译主程序并通过 `-ldflags` 注入后端版本;
6. 单独构建 updater sidecar;
7. 运行时镜像安装 Nginx、CA 证书和 tzdata,默认暴露 6893、80、443。
当前运行镜像不预装 `frpc`,内网穿透需要在应用内按需下载并保存到数据目录。文档若仍写成“镜像内置 frpc”,应按当前 Dockerfile 修正。
### 8.2 Compose 形态
| 文件 | 场景 | 特征 |
| --- | --- | --- |
| `docker-compose.yml` | 本地源码构建 | bridge 网络、主服务和 updater sidecar |
| `docker-compose.host.yml` | fnOS 等已占用 80/443 | host 网络,默认反代端口 18080/9443 |
| `docker-compose.hub.yml` | 只保留编排文件部署 | BuildKit 从 Git URL 拉源码构建,适合干净机器 |
三个 Compose 文件都使用固定项目名 `havline`,并支持 `HAVLINE_DATA_PATH` 将默认命名卷替换为绑定目录。`docker-compose.hub.yml` 当前从 `https://jayhub.top/mrlee/Havline.git#main` 构建和升级,部署环境需要确认该地址、分支和实际发布仓库的关系。
### 8.3 一键升级
Docker 模式由主服务和 updater sidecar 组成:主服务只通过共享 Unix Socket 请求升级,sidecar 才挂载 Docker Socket。升级过程涉及拉取/构建目标代码、重建服务、健康检查和失败回滚。
该设计降低了主业务进程直接控制 Docker 的权限,但 updater 仍然拥有高权限宿主机能力。升级协议、项目目录挂载、Compose 文件名、服务名、镜像标签和回滚条件必须作为一个整体测试。
## 九、关键业务流程
### 9.1 新建本机反向代理
```text
前端表单
→ POST /api/proxies
→ Proxy Handler / Proxy Service
→ 写入 SQLite
→ 生成 Nginx 规则与证书引用
→ 语法检查
→ 原子写入和备份
→ Nginx reload
→ 可选同步 Cloudflare ingress/DNS
```
### 9.2 DDNS 与证书
DDNS 任务由 scheduler 周期触发,也可通过 API 手动执行。证书申请使用 ACME DNS-01,凭据从加密存储读取,申请过程通过 Job 和日志流反馈给前端,成功后更新证书元数据并供 Nginx 或远程 Agent 使用。
### 9.3 FRP 与 Agent
本地 Havline 保存服务端和代理定义,负责生成 `frpc.toml`、下载/激活 FRP 二进制、启动和停止本地进程。公网 Agent 通过 SSH 安装或管理,随后由 Havline 调用 Agent API 完成 frps、Nginx、证书、路由和诊断操作。路由漂移、健康、版本和配置回滚使该链路具备运维闭环,但也使错误恢复路径较长。
### 9.4 Cloudflare Tunnel
反向代理规则可以选择本机出口或 Cloudflare 出口。托管隧道从规则派生 ingress,并负责本地配置、Cloudflare 云端路由、DNS 记录、隧道进程、健康事件和漂移修复。隧道配置页单独呈现运行指标、拓扑、路由和控制操作,说明该功能已经从实验性集成进入主要业务边界。
## 十、现有文档与实现的偏差
本次读取发现,正式代码已经进入 v1.9.0,但部分历史文档仍停留在早期版本,使用前应区分“历史快照”和“当前操作说明”:
1. `docs/DEPLOYMENT.md` 顶部仍标注适用版本 v1.1.0,迁移数量、包名和若干功能描述明显早于当前实现;
2. 该部署文档仍强调手工设置会话密钥,并按历史固定默认值描述风险,而当前代码在未配置时会生成并持久化随机密钥;
3. `docs/README.md` 已将 `project-analysis-2026-09-27.md` 标记为 v1.2.19 快照,不能代表当前 v1.9.0 的完整 Cloudflare 和品牌状态;
4. `CHANGELOG.md` 的 `[Unreleased]` 仍包含品牌全量切换说明,而 v1.9.0 已记录 Cloudflare 功能,版本状态需要在发布流程中明确;
5. 当前代码、Compose 和环境变量已统一使用 `HAVLINE_*`,旧 `FONU_*` 只应在历史文档中保留,并需要避免被新部署步骤继续复制;
6. `fnos-native/app-src` 是另一个包含源码副本的交付边界,修改主 `havline` 后若需要发布 fnOS 原生包,必须明确同步策略,不能默认认为两个目录自动一致。
建议把文档分成三类:当前部署手册、当前功能基线、历史分析/方案。历史文件保留原内容,但标题和索引应明确版本和时效,避免用户把 v1.1.0 的命令用于 v1.9.0。
## 十一、风险与改进优先级
### P0:发布与数据安全
- 更新部署文档,使会话密钥、数据卷、备份和当前 Compose 变量与代码一致;
- 验证 updater sidecar 的 Socket 鉴权、项目目录边界、升级失败回滚和异常重启;
- 对数据库、证书、Token、私钥和备份归档建立统一的敏感数据保护说明;
- 明确 `docker-compose.hub.yml` 的 Git 地址、分支和发布仓库,避免升级拉到非预期源。
### P1:复杂集成稳定性
- 为 FRP/Agent/Cloudflare 的“配置写入 → 进程重载 → 健康检查 → 漂移修复”建立端到端测试;
- 把远程 Agent 调用的超时、重试、版本兼容和错误恢复状态统一建模;
- 验证 Cloudflare DNS 清理只影响 Havline 接管的记录;
- 检查迁移从已有实例升级到 `036` 的完整性,并验证备份恢复后凭据和运行文件可用。
### P2:可维护性
- 拆分大型 Vue 页面和大型 FRP/Agent 服务文件;
- 统一 API 类型、错误码、异步 Job、日志流和状态映射;
- 继续保持路由鉴权覆盖测试,并为每个外部进程管理器补充失败注入测试;
- 将重复的配置版本、原子写入和回滚逻辑沉淀到已有 `fsutil` 或明确的领域接口中。
### P3:交付一致性
- 明确 `havline`、`fnos-native/app-src`、fnOS `.fpk` 和独立文档站点之间的发布关系;
- 在 CI 中增加版本源、Compose 变量、文档版本和嵌入前端资源的一致性检查;
- 清理或隔离外层临时数据库、日志和构建产物,减少分析时误把运行状态当源码事实的风险。
## 十二、建议的后续验证清单
在把本分析作为发布依据前,建议在 `havline` 目录执行:
```bash
go test ./... -count=1
go vet ./...
cd web
npm run typecheck
npm test
npm run build
```
部署验证至少覆盖:
- 空数据目录首次启动并确认随机 `session_secret` 权限;
- 旧数据卷升级并确认数据库迁移全部完成;
- Compose bridge、host、hub 三种形态分别启动;
- Nginx 配置写入、语法错误、回滚和重载失败;
- FRP 本地进程、VPS Agent 和路由漂移;
- Cloudflare 隧道、DNS、ingress 同步、健康事件和恢复;
- 备份导出、全量恢复、凭据解密和证书/配置文件恢复;
- updater sidecar 升级成功、健康检查失败和回滚。
## 十三、最终判断
当前 `havline` 已经是一个功能较完整的 NAS 公网访问控制面,代码、部署和文档均已完成从 Fonu 到 Havline 的大部分迁移。架构主线清晰:Go 负责控制面和外部系统编排,Vue 负责交互,SQLite 负责状态,文件系统负责运行配置和密钥,Nginx/FRP/Cloudflare 负责不同公网出口。
项目下一阶段的主要工作不在于继续堆叠入口,而在于收敛交付事实:同步当前部署文档、强化高权限升级边界、保证数据库与文件数据的一致备份恢复、降低 FRP/Agent/Cloudflare 集成的故障恢复成本,并把历史方案与当前实现明确分层。