Files
havline-native/app-src/docs/project-analysis-2026-09-27.md
T

984 lines
37 KiB
Markdown
Raw Normal View History

# Fonu 项目详细分析(v1.2.19)
> 分析日期:2026-09-27
> 分析对象:`F:\Fonu-main\Fonu-1.0.2`
> 代码版本:`VERSION = 1.2.19`,前端 `package.json = 1.2.19`,内置 Agent `0.12.7`
> 分析方式:源码静态阅读、目录与依赖统计、迁移与路由核对、构建级验证
> 验证结果:`go test ./... -count=1`、`go vet ./...`、`npm run typecheck`、`npm test` 均通过
---
## 一、结论摘要
Fonu 是面向飞牛 fnOS 和家庭 NAS 场景的「公网访问管理面板」。它把四类原本分散的运维能力收敛到一个 Go 单体和 Vue 管理后台中:
1. 本机 Nginx 反向代理与配置版本管理;
2. 多家 DNS 服务商的 DDNS 自动同步;
3. ACME 证书申请、续期、导入与下载;
4. 基于 FRP 的内网穿透,以及部署在公网 VPS 上的 `fonu-agent` 远程管理。
项目当前形态不是单纯 CRUD 面板,而是已经形成「本地控制面 + 公网 Agent + 定时任务 + SQLite 状态库 + Nginx 配置生成器」的完整运维系统。
主要优点:
- **模块边界较清楚**:`internal` 下按 `proxy`、`nginx`、`ddns`、`acme`、`frp`、`agent`、`monitor`、`backup` 等领域拆分,应用装配集中在 `internal/app/app.go`。
- **交付链路完整**:同一套源码可构建 Docker 镜像、Linux amd64/arm64 原生安装包,并内嵌双架构 `fonu-agent`。
- **关键配置具备回滚能力**:本机 Nginx 主配置、规则自定义配置,以及公网 Agent 上的 vhost 都有版本列表、差异和回滚入口。
- **自动化程度较高**:DDNS、证书续期、中国 IP 段更新、健康巡检、指标采样、自动备份均由调度器统一驱动。
- **测试基础设施已建立**:后端 90 个 Go 测试文件;前端已有 Vitest、TypeScript 严格模式与 CI 验证流程。
需要重点关注:
- **FRP 是系统复杂度中心**:`internal/frp` 约 6049 行,`internal/agent` 约 2079 行,二者共同承担 VPS 配置、进程、Nginx、证书、frps、诊断、版本与漂移检测,修改风险高于其他模块。
- **前端巨型组件明显**:`ProxyView.vue` 约 3150 行,`SettingsView.vue` 约 2144 行,`AgentView.vue` 约 1807 行,维护和样式隔离成本较高。
- **仍存在历史遗留接口引用**:前端保留 `/api/frp` 旧方法,`DdnsView.vue` 仍调用它,但后端当前只注册 `/api/frpmulti/*`;该调用落入 `catch`,会造成 DDNS 页的 FRP 提示状态始终为关闭。
- **初始管理员密码会进入系统日志**:`auth.EnsureDefaultAdmin` 将初始密码写入应用日志,日志中心的系统日志又会读取该文件,安全边界需要收紧。
- **仓库工作区有未提交改动**:当前有归档目录删除、内置 Agent 二进制更新、品牌图片更新等本地改动;分析时没有修改或回退这些内容。
---
## 二、仓库定位与目录边界
外层目录 `F:\Fonu-main` 不是项目源码仓库,而是工作区容器:
```text
F:\Fonu-main
├─ .breezell/ 工作区辅助目录
├─ .gocache-agent/ Agent 构建缓存
├─ docs/ 外层临时文档
├─ Fonu-1.0.2/ 真正的 Git 仓库与源码根目录
├─ agent-tsc.log
├─ fonu-image-build4.log
└─ frps-m1-tsc.log
```
真正的 Git 仓库位于 `F:\Fonu-main\Fonu-1.0.2`:
- 当前分支:`main`
- 远端:`origin/main`
- 最近提交:`70b891c feat(scripts): 新增 upgrade-hub.sh,仅编排文件的部署可一条命令升级`
- 当前未提交内容:
- 删除 `internal/frp-1.0.2/*`;
- 删除 `web/src/views/FrpView-1.0.2.vue`;
- 修改 `internal/frp/agentbin/fonu-agent-linux-amd64`;
- 修改 `internal/frp/agentbin/fonu-agent-linux-arm64`;
- 修改 `web/public/favicon.png`、`web/src/assets/brand/logo.png`;
- 新增 `web/public/111favicon.png`、`web/src/assets/brand/logo00.png`。
项目主体目录:
| 目录 | 作用 |
| --- | --- |
| `cmd/fonu` | 本地控制面主程序入口,嵌入前端静态资源 |
| `cmd/fonu-agent` | 公网 VPS Agent 入口 |
| `internal` | 全部业务实现,共 29 个领域包 |
| `migrations` | SQLite 迁移,共 27 个 SQL 文件 |
| `web` | Vue 3 + Vite 管理后台 |
| `deploy` | Nginx MIME 类型等部署资源 |
| `scripts` | 原生安装、升级、Agent 安装和 Linux 打包脚本 |
| `docs` | 设计、审查、部署与 FRP 文档 |
| `.github/workflows` | CI、Docker 发布、GitHub Release |
---
## 三、总体架构
```mermaid
flowchart LR
Browser[浏览器管理后台] -->|HTTP :6893 / Cookie / Bearer Token| App[fonu 控制面]
Public[公网访客] -->|HTTP/HTTPS| Nginx[本机 Nginx]
Nginx -->|反向代理| NAS[NAS 内网服务]
App --> DB[(SQLite /data/fonu.db)]
App --> FS[数据目录 /data]
App --> NginxCtl[Nginx 配置生成与重载]
App --> DDNS[Cloudflare / DNSPod / 阿里云 / 腾讯云 / 火山引擎]
App --> ACME[ACME CA]
App --> Scheduler[后台调度器]
App -->|frpc.toml / 进程管理| FRPC[本机 frpc]
FRPC -->|隧道| FRPS[公网 VPS frps]
FRPS -->|vhost| AgentNginx[VPS Nginx]
AgentNginx -->|隧道回源| Nginx
App -->|Bearer Token API :7700| Agent[fonu-agent]
Agent --> AgentNginx
Agent --> FRPS
Agent --> CertStore[VPS 证书目录]
```
运行角色:
| 角色 | 运行位置 | 职责 |
| --- | --- | --- |
| `fonu` | NAS / Docker / 原生 Linux | 管理后台、本机 Nginx、DDNS、证书、frpc、调度器、数据持久化 |
| `fonu-agent` | 公网 VPS | 管理 VPS 上的 Nginx vhost、证书副本、frps 进程与配置,并提供诊断只读接口 |
| Nginx | NAS 与 VPS 两端 | NAS 侧负责反代与流量入口;VPS 侧负责公网反代和 frps vhost 回源 |
| frpc / frps | NAS / VPS | 在无公网 IP 时建立 TCP、HTTP、HTTPS 等隧道 |
| SQLite | NAS 数据目录 | 保存账号、会话、代理规则、DDNS、证书、FRP、指标、健康、备份元数据等 |
---
## 四、技术栈与构建交付
### 4.1 后端
`go.mod` 声明 Go 1.23,模块路径为 `github.com/fonu/fonu`。本次验证环境使用 Go 1.26.2,可正常构建与测试。
关键依赖:
| 能力 | 依赖 |
| --- | --- |
| HTTP 服务 | Go 标准库 `net/http` 与 `http.ServeMux` |
| SQLite | `modernc.org/sqlite`,纯 Go,不依赖 CGO |
| ACME | `github.com/go-acme/lego/v4` |
| DNSPod | `github.com/nrdcg/dnspod-go` |
| 腾讯云 | `tencentcloud-sdk-go` |
| 火山引擎 | `volcengine-go-sdk` |
| SSH | `golang.org/x/crypto/ssh` |
| 密码哈希 | `golang.org/x/crypto/bcrypt` |
| 日志轮转 | `gopkg.in/natefinch/lumberjack.v2` |
后端有两个入口:
- `cmd/fonu/main.go`:加载配置、解析迁移目录、嵌入 `web/dist`、启动 `app.New`、监听退出信号。
- `cmd/fonu-agent/main.go`:解析监听地址、Token、Nginx 目录、证书目录、frps 路径,启动 Agent HTTP 服务。
### 4.2 前端
前端使用 Vue 3.5、TypeScript 5.7、Vite 6:
| 能力 | 依赖 |
| --- | --- |
| UI 组件 | Naive UI |
| 路由 | Vue Router |
| 图表 | ECharts + vue-echarts |
| 配置编辑器 | CodeMirror 6 |
| 拖拽排序 | SortableJS |
| 图标 | `@vicons/ionicons5` |
| 测试 | Vitest |
`web/src` 共 52 个 TypeScript / Vue 源文件。前端测试目前有 2 个 spec、13 条测试,主要集中在格式化与状态映射纯函数。
### 4.3 Docker 与原生安装
项目提供三种 Compose:
| 文件 | 场景 | 网络 |
| --- | --- | --- |
| `docker-compose.yml` | 普通 Docker、本地源码构建 | bridge,映射 6893/18080/9443 |
| `docker-compose.host.yml` | 飞牛 fnOS 等已占用 80/443 的环境 | host 网络,默认反代端口 18080/9443 |
| `docker-compose.hub.yml` | 干净机器只保留一个编排文件 | BuildKit 直接从 Git 仓库拉源码构建 |
Dockerfile 的主要流程:
1. 从根目录 `VERSION` 读取版本;
2. 构建前端;
3. 交叉编译 Linux amd64 / arm64 `fonu-agent`;
4. 将前端 `dist` 复制到 `cmd/fonu/web/dist` 供 `go:embed`;
5. 编译 `fonu` 主程序;
6. 运行时镜像安装 Nginx、CA 证书、tzdata;
7. 默认监听 6893,暴露 80/443,数据目录为 `/data`。
注意:当前 Dockerfile 不预装 `frpc`,需要用户在「应用配置」中按需下载。旧版 README 或部署文档中若仍表述为“镜像内置 frpc”,应按当前实现校正。
### 4.4 版本一致性
当前版本事实:
| 位置 | 值 |
| --- | --- |
| `VERSION` | `1.2.19` |
| `web/package.json` | `1.2.19` |
| `AgentVersion` | `0.12.7` |
| `internal/version.Version` | 构建时通过 `-ldflags` 注入,默认 `dev` |
前端构建时优先读取 `VITE_APP_VERSION`,否则回落到 `package.json`。Dockerfile 将 `VERSION` 注入前端和后端。
---
## 五、后端模块分析
### 5.1 规模概览
后端共有 235 个 `.go` 文件,其中 90 个是 `_test.go`。按非测试源码粗略统计,主要模块规模如下:
| 包 | 文件数 | 约行数 | 职责 |
| --- | ---: | ---: | --- |
| `frp` | 23 | 6049 | FRP 多服务端、frpc 运行、Agent 调用、诊断、健康、漂移、版本 |
| `api` | 22 | 3241 | HTTP 路由、处理器、中间件、响应封装 |
| `ddns` | 14 | 2379 | 多服务商 DDNS、记录同步、凭证、Zone 解析 |
| `nginx` | 23 | 2186 | Nginx 配置生成、校验、启动、重载、版本与回滚 |
| `agent` | 4 | 2079 | 公网 VPS Agent 的全部接口与配置写入 |
| `notify` | 7 | 1418 | 通知配置、加密存储、邮件、Webhook、Telegram 与国内 IM |
| `acme` | 8 | 1370 | ACME 申请、续期、导入、导出、任务日志 |
| `proxy` | 3 | 1015 | 反代规则模型、SQLite Store 与端口冲突校验 |
| `service` | 3 | 595 | 反代与 Nginx 配置的业务服务层 |
| `monitor` | 1 | 559 | 资源、隧道、服务端、Agent、配置漂移巡检 |
| `logstore` | 4 | 685 | 系统日志、访问日志、错误日志读取与显示转换 |
| `traffic` | 3 | 421 | Nginx access log 跟踪、流量聚合、在线连接 |
| `backup` | 3 | 401 | 导出、恢复、校验、自动备份与保留策略 |
| `certificate` | 2 | 353 | 证书元数据 Store 与导入 |
| `auth` | 2 | 334 | 管理员、会话、API Token 验证 |
| `chinacidr` | 1 | 301 | 中国 IP 段下载、生成与 Nginx 联动 |
| `metrics` | 1 | 231 | 指标历史写入、查询与分桶 |
| `config` | 1 | 201 | 环境变量、数据目录、会话密钥 |
| `settings` | 1 | 193 | 设置项读写、默认值与类型转换 |
| `fsutil` | 1 | 164 | 原子写、备份、版本列表与回滚基础 |
### 5.2 应用装配:`internal/app/app.go`
`app.New` 是理解整个系统的入口,按顺序完成:
1. 创建日志目录与 JSON 日志;
2. 创建数据、Nginx、日志、证书、FRP 目录;
3. 解析或自动生成会话密钥;
4. 打开 SQLite 并执行迁移;
5. 创建 AES-GCM 凭据加密盒;
6. 初始化管理员账号;
7. 创建 proxy、settings、ddns、certificate、nginx 等 Store / Service;
8. 创建 FRP 多服务端服务;
9. 创建 HTTP 路由;
10. 创建调度器与流量采集器;
11. 启动 DDNS、ACME、中国 IP、巡检、自动备份任务;
12. 初始化 Nginx 配置并恢复 frpc 运行状态;
13. 启动 HTTP 服务,退出时关闭 Nginx、frpc 和数据库。
这种集中装配方式让依赖关系清晰,但也使 `app.go` 成为高耦合入口。新增模块时应继续通过构造参数或小型接口注入,避免在 `app.New` 中堆积具体职责。
### 5.3 配置与数据目录:`internal/config`
关键环境变量:
| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `FONU_LISTEN` | `:6893` | 管理后台监听地址 |
| `FONU_DATA_DIR` | `/data` 或当前目录 `.data` | 数据根目录 |
| `FONU_SESSION_SECRET` | 空 | 会话签名与凭据加密密钥;空时自动生成 |
| `FONU_NGINX_BIN` | `nginx` | Nginx 可执行文件 |
| `FONU_NGINX_PID` | `<DataDir>/nginx/nginx.pid` | Nginx PID |
| `FONU_NGINX_MIME_TYPES` | `/etc/nginx/mime.types` 或仓库资源 | MIME 类型文件 |
| `FONU_NGINX_HTTP_PORT` | `80` | 新建 HTTP 反代默认端口 |
| `FONU_NGINX_HTTPS_PORT` | `443` | 新建 HTTPS 反代默认端口 |
| `FONU_FRPC_BIN` | `frpc` | frpc 可执行文件 |
| `FONU_FRPC_PID` | `<DataDir>/frp/frpc.pid` | frpc PID 文件 |
会话密钥策略值得肯定:
- 显式配置时优先使用;
- 未配置时生成 32 字节随机值;
- 持久化为 `<DataDir>/session_secret`,权限 0600;
- 使用临时文件加改名写入,避免半文件;
- 已经使用历史默认值时只提示,不自动替换,避免历史密文全部无法解密。
数据目录典型结构:
```text
<DataDir>/
├─ fonu.db
├─ session_secret
├─ ssh_known_hosts
├─ nginx/
│ ├─ nginx.conf
│ ├─ rules/
│ ├─ custom/
│ ├─ backups/
│ ├─ errors/
│ └─ china_cidr.conf
├─ certs/
├─ frp/
│ ├─ frpc.toml
│ └─ frpc.pid
├─ logs/
│ ├─ app.log
│ ├─ access.log
│ ├─ error.log
│ └─ frpc.log
└─ backups/
```
### 5.4 数据库:`internal/db`
SQLite 初始化参数:
- `foreign_keys(1)`:启用外键;
- `busy_timeout(5000)`:锁等待 5 秒;
- `SetMaxOpenConns(1)`:单连接串行访问。
单连接是保守且适合 NAS 单机场景的选择,能避免多连接写冲突,但所有 API、调度任务和日志查询都共享一个数据库连接。当历史指标、健康事件和流量查询增大时,响应延迟可能互相影响。
迁移系统特点:
- 按文件名前缀数字排序;
- 每个迁移在独立事务中执行;
- 成功后写入 `schema_migrations`;
- 已应用版本跳过;
- 迁移文件不嵌入二进制,运行时通过 `ResolveMigrationsDir` 在多个候选路径查找。
### 5.5 认证与凭据
认证能力分三类:
1. 管理员账号:bcrypt 哈希;
2. 浏览器会话:32 字节随机 Token,数据库保存,默认 7 天;
3. API Token:随机明文只返回一次,数据库保存 SHA-256,支持 `read` / `write` 范围。
登录保护:
- 同一 IP 在 5 分钟内失败 5 次后返回 429;
- 登录失败会进入通知统计;
- 修改密码后删除该账号的其他会话,保留当前会话;
- Cookie 使用 `HttpOnly`、`SameSite=Lax`,并根据直连 TLS 或 `X-Forwarded-Proto` 设置 `Secure`。
凭据加密:
- 使用会话密钥经 SHA-256 派生 AES-256-GCM;
- 加密 DDNS、FRP、通知、SSH、Dashboard 和 OIDC 等敏感字段;
- 部分凭据 API 只返回 `has_token` / `has_password` 等状态,不回传明文。
---
## 六、数据库模型
### 6.1 表清单
| 表 | 迁移 | 作用 |
| --- | --- | --- |
| `schema_migrations` | 001 | 迁移版本 |
| `admins` | 001 | 管理员账号 |
| `sessions` | 001 | 浏览器会话 |
| `api_tokens` | 025 | API 脚本令牌 |
| `proxy_rules` | 001 / 006 / 010-014 | 本机反向代理规则 |
| `proxy_hosts` | 006 / 010 | 规则域名与监听端口 |
| `proxy_custom_endpoints` | 024 | 自定义 Nginx 规则解析出的域名端口占用 |
| `proxy_traffic` | 009 | 规则累计流量 |
| `ddns_configs` | 002-008 / 022 | DDNS 任务、记录、凭证、自定义 IP |
| `certificates` | 002 / 004 / 005 | 证书元数据、CA、多域名 |
| `settings` | 001 | 键值设置 |
| `frp_servers` | 015-016 / 020 / 023 | FRP 服务端、认证、Dashboard、Agent、SSH |
| `frp_proxies` | 015 / 018 / 021 | FRP 穿透规则与扩展选项 |
| `frp_runtime_status` | 015 / 019 | frpc 期望状态、进程状态、连接状态 |
| `frp_server_diagnostics` | 017 | 服务端延迟与可用性采样 |
| `route_health_events` | 026 | 公网规则状态变更事件 |
| `metrics_samples` | 027 | 本机与规则指标历史 |
### 6.2 数据设计亮点
- `proxy_hosts(hostname, listen_port)` 唯一,避免同一域名与端口被多条规则占用。
- `proxy_custom_endpoints` 单独记录自定义 Nginx 配置的监听端点,保证自定义模式也能参与冲突校验。
- `frp_proxies.type` 在 021 迁移中扩展到 8 种类型,并为 TCP / UDP 远程端口分别建立唯一索引。
- `frp_runtime_status` 将“用户期望状态”和“实际进程/连接状态”分开,有助于区分手动停止和异常掉线。
- `route_health_events` 只记录状态变化,不保存每轮原始采样,降低长期数据量。
- `metrics_samples` 采用 `(scope, metric, value, at)` 纵向结构,增加指标不需要改表。
### 6.3 数据生命周期
| 数据 | 清理方式 |
| --- | --- |
| 会话 | 校验过期时删除;修改密码时删除其他会话 |
| 健康事件 | 巡检按保留期清理 |
| 指标采样 | 巡检按保留期清理 |
| 备份归档 | 按设置中的保留份数剪枝 |
| Nginx 配置版本 | 默认保留最近 10 个有效变化 |
| 日志 | 由 `logstore` 与轮转策略处理 |
---
## 七、HTTP API 与认证面
### 7.1 路由规模
当前 `internal/api/router.go` 中:
- 受保护路由约 113 条:使用 `SessionMiddleware + RequireAuth`;
- 匿名路由 5 条:版本、登录状态、登录、退出、公开状态页;
- 静态资源与 SPA fallback:非 `/api/` 路由全部交给前端。
匿名接口:
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/api/version` | 版本与默认反代端口 |
| GET | `/api/auth/status` | 是否初始化、是否已登录 |
| POST | `/api/auth/login` | 登录 |
| POST | `/api/auth/logout` | 退出 |
| GET | `/api/status-page` | 默认关闭的只读状态页 |
### 7.2 API 分组
| 分组 | 代表路径 | 能力 |
| --- | --- | --- |
| 认证 | `/api/auth/*` | 登录、退出、改密 |
| API Token | `/api/settings/tokens` | 创建、列出、撤销 |
| 仪表盘 | `/api/status` | 公网 IP、DDNS、证书、反代、资源和请求趋势 |
| 本机反代 | `/api/proxies*` | 规则 CRUD、排序、流量、连接、实时日志 |
| Nginx 配置 | `/api/proxies/{id}/nginx`、`/api/settings/nginx/*` | 自动/自定义配置、校验、回滚、版本 |
| DDNS | `/api/ddns*` | 任务、测试、全部更新、单任务更新 |
| 证书 | `/api/certificates*` | CA 选项、申请、SSE 日志、导入、续期、下载、删除 |
| 设置 | `/api/settings` | 常规、通知、安全、高级设置 |
| 日志 | `/api/logs/*` | 访问、错误、系统、SSE |
| 指标 | `/api/metrics/history` | 分桶历史趋势 |
| 备份 | `/api/backup*` | 导出、恢复、归档、下载、删除 |
| 服务发现 | `/api/discovery/scan` | 扫描常见 NAS 服务端口 |
| FRP 多服务端 | `/api/frpmulti/*` | 服务端、frpc、规则、诊断、版本下载 |
| 公网 Agent | `/api/frpmulti/servers/{id}/agent/*` | 状态、Nginx、frps、证书、路由、漂移、指标 |
### 7.3 Agent API
`fonu-agent` 当前提供 20 个 `/api/v1/*` 接口,全部使用 Bearer Token:
| 类别 | 接口 |
| --- | --- |
| 基础 | `ping`、`status` |
| 路由 | 列表、写入、详情、删除、版本、回滚 |
| Nginx | 安装、重载、日志 |
| frps | 配置读取与写入、安装、启停重启 |
| 证书 | 写入与列表 |
| 中国 IP | `PUT /china-cidr` |
| 指标 | `GET /metrics` |
Agent 侧的重要安全措施:
- SSH 安装使用 TOFU 主机密钥校验,首次记录,后续变更拒绝;
- frps 下载只允许官方地址和固定加速前缀;
- frps 安装包下载后校验 SHA-256;
- Basic Auth 使用纯 Go apr1,不依赖 VPS 上的 `openssl`;
- 写 vhost 前保留版本,`nginx -t` 失败时恢复上一个可用配置。
---
## 八、前端结构
### 8.1 路由与页面
前端共 11 条路由定义:
| 路径 | 名称 | 是否公开 | 页面 |
| --- | --- | --- | --- |
| `/setup` | - | 重定向 | 跳转登录 |
| `/login` | `login` | 是 | 登录 |
| `/status` | `status` | 是 | 公开状态页 |
| `/dashboard` | `dashboard` | 否 | 仪表盘 |
| `/ddns` | `ddns` | 否 | DDNS |
| `/certificates` | `certificates` | 否 | 证书管理 |
| `/proxies` | `proxies` | 否 | 本机反向代理 |
| `/frp` | `frp` | 否 | 内网穿透 |
| `/frp-agent` | `frp-agent` | 否 | 公网服务端 |
| `/public-proxy` | `public-proxy` | 否 | 公网反代 |
| `/logs` | `logs` | 否 | 日志中心 |
| `/settings` | `settings` | 否 | 系统设置 |
路由守卫在每次受保护页面跳转前请求 `/api/auth/status`。公开状态页不做登录检查,避免匿名访问时产生无意义请求。
### 8.2 页面规模
| 文件 | 约行数 | 主要职责 |
| --- | ---: | --- |
| `ProxyView.vue` | 3150 | 本机反代规则、详情、日志、Nginx 配置 |
| `SettingsView.vue` | 2144 | 常规、通知、安全、高级、备份、Token |
| `AgentView.vue` | 1807 | 公网服务端、Agent、frps 生命周期、诊断 |
| `CertificatesView.vue` | 1685 | 证书列表、申请、续期、下载、导入 |
| `DashboardView.vue` | 1679 | 状态卡、趋势、健康、流量 |
| `DdnsView.vue` | 1568 | DDNS 任务、记录、服务商配置 |
| `FrpView.vue` | 1406 | frpc 服务端和穿透规则 |
| `ProxyRemoteView.vue` | 1150 | 公网反代部署、漂移、健康、版本 |
| `LogsView.vue` | 860 | 系统、访问、错误、FRP 日志 |
这些页面都由 Composition API 直接承载大量状态和逻辑。当前结构适合快速迭代,但需要继续拆分:
- `ProxyView.vue`:拆规则表单、Nginx 面板、客户端面板、日志面板;
- `AgentView.vue`:拆服务端表单、日志弹窗、详情弹窗、延迟图;
- `SettingsView.vue`:拆通知、安全、备份、Token、状态页卡片。
### 8.3 API 客户端
`web/src/api/client.ts` 统一处理:
- `credentials: include`;
- JSON 请求体;
- 30 秒普通请求超时;
- 10 分钟证书操作超时;
- 错误信息归一化;
- Blob 下载与文件名解析。
`web/src/api/types.ts` 约 923 行,集中定义后端响应结构。类型覆盖较全,但仍保留旧 FRP 单体接口类型,形成历史冗余。
### 8.4 前端测试
已有:
- `web/src/utils/status.spec.ts`:状态值到 UI 状态的映射回归;
- `web/src/utils/format.spec.ts`:格式化函数。
缺口:
- API 客户端无测试;
- 页面组件无测试;
- 轮询与可见性行为无测试;
- 配置差异、回滚、表单校验等高风险交互仍只能依赖类型检查与人工验证。
---
## 九、核心业务链路
### 9.1 本机反向代理
数据流:
```text
前端保存规则
→ proxy.Store 校验域名与端口唯一性
→ 写入 proxy_rules / proxy_hosts
→ 生成 Nginx 配置
→ nginx -t 校验
→ 热重载或重启
→ 写入流量采集与日志
```
要点:
- 规则支持多域名、多端口、IPv4/IPv6 监听、HTTPS 与 HTTP 跳转;
- 安全设置保存在 `security_json`,包括 IP 黑白名单、仅中国大陆、Basic Auth、限流、连接数限制、安全响应头等;
- 自动模式由模板生成,自定义模式允许编辑但会解析监听端点参与冲突校验;
- 配置写入前保留 `.bak` 与 `.versions`,失败不覆盖当前可用配置;
- Nginx 配置版本支持列表、读取、差异对比和回滚。
### 9.2 DDNS
支持的服务商:
- Cloudflare;
- DNSPod;
- 阿里云 DNS;
- 腾讯云 DNS;
- 火山引擎 DNS。
能力:
- 单任务多根域名 / 多记录;
- 最长后缀 Zone 匹配;
- IPv4 / IPv6;
- 自定义固定 IP;
- 手动测试与全部更新;
- 定时任务按设置间隔运行,默认 5 分钟;
- 凭证加密保存;
- IP 变化、失败等情况进入通知系统。
### 9.3 证书
证书链路:
```text
选择 CA 与域名
→ 复用 DDNS 凭证
→ DNS-01 挑战
→ lego 申请
→ 写入证书目录
→ 更新 certificates 表
→ 重载 Nginx
→ 前端通过 SSE 获取任务日志
```
当前 CA 相关能力:
- Let's Encrypt;
- ZeroSSL;
- LiteSSL;
- LiteSSL 强制 EAB;
- 可配置 DNS 递归解析器,避免容器内 `127.0.0.11` 无法解析权威 NS;
- 支持导入 PEM、下载 ZIP / 证书 / 私钥;
- 每天执行续期巡检,按到期阈值提醒。
### 9.4 FRP 与公网 Agent
FRP 模块当前支持:
- 多台公网 frps;
- 8 种穿透类型:tcp、udp、http、https、tcpmux、stcp、sudp、xtcp;
- frpc 多版本下载、启用与删除;
- frpc 配置生成与进程管理;
- frps.toml 同源生成;
- 服务端认证方式:token、none、OIDC;
- frpc 流量、连接数与速率;
- 服务端 TCP / 登录 / 代理 / DNS / 归属地诊断;
- SSH 一键安装、升级、卸载 `fonu-agent`;
- Agent 状态、Nginx、frps 配置、证书、日志、指标;
- 公网反代 vhost 部署、漂移检测、版本与回滚;
- 四段健康检查:DNS、证书、隧道、回源服务。
主要风险点:
- `internal/frp/service.go` 和 `internal/agent/server.go` 都是超大文件;
- 宿主机 Nginx 模板与 Agent Nginx 模板存在两份实现,需要人工保持同步;
- Agent 与主程序之间存在版本兼容契约,旧 Agent 通过 404/405 映射升级提示,但接口演进仍需谨慎;
- VPS 上的系统环境差异大,尤其是 Nginx 安装位置、systemd、宝塔环境与 PATH。
### 9.5 监控、健康与通知
调度器每 2 分钟执行 `monitor`:
- 本机 CPU、内存、磁盘;
- 资源阈值;
- 本机规则流量;
- 公网规则 DNS / 证书 / 隧道 / 服务四段健康;
- frps 服务端掉线;
- Agent 不可达;
- 公网 vhost 配置漂移。
通知渠道:
- SMTP 邮件;
- Webhook;
- Telegram;
- Bark;
- ntfy;
- Gotify;
- 企业微信;
- 钉钉;
- 飞书。
告警策略已具备:
- 连续失败阈值;
- 恢复通知;
- 资源告警冷却;
- 配置漂移冷却;
- 登录失败窗口统计。
### 9.6 指标、日志与流量
指标:
- 本机资源与每条公网规则流量写入 `metrics_samples`;
- 查询时按桶取平均,前端支持 1 小时、24 小时、7 天趋势;
- 避免每加一个指标就改表结构。
日志:
- `app.log`:应用系统日志;
- `access.log`:本机 Nginx 访问日志;
- `error.log`:本机 Nginx 错误日志;
- `frpc.log`:FRP 客户端日志;
- 日志中心支持分页、筛选和 SSE 实时访问/错误日志。
流量:
- `traffic.Collector` 跟踪 access log 增量;
- 汇总到规则和域名;
- 计算累计上下行、速率与当前连接数;
- 与监控巡检共用主机维度聚合算法,减少口径漂移。
### 9.7 备份与恢复
能力:
- 手动导出;
- 上传恢复;
- 定时自动备份;
- 服务端归档列表;
- 下载、恢复、删除;
- 恢复前校验归档;
- 按保留份数剪枝;
- 自动备份失败通知。
恢复会覆盖数据库与证书等关键数据,前端需要二次确认。由于 SQLite 正在运行,恢复后推荐重启 Fonu。
---
## 十、后台调度
`internal/app/app.go` 注册的任务:
| 任务 | 间隔 | 作用 |
| --- | --- | --- |
| DDNS | 默认 5 分钟,可在设置中调整 | 检测公网 IP 并同步 DNS |
| ACME | 24 小时 | 证书续期与到期提醒 |
| 中国 IP 段 | 默认 24 小时,可在设置中调整 | 下载并生成 Nginx geo 配置 |
| monitor | 2 分钟 | 资源、隧道、服务端、Agent、漂移巡检与指标采样 |
| backup | 24 小时 | 自动备份并按策略保留 |
调度器还负责:
- 任务 panic 恢复;
- 非正间隔跳过;
- 应用退出时通过 Context 取消。
---
## 十一、部署与运维
### 11.1 推荐部署形态
飞牛 fnOS 推荐:
```bash
docker run -d \
--name fonu \
--net=host \
-v /vol1/docker/fonu-data:/data \
--restart unless-stopped \
fonu:local
```
管理入口 `http://<NAS-IP>:6893`,默认反代端口 18080/9443,避免和系统 Nginx 抢占 80/443。
### 11.2 升级方式
三种方式:
1. 本地源码:`docker compose up -d --build`;
2. 单文件编排:保留 `docker-compose.hub.yml`,重新执行 `docker compose up -d --build`;
3. 原生安装:使用 `scripts/build-linux.ps1` 构建包,再由 `scripts/fonu-install.sh` 安装。
升级脚本:
- `scripts/upgrade.sh`:拉代码、构建、重建、健康检查,支持 `--no-pull` 和回滚;
- `scripts/upgrade-hub.sh`:仅使用编排文件的部署升级;
- `scripts/agent-install.sh`:安装 `fonu-agent`。
### 11.3 数据持久化
默认使用命名卷 `fonu-data`。如需绑定宿主机目录,在 `.env` 中设置:
```dotenv
FONU_DATA_PATH=/vol1/docker/fonu-data
```
Compose 约定以 `/` 或 `.` 开头的值解释为 bind mount,其余字符串仍是命名卷。
`FONU_SESSION_SECRET` 同时承担凭据解密,已有数据后不能随意替换,否则 DDNS、FRP、通知和 SSH 等密文可能无法解密。
---
## 十二、工程化、测试与质量门禁
### 12.1 CI
`.github/workflows/ci.yml` 已实现完整验证顺序:
1. Checkout;
2. 安装 Node 22;
3. `npm ci`;
4. `npm run build`;
5. `npm test`;
6. 将 `web/dist` 同步到 `cmd/fonu/web/dist`;
7. 安装 Go;
8. `go vet ./...`;
9. `go test ./... -count=1`;
10. `go build ./...`。
顺序不可颠倒,因为 `cmd/fonu/main.go` 使用 `//go:embed all:web/dist`,干净检出时前端产物必须先生成。
当前 CI 尚未开启 `gofmt -l` 卡口,原因是仓库仍有历史未格式化文件。建议在集中执行一次 `gofmt -w` 后开启。
### 12.2 发布工作流
- `docker-release.yml`:tag 或手动触发,构建 amd64/arm64 镜像并生成多架构 manifest;
- `github-release.yml`:根据 `CHANGELOG.md` 生成 GitHub Release。
### 12.3 测试覆盖
Go 测试分布较广,`nginx` 包测试最多,其次为 `frp`、`acme`、`api`。
当前没有测试文件的包:
- `internal/discovery`;
- `internal/secret`;
- `internal/service`;
- `internal/settings`;
- `internal/version`;
- `cmd/fonu`;
- `cmd/fonu-agent`。
优先级较高的补测:
1. `internal/service` 的规则保存、Nginx 自定义配置与回滚;
2. `internal/settings` 的类型转换、默认值和迁移;
3. `internal/secret` 的加解密与密钥错误提示;
4. 路由与中间件测试,确保新增受保护接口不会漏鉴权;
5. Agent `readLimitState`、`writeLimitState` 与配置回滚;
6. 前端 API 客户端和轮询组合式函数。
### 12.4 本次验证
本次分析实际执行:
```bash
go test ./... -count=1
go vet ./...
cd web && npm run typecheck
cd web && npm test
```
结果:
- Go:所有含测试包通过;
- Go vet:无输出,退出码 0;
- 前端类型检查:通过;
- 前端测试:2 个测试文件、13 条测试全部通过。
### 12.5 格式化
仓库目前仍存在历史未格式化文件。本次只做过读取和测试,没有批量执行 `gofmt -w`,以免把格式化噪音混入用户当前未提交的改动。
---
## 十三、当前风险与技术债
### 13.1 功能与兼容性
1. **旧 `/api/frp` 客户端残留**
- `web/src/api/client.ts` 仍定义 `getFRP`、`saveFRP`、`getFRPStatus`、`syncFRPDomains`、`getFRPLogs`;
- `web/src/views/DdnsView.vue` 在 `init()` 中调用 `api.getFRP()`;
- 后端当前只注册 `/api/frpmulti/*`,因此该请求失败后进入 `catch`,`frpEnabled` 被设为 `false`;
- 影响:DDNS 页可能无法显示与 FRP 相关的提示;
- 建议:删除旧类型与旧方法,或改为调用 `/api/frpmulti/status`。
2. **Agent 版本兼容需要长期维护**
- 主程序通过 404/405 识别旧 Agent,并提示升级;
- 新接口应继续沿用最低版本提示,避免前端只看到网络错误。
3. **宿主机与 Agent 的 Nginx 模板双份实现**
- 一边是 `internal/nginx`,一边是 `internal/agent`;
- 修改一处忘记另一处会造成本机与公网行为不一致;
- 建议抽共享模板包,至少共享安全指令、限流、geo、WebSocket、TLS 片段。
### 13.2 安全
1. **初始管理员密码写入应用日志**
- `auth.EnsureDefaultAdmin` 记录用户名和密码;
- `logstore.appendSystemAttrs` 会把密码原样拼进系统日志;
- `GET /api/logs/system` 可读取该系统日志;
- 建议只在进程首次启动时输出到终端一次,不写入持久日志;或生成一次性初始密码文件并设置 0600。
2. **Agent 默认明文 HTTP 监听**
- 默认 `:7700`,Bearer Token 直接在网络中传输;
- 如果直接暴露公网,需要 HTTPS、内网限制或前置反向代理;
- 建议默认绑定 `127.0.0.1`,由 Nginx 或隧道暴露,并把公网绑定作为显式选项。
3. **Agent Token 比较未使用常量时间**
- Agent 使用字符串比较;
- 建议使用 `crypto/subtle.ConstantTimeCompare`。
4. **Agent 限流状态反序列化失败会静默清空**
- `readLimitState` 忽略 JSON 错误;
- 后续写入可能删除 zone 文件;
- 建议解析失败返回错误并保留原文件。
5. **备份上传仍允许 64MB**
- 已有 `CreateTemp` 修复路径穿越;
- 仍建议降低上限并增加频率限制。
### 13.3 性能与并发
1. **SQLite 单连接**
- 当前实现简单可靠,但监控查询、日志、指标与 API 会竞争同一连接;
- 可通过缩短事务、减少大查询、为高频读增加轻量缓存来缓解。
2. **前端大页面与高频轮询**
- 多个页面存在 15 秒轮询,仪表盘流量轮询更快;
- 轮询请求应加入 in-flight 互斥,并在页面隐藏时暂停;
- 可以统一抽 `useVisibilityPolling`。
3. **巨型后端文件**
- `internal/frp/service.go` 约 1805 行;
- `internal/agent/server.go` 约 1551 行;
- 建议按能力拆分文件,例如 rate limit、nginx template、frps install、route versions、diagnostics。
### 13.4 维护性
1. **前端样式与弹窗外壳重复**
- 多个页面重复实现左列表、弹窗外壳和状态色;
- 建议抽 `SidebarList.vue`、`ModalShell.vue` 和统一状态令牌。
2. **硬编码颜色仍需收敛**
- `variables.css` 已有品牌令牌;
- 页面中仍有大量直接色值与重复色板。
3. **旧文档时效**
- `project-audit-2026-09-24.md` 基于 v1.2.6,其中很多 P0/P1 已实施;
- 阅读旧审查时应以 CHANGELOG、当前源码和本文档为准。
4. **归档目录删除尚未提交**
- `internal/frp-1.0.2` 与 `FrpView-1.0.2.vue` 在工作树中已删除但未提交;
- 分析时保持原样,没有回退或提交。
---
## 十四、建议改进路线
### P0:先处理明确问题
1. 修正 `DdnsView` 对旧 `/api/frp` 的调用,切到 `/api/frpmulti/status`。
2. 初始管理员密码不再写入持久应用日志。
3. Agent Token 比较改为常量时间。
4. `readLimitState` 解析失败时拒绝覆盖原状态。
5. 明确 Agent 默认监听策略,最好默认仅本机或要求 HTTPS。
### P1:降低核心模块风险
1. 抽共享 Nginx 模板包,统一本机与 Agent 的安全、限流、geo、TLS 逻辑。
2. 拆分 `internal/frp/service.go` 与 `internal/agent/server.go`。
3. 拆分 `ProxyView.vue`、`SettingsView.vue`、`AgentView.vue`。
4. 为路由鉴权、配置回滚、Agent 版本兼容补测试。
5. 前端轮询统一抽 composable,加入互斥与页面可见性控制。
### P2:工程质量提升
1. 全仓执行一次 `gofmt -w`,随后在 CI 中开启 `gofmt -l` 卡口。
2. 清理旧 FRP 类型、旧 API 方法和未使用样式。
3. 收敛颜色令牌与重复弹窗外壳。
4. 增加前端 API 客户端测试与关键页面组件测试。
5. 为 SQLite 高频查询建立基准,确认单连接是否满足长期指标数据。
### P3:能力增强
1. 加密迁移包:导出并导入含敏感凭据的完整实例。
2. 审计日志:记录谁在何时修改了规则、服务端、证书和设置。
3. 2FA / TOTP 与登录设备管理。
4. SMART 磁盘健康与 UPS 状态。
5. Prometheus `/metrics` 导出。
6. 状态页事故记录与服务入口聚合页。
---
## 十五、关键源码索引
| 目标 | 文件 |
| --- | --- |
| 应用装配 | `internal/app/app.go` |
| 配置 | `internal/config/config.go` |
| 数据库迁移 | `internal/db/db.go`、`migrations/*.sql` |
| HTTP 路由 | `internal/api/router.go` |
| 认证中间件 | `internal/api/middleware.go` |
| 登录与会话 | `internal/auth/auth.go`、`internal/api/auth_handler.go` |
| 本机反代 | `internal/proxy/proxy.go`、`internal/service/nginx_config.go` |
| Nginx 生成与重载 | `internal/nginx/*.go` |
| DDNS | `internal/ddns/*.go` |
| 证书 | `internal/acme/*.go`、`internal/certificate/*.go` |
| FRP 主服务 | `internal/frp/service.go` |
| FRP 模型 | `internal/frp/model.go` |
| Agent 客户端 | `internal/frp/agent_client.go` |
| 公网 Agent | `internal/agent/server.go`、`internal/agent/versions.go` |
| 监控巡检 | `internal/monitor/monitor.go` |
| 通知 | `internal/notify/*.go` |
| 日志 | `internal/logstore/*.go` |
| 流量 | `internal/traffic/collector.go` |
| 指标 | `internal/metrics/*.go` |
| 备份 | `internal/backup/*.go` |
| 前端路由 | `web/src/router/index.ts` |
| 前端 API | `web/src/api/client.ts`、`web/src/api/types.ts` |
| 前端布局 | `web/src/layouts/AppLayout.vue` |
| CI | `.github/workflows/ci.yml` |
| 容器构建 | `Dockerfile`、`docker-compose*.yml` |
---
## 十六、总结
Fonu 已经从一个 Nginx 反代面板发展为覆盖「本地入口、DNS、证书、内网穿透、公网 Agent、监控告警、备份恢复」的 NAS 公网访问控制面。
它的优势在于:功能完整、模块边界总体清楚、迁移与部署链路成熟、关键配置有版本和回滚、自动化任务覆盖主要运维场景。
当前最需要做的不是继续横向增加功能,而是先把 FRP / Agent 这条最复杂的链路拆小,把旧接口残留和日志泄密等明确问题清掉,再补上路由鉴权、配置回滚和 Agent 兼容测试。这样后续增加迁移包、审计、2FA、Prometheus 等能力时,才不会把复杂度继续堆进两个超大服务文件。