Compare commits

..

228 Commits

Author SHA1 Message Date
ryan 1be2461716 [优化] 修复构建 2026-06-02 23:43:10 +08:00
ryan a0e9484e37 [优化] 重构数据库迁移逻辑,整合遗留和Goose迁移处理 2026-06-02 23:35:02 +08:00
ryan 4ca6f2957b [优化] 重构数据库迁移逻辑,整合遗留和Goose迁移处理 2026-06-02 22:27:04 +08:00
ryan dfd040a9de [优化] 确保所有管理的进程在停止时被正确取消和清理 2026-06-02 21:30:48 +08:00
ryan f29292dd81 [优化] 添加进程管理功能,支持PID文件处理和孤儿进程清理 2026-06-02 21:08:04 +08:00
ryan 4566fc1f53 [优化] 增强 WebSocket 处理逻辑,添加上下文取消支持和关闭机制
[优化] 重构 WebSocket 处理逻辑,添加消息处理接口和心跳机制
2026-06-02 19:37:03 +08:00
ryan 4e58bdd85b [优化] 重构 WebSocket 客户端,整合共享连接逻辑并简化代码 2026-06-02 17:35:32 +08:00
ryan c009b9e283 [优化] 修复增强 2026-06-02 17:24:12 +08:00
ryan 4e33e0e521 [优化] 添加进程重启机制和指数退避策略以增强稳定性 2026-06-02 17:18:14 +08:00
ryan 7252fb6285 [优化] 重构进程管理逻辑,添加自动重启和退避机制 2026-06-02 17:12:34 +08:00
ryan 2220e45989 [优化] 更新 Docker 部署命令,增加对 HTTP3的支持 2026-06-02 16:49:15 +08:00
ryan 6158a487cf [优化] 添加多语言支持的验证页面文本 2026-06-02 16:11:46 +08:00
ryan 9f9c609809 [优化] 添加 HTTP/3 支持配置选项 2026-06-02 16:07:44 +08:00
ryan e4c6ce9062 [优化] 添加自定义状态码匹配方法 2026-06-02 08:34:03 +08:00
ryan 81dd44c8fc [优化] 添加自定义状态码匹配方法 2026-06-02 08:31:39 +08:00
ryan 3825a7f29a [优化] 自定义不存在页面返回状态码 2026-06-02 08:14:15 +08:00
ryan d21643feed [优化] 更新文档 2026-06-02 00:15:51 +08:00
ryan 2525664013 [优化] 更新文档 2026-06-02 00:13:39 +08:00
ryan a850b0a188 [优化] 修复重启 frpc 挂掉问题 2026-06-01 22:45:21 +08:00
ryan fd8148c0db [优化] 界面优化 2026-06-01 22:33:41 +08:00
ryan edd98f4ff0 [优化] 优化 2026-06-01 22:24:41 +08:00
ryan d8f98e218f [优化] 修复一个升级数据库的错误 2026-06-01 22:00:37 +08:00
ryan fefe205158 [优化] 增加 FRPS WebUI 支持,添加相关配置和数据库迁移 2026-06-01 21:59:42 +08:00
ryan d6e7e2baa2 [优化] 增加自动更新功能,支持更新请求和版本管理 2026-06-01 21:50:22 +08:00
ryan cc50cc695e [优化] 更新 Docker 镜像名称并调整日志级别配置 2026-06-01 21:35:48 +08:00
ryan e43312d4c6 [优化] 增加 IP 处理逻辑并优化心跳负载 2026-06-01 21:16:14 +08:00
ryan 92df7d5c84 [优化] 增加中继 Vhost HTTP 端口支持并优化相关逻辑 2026-06-01 21:11:25 +08:00
ryan 9632b4e3b8 [优化] 增加中继 Vhost HTTP 端口支持并优化相关逻辑 2026-06-01 21:02:15 +08:00
ryan 3b979eb5d5 [优化] 修复隧道相关配置支持 2026-06-01 20:34:02 +08:00
ryan 2635a47d29 [优化] 优化脚本 2026-06-01 20:07:50 +08:00
ryan e00d67f2d9 [优化] 压缩脚本到 V16 2026-06-01 19:36:52 +08:00
ryan 581822d905 [优化] 修复 Agent CI 2026-06-01 19:04:01 +08:00
ryan b5ebfff19b [优化] Action 稳定性提升 2026-06-01 18:53:21 +08:00
ryan eed227b999 [优化] 修复迁移 2026-06-01 18:43:10 +08:00
ryan 8f08a962e6 [优化] 修复迁移 2026-06-01 17:50:49 +08:00
ryan d3ce26414c [优化] 移除数据库迁移中的 ApplyCurrentSchema 调用 2026-06-01 17:32:30 +08:00
ryan 663da01bda [优化] 修复数据库迁移问题 2026-06-01 17:15:17 +08:00
ryan df63b0113a [优化] 添加 OpenFlared API 支持,增强心跳和配置管理功能 2026-06-01 16:41:46 +08:00
ryan d09e64ddc6 [优化] 修复 2026-06-01 16:33:22 +08:00
ryan b968043117 [优化] 修复 2026-06-01 16:20:19 +08:00
ryan 31d10195ca [优化] 提升WS连接稳定性 2026-06-01 16:02:15 +08:00
ryan 76f3428f5d [优化] 字段修复 2026-06-01 16:02:02 +08:00
ryan 9de33f7064 [优化] 数据库结构优化 2026-06-01 15:32:08 +08:00
ryan fe13db95c2 [优化] Relay节点 IP 检测功能,自动设置 NodeIP 配置 2026-06-01 14:42:50 +08:00
ryan 6ddd2da2e8 [优化] 重构节点详情页面 2026-06-01 14:39:33 +08:00
ryan 054dc1a8a8 [优化] 添加 Relay frps 连接和代理计数字段,重构相关逻辑以支持监控和可观测性 2026-06-01 14:31:42 +08:00
ryan 394e3c4855 [优化] 更新数据库迁移逻辑,添加 v19 版本验证,重构隧道相关表结构 2026-06-01 14:06:36 +08:00
ryan af36676e2e Merge branch 'doc' 2026-06-01 14:01:40 +08:00
ryan 6fa31cafc7 [优化] 更新节点类型支持,添加隧道客户端,重构相关路由和配置 2026-06-01 14:01:05 +08:00
ryan a8e8a940a0 [优化] 优化 WAF IP 组同步功能及相关文档更新 2026-06-01 13:55:04 +08:00
ryan a092935623 [优化] 文档优化 2026-06-01 12:22:16 +08:00
ryan 5af13d0709 [优化] 修复 docker 2026-06-01 12:03:09 +08:00
ryan 9a2616dc0e [优化] 文档 2026-06-01 11:48:55 +08:00
ryan c4e9e94117 [优化] 修复 Docker 启动 2026-06-01 11:35:15 +08:00
ryan fce2e014e5 [修复] 节点类型字段名不匹配:前端 type 改为 node_type 对齐后端 JSON tag 2026-06-01 11:26:01 +08:00
ryan 7372ac230b [优化] 优化界面 2026-06-01 11:11:48 +08:00
ryan 77bdb8bf0e [优化] 优化界面 2026-06-01 11:05:00 +08:00
ryan fd745d33cb [优化] 更新 InlineMessage 组件,支持动态反馈和静态警告显示 2026-06-01 10:45:24 +08:00
ryan 65f899d334 [新增] 集成 Sonner 通知库,添加 Toaster 组件并在 InlineMessage 中使用 2026-06-01 10:45:24 +08:00
ryan f034b73a47 [新增] 添加数据库迁移和 GORM 模型验证测试,确保所有模型均已注册 2026-06-01 10:33:59 +08:00
ryan bd7f008322 [新增] 添加自动 IP 组规则的抓取记录功能,支持查看已抓取 IP 列表及其到期状态 2026-06-01 10:33:59 +08:00
ryan 2dc7e72621 [优化] 更新 go.mod 和 go.sum,移除不必要的依赖并添加新的依赖项 2026-06-01 10:11:51 +08:00
ryan 2d542733f9 [优化] 更新清理预发布标签的脚本,支持删除未绑定的正式标签和悬空的 GitHub 发布 2026-06-01 10:06:08 +08:00
ryan c677edba06 [新增] action 2026-06-01 09:57:41 +08:00
ryan b827baf19f [新增] 添加自动 IP 组规则测试功能,支持在保存前验证 Expr 规则命中情况 2026-06-01 09:50:43 +08:00
ryan 73beedfc09 [优化] 调整 openflared 和 openflare_relay 基础镜像为 frp 官方镜像 2026-06-01 09:37:54 +08:00
ryan bc1b861841 [优化] 添加自动 IP 组功能,支持按 Expr 规则聚合请求日志并更新 IP 列表 2026-06-01 09:34:33 +08:00
ryan dfb3972b15 [优化] DockerFile 2026-06-01 09:34:17 +08:00
ryan 330771e7c7 [新增] 内网穿透隧道前端管理与代理规则绑定支持 (P5) 2026-06-01 09:20:45 +08:00
ryan 9ded8c71da [优化] Phase4 2026-06-01 09:03:55 +08:00
ryan 77ad3ea7e3 [优化] 代码优化 2026-06-01 09:03:35 +08:00
ryan 95d7045b4a [优化] 添加 WAF IP 组功能,包括 CRUD 接口和前端页面支持 2026-06-01 08:53:03 +08:00
ryan d5f46138d5 [优化] Phase3 2026-06-01 08:47:09 +08:00
ryan 4196343ad3 [优化] Phase2 2026-06-01 08:38:31 +08:00
ryan 78047d1b38 [优化] 更新 docker-image.yml,调整浮动标签逻辑以支持 beta 版本 2026-05-31 22:16:49 +08:00
ryan c2bd416daf [优化] 更新 README.md,添加 BETA 版本警告信息 2026-05-31 22:10:39 +08:00
ryan 6e5d49c988 [优化] 更新 README.md,添加 BETA 版本警告信息 2026-05-31 22:10:23 +08:00
ryan 9da1ce8456 [优化] 移除清理预发布标签工作流中的确认输入 2026-05-31 22:04:06 +08:00
ryan 9f9cbd4ede [优化] 移除清理预发布标签工作流中的确认输入 2026-05-31 22:02:49 +08:00
ryan da1409fdac [优化] 优化界面 2026-05-31 21:58:30 +08:00
ryan 174198c283 [修复] 更新 OpenResty 模板和 Lua 逻辑以增强可读性和稳定性 2026-05-31 21:49:43 +08:00
ryan 796bf1c22f [优化] 添加错误日志路径占位符并更新相关逻辑 2026-05-31 21:26:45 +08:00
ryan 80dd5f8b31 [优化] 更新 Lua 包路径检查逻辑以避免重复添加 2026-05-31 21:17:36 +08:00
ryan 14d41ad807 [优化] 代码优化 2026-05-31 21:03:41 +08:00
ryan fe2414ead5 [优化] 代码优化 2026-05-31 20:48:15 +08:00
ryan 649287a775 [优化] 代码优化 2026-05-31 20:47:03 +08:00
ryan 2514e7edc4 [优化] 代码优化 2026-05-31 20:31:57 +08:00
ryan 7ab11154e3 [优化] 代码优化 2026-05-31 20:29:15 +08:00
ryan 97c10b8d0b [优化] 代码优化 2026-05-31 20:21:46 +08:00
ryan ceae693a20 [优化] 代码优化 2026-05-31 20:14:19 +08:00
ryan edb356f40e [优化] 代码优化 2026-05-31 20:09:40 +08:00
ryan 81ba309650 [优化] 代码优化 2026-05-31 20:05:35 +08:00
ryan 5612403d48 [优化] 代码优化 2026-05-31 20:02:01 +08:00
ryan 4775e5cb73 [优化] 代码优化 2026-05-31 19:59:09 +08:00
ryan bc3d9ee285 [优化] 代码优化 2026-05-31 19:54:17 +08:00
ryan e654441127 [优化] 配置渲染从 Server 转移到 Agent 2026-05-31 19:51:25 +08:00
ryan a987c0d681 [优化] 优化界面 2026-05-31 15:31:18 +08:00
ryan a85919fd9e [优化] 更新文档 2026-05-31 15:24:02 +08:00
ryan 4cb8928e4e [优化] 移除 Turnstile 相关功能和配置 2026-05-31 15:22:30 +08:00
ryan 57616626fd [优化] 优化代码 2026-05-31 14:53:04 +08:00
ryan 449d0a5c5b [优化] 更新文档 2026-05-31 14:52:37 +08:00
ryan 1c89db8ffa [优化] 补充测试 2026-05-31 14:49:21 +08:00
ryan 46f49cc349 [优化] 重构数据库迁移逻辑,添加版本管理和验证功能 2026-05-31 14:39:37 +08:00
ryan f365b3d331 [优化] 重构数据库迁移逻辑,添加版本管理和验证功能 2026-05-31 14:32:43 +08:00
ryan 4ae6c2718f [优化] 添加节点 IP 手动覆盖功能,更新相关文档和测试用例 2026-05-31 14:12:29 +08:00
ryan 8894620b92 [优化] 代码优化 2026-05-31 14:02:49 +08:00
ryan c2fcd2eddf [优化] 重构 ACME 客户端逻辑,简化证书获取和 DNS 提供者设置 2026-05-31 14:02:49 +08:00
ryan ec70794577 [优化] 重构 API 处理逻辑,简化参数解析和响应处理 2026-05-31 14:02:48 +08:00
ryan bcd669722e [优化] 优化代码 2026-05-31 13:23:49 +08:00
ryan 9975ac90c4 [优化] 邮件工具类去耦合 2026-05-31 13:19:47 +08:00
ryan 632c455229 docs: update deployment instructions for Agent to recommend Docker method 2026-05-31 13:13:52 +08:00
ryan b60cde02ac docs: sync and translate english documentation 2026-05-31 13:10:51 +08:00
ryan 21ed214ba9 [优化] 移除过时的 Docker 相关字段和测试用例 2026-05-31 13:09:53 +08:00
ryan f4a53d6b5f [优化] 增加断开 WebSocket 客户端的功能,优化连接管理 2026-05-30 19:05:20 +08:00
ryan cef3694d11 [优化] 界面优化 2026-05-30 18:07:18 +08:00
ryan 631d32e5d0 [优化] POW 与 WAF 合并 2026-05-30 17:42:23 +08:00
ryan c74b70b62e [优化] 界面优化 2026-05-30 16:31:26 +08:00
ryan fa9ecb5690 [优化] 界面优化 2026-05-30 16:25:17 +08:00
ryan b9cde88bf6 [优化] 重构 WAF 和 PoW 处理逻辑,使用 require 加载运行时模块,更新相关测试以验证新行为 2026-05-30 16:19:55 +08:00
ryan f03718ce8c [优化] 更新 Docker 部署指令,添加镜像拉取和容器移除命令 2026-05-30 16:08:43 +08:00
ryan 3423175006 [优化] 重构升级处理逻辑,添加备份二进制文件移除功能,更新相关测试以验证新行为 2026-05-30 16:05:48 +08:00
ryan d619deec96 [优化] 添加 WAF 阻止逻辑以短路 PoW 处理,更新测试以验证新行为 2026-05-30 15:46:34 +08:00
ryan e094f4a3b7 [优化] 移除不必要的支持文件过滤函数,更新相关测试以验证 WAF 配置包含 2026-05-30 15:39:18 +08:00
ryan 1bff2dadd4 [优化] 合并 WAF 和 PoW 访问处理逻辑,更新相关函数以支持新的配置格式 2026-05-30 15:12:49 +08:00
ryan 602e7f5e9c [优化] 修复 --version 错误 2026-05-30 13:24:23 +08:00
ryan 9ec3d5b42d [优化] 格式化 2026-05-30 13:15:20 +08:00
ryan 28b1305906 [优化] WAF 界面优化 2026-05-30 13:12:57 +08:00
ryan a80376972c [优化] 使用 slog 替代 fmt 进行日志输出 2026-05-30 12:27:00 +08:00
ryan 8300d3ec1c [新增] 添加 WAF 规则组及其绑定的 API 支持,更新前端页面以集成 WAF 功能 2026-05-30 12:16:28 +08:00
ryan 290ddd7b51 [优化] 添加获取折叠访问日志 IP 概要的 API 和前端支持 2026-05-30 10:48:06 +08:00
ryan 5d7a4469ea [优化] 增加 noop apply 报告逻辑,确保在配置未变更时记录应用日志 2026-05-30 10:27:10 +08:00
ryan 4e339caa9a [优化] 增加对节点 IP 的自动探测,优先通过第三方 API 获取公网 IP 2026-05-30 10:19:47 +08:00
ryan 2a00d21987 [文档] Doc 2026-05-30 09:51:36 +08:00
ryan f086edda3b [文档] Doc 2026-05-29 12:00:09 +08:00
ryan 899b4e6068 [优化] 优化 Docker 部署命令,移除不必要的端口映射 2026-05-29 11:46:09 +08:00
ryan fa23cad9e9 [#12] Auto-update downloads and executes binary with no signature or checksum verification 2026-05-29 11:29:33 +08:00
ryan 806863f303 [修复] 修复 WS 连接下更新无法下发 2026-05-29 11:08:05 +08:00
ryan ab8e3d4705 [优化] 更新 openresty_observability_port 描述,增强健康检查逻辑,使用 stub_status 代替 openresty -t 2026-05-29 10:52:25 +08:00
ryan fe7f7da537 [优化] 更新 openresty_observability_port 描述,增强健康检查逻辑,使用 stub_status 代替 openresty -t 2026-05-29 10:50:13 +08:00
ryan 944b98d4d0 [新增] 实现节点强制同步功能,允许通过 API 请求强制同步配置 2026-05-29 10:44:14 +08:00
ryan 32dc7ef68e [新增] 实现节点强制同步功能,允许通过 API 请求强制同步配置 2026-05-29 10:34:25 +08:00
ryan 32762fdf3c [新增] 实现安全兜底配置功能,允许在无历史配置时启动 OpenResty 并返回 503 状态 2026-05-29 10:07:49 +08:00
ryan 79ed8fd6ab [新增] 实现 Agent WebSocket 连接升级功能,支持状态上报和配置广播 2026-05-29 09:52:34 +08:00
ryan 4257b6fd5a [优化] 移除 Docker 运行命令中的数据卷挂载 2026-05-29 09:39:49 +08:00
ryan 8dfe31c1c5 [新增] 补充旧版本 agent 卸载脚本 2026-05-29 09:13:31 +08:00
ryan 37486eb0c9 [优化] 增强 OSCommandRunner 的命令执行逻辑,添加临时文件处理和详细日志记录 2026-05-28 23:50:07 +08:00
ryan 462deb4820 [新增] 添加 Docker 安装命令构建逻辑并更新节点详情页面 2026-05-28 23:41:18 +08:00
ryan f8509eed26 [优化] 优化对主配置路径的存在性检查以增强健康检查逻辑 2026-05-28 23:29:01 +08:00
ryan 6e0b6df314 [新增] 添加 MIME 类型支持和更新 Docker Compose 配置 2026-05-28 23:20:53 +08:00
ryan 21962db3bf [新增] 添加 Docker Compose 2026-05-28 23:15:04 +08:00
ryan b0117b7c84 [新增] 同步更新英文版文档 2026-05-28 23:00:48 +08:00
ryan 95d58eb724 [新增] Agent 架构调整, 采用集成镜像方式 2026-05-28 22:59:50 +08:00
ryan c856faca50 [新增] 更新文档 2026-05-28 22:56:39 +08:00
ryan 5a0821274b [新增] 更新文档 2026-05-28 22:50:24 +08:00
ryan b69bdf838d [新增] 优化版本号生成逻辑,确保使用最大日序列号 2026-05-26 21:29:03 +08:00
ryan c35eb749c9 [新增] 添加转换上传的 TLS 证书为 ACME 管理证书的功能 2026-05-26 21:16:03 +08:00
ryan e3c84c017a [新增] 添加转换上传的 TLS 证书为 ACME 管理证书的功能 2026-05-26 21:06:50 +08:00
ryan 112694f860 [新增] 添加删除预发布标签清理工作流 2026-05-26 11:18:20 +08:00
ryan bd69ac51b5 [新增] 添加预览预发布标签清理工作流 2026-05-26 11:15:48 +08:00
ryan 46fb1a2b79 Revert "[优化] 添加基本鉴权支持,更新相关逻辑以生成 htpasswd 文件"
This reverts commit c9a532db65.
2026-05-26 10:57:21 +08:00
ryan c9a532db65 [优化] 添加基本鉴权支持,更新相关逻辑以生成 htpasswd 文件 2026-05-26 10:41:41 +08:00
ryan be68b581e9 [优化] 添加基础鉴权支持,包括用户名和密码字段,并更新相关逻辑和测试用例 2026-05-26 10:36:00 +08:00
ryan 8853933adc [优化] 修复基本鉴权逻辑,确保 Lua 块正确关闭并添加相关测试用例 2026-05-26 10:29:40 +08:00
ryan 048f6e4535 [优化] 调整 Nginx 配置生成逻辑,优化访问控制和代理位置块的渲染顺序 2026-05-26 10:17:37 +08:00
ryan 7b9c8996f9 [优化] 更新数据库模式版本至12,添加基础鉴权字段支持 2026-05-26 09:57:56 +08:00
ryan 8947bdc8d8 [优化] 更新 PublishConfigVersion 函数以支持强制发布选项,并调整相关调用 2026-05-26 09:56:18 +08:00
ryan baef42f920 [优化] 添加基础鉴权配置支持,包括用户名和密码 2026-05-26 09:37:42 +08:00
ryan dd58e0df66 [优化] 添加 OpenRestyResolvers 配置支持自定义 DNS 解析器 2026-05-26 09:18:52 +08:00
ryan bddf641bf1 [优化] 添加 OpenRestyResolvers 配置支持自定义 DNS 解析器 2026-05-26 09:10:08 +08:00
ryan 83a426d3d6 [优化] 添加清理历史快照功能 2026-05-25 16:41:07 +08:00
ryan 4f698be0a5 [优化] 更新 CORS 配置以支持动态源和凭证 2026-05-25 16:29:07 +08:00
ryan e9fb331214 [fix] 修复构建 2026-05-25 16:22:51 +08:00
ryan 5d6d68d0a1 [优化] 更新 Go 版本要求至 1.25+ 2026-05-25 16:18:07 +08:00
ryan c8e2c3620e [优化] 结构优化 2026-05-25 16:12:22 +08:00
ryan af8e9b477e [优化] 导航调整 2026-05-25 16:05:56 +08:00
ryan 314f6fd3f4 [优化] 移除注册相关功能的代码和配置 2026-05-25 16:03:38 +08:00
ryan 7eee788720 [优化] UI improve 2026-05-25 15:47:56 +08:00
ryan f6e4967a9a [新增] 添加 ACME 和 DNS 账号管理功能,支持证书申请与续期 2026-05-25 14:56:05 +08:00
ryan 7afe4e5d78 [新增] 添加 ACME 和 DNS 账号管理功能,支持证书申请与续期 2026-05-25 14:53:22 +08:00
Ryan c6a055d5d3 Update README.md 2026-05-13 14:00:53 +08:00
ryan 9a89428405 [修复] 个人设置查看第三方认证源与增加解绑功能 2026-05-13 12:09:15 +08:00
ryan 370d58ac4d OIDC 文档 2026-05-13 11:46:33 +08:00
ryan e85df49962 OIDC 2026-05-13 11:44:01 +08:00
ryan 856e3f46d2 gitignore 2026-05-13 10:21:18 +08:00
ryan 2d6cc908f5 优化文档 2026-05-09 18:10:19 +08:00
ryan 797a15ae70 vite-press init 2026-05-09 17:37:06 +08:00
ryan 8730f99fef UPDATE README 2026-04-26 10:25:44 +08:00
ryan 8ad4defcc7 [功能] POW 有效期优化 2026-04-25 20:49:08 +08:00
ryan d3d32a6b6b [fix] anubis 2026-04-25 19:48:30 +08:00
ryan 9c57ec2f5c [功能] POW 集成 2026-04-19 23:00:57 +08:00
ryan f8c1fe804d [修复] 修复github登录问题 2026-04-01 10:44:34 +08:00
ryan 89489c8488 [功能] 添加卸载脚本以支持彻底卸载 OpenFlare Agent 并清空本地数据 2026-04-01 10:24:05 +08:00
ryan d425e34f71 [优化] 更新默认服务器块,添加 HTTPS 支持并启用 SSL 握手拒绝 2026-04-01 10:02:40 +08:00
ryan 49472b54bf [功能] 添加域名证书绑定支持,允许为每个域名单独选择证书并优化相关逻辑 2026-04-01 09:57:40 +08:00
ryan a002d98f3a [优化] 更新域名列表输入组件,优化按钮样式并支持自定义容器类型 2026-04-01 09:34:17 +08:00
ryan 77457250cf [功能] 更新域名列表输入组件,支持为每个域名选择证书并优化相关逻辑 2026-04-01 09:27:41 +08:00
ryan cff815bd47 [功能] 支持为 HTTPS 启用多个证书,更新相关逻辑和测试 2026-03-31 14:16:32 +08:00
ryan 97fa56b1af [功能] 添加域名列表输入组件,支持动态建议和批量输入 2026-03-31 13:29:30 +08:00
ryan 355791f2e4 [优化] 文本优化 2026-03-31 13:16:00 +08:00
ryan 65ecc27907 [优化] 文本优化 2026-03-31 13:13:38 +08:00
ryan c2184affed [功能] 添加批量更新选项接口,支持一次性更新多个配置项,更新相关逻辑和测试 2026-03-30 16:48:10 +08:00
ryan 7d9190a8d8 [功能] 禁用新用户注册功能,更新相关逻辑和测试 2026-03-30 16:01:25 +08:00
ryan 4b1e75f86b [修改] 文本优化 2026-03-30 15:46:54 +08:00
ryan 25fe178cb2 [功能] 添加网站创建抽屉组件,支持域名和上游地址输入,更新相关逻辑和测试 2026-03-30 15:07:59 +08:00
ryan 383a039338 [功能] 接口与校验改造 2026-03-30 14:45:28 +08:00
ryan e39a8995f6 [功能] 添加站点名称和多域名支持到代理路由,更新相关逻辑和测试 2026-03-30 14:11:30 +08:00
ryan 894745d43a [功能] 优化节点 IP 解析逻辑,优先使用公网地址并添加相关测试 2026-03-30 13:09:30 +08:00
ryan 39d54c2fe4 [文档] 升级代理路由规则为网站配置,支持多域名绑定与共享设置 2026-03-30 11:13:57 +08:00
ryan fdadd76945 [功能] 添加抽屉组件并重构代理路由页面,优化规则创建体验 2026-03-30 10:32:49 +08:00
ryan 6e109fd3f7 [功能] 移除前端开发规范中的禁止项和测试交付要求,简化文档内容 2026-03-27 13:55:15 +08:00
ryan f14ba66a11 [功能] 更新组件库hero3.0.1 2026-03-27 13:34:35 +08:00
ryan 6b1d2e8af9 [功能] 移除代理路由页面中的缓存和请求头列,简化显示内容 2026-03-27 11:18:37 +08:00
ryan a0fff76fcb [?] update 2026-03-24 19:02:00 +08:00
ryan 4fa8f073a3 [功能] 重构代理路由页面,优化输入组件和样式 2026-03-20 23:29:18 +08:00
ryan a6787ac30d [功能] 添加新的输入、文本区域、标签和开关组件,优化样式和功能 2026-03-20 23:15:35 +08:00
ryan 2c87254bb3 [功能] 更新代理路由页面,集成新的输入和选择组件,优化域名选择逻辑 2026-03-20 22:52:57 +08:00
ryan 1fd4b22b9c [功能] 重构代理路由页面的单元测试,优化fetch模拟和输入验证逻辑 2026-03-20 22:37:02 +08:00
ryan be9744abc6 [功能] 重构代理路由页面的单元测试,优化fetch模拟和输入验证逻辑 2026-03-20 22:20:13 +08:00
ryan afd891f0f6 [功能] 添加源站管理功能,包括源站的创建、更新、删除及列表展示 2026-03-20 20:01:42 +08:00
ryan edd31da527 [功能] 添加代理路由页面的单元测试,支持通配符和精确域名的规则生成 2026-03-20 19:42:29 +08:00
ryan 7b9377eb21 [文档] 文档更新 2026-03-19 21:17:23 +08:00
ryan dc72c78b7f [优化] 界面优化 2026-03-19 21:00:43 +08:00
ryan 9eeccb5fc6 [功能] 添加数据库观测数据清理功能,支持手动和自动清理策略 2026-03-19 20:48:45 +08:00
ryan a1b3204204 [功能] 添加遗留观察性索引和表的删除逻辑,优化数据库迁移过程 2026-03-19 20:26:18 +08:00
ryan 8737e146d1 [修改] 分片逻辑修改为基于ID 2026-03-19 17:57:30 +08:00
ryan ae72f2da9a [功能] 实现数据库版本管理与迁移逻辑,确保数据库结构与版本一致性 2026-03-19 16:45:22 +08:00
ryan f26fcd028e [功能] 添加迁移遗留观察性列的功能,支持从 raw_json 填充 metadata_json 2026-03-19 16:31:05 +08:00
ryan dd49b2777d [功能] 实现节点访问日志的分片支持,优化日志查询和管理逻辑 2026-03-19 16:19:46 +08:00
503 changed files with 91883 additions and 14257 deletions
+11
View File
@@ -0,0 +1,11 @@
.git
.idea
anubis-source
**/node_modules
**/.next
**/build
**/dist
**/.cache
**/coverage
**/*.db
**/*.log
+1 -1
View File
@@ -100,7 +100,7 @@ jobs:
while read -r GOOS GOARCH ASSET_NAME; do
GOOS="$GOOS" GOARCH="$GOARCH" \
go build -trimpath -ldflags "-s -w -X 'openflare-agent/internal/config.AgentVersion=$VERSION'" -o "../dist/$ASSET_NAME" ./cmd/agent
go build -trimpath -ldflags "-s -w -X 'openflare-agent/internal/config.Version=$VERSION'" -o "../dist/$ASSET_NAME" ./cmd/agent
done <<'EOF'
linux amd64 openflare-agent-linux-amd64
linux arm64 openflare-agent-linux-arm64
+1 -5
View File
@@ -1,5 +1 @@
blank_issues_enabled: false
contact_links:
- name: 赞赏支持
url: https://iamazing.cn/page/reward
about: 请作者喝杯咖啡,以激励作者持续开发
blank_issues_enabled: false
@@ -0,0 +1,94 @@
name: Cleanup prerelease tags
on:
workflow_dispatch:
permissions:
contents: write
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve version metadata
id: version
run: |
SHOULD_RUN=true
VERSION="all-prerelease-tags"
echo "should_run=$SHOULD_RUN" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if [[ "$VERSION" =~ ^v[0-9]+(\.[0-9]+)*$ ]]; then
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
else
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
fi
- name: Delete prerelease, dangling, and unbound releases/tags
if: steps.version.outputs.should_run == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Fetch all tags from remote to ensure full synchronization
git fetch --tags --force
# Get all local/remote git tags starting with 'v'
mapfile -t GIT_TAGS < <(git tag --list 'v*' | sort -V)
# Get all GitHub releases (tags associated with releases)
mapfile -t GH_RELEASES < <(gh release list --limit 1000 --json tagName --jq '.[].tagName' 2>/dev/null || true)
# Helper function to check array containment
contains_element() {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
DELETED_TAGS=0
DELETED_RELEASES=0
echo "=== Phase 1: Checking and cleaning Git tags ==="
for TAG in "${GIT_TAGS[@]}"; do
if [[ "$TAG" =~ ^v[0-9]+(\.[0-9]+)*$ ]]; then
# Formal release tag
if ! contains_element "$TAG" "${GH_RELEASES[@]}"; then
echo "Delete formal tag not bound to any GitHub release: $TAG"
git push origin --delete "refs/tags/$TAG" || true
git tag -d "$TAG" || true
DELETED_TAGS=$((DELETED_TAGS + 1))
else
echo "Keep formal release tag (bound to release): $TAG"
fi
else
# Prerelease tag
if contains_element "$TAG" "${GH_RELEASES[@]}"; then
echo "Delete prerelease release: $TAG"
gh release delete "$TAG" --yes || true
DELETED_RELEASES=$((DELETED_RELEASES + 1))
fi
echo "Delete prerelease tag: $TAG"
git push origin --delete "refs/tags/$TAG" || true
git tag -d "$TAG" || true
DELETED_TAGS=$((DELETED_TAGS + 1))
fi
done
echo "=== Phase 2: Checking and cleaning dangling GitHub releases ==="
for REL_TAG in "${GH_RELEASES[@]}"; do
if ! contains_element "$REL_TAG" "${GIT_TAGS[@]}"; then
echo "Delete GitHub release not bound to any Git tag: $REL_TAG"
gh release delete "$REL_TAG" --yes || true
DELETED_RELEASES=$((DELETED_RELEASES + 1))
fi
done
echo "=== Summary ==="
echo "Successfully deleted $DELETED_TAGS tag(s) and $DELETED_RELEASES release(s)."
+187
View File
@@ -0,0 +1,187 @@
name: Docker image build (Agent)
on:
workflow_dispatch:
inputs:
version:
description: "Image version/tag to publish, for example v1.0.0-beta"
required: false
type: string
push:
tags: ["v*"]
permissions:
contents: read
packages: write
attestations: write
id-token: write
jobs:
build:
name: Build (${{ matrix.arch }})
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-24.04
- arch: arm64
platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: Set image metadata
shell: bash
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
POINTED_TAG="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)"
INPUT_VERSION="${INPUT_VERSION//[[:space:]]/}"
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}-agent" >> "$GITHUB_ENV"
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
VERSION="${GITHUB_REF_NAME}"
elif [[ -n "$INPUT_VERSION" ]]; then
VERSION="$INPUT_VERSION"
elif [[ -n "$POINTED_TAG" ]]; then
VERSION="$POINTED_TAG"
else
echo "workflow_dispatch requires an explicit version input when HEAD is not tagged" >&2
exit 1
fi
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: build
uses: docker/build-push-action@v7
with:
context: .
file: ./openflare_agent/Dockerfile
platforms: ${{ matrix.platform }}
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
build-args: |
VERSION=${{ env.VERSION }}
cache-from: type=gha,scope=docker-agent-${{ matrix.arch }}
cache-to: type=gha,mode=max,ignore-error=true,timeout=20m,scope=docker-agent-${{ matrix.arch }}
- name: Export digest
shell: bash
run: |
mkdir -p /tmp/agent-digests
touch "/tmp/agent-digests/${DIGEST#sha256:}"
env:
DIGEST: ${{ steps.build.outputs.digest }}
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: agent-digests-${{ matrix.arch }}
path: /tmp/agent-digests/*
if-no-files-found: error
retention-days: 1
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
merge:
name: Merge multi-arch manifest
runs-on: ubuntu-24.04
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: Set image metadata
shell: bash
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
POINTED_TAG="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)"
INPUT_VERSION="${INPUT_VERSION//[[:space:]]/}"
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}-agent" >> "$GITHUB_ENV"
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
VERSION="${GITHUB_REF_NAME}"
elif [[ -n "$INPUT_VERSION" ]]; then
VERSION="$INPUT_VERSION"
elif [[ -n "$POINTED_TAG" ]]; then
VERSION="$POINTED_TAG"
else
echo "workflow_dispatch requires an explicit version input when HEAD is not tagged" >&2
exit 1
fi
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/agent-digests
pattern: agent-digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest list
working-directory: /tmp/agent-digests
shell: bash
run: |
shopt -s nullglob
references=()
for digest in *; do
references+=("${IMAGE}@sha256:${digest}")
done
if [ ${#references[@]} -eq 0 ]; then
echo "No digests found in /tmp/agent-digests" >&2
exit 1
fi
if [[ "${VERSION}" =~ (alpha|beta|rc) ]]; then
FLOATING_TAG="beta"
else
FLOATING_TAG="latest"
fi
docker buildx imagetools create \
-t "${IMAGE}:${VERSION}" \
-t "${IMAGE}:${FLOATING_TAG}" \
"${references[@]}"
- name: Inspect image
run: docker buildx imagetools inspect "${IMAGE}:${VERSION}"
+187
View File
@@ -0,0 +1,187 @@
name: Docker image build (OpenFlared)
on:
workflow_dispatch:
inputs:
version:
description: "Image version/tag to publish, for example v1.0.0-beta"
required: false
type: string
push:
tags: ["v*"]
permissions:
contents: read
packages: write
attestations: write
id-token: write
jobs:
build:
name: Build (${{ matrix.arch }})
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-24.04
- arch: arm64
platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: Set image metadata
shell: bash
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
POINTED_TAG="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)"
INPUT_VERSION="${INPUT_VERSION//[[:space:]]/}"
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/openflared" >> "$GITHUB_ENV"
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
VERSION="${GITHUB_REF_NAME}"
elif [[ -n "$INPUT_VERSION" ]]; then
VERSION="$INPUT_VERSION"
elif [[ -n "$POINTED_TAG" ]]; then
VERSION="$POINTED_TAG"
else
echo "workflow_dispatch requires an explicit version input when HEAD is not tagged" >&2
exit 1
fi
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: build
uses: docker/build-push-action@v7
with:
context: .
file: ./openflared/Dockerfile
platforms: ${{ matrix.platform }}
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
build-args: |
VERSION=${{ env.VERSION }}
cache-from: type=gha,scope=docker-flared-${{ matrix.arch }}
cache-to: type=gha,mode=max,ignore-error=true,timeout=20m,scope=docker-flared-${{ matrix.arch }}
- name: Export digest
shell: bash
run: |
mkdir -p /tmp/flared-digests
touch "/tmp/flared-digests/${DIGEST#sha256:}"
env:
DIGEST: ${{ steps.build.outputs.digest }}
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: flared-digests-${{ matrix.arch }}
path: /tmp/flared-digests/*
if-no-files-found: error
retention-days: 1
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
merge:
name: Merge multi-arch manifest
runs-on: ubuntu-24.04
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: Set image metadata
shell: bash
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
POINTED_TAG="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)"
INPUT_VERSION="${INPUT_VERSION//[[:space:]]/}"
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/openflared" >> "$GITHUB_ENV"
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
VERSION="${GITHUB_REF_NAME}"
elif [[ -n "$INPUT_VERSION" ]]; then
VERSION="$INPUT_VERSION"
elif [[ -n "$POINTED_TAG" ]]; then
VERSION="$POINTED_TAG"
else
echo "workflow_dispatch requires an explicit version input when HEAD is not tagged" >&2
exit 1
fi
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/flared-digests
pattern: flared-digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest list
working-directory: /tmp/flared-digests
shell: bash
run: |
shopt -s nullglob
references=()
for digest in *; do
references+=("${IMAGE}@sha256:${digest}")
done
if [ ${#references[@]} -eq 0 ]; then
echo "No digests found in /tmp/flared-digests" >&2
exit 1
fi
if [[ "${VERSION}" =~ (alpha|beta|rc) ]]; then
FLOATING_TAG="beta"
else
FLOATING_TAG="latest"
fi
docker buildx imagetools create \
-t "${IMAGE}:${VERSION}" \
-t "${IMAGE}:${FLOATING_TAG}" \
"${references[@]}"
- name: Inspect image
run: docker buildx imagetools inspect "${IMAGE}:${VERSION}"
+187
View File
@@ -0,0 +1,187 @@
name: Docker image build (Relay)
on:
workflow_dispatch:
inputs:
version:
description: "Image version/tag to publish, for example v1.0.0-beta"
required: false
type: string
push:
tags: ["v*"]
permissions:
contents: read
packages: write
attestations: write
id-token: write
jobs:
build:
name: Build (${{ matrix.arch }})
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-24.04
- arch: arm64
platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: Set image metadata
shell: bash
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
POINTED_TAG="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)"
INPUT_VERSION="${INPUT_VERSION//[[:space:]]/}"
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}-relay" >> "$GITHUB_ENV"
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
VERSION="${GITHUB_REF_NAME}"
elif [[ -n "$INPUT_VERSION" ]]; then
VERSION="$INPUT_VERSION"
elif [[ -n "$POINTED_TAG" ]]; then
VERSION="$POINTED_TAG"
else
echo "workflow_dispatch requires an explicit version input when HEAD is not tagged" >&2
exit 1
fi
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: build
uses: docker/build-push-action@v7
with:
context: .
file: ./openflare_relay/Dockerfile
platforms: ${{ matrix.platform }}
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
build-args: |
VERSION=${{ env.VERSION }}
cache-from: type=gha,scope=docker-relay-${{ matrix.arch }}
cache-to: type=gha,mode=max,ignore-error=true,timeout=20m,scope=docker-relay-${{ matrix.arch }}
- name: Export digest
shell: bash
run: |
mkdir -p /tmp/relay-digests
touch "/tmp/relay-digests/${DIGEST#sha256:}"
env:
DIGEST: ${{ steps.build.outputs.digest }}
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: relay-digests-${{ matrix.arch }}
path: /tmp/relay-digests/*
if-no-files-found: error
retention-days: 1
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
merge:
name: Merge multi-arch manifest
runs-on: ubuntu-24.04
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: Set image metadata
shell: bash
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
POINTED_TAG="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)"
INPUT_VERSION="${INPUT_VERSION//[[:space:]]/}"
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}-relay" >> "$GITHUB_ENV"
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
VERSION="${GITHUB_REF_NAME}"
elif [[ -n "$INPUT_VERSION" ]]; then
VERSION="$INPUT_VERSION"
elif [[ -n "$POINTED_TAG" ]]; then
VERSION="$POINTED_TAG"
else
echo "workflow_dispatch requires an explicit version input when HEAD is not tagged" >&2
exit 1
fi
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/relay-digests
pattern: relay-digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log into registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest list
working-directory: /tmp/relay-digests
shell: bash
run: |
shopt -s nullglob
references=()
for digest in *; do
references+=("${IMAGE}@sha256:${digest}")
done
if [ ${#references[@]} -eq 0 ]; then
echo "No digests found in /tmp/relay-digests" >&2
exit 1
fi
if [[ "${VERSION}" =~ (alpha|beta|rc) ]]; then
FLOATING_TAG="beta"
else
FLOATING_TAG="latest"
fi
docker buildx imagetools create \
-t "${IMAGE}:${VERSION}" \
-t "${IMAGE}:${FLOATING_TAG}" \
"${references[@]}"
- name: Inspect image
run: docker buildx imagetools inspect "${IMAGE}:${VERSION}"
@@ -1,4 +1,4 @@
name: Docker image builds
name: Docker image build (Server)
on:
workflow_dispatch:
@@ -61,7 +61,7 @@ jobs:
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log into registry
uses: docker/login-action@v3
@@ -72,7 +72,7 @@ jobs:
- name: Build and push
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: ./openflare_server
file: ./openflare_server/Dockerfile
@@ -80,22 +80,22 @@ jobs:
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
build-args: |
VERSION=${{ env.VERSION }}
cache-from: type=gha,scope=docker-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=docker-${{ matrix.arch }}
cache-from: type=gha,scope=docker-server-${{ matrix.arch }}
cache-to: type=gha,mode=max,ignore-error=true,timeout=20m,scope=docker-server-${{ matrix.arch }}
- name: Export digest
shell: bash
run: |
mkdir -p /tmp/digests
touch "/tmp/digests/${DIGEST#sha256:}"
mkdir -p /tmp/server-digests
touch "/tmp/server-digests/${DIGEST#sha256:}"
env:
DIGEST: ${{ steps.build.outputs.digest }}
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ matrix.arch }}
path: /tmp/digests/*
name: server-digests-${{ matrix.arch }}
path: /tmp/server-digests/*
if-no-files-found: error
retention-days: 1
@@ -143,12 +143,12 @@ jobs:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
path: /tmp/server-digests
pattern: server-digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log into registry
uses: docker/login-action@v3
@@ -158,7 +158,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest list
working-directory: /tmp/digests
working-directory: /tmp/server-digests
shell: bash
run: |
shopt -s nullglob
@@ -168,13 +168,19 @@ jobs:
done
if [ ${#references[@]} -eq 0 ]; then
echo "No digests found in /tmp/digests" >&2
echo "No digests found in /tmp/server-digests" >&2
exit 1
fi
if [[ "${VERSION}" =~ (alpha|beta|rc) ]]; then
FLOATING_TAG="beta"
else
FLOATING_TAG="latest"
fi
docker buildx imagetools create \
-t "${IMAGE}:${VERSION}" \
-t "${IMAGE}:latest" \
-t "${IMAGE}:${FLOATING_TAG}" \
"${references[@]}"
- name: Inspect image
+335 -206
View File
@@ -1,7 +1,7 @@
name: Release
permissions:
contents: write
name: Release
permissions:
contents: write
on:
workflow_dispatch:
inputs:
@@ -11,20 +11,20 @@ on:
type: string
push:
tags: ["v*"]
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.version.outputs.should_run }}
version: ${{ steps.version.outputs.version }}
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.version.outputs.should_run }}
version: ${{ steps.version.outputs.version }}
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve version metadata
id: version
env:
@@ -52,191 +52,320 @@ jobs:
fi
echo "should_run=$SHOULD_RUN" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if [[ "$VERSION" =~ ^v[0-9]+(\.[0-9]+)*$ ]]; then
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
else
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
fi
build-frontend:
needs: prepare
if: needs.prepare.outputs.should_run == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Build Frontend
env:
CI: ""
VERSION: ${{ needs.prepare.outputs.version }}
run: |
cd openflare_server/web
corepack enable
pnpm install --frozen-lockfile
NEXT_PUBLIC_APP_VERSION="$VERSION" pnpm build
- name: Upload Frontend Artifact
uses: actions/upload-artifact@v4
with:
name: frontend-build
path: openflare_server/web/build
retention-days: 1
build-binaries:
needs:
- prepare
- build-frontend
if: needs.prepare.outputs.should_run == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: linux
goarch: amd64
asset_name: openflare-server-linux-amd64
- goos: linux
goarch: arm64
asset_name: openflare-server-linux-arm64
- goos: darwin
goarch: amd64
asset_name: openflare-server-darwin-amd64
- goos: darwin
goarch: arm64
asset_name: openflare-server-darwin-arm64
- goos: windows
goarch: amd64
asset_name: openflare-server-windows-amd64.exe
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download Frontend Artifact
uses: actions/download-artifact@v4
with:
name: frontend-build
path: openflare_server/web/build
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: openflare_server/go.mod
- name: Build Server
working-directory: openflare_server
env:
CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
ASSET_NAME: ${{ matrix.asset_name }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
go mod download
mkdir -p ../dist
go build -trimpath -ldflags "-s -w -X 'openflare/common.Version=$VERSION'" -o "../dist/$ASSET_NAME" .
- name: Upload Binary Artifact
uses: actions/upload-artifact@v4
with:
name: server-${{ matrix.goos }}-${{ matrix.goarch }}
path: dist/${{ matrix.asset_name }}
retention-days: 1
build-agent-binaries:
needs: prepare
if: needs.prepare.outputs.should_run == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: linux
goarch: amd64
asset_name: openflare-agent-linux-amd64
- goos: linux
goarch: arm64
asset_name: openflare-agent-linux-arm64
- goos: darwin
goarch: amd64
asset_name: openflare-agent-darwin-amd64
- goos: darwin
goarch: arm64
asset_name: openflare-agent-darwin-arm64
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: openflare_agent/go.mod
- name: Build Agent
working-directory: openflare_agent
env:
CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
ASSET_NAME: ${{ matrix.asset_name }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
go mod download
mkdir -p ../dist
go build -trimpath -ldflags "-s -w -X 'openflare-agent/internal/config.AgentVersion=$VERSION'" -o "../dist/$ASSET_NAME" ./cmd/agent
- name: Upload Agent Artifact
uses: actions/upload-artifact@v4
with:
name: agent-${{ matrix.goos }}-${{ matrix.goarch }}
path: dist/${{ matrix.asset_name }}
retention-days: 1
release:
needs:
- prepare
- build-binaries
- build-agent-binaries
if: needs.prepare.outputs.should_run == 'true'
runs-on: ubuntu-latest
steps:
- name: Download Server Artifacts
uses: actions/download-artifact@v4
with:
pattern: "server-*"
path: dist
merge-multiple: true
- name: Download Agent Artifacts
uses: actions/download-artifact@v4
with:
pattern: "agent-*"
path: dist
merge-multiple: true
- name: Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.prepare.outputs.version }}
name: ${{ needs.prepare.outputs.version }}
target_commitish: ${{ github.sha }}
files: dist/*
draft: false
prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }}
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if [[ "$VERSION" =~ ^v[0-9]+(\.[0-9]+)*$ ]]; then
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
else
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
fi
build-frontend:
needs: prepare
if: needs.prepare.outputs.should_run == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Build Frontend
env:
CI: ""
VERSION: ${{ needs.prepare.outputs.version }}
run: |
cd openflare_server/web
corepack enable
pnpm install --frozen-lockfile
NEXT_PUBLIC_APP_VERSION="$VERSION" pnpm build
- name: Upload Frontend Artifact
uses: actions/upload-artifact@v4
with:
name: frontend-build
path: openflare_server/web/build
retention-days: 1
build-binaries:
needs:
- prepare
- build-frontend
if: needs.prepare.outputs.should_run == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: linux
goarch: amd64
asset_name: openflare-server-linux-amd64
- goos: linux
goarch: arm64
asset_name: openflare-server-linux-arm64
- goos: darwin
goarch: amd64
asset_name: openflare-server-darwin-amd64
- goos: darwin
goarch: arm64
asset_name: openflare-server-darwin-arm64
- goos: windows
goarch: amd64
asset_name: openflare-server-windows-amd64.exe
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download Frontend Artifact
uses: actions/download-artifact@v4
with:
name: frontend-build
path: openflare_server/web/build
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: openflare_server/go.mod
- name: Build Server
working-directory: openflare_server
env:
CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
ASSET_NAME: ${{ matrix.asset_name }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
go mod download
mkdir -p ../dist
go build -trimpath -ldflags "-s -w -X 'openflare/common.Version=$VERSION'" -o "../dist/$ASSET_NAME" .
- name: Upload Binary Artifact
uses: actions/upload-artifact@v4
with:
name: server-${{ matrix.goos }}-${{ matrix.goarch }}
path: dist/${{ matrix.asset_name }}
retention-days: 1
build-agent-binaries:
needs: prepare
if: needs.prepare.outputs.should_run == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: linux
goarch: amd64
asset_name: openflare-agent-linux-amd64
- goos: linux
goarch: arm64
asset_name: openflare-agent-linux-arm64
- goos: darwin
goarch: amd64
asset_name: openflare-agent-darwin-amd64
- goos: darwin
goarch: arm64
asset_name: openflare-agent-darwin-arm64
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: openflare_agent/go.mod
- name: Build Agent
working-directory: openflare_agent
env:
CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
ASSET_NAME: ${{ matrix.asset_name }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
go mod download
mkdir -p ../dist
go build -trimpath -ldflags "-s -w -X 'openflare-agent/internal/config.Version=$VERSION'" -o "../dist/$ASSET_NAME" ./cmd/agent
(cd ../dist && sha256sum "$ASSET_NAME" > "$ASSET_NAME.sha256")
- name: Upload Agent Artifact
uses: actions/upload-artifact@v4
with:
name: agent-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
dist/${{ matrix.asset_name }}
dist/${{ matrix.asset_name }}.sha256
retention-days: 1
build-relay-binaries:
needs: prepare
if: needs.prepare.outputs.should_run == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: linux
goarch: amd64
asset_name: openflare-relay-linux-amd64
- goos: linux
goarch: arm64
asset_name: openflare-relay-linux-arm64
- goos: darwin
goarch: amd64
asset_name: openflare-relay-darwin-amd64
- goos: darwin
goarch: arm64
asset_name: openflare-relay-darwin-arm64
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: openflare_relay/go.mod
- name: Build Relay
working-directory: openflare_relay
env:
CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
ASSET_NAME: ${{ matrix.asset_name }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
go mod download
mkdir -p ../dist
go build -trimpath -ldflags "-s -w -X 'openflare-relay/internal/config.Version=$VERSION'" -o "../dist/$ASSET_NAME" ./cmd/relay
(cd ../dist && sha256sum "$ASSET_NAME" > "$ASSET_NAME.sha256")
- name: Upload Relay Artifact
uses: actions/upload-artifact@v4
with:
name: relay-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
dist/${{ matrix.asset_name }}
dist/${{ matrix.asset_name }}.sha256
retention-days: 1
build-flared-binaries:
needs: prepare
if: needs.prepare.outputs.should_run == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: linux
goarch: amd64
asset_name: openflared-linux-amd64
- goos: linux
goarch: arm64
asset_name: openflared-linux-arm64
- goos: darwin
goarch: amd64
asset_name: openflared-darwin-amd64
- goos: darwin
goarch: arm64
asset_name: openflared-darwin-arm64
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: openflared/go.mod
- name: Build Flared
working-directory: openflared
env:
CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
ASSET_NAME: ${{ matrix.asset_name }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
go mod download
mkdir -p ../dist
go build -trimpath -ldflags "-s -w -X 'openflare-flared/internal/config.Version=$VERSION'" -o "../dist/$ASSET_NAME" ./cmd/flared
(cd ../dist && sha256sum "$ASSET_NAME" > "$ASSET_NAME.sha256")
- name: Upload Flared Artifact
uses: actions/upload-artifact@v4
with:
name: flared-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
dist/${{ matrix.asset_name }}
dist/${{ matrix.asset_name }}.sha256
retention-days: 1
release:
needs:
- prepare
- build-binaries
- build-agent-binaries
- build-relay-binaries
- build-flared-binaries
if: needs.prepare.outputs.should_run == 'true'
runs-on: ubuntu-latest
steps:
- name: Download Server Artifacts
uses: actions/download-artifact@v4
with:
pattern: "server-*"
path: dist
merge-multiple: true
- name: Download Agent Artifacts
uses: actions/download-artifact@v4
with:
pattern: "agent-*"
path: dist
merge-multiple: true
- name: Download Relay Artifacts
uses: actions/download-artifact@v4
with:
pattern: "relay-*"
path: dist
merge-multiple: true
- name: Download Flared Artifacts
uses: actions/download-artifact@v4
with:
pattern: "flared-*"
path: dist
merge-multiple: true
- name: Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.prepare.outputs.version }}
name: ${{ needs.prepare.outputs.version }}
target_commitish: ${{ github.sha }}
files: dist/*
draft: false
prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }}
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+8 -4
View File
@@ -14,7 +14,6 @@ logs
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
@@ -43,7 +42,12 @@ go.work.sum
# .idea/
# .vscode/
*.log
.DS_Store
.codex-cache
.codex-cache
/.gomodcache/
*.mmdb
!openflare_agent/internal/geoipdata/GeoLite2-Country.mmdb
*-source
*-source.*
.codex*
+89 -41
View File
@@ -1,41 +1,89 @@
# AGENTS.md
本文件是 OpenFlare 的 AI 接手入口,不承载详细设计、规范和计划。接手项目时,先按顺序阅读以下文档:
1. [docs/design.md](./docs/design.md)
作用:理解当前 MVP 的产品范围、系统边界、核心对象和整体架构。
2. [docs/development-guidelines.md](./docs/development-guidelines.md)
作用:理解当前开发规范,包括技术基线、分层约束、数据模型边界、API 约定、Agent 约束、测试要求。
3. [docs/development-plan.md](./docs/development-plan.md)
作用:理解当前开发阶段、实施顺序、阶段目标和验收标准。
4. [docs/frontend-development-guidelines.md](./docs/frontend-development-guidelines.md)
作用:理解新版前端的技术选型、目录分层、组件规范、请求层、状态管理、样式和测试约束。
5. [docs/deployment.md](./docs/deployment.md)
作用:理解当前的部署方式和联调步骤,确保开发过程中产出的功能能够成功部署和验证。
6. [docs/app-config.md](./docs/app-config.md)
作用:系统启动时支持的环境变量和配置项说明,确保开发过程中新增的配置项能够正确使用和文档化。
## 执行要求
* 如果实现内容超出 `docs/design.md` 的范围,先修改设计文档,再继续编码。
* 如果实现方式违反 `docs/development-guidelines.md`,应优先调整方案,而不是绕过规范。
* 如果需求与当前开发阶段冲突,优先遵守 `docs/development-plan.md` 的阶段顺序。
* 如果任务涉及前端改造或管理端 UI,必须同时阅读 `docs/frontend-development-guidelines.md`。
## 文档维护要求
当以下内容发生变化时,应同步更新对应文档:
* 产品启动配置部署方式发生变化时: 更新 `docs/deployment.md`和 `README.md`
* 产品范围或系统边界变化:更新 `docs/design.md`
* 开发约束、代码规范、接口约定变化:更新 `docs/development-guidelines.md`
* 阶段目标、顺序、验收标准变化:更新 `docs/development-plan.md`
* 前端目录分层、组件规范、样式体系、测试基线变化:更新 `docs/frontend-development-guidelines.md`
* 环境变量或配置项变化:更新 `docs/app-config.md`
# AGENTS.md
本文件是 OpenFlare 的 AI 接手入口,不承载详细设计、规范和计划。接手项目时,请根据以下分层文档指引进行阅读与开发:
## 1. 核心必读文档(Level 3 & Level 4)- 必须阅读 ⚠️
为了理解 OpenFlare 的设计理念、产品边界、核心机制以及代码编写的工程约束,**AI 在接手项目时必须首先且完整阅读以下文档**:
### Level 3: 面向贡献者的参阅文档 (Contributor References)
* **[docs/design/index.md](./docs/design/index.md)**
*作用:理解当前 MVP 的产品范围、系统边界、核心对象和长期约束。*
* **[docs/design/architecture.md](./docs/design/architecture.md)**
*作用:理解 Server、Agent、OpenResty 与前端的职责边界与网络拓扑。*
* **[docs/design/agent-design.md](./docs/design/agent-design.md)**
*作用:理解 Agent 设计原则、与 Server 交互时序、OpenResty 管控、配置版本发布与三阶段异常回滚模型。*
* **[docs/design/development.md](./docs/design/development.md)**
*作用:了解如何搭建本地开发环境,运行后端 Server、Agent 和前端开发服务器,以及运行测试与构建的命令。*
* **[docs/design/repository.md](./docs/design/repository.md)**
*作用:熟悉仓库的整体物理结构和各子目录的职责。*
### Level 4: 面向 AI 的开发指导规范 (AI Guidelines)
* **[docs/guildline/development-constraints.md](./docs/guildline/development-constraints.md)**
*作用:掌握核心后端/Agent/前端分层约束、数据模型规范、数据库迁移升级协议、API 与鉴权设计准则。*
* **[docs/guildline/Guidelines.md](./docs/guildline/Guidelines.md)**
*作用:通用的 Go 后端开发与高质量编码准则,包括架构、并发、错误处理、安全及工作流程。*
* **[docs/guildline/Project.md](./docs/guildline/Project.md)**
*作用:针对 OpenFlare 后端特定的控制器参数解析、响应处理、纯净工具类与数据库逻辑完全隔离、Go 泛型切片去重及 JSON 序列化避坑细则。*
---
## 2. 按需查阅文档(Level 2)- 根据需求阅读 💡
当开发任务涉及具体的系统部署、升级、接口联调或配置字段查阅时,**AI 应当根据需求阅读相应的参考手册**:
### Level 2: 面对高级用户/开发者的参阅文档 (Reference Manuals)
* **[docs/reference/configuration.md](./docs/reference/configuration.md)**
*作用:系统启动时支持的所有环境变量、命令行参数、运行时 Option 选项和 Agent 配置文件字段。*
* **[docs/reference/cli.md](./docs/reference/cli.md)**
*作用:Server 与 Agent 可用的命令行参数、安装/卸载脚本参数等参考。*
* **[docs/reference/api.md](./docs/reference/api.md)**
*作用:管理端 API 与 Agent API 的响应结构、路径和详细鉴权约定。*
### Level 2: 部署与升级指南 (Deployment Guides)
* **[docs/deployment/deployment.md](./docs/deployment/deployment.md)**
*作用:理解 Server 和 Agent 的单机、Docker 部署配置,以及 Agent 接入、升级、卸载和联调步骤。*
* **[docs/deployment/server.md](./docs/deployment/server.md)**
*作用:如何配置系统配置、服务环境变量并正确启动 Server 服务。*
* **[docs/deployment/agent.md](./docs/deployment/agent.md)**
*作用:理解 Agent 接入的 discovery/agent 令牌鉴权机制、本地配置文件及 Docker 部署参数。*
* **[docs/deployment/upgrade.md](./docs/deployment/upgrade.md)**
*作用:Server 及各代理节点 Agent 的升级步骤与维护策略。*
---
## 3. 新手与业务教程(Level 1)- 体验与排障参考 📘
如果任务涉及优化最终用户体验、丰富业务能力或排查常见故障,可参阅面向普通用户的指南:
### Level 1: 面向新手用户的教程文档 (Novice Tutorials)
* **[docs/guide/quick-start.md](./docs/guide/quick-start.md)**:五分钟内基于 Docker Compose 快速跑起 Server 和首个 Agent 节点的完整闭环。
* **[docs/guide/usage.md](./docs/guide/usage.md)**:反向代理网站、源站、证书托管、配置发布与回滚的常规界面操作与观测功能使用指南。
* **[docs/guide/sso.md](./docs/guide/sso.md)**:系统如何配置 GitHub OAuth 及标准 OIDC 第三方登录,以及绑定本地账户的流程。
* **[docs/guide/first-site.md](./docs/guide/first-site.md)**:从零开始配置、发布并验证第一个代理网站的完整步骤。
* **[docs/guide/troubleshooting.md](./docs/guide/troubleshooting.md)**:常见数据库迁移、节点离线、OpenResty 校验失败、SSL 证书失效等故障的表现症状及标准排障路径。
---
## 执行要求
* 如果实现内容超出 [产品边界](./docs/design/index.md),先修改设计文档,再继续编码。
* 如果实现方式违反 [开发约束](./docs/guildline/development-constraints.md),应优先调整方案,而不是绕过规范。
* 如果实现方式涉及后端代码逻辑,必须严格遵循 [docs/guildline/](./docs/guildline/) 下的所有开发准则。
* 如果需求与当前阶段原则冲突,优先遵守 [开发约束](./docs/guildline/development-constraints.md) 中的变更准入与验收标准。
* 如果任务涉及前端改造或管理端 UI,必须同时遵守 [开发约束](./docs/guildline/development-constraints.md) 中的前端规范。
## 文档维护要求
当以下内容发生变化时,应同步更新对应中文文档,不要同步英文文档:
* 产品范围或系统边界变化:更新 `docs/design/index.md`
* 系统结构、模块职责变化:更新 `docs/design/architecture.md`
* 发布、同步、回滚与 Agent 模型变化:更新 `docs/design/agent-design.md`
* 业务分层、数据模型边界、接口约定、阶段原则、测试基线变化:更新 `docs/guildline/development-constraints.md`
* 后端开发规范、代码质量要求、重构模式、去重逻辑与避坑指南变化:更新 `docs/guildline/` 下的对应开发准则文件
* 产品启动、部署、升级、联调方式变化:更新 `docs/guide/quick-start.md`、`docs/deployment/deployment.md` 和 `README.md`
* 用户操作路径、常见场景变化:更新 `docs/guide/usage.md`
* 本地开发、测试、构建方式变化:更新 `docs/design/development.md`
* 常见故障、排查路径变化:更新 `docs/guide/troubleshooting.md`
* 环境变量、命令行参数、运行时配置、Agent 配置变化:更新 `docs/reference/configuration.md`
+99 -129
View File
@@ -1,14 +1,10 @@
<p align="right">
<strong>中文</strong> | <a href="./README.en.md">English</a>
</p>
<div align="center">
[//]: # ( <img src="./openflare_server/web/public/logo.png" width="120" height="120" alt="OpenFlare logo">)
# OpenFlare
轻量、自托管的 OpenResty 控制面,用于管理反向代理规则、配置发布、节点同步、TLS 证书与基础可观测能力。
**[📖 English](./README.md) | [中文](./README.zh-CN.md)**
OpenFlare is an open-source CDN orchestration and edge security platform. It supports reverse proxies, centralized configuration synchronization, secure intranet penetration (Tunnels), dynamic WAF protection, and anti-CC challenges.
</div>
@@ -24,63 +20,37 @@
</a>
</p>
## 为什么存在
> [!WARNING]
> After logging in for the first time with the `root` user, make sure to change the default password `123456`.
>
> The BETA version is a temporary product for the development and testing phase. It may contain unknown issues and should not be used in production environments.
OpenFlare 解决的是一类朴素但高频的运维问题:
## Documentation
* 在一个管理端里维护域名到源站的反向代理规则
* 生成完整 OpenResty 配置并以不可变版本发布
* 让节点侧 Agent 自动拉取、校验、reload 与失败回滚
* 统一托管证书、域名、节点凭证与版本状态
* 提供足够实用的总览、节点详情与访问分析能力
**https://open-flare.pages.dev**
## 核心能力
Quick links:
* 配置版本化:支持预览、发布、激活、历史回滚
* Agent 自动应用:周期性同步、落盘、`openresty -t`、`openresty -s reload`、失败自动回滚
* OpenResty 托管:统一管理主配置模板、性能参数、缓存参数与受管路由
* TLS 与域名管理:支持证书托管、域名资产维护、精确匹配与通配符匹配
* 访问与节点观测:支持请求窗口聚合、状态码分布、来源分布、节点资源与健康事件展示
* [Quick Start](https://open-flare.pages.dev/en/guide/quick-start)
* [Deployment Guide](https://open-flare.pages.dev/en/guide/deployment)
* [Configuration Reference](https://open-flare.pages.dev/reference/configuration)
* [System Design](https://open-flare.pages.dev/design/)
## 系统架构
## Core Features
```text
OpenFlare Server (Gin + GORM + SQLite/PostgreSQL + Web UI)
|
| HTTP API / Config Pull
v
OpenFlare Agent (register / heartbeat / sync / apply / update)
|
v
Local OpenResty or Docker OpenResty
|
v
Origin
```
* **Centralized Real-Time Config Sync**: Sync configurations across all nodes in real time via WebSockets and heartbeats with sub-second hot reload. Instantly retrieve alerts and statuses. No manual SSH login or online patching required.
* **Distributed CDN Orchestration**: Orchestrate scattered and independent OpenResty nodes into a highly collaborative distributed Content Delivery Network (CDN) fleet, supporting website-level multi-domain aggregation, upstream Keepalive, and multi-origin load balancing.
* **Secure Intranet Penetration (Tunnels)**: An open-source alternative to Cloudflare Tunnels. Expose local intranet services securely to the public network without a public IP or exposing inbound ports.
* **Edge WAF Safety Protection**: Provides global and custom rule groups, supporting IP/CIDR filtering, MaxMind GeoIP country-level regional access control, asynchronous differential synchronization of IP group members without Nginx reloads, and custom block responses.
* **Anti-CC & Human-Machine Challenge (PoW)**: Built-in high-performance client-side cryptographic Proof of Work challenges (similar to Turnstile) to block and intercept botnets and scrapers at the gateway edge in seconds.
* **Publish & Sync Model**: Based on immutable configuration versions (`YYYYMMDD-NNN`), preview and compare differences before publishing, a single globally active version, and one-click sub-second rollbacks.
* **Three-Stage Disaster Recovery & Rollback**: Supports automatic node backup rollback, a built-in safety fallback page (Port 80/503 keeping status monitoring and security interception active), and an abnormal configuration blocklist.
* **Automated Certificate Hosting**: Supports dynamic certificate upload, automatic multi-domain certificate matching and binding, ACME automatic renewal, and full lifecycle status tracking.
* **Unified Observability**: Aggregates node request counts, provides real-time access analysis, host/Nginx resource snapshots, health logs, and a re-upload buffer for network fluctuations.
职责划分:
## Quick Start
* `openflare_server`:管理端 UI、管理 API、Agent API、配置渲染、版本发布与状态存储
* `openflare_agent`:节点注册、心跳、同步、本地写入、校验、reload、回滚、自更新
* `openflare_server/web`:新版管理端前端,静态导出后由 Go Server 托管
## 界面预览
### 仪表盘总览
![OpenFlare dashboard overview](./docs/assets/readme/dashboard-overview.png)
### 节点详情
![OpenFlare node detail](./docs/assets/readme/node-detail.png)
### 配置新增
![OpenFlare version release](./docs/assets/readme/version-release.png)
## 快速开始
### 1. 启动 Server
### 1. Launch Server
```yaml
services:
@@ -112,30 +82,45 @@ services:
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
volumes:
- openflare-data:/data
volumes:
postgres-data:
openflare-data:
```
```bash
docker compose up -d
```
访问地址:`http://localhost:3000`
Access at: `http://localhost:3000`
默认账号:
Default credentials:
* 用户名:`root`
* 密码:`123456`
* Username: `root`
* Password: `123456`
### 2. 接入 Agent
### 2. Install Agent
**注意:** 安装agent前需确保存已经安装了Docker, 虽然支持裸Openresty,但未得到充分验证,可能存在未知问题.
Before installing an Agent, please install OpenResty on the target node first, or use the Agent Docker image with OpenResty built-in.
使用 `discovery_token` 接入:
You can copy the installation command from **Node Management -> Details -> Node Info -> Node Token & Deployment** in the control panel, or directly use the scripts below:
#### Docker Deployment
For Docker deployment, you can directly run the Agent image:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443 \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
ghcr.io/rain-kl/openflare-agent:latest
```
#### Local Installation
Using `discovery_token` to register:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
@@ -143,7 +128,7 @@ curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/inst
--discovery-token YOUR_DISCOVERY_TOKEN
```
使用节点专属 `agent_token`:
Using node-specific `agent_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
@@ -151,84 +136,69 @@ curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/inst
--agent-token YOUR_AGENT_TOKEN
```
安装脚本默认写入 `/opt/openflare-agent`,创建 `openflare-agent.service`,并可重复执行以重装或升级 Agent。
The installation script defaults to `/opt/openflare-agent`, creates a `openflare-agent.service`, automatically searches for `openresty`, and can be executed repeatedly to reinstall or upgrade the Agent.
### 3. 发布第一份配置
### 3. Uninstall Agent
1. 登录管理端并新增反代规则
2. 在发布前查看预览或变更摘要
3. 激活新版本
4. 等待 Agent 在后续 heartbeat 中拉取并应用配置
版本号格式固定为 `YYYYMMDD-NNN`,历史版本不可变,回滚通过重新激活旧版本完成。
## 仓库结构
* `openflare_server`:Gin + GORM + SQLite/PostgreSQL 单体控制面
* `openflare_server/web`:Next.js 15 App Router 管理端前端
* `openflare_agent`:Go 单体 Agent
* `scripts`:安装脚本与辅助脚本
* `docs`:设计、规范、部署与配置文档
## 本地开发
### Server
To completely uninstall the Agent and clear local data, run:
```bash
cd openflare_server
export SESSION_SECRET='replace-with-random-string'
export SQLITE_PATH='./openflare.db'
# 可选:设置 DSN 或 SQL_DSN 后切换到 PostgreSQL。
# 如果 PostgreSQL 为空且 ./openflare.db 存在,启动时会自动迁移 SQLite 数据。
# export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
go run .
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash
```
### Frontend
The uninstallation script will stop and remove the `openflare-agent.service`, and delete the entire `/opt/openflare-agent` directory. It will not delete the local OpenResty installation.
```bash
cd openflare_server/web
pnpm install
pnpm dev
```
### 4. Publish Your First Configuration
### Agent
1. Log in to the management panel and add a reverse proxy rule.
2. View the preview or change summary before publishing.
3. Activate the new version.
4. Agents will receive the configuration and apply it via WebSocket notification or subsequent heartbeats.
```bash
cd openflare_agent
go run ./cmd/agent -config /path/to/agent.json
```
The version number format is fixed as `YYYYMMDD-NNN`. Historical versions are immutable, and rollback is achieved by reactivating an older version.
## 文档导航
## UI Preview
建议按以下顺序阅读:
### Dashboard Overview
1. [docs/design.md](./docs/design.md)
2. [docs/development-guidelines.md](./docs/development-guidelines.md)
3. [docs/development-plan.md](./docs/development-plan.md)
4. [docs/frontend-development-guidelines.md](./docs/frontend-development-guidelines.md)
5. [docs/deployment.md](./docs/deployment.md)
6. [docs/app-config.md](./docs/app-config.md)
![OpenFlare dashboard overview](./docs/assets/readme/dashboard-overview.png)
## 管理端与接口
### Node Details
管理端当前覆盖:
![OpenFlare node detail](./docs/assets/readme/node-detail.png)
* 反代规则
* 配置版本
* 节点管理
* 应用记录
* TLS 证书
* 域名管理
* 用户管理
* 设置
* 版本更新
### Proxy Configuration
登录管理端后,可访问 Swagger UI:`/swagger/index.html`
![OpenFlare version release](./docs/assets/readme/proxy-route-detail.png)
如需重新生成 Swagger 文档,请使用与服务端依赖一致的版本:
`go install github.com/swaggo/swag/cmd/swag@v1.16.4`
## Management Panel & API
## 开源协议
The management panel includes:
本项目采用 [Apache License 2.0](./LICENSE) 开源。
* Reverse Proxy Rules
* Configuration Versions
* Node Management
* Application Records
* TLS Certificates
* Domain Management
* WAF Rule Groups
* User Management
* Settings
* Version Updates
* POW Rules
After logging in to the dashboard, access Swagger UI at: `/swagger/index.html`
## License
This project is licensed under [Apache License 2.0](./LICENSE).
## Star History
<a href="https://www.star-history.com/?repos=Rain-kl%2FOpenFlare&type=date&legend=bottom-right">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=Rain-kl/OpenFlare&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=Rain-kl/OpenFlare&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=Rain-kl/OpenFlare&type=date&legend=top-left" />
</picture>
</a>
+205
View File
@@ -0,0 +1,205 @@
<div align="center">
# OpenFlare
**[English](./README.md) | [📖 中文](./README.zh-CN.md)**
OpenFlare 是开源 CDN 编排与边缘安全平台。它支持反向代理、集中式配置同步、内网穿透(Tunnels)、动态 WAF 防护以及防 CC 挑战。
</div>
<p align="center">
<a href="https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/LICENSE">
<img src="https://img.shields.io/github/license/Rain-kl/OpenFlare?color=brightgreen" alt="license">
</a>
<a href="https://github.com/Rain-kl/OpenFlare/releases/latest">
<img src="https://img.shields.io/github/v/release/Rain-kl/OpenFlare?color=brightgreen&include_prereleases" alt="release">
</a>
<a href="https://github.com/Rain-kl/OpenFlare/pkgs/container/openflare">
<img src="https://img.shields.io/badge/GHCR-ghcr.io%2Frain--kl%2Fopenflare-brightgreen" alt="ghcr">
</a>
</p>
> [!WARNING]
> 使用 `root` 用户初次登录系统后,务必修改默认密码 `123456`。
>
> BETA 版本为开发测试阶段的临时产物,可能存在未知问题,请勿在生产环境使用。
## 文档
**https://open-flare.pages.dev**
常用入口:
* [快速开始](https://open-flare.pages.dev/guide/quick-start)
* [部署说明](https://open-flare.pages.dev/guide/deployment)
* [配置项参考](https://open-flare.pages.dev/reference/configuration)
* [系统设计](https://open-flare.pages.dev/design/)
## 核心能力
* **中心化实时配置同步**:通过 WebSocket 与心跳实现全网节点配置秒级同步下发与热生效,告警与状态即时回收,无须手动 SSH 登录或在线打补丁。
* **分布式 CDN 编排集群**:将分散独立的 OpenResty 节点编排为高度协同的分布式内容分发网络(CDN)舰队,支持网站级多域名聚合、上游 Keepalive 与多源站负载均衡。
* **安全内网穿透(Tunnels)**:开源版的 Cloudflare Tunnels。无须公网 IP 或暴露入向端口,安全反向穿透本地内网服务至公网。
* **边缘 WAF 安全防护**:提供全局及自定义规则组,支持 IP/CIDR 过滤、MaxMind GeoIP 国家级地域准入、IP 组成员异步差分同步免 Nginx 重载以及自定义拦截响应。
* **防 CC 与人机挑战(PoW)**:内置高性能客户端密码学 Proof of Work 挑战(类似 Turnstile),在网关边缘秒级拦截并阻断僵尸网络与爬虫。
* **发布与同步模型**:基于不可变配置版本(`YYYYMMDD-NNN`)、发布前预览对比差异、全局单激活版本与一键秒级回滚。
* **三阶段容灾回滚**:支持节点备份自动回滚、内置安全兜底页面(Port 80/503 保持状态监控与安全拦截)及异常配置阻断名单。
* **证书托管自动化**:支持证书动态上传、多域名证书自动匹配绑定、ACME 自动续期与全生命周期状态追踪。
* **统一观测与可观测性**:聚合节点请求数、实时访问分析、宿主机与 Nginx 资源快照、健康日志及网络波动补传缓冲。
## 快速开始
### 1. 启动 Server
```yaml
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
interval: 10s
timeout: 5s
retries: 5
openflare:
image: ghcr.io/rain-kl/openflare:latest
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-random-string
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
volumes:
postgres-data:
```
```bash
docker compose up -d
```
访问地址:`http://localhost:3000`
默认账号:
* 用户名:`root`
* 密码:`123456`
### 2. 安装 Agent
安装 Agent 前请先在节点上安装 OpenResty,或改用内置 OpenResty 的 Agent Docker 镜像。
你可以在控制面板的节点管理->详情->节点信息->节点标识与部署复制安装命令,或直接使用下面的脚本:
#### Docker 部署
Docker 部署可直接运行 Agent 镜像:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443/tcp -p 443:443/udp \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
ghcr.io/rain-kl/openflare-agent:latest
```
#### 本地部署
使用 `discovery_token` 接入:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--discovery-token YOUR_DISCOVERY_TOKEN
```
使用节点专属 `agent_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
安装脚本默认写入 `/opt/openflare-agent`,创建 `openflare-agent.service`,自动查找 `openresty`,并可重复执行以重装或升级 Agent。
### 3. 卸载 Agent
如需彻底卸载 Agent 并清空本地数据,可执行:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash
```
卸载脚本会先停止并移除 `openflare-agent.service`、删除整个 `/opt/openflare-agent` 目录,不会删除本机 OpenResty。
### 4. 发布第一份配置
1. 登录管理端并新增反代规则
2. 在发布前查看预览或变更摘要
3. 激活新版本
4. Agent 通过 WebSocket 通知或后续 heartbeat 拉取并应用配置
版本号格式固定为 `YYYYMMDD-NNN`,历史版本不可变,回滚通过重新激活旧版本完成。
## 界面预览
### 仪表盘总览
![OpenFlare dashboard overview](./docs/assets/readme/dashboard-overview.png)
### 节点详情
![OpenFlare node detail](./docs/assets/readme/node-detail.png)
### 配置新增
![OpenFlare version release](./docs/assets/readme/proxy-route-detail.png)
## 管理端与接口
管理端当前覆盖:
* 反代规则
* 配置版本
* 节点管理
* 应用记录
* TLS 证书
* 域名管理
* WAF 规则组
* 用户管理
* 设置
* 版本更新
* POW 规则
登录管理端后,可访问 Swagger UI:`/swagger/index.html`
## 开源协议
本项目采用 [Apache License 2.0](./LICENSE) 开源。
## Star History
<a href="https://www.star-history.com/?repos=Rain-kl%2FOpenFlare&type=date&legend=bottom-right">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=Rain-kl/OpenFlare&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=Rain-kl/OpenFlare&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=Rain-kl/OpenFlare&type=date&legend=top-left" />
</picture>
</a>
-1
View File
@@ -1 +0,0 @@
1.0.x
+52
View File
@@ -0,0 +1,52 @@
services:
agent:
build:
context: .
dockerfile: openflare_agent/Dockerfile
container_name: openflare-agent
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "127.0.0.1:18081:18081"
volumes:
- ./openflare_agent/data/:/data
environment:
OPENFLARE_SERVER_URL: "http://host.docker.internal:3000"
OPENFLARE_AGENT_TOKEN: "07800f31d3f181e65d18dca1407d821c"
LOG_LEVEL: "debug"
extra_hosts:
- "host.docker.internal:host-gateway"
relay:
build:
context: .
dockerfile: openflare_relay/Dockerfile
container_name: openflare-relay
network_mode: host
restart: unless-stopped
environment:
OPENFLARE_SERVER_URL: http://host.docker.internal:3000
OPENFLARE_DISCOVERY_TOKEN: 85464eeb72c49abc430569d6b9c77f78
LOG_LEVEL: "debug"
extra_hosts:
- "host.docker.internal:host-gateway"
flared:
build:
context: .
dockerfile: openflared/Dockerfile
container_name: openflare-flared
network_mode: "host"
restart: unless-stopped
volumes:
- ./openflared/data/:/app/data
environment:
OPENFLARE_SERVER_URL: "http://host.docker.internal:3000"
OPENFLARE_TUNNEL_TOKEN: deb0783ac1e264a9d86440169aca0f09
+18
View File
@@ -0,0 +1,18 @@
/coverage
/src/client/shared.ts
/src/node/shared.ts
*.log
*.tgz
.DS_Store
.idea
.temp
.vite_opt_cache
.vscode
dist
cache
temp
examples-temp
node_modules
pnpm-global
TODOs.md
*.timestamp-*.mjs
+8
View File
@@ -0,0 +1,8 @@
{
"plugins": {
"postcss-rtlcss": {
"ltrPrefix": ":where([dir=\"ltr\"])",
"rtlPrefix": ":where([dir=\"rtl\"])"
}
}
}
+75
View File
@@ -0,0 +1,75 @@
import { defineConfig, type HeadConfig, resolveSiteDataByRoute } from 'vitepress'
import llmstxt from 'vitepress-plugin-llms'
const prod = !!process.env.NETLIFY
export default defineConfig({
title: 'OpenFlare',
lastUpdated: true,
cleanUrls: true,
ignoreDeadLinks: true,
metaChunk: true,
srcExclude: [
'zh/**',
'components/**',
'snippets/**'
],
markdown: {
math: true
},
sitemap: {
hostname: 'https://openflare.io'
},
head: [
['meta', { name: 'theme-color', content: '#10b981' }],
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:site_name', content: 'OpenFlare' }],
['meta', { property: 'og:url', content: 'https://openflare.io/' }]
],
themeConfig: {
socialLinks: [
{ icon: 'github', link: 'https://github.com/Rain-kl/OpenFlare' }
],
search: {
provider: 'local'
}
},
locales: {
root: { label: '简体中文', lang: 'zh-Hans', dir: 'ltr' },
en: { label: 'English', lang: 'en-US', dir: 'ltr' }
},
vite: {
plugins: [
prod &&
llmstxt({
workDir: '.',
ignoreFiles: ['index.md']
})
],
experimental: {
enableNativePlugin: true
}
},
transformPageData: prod
? (pageData, ctx) => {
const site = resolveSiteDataByRoute(
ctx.siteConfig.site,
pageData.relativePath
)
const title = `${pageData.title || site.title} | ${
pageData.description || site.description
}`
;((pageData.frontmatter.head ??= []) as HeadConfig[]).push(
['meta', { property: 'og:locale', content: site.lang }],
['meta', { property: 'og:title', content: title }]
)
}
: undefined
})
+4
View File
@@ -0,0 +1,4 @@
import Theme from 'vitepress/theme'
import './styles.css'
export default Theme
+20
View File
@@ -0,0 +1,20 @@
:root {
--vp-c-brand-1: #059669;
--vp-c-brand-2: #10b981;
--vp-c-brand-3: #34d399;
--vp-c-brand-soft: rgba(16, 185, 129, 0.16);
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: linear-gradient(120deg, #059669, #2563eb);
--vp-font-family-base:
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
}
.VPHomeHero .text,
.VPHomeHero .tagline {
max-width: 760px;
}
.VPFeature {
border-radius: 8px;
}
-160
View File
@@ -1,160 +0,0 @@
# OpenFlare 配置项说明
本文档汇总 OpenFlare `1.0.0` 当前支持的 Server 与 Agent 配置项,只保留仍然有效的启动、部署与运行参数。
## 1. Server 配置
Server 支持三类配置来源:
1. 命令行参数
2. 环境变量
3. 数据库 `Option` 表中的运行时配置
### 1.1 命令行参数
```bash
cd openflare_server
go run . --port 3000 --log-dir ./logs
```
| 参数 | 作用 | 默认值 |
| --- | --- | --- |
| `--port` | 指定 Server 监听端口 | `3000` |
| `--log-dir` | 指定日志目录 | 空 |
| `--version` | 输出当前版本后退出 | `false` |
| `--help` | 输出帮助信息后退出 | `false` |
### 1.2 环境变量
| 环境变量 | 作用 | 默认值 |
| --- | --- | --- |
| `PORT` | Server 监听端口 | `3000` |
| `GIN_MODE` | Gin 运行模式 | 非 `debug` 时按 release |
| `LOG_LEVEL` | 日志等级 | `info` |
| `SESSION_SECRET` | Session 签名密钥 | 启动时随机生成 |
| `SQLITE_PATH` | SQLite 数据库文件路径 | `openflare.db` |
| `DSN` | PostgreSQL DSN,设置后优先于 SQLite | 空 |
| `SQL_DSN` | 兼容旧命名的 PostgreSQL DSN,优先级低于 `DSN` | 空 |
| `REDIS_CONN_STRING` | Redis 连接串 | 空 |
| `UPLOAD_PATH` | 上传目录 | `upload` |
| `AGENT_TOKEN` | 兼容旧部署的全局 Agent Token | 空 |
说明:
* `DSN` 与 `SQL_DSN` 同时存在时优先使用 `DSN`
* `DSN` 或 `SQL_DSN` 与 `SQLITE_PATH` 同时存在时优先使用 PostgreSQL
* 当目标 PostgreSQL 数据库为空且本地 `SQLITE_PATH` 文件存在时,Server 启动阶段会自动迁移 SQLite 数据,并在日志中输出按表迁移进度
* `SESSION_SECRET` 生产环境必须显式配置
* `REDIS_CONN_STRING` 未配置时,相关能力回退为进程内实现
### 1.3 `Option` 表中的运行时配置
以下配置由管理端设置页维护,可热更新:
| 配置项 | 作用 | 默认值 |
| --- | --- | --- |
| `AgentHeartbeatInterval` | Agent 心跳间隔(毫秒) | `10000` |
| `NodeOfflineThreshold` | 节点离线阈值(毫秒) | `120000` |
| `AgentUpdateRepo` | Agent 自更新仓库 | `Rain-kl/OpenFlare` |
| `GeoIPProvider` | 节点/IP 归属解析方式 | `ipinfo` |
| `GlobalApiRateLimitNum` / `GlobalApiRateLimitDuration` | 全局 API 限流次数 / 时间窗口 | `300` / `180` |
| `GlobalWebRateLimitNum` / `GlobalWebRateLimitDuration` | 全局 Web 限流次数 / 时间窗口 | `300` / `180` |
| `UploadRateLimitNum` / `UploadRateLimitDuration` | 上传接口限流次数 / 时间窗口 | `50` / `60` |
| `DownloadRateLimitNum` / `DownloadRateLimitDuration` | 下载接口限流次数 / 时间窗口 | `50` / `60` |
| `CriticalRateLimitNum` / `CriticalRateLimitDuration` | 敏感接口限流次数 / 时间窗口 | `100` / `1200` |
### 1.4 OpenResty 参数
OpenResty 性能参数与缓存参数继续统一保存在 `Option` 表。当前常用项包括:
* `OpenRestyWorkerProcesses`
* `OpenRestyWorkerConnections`
* `OpenRestyWorkerRlimitNofile`
* `OpenRestyKeepaliveTimeout`
* `OpenRestyProxyConnectTimeout`
* `OpenRestyProxySendTimeout`
* `OpenRestyProxyReadTimeout`
* `OpenRestyProxyBufferingEnabled`
* `OpenRestyGzipEnabled`
* `OpenRestyCacheEnabled`
* `OpenRestyCachePath`
* `OpenRestyCacheMaxSize`
这类参数必须以结构化方式校验、保存并参与版本渲染。
* 管理端不再暴露 `resolver` 配置;规则上游统一渲染为 named `upstream` 并启用 keepalive,单上游如带 base path 或 query,会在 `proxy_pass` 中补回原始 URI。
* 多上游仍要求每个上游都为纯 `scheme://host[:port]`,且同一规则内协议一致,避免在负载均衡模式下引入不可预测的 URI 差异。
* `OpenRestyCacheEnabled` 用于启用缓存基础设施与全局默认参数;实际是否缓存、按 URL / 后缀 / 路径等命中策略由各条 `proxy_routes` 单独决定,不再默认对所有规则开启缓存。
* 默认缓存 Key 为 `$scheme$host$request_uri`,更贴近代理域名维度;如需按其他维度命中,可在性能页显式覆盖。
* 默认 `keepalive_timeout` 为 `20` 秒,默认 `proxy_connect_timeout` 为 `3` 秒,优先兼顾资源占用与回源失败切换速度。
* 默认事件模型为 `epoll`,并默认开启 `multi_accept`;HTTPS 监听默认使用独立 `http2 on;` 指令,避免新版 Nginx/OpenResty 对 `listen ... http2` 的弃用告警。
### 1.5 前端构建环境变量
| 环境变量 | 作用 | 默认值 |
| --- | --- | --- |
| `NEXT_PUBLIC_API_BASE_URL` | 前端请求 API 的基础路径 | `/api` |
| `NEXT_PUBLIC_APP_VERSION` | 前端展示版本号 | `dev` |
| `NEXT_DEV_BACKEND_URL` | 本地开发服务器代理的后端地址 | `http://127.0.0.1:3000` |
## 2. Agent 配置
Agent 当前支持:
1. `-config` 命令行参数
2. `agent.json` 配置文件
3. 少量日志相关环境变量
### 2.1 Agent 环境变量
| 环境变量 | 作用 | 默认值 |
| --- | --- | --- |
| `LOG_LEVEL` | Agent 日志等级 | `info` |
### 2.2 Agent 命令行参数
| 参数 | 作用 | 默认值 |
| --- | --- | --- |
| `-config` | 指定 Agent 配置文件路径 | `./agent.json` |
### 2.3 Agent 配置字段
| 字段 | 作用 | 是否必填 | 默认值/行为 |
| --- | --- | --- | --- |
| `server_url` | 控制面地址 | 是 | 无 |
| `agent_token` | 节点专属认证 Token | 与 `discovery_token` 二选一 | 空 |
| `discovery_token` | 首次自动注册使用的全局 Token | 与 `agent_token` 二选一 | 空 |
| `node_name` | 节点名称 | 否 | 自动使用主机名 |
| `node_ip` | 节点 IP | 否 | 自动探测 |
| `openresty_path` | 本机 OpenResty 路径 | 否 | 空,未设置时走 Docker 模式 |
| `openresty_container_name` | Docker 模式下的容器名 | 否 | `openflare-openresty` |
| `openresty_docker_image` | Docker 模式下的镜像 | 否 | `openresty/openresty:alpine` |
| `openresty_observability_port` | 本地观测端口 | 否 | `18081` |
| `docker_binary` | Docker 可执行文件名或路径 | 否 | `docker` |
| `data_dir` | Agent 数据目录 | 否 | 配置文件所在目录下的 `data` |
| `main_config_path` | OpenResty 主配置写入路径 | 否 | 本机模式建议显式配置 |
| `route_config_path` | 路由配置写入路径 | 否 | `data_dir/etc/nginx/conf.d/openflare_routes.conf` |
| `cert_dir` | 本机证书写入目录 | 否 | `data_dir/etc/nginx/certs` |
| `openresty_cert_dir` | OpenResty 读取证书目录 | 否 | 随运行模式变化 |
| `lua_dir` | 本机 Lua 脚本写入目录 | 否 | `data_dir/etc/nginx/lua` |
| `openresty_lua_dir` | OpenResty 读取 Lua 目录 | 否 | 随运行模式变化 |
| `observability_buffer_path` | 观测补报缓冲文件路径 | 否 | `data_dir/var/lib/openflare/observability-buffer.json` |
| `observability_replay_minutes` | 自动补传最近观测窗口分钟数 | 否 | `15` |
| `state_path` | Agent 本地状态文件路径 | 否 | `data_dir/var/lib/openflare/agent-state.json` |
| `heartbeat_interval` | 心跳间隔 | 否 | `10000` 毫秒 |
| `request_timeout` | HTTP 请求超时 | 否 | `10000` 毫秒 |
说明:
* `agent_token` 与 `discovery_token` 不能同时为空
* `heartbeat_interval` 与 `request_timeout` 支持毫秒整数或 Go duration 字符串
* 未配置 `openresty_path` 时默认使用 Docker OpenResty 模式
## 3. 维护要求
以下内容变化时,必须同步更新本文档:
* Server 命令行参数
* Server 环境变量
* Agent 命令行参数
* Agent 配置字段
* 任一配置项的默认值、用途或示例
Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

+119
View File
@@ -0,0 +1,119 @@
import { defineAdditionalConfig, type DefaultTheme } from 'vitepress'
export default defineAdditionalConfig({
description:
'OpenFlare 是轻量、自托管的 OpenResty 控制面,用于管理反向代理、配置发布、节点同步、TLS 证书与基础观测。',
themeConfig: {
nav: nav(),
sidebar: {
'/guide/': { base: '/guide/', items: sidebarGuide() },
'/reference/': { base: '/reference/', items: sidebarReference() },
'/design/': { base: '/design/', items: sidebarDesign() }
},
editLink: {
pattern: 'https://github.com/Rain-kl/OpenFlare/edit/main/docs/:path',
text: '在 GitHub 上编辑此页面'
},
footer: {
message: '基于 Apache License 2.0 发布',
copyright: 'Copyright © OpenFlare contributors'
},
docFooter: {
prev: '上一页',
next: '下一页'
},
outline: {
label: '页面导航'
},
lastUpdated: {
text: '最后更新于'
},
notFound: {
title: '页面未找到',
quote: '这份文档还没有对应页面。',
linkLabel: '前往首页',
linkText: '回到 OpenFlare 文档'
},
langMenuLabel: '语言',
returnToTopLabel: '回到顶部',
sidebarMenuLabel: '菜单',
darkModeSwitchLabel: '主题',
lightModeSwitchTitle: '切换到浅色模式',
darkModeSwitchTitle: '切换到深色模式',
skipToContentLabel: '跳转到内容'
}
})
function nav(): DefaultTheme.NavItem[] {
return [
{ text: '指南', link: '/guide/', activeMatch: '/guide/' },
{ text: '参考', link: '/reference/', activeMatch: '/reference/' },
{ text: '设计', link: '/design/', activeMatch: '/design/' }
]
}
function sidebarGuide(): DefaultTheme.SidebarItem[] {
return [
{
text: '指南',
items: [
{ text: '概览', link: '' },
{ text: '快速开始', link: 'quick-start' },
{ text: '基础使用', link: 'usage' },
{ text: '内网穿透与隧道使用', link: 'tunnel-usage' },
{ text: 'WAF 安全防护使用', link: 'waf-usage' },
{ text: 'WAF 自动 IP 组语法', link: 'waf-ip-group-expr' },
{ text: 'SSO 登录配置', link: 'sso' },
{ text: '发布第一份配置', link: 'first-site' },
{ text: '故障排查', link: 'troubleshooting' },
{ text: '引用与致谢', link: 'credits' }
]
}
]
}
function sidebarReference(): DefaultTheme.SidebarItem[] {
return [
{
text: '参考',
items: [
{ text: '概览', link: '' },
{ text: '系统架构', link: '../design/architecture' },
{ text: '启动 Server', link: '../deployment/server' },
{ text: '接入 Agent', link: '../deployment/agent' },
{ text: '部署说明', link: '../deployment/deployment' },
{ text: '部署 Relay (Tunnel)', link: '../deployment/relay' },
{ text: '部署 OpenFlared', link: '../deployment/openflared' },
{ text: '升级与维护', link: '../deployment/upgrade' },
{ text: '配置项', link: 'configuration' },
{ text: '命令与脚本', link: 'cli' },
{ text: 'API 约定', link: 'api' }
]
}
]
}
function sidebarDesign(): DefaultTheme.SidebarItem[] {
return [
{
text: '设计',
items: [
{ text: '产品边界', link: '' },
{ text: '系统架构', link: 'architecture' },
{ text: 'Agent 与发布模型', link: 'agent-design' },
{ text: '内网穿透隧道设计', link: 'tunnel-design' },
{ text: 'WAF 设计', link: 'waf-design' },
{ text: '仓库结构', link: 'repository' }
]
}
]
}
-242
View File
@@ -1,242 +0,0 @@
# OpenFlare 部署说明
本文档只保留 OpenFlare `1.0.0` 的当前部署基线、联调入口与升级方式。
## 1. 前置条件
### 1.1 Server
* Go 1.24+
* Node.js 18+
* 可写 SQLite 文件目录,或可访问的 PostgreSQL 实例
### 1.2 Agent
* Go 1.23+
* 对 Agent 数据目录有写权限
* 本机模式下可执行 `openresty -t` 与 `openresty -s reload`
* Docker 模式下具备 Docker 执行权限
## 2. 启动 Server
### 2.1 构建前端
```bash
cd openflare_server/web
corepack enable
pnpm install
pnpm build
```
`pnpm build` 会生成供 Go Server 托管的静态产物。
### 2.2 源码启动
```bash
cd openflare_server
export SESSION_SECRET='replace-with-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
# 可选:设置后优先使用 PostgreSQL。
# 如果 PostgreSQL 为空且本地 SQLite 文件存在,启动时会自动迁移数据。
# export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
go run .
```
默认监听 `3000` 端口。
### 2.3 Docker Compose 启动
```yaml
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
interval: 10s
timeout: 5s
retries: 5
openflare:
image: ghcr.io/rain-kl/openflare:latest
container_name: openflare
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-random-string
SQLITE_PATH: /data/openflare.db
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
volumes:
- openflare-data:/data
volumes:
postgres-data:
openflare-data:
```
```bash
docker compose up -d
```
### 2.4 首次登录
访问 `http://localhost:3000`
默认账号:
* 用户名:`root`
* 密码:`123456`
### 2.5 Swagger
登录管理端后访问:`http://localhost:3000/swagger/index.html`
如需在本地重新生成文档:
```bash
go install github.com/swaggo/swag/cmd/swag@v1.16.4
cd openflare_server
swag init -g main.go -o docs
```
## 3. Agent 配置
当前支持两种接入模式。
### 3.1 使用节点专属 `agent_token`
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "./data",
"openresty_container_name": "openflare-openresty",
"openresty_docker_image": "openresty/openresty:alpine",
"openresty_observability_port": 18081,
"observability_replay_minutes": 15,
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
### 3.2 使用全局 `discovery_token`
```json
{
"server_url": "http://127.0.0.1:3000",
"discovery_token": "replace-with-global-discovery-token",
"data_dir": "./data",
"openresty_container_name": "openflare-openresty",
"openresty_docker_image": "openresty/openresty:alpine",
"openresty_observability_port": 18081,
"observability_replay_minutes": 15,
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
说明:
* `agent_token` 与 `discovery_token` 至少填写一个
* 未配置 `openresty_path` 时默认使用 Docker OpenResty
* Agent 会暴露本机观测端口并在 server 恢复后补传最近窗口数据
## 4. 启动 Agent
### 4.1 直接运行
```bash
cd openflare_agent
export LOG_LEVEL='info'
go run ./cmd/agent -config /path/to/agent.json
```
### 4.2 编译后二进制运行
```bash
cd openflare_agent
go build -o openflare-agent ./cmd/agent
export LOG_LEVEL='info'
./openflare-agent -config /path/to/agent.json
```
## 5. 最小联调步骤
1. 在管理端准备 `agent_token` 或 `discovery_token`
2. 启动 Agent 并确认节点上线
3. 新增一条启用中的反代规则
4. 生成并激活新版本
5. 确认 Agent 拉取配置、执行 `openresty -t`、reload 并上报结果
预期管理端可看到:
* 节点在线状态
* 节点当前版本
* 最近一次应用结果
* 自动注册后的专属 `agent_token`
## 6. 升级说明
* Root 用户可在管理端顶栏检查并升级 Server 正式版
* 如需尝试 preview 版本,可手动检查对应发布
* 节点 Agent 默认只跟随正式版自动更新;preview 升级需要手动触发
* 也可通过上传 Server 二进制的方式执行确认升级
## 7. 常用验证命令
### 7.1 Server
```bash
cd openflare_server
GOCACHE=/tmp/openflare-go-cache go test ./...
```
### 7.2 Agent
```bash
cd openflare_agent
GOCACHE=/tmp/openflare-go-cache go test ./...
```
### 7.3 Frontend
```bash
cd openflare_server/web
pnpm build
```
## 8. Agent 一键部署
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--discovery-token YOUR_DISCOVERY_TOKEN
```
支持参数:
* `--server-url`
* `--discovery-token`
* `--agent-token`
* `--install-dir`
* `--repo`
* `--no-service`
安装脚本会下载最新 Agent、生成 `agent.json`、创建 `openflare-agent.service` 并启动服务。
## 9. 文档维护要求
部署方式、升级方式、接入模式或联调流程变化时,同步更新本文档和 `README.md`。
+175
View File
@@ -0,0 +1,175 @@
# 接入 Agent
你会学到:Agent 的职责、两种接入 Token 的区别、安装脚本参数、`agent.json` 配置方式,以及如何确认节点已经上线。
OpenFlare Agent 运行在代理节点侧。它不会接收远程 shell 指令,而是通过 Agent API 拉取控制面发布的配置版本,在本地写入 OpenResty 文件、执行配置校验、reload,并在失败时尝试回滚到可运行配置。
## 接入方式
| 方式 | 适用场景 |
| --- | --- |
| `discovery_token` | 首次自动注册节点,由 Server 置换为节点专属凭证 |
| `agent_token` | 已在管理端创建或分配节点,直接使用节点专属凭证接入 |
`agent_token` 与 `discovery_token` 至少填写一个。
### 凭证获取路径
- **`discovery_token`(自动注册凭证)**:登录管理端后台,导航至「系统设置」->「自动注册」,在页面中可直接生成、查看和复制全局的自动注册凭证。
- **`agent_token`(节点专属凭证)**:登录管理端后台,导航至「节点管理」->「新增节点」,填写节点基本信息保存后,在节点详情页面即可直接复制该节点专属的接入 Token。
## 一键安装
使用 `discovery_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--discovery-token YOUR_DISCOVERY_TOKEN
```
使用节点专属 `agent_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
安装脚本会下载最新 Agent,默认写入 `/opt/openflare-agent`,生成 `agent.json`,并在 Linux + systemd 环境创建 `openflare-agent.service`。
支持参数:
| 参数 | 说明 |
| --- | --- |
| `--server-url` | Server 地址,必填 |
| `--discovery-token` | 首次自动注册 Token |
| `--agent-token` | 节点专属 Token |
| `--install-dir` | 安装目录,默认 `/opt/openflare-agent` |
| `--openresty-path` | OpenResty 二进制路径,未传时自动查找 `openresty` |
| `--repo` | 下载 Agent 的 GitHub 仓库,默认 `Rain-kl/OpenFlare` |
| `--no-service` | 不创建 systemd 服务 |
## 配置文件
默认配置文件路径:
```text
/opt/openflare-agent/agent.json
```
本地配置示例:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "./data",
"openresty_path": "openresty",
"openresty_observability_port": 18081,
"observability_replay_minutes": 15,
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
自定义 OpenResty 路径示例:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "/var/lib/openflare-agent",
"openresty_path": "/usr/local/openresty/nginx/sbin/openresty",
"main_config_path": "/var/lib/openflare-agent/etc/nginx/nginx.conf",
"route_config_path": "/var/lib/openflare-agent/etc/nginx/conf.d/openflare_routes.conf",
"access_log_path": "/var/lib/openflare-agent/var/log/openflare/access.log",
"cert_dir": "/var/lib/openflare-agent/etc/nginx/certs",
"lua_dir": "/var/lib/openflare-agent/etc/nginx/lua",
"runtime_config_dir": "/var/lib/openflare-agent/etc/openflare",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
如果不配置 `openresty_path`,Agent 默认调用 `openresty`。完整字段见 [配置项参考](../reference/configuration.md#agent-配置字段)。
## Docker 运行
Docker 部署时直接运行内置 OpenResty 的 Agent 镜像:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443/tcp -p 443:443/udp \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
ghcr.io/rain-kl/openflare-agent:latest
```
## 启动与验证
systemd 环境:
```bash
systemctl status openflare-agent
journalctl -u openflare-agent -f
```
手动启动:
```bash
/opt/openflare-agent/openflare-agent -config /opt/openflare-agent/agent.json
```
源码运行:
```bash
cd openflare_agent
export LOG_LEVEL='info'
go run ./cmd/agent -config /path/to/agent.json
```
编译后二进制运行:
```bash
cd openflare_agent
go build -o openflare-agent ./cmd/agent
export LOG_LEVEL='info'
./openflare-agent -config /path/to/agent.json
```
在管理端确认:
| 位置 | 期望结果 |
| --- | --- |
| 节点列表 | 节点在线 |
| 节点详情 | 能看到心跳时间、当前版本和基础资源信息 |
| 应用记录 | 发布配置后出现应用结果 |
## 卸载
如需彻底卸载 Agent 并清空本地数据:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash
```
支持参数:
| 参数 | 说明 |
| --- | --- |
| `--install-dir` | 安装目录,默认 `/opt/openflare-agent` |
| `--service-name` | systemd 服务名,默认 `openflare-agent` |
卸载脚本只移除 Agent 服务、进程和安装目录,不会删除本机 OpenResty。
## 常见问题
| 现象 | 处理步骤 |
| --- | --- |
| `agent_token 和 discovery_token 不能同时为空` | 检查 `agent.json` 至少配置了一个 Token |
| 节点一直离线 | 在 Agent 节点执行 `curl -I http://your-server:3000`,确认 Server 地址可达 |
| OpenResty 没有启动 | 查看 `journalctl -u openflare-agent`,确认 `openresty_path` 可执行且 80/443 端口未被占用 |
| 发布后重复失败 | Agent 会阻断同一 `version + checksum` 的重复应用;需要修正配置后重新发布,或激活旧版本回滚 |
+287
View File
@@ -0,0 +1,287 @@
# 部署说明
你会学到:OpenFlare 的推荐部署方式、Server 与 Agent 的运行要求、源码启动方式、联调步骤、升级与卸载入口。
生产环境建议使用 PostgreSQL 作为 Server 数据库,并为 Server 显式配置 `SESSION_SECRET`。Agent 部署方式推荐为 Docker 部署(即直接使用内置 OpenResty 的 Agent 镜像);亦支持通过安装脚本或手动本地运行。
## 部署拓扑
### 标准反代流量路径
```text
Browser
|
v
OpenFlare Server :3000
|
| Agent API / heartbeat / config pull
v
OpenFlare Agent
|
v
OpenResty binary
|
v
Origin service
```
### 内网穿透流量路径
```text
Browser
|
v
OpenResty (Agent, WAF/HTTPS 终结) <-- TunnelRelay 节点
|
| proxy_pass (127.0.0.1:{vhost_port})
v
OpenFlareRelay (frps 进程) <-- TunnelRelay 节点
|
| frp 隧道协议
v
OpenFlared (frpc 客户端) <-- 内网服务器
|
v
Internal Service (192.168.x.x)
```
## 前置条件
Server:
| 项目 | 要求 |
| --- | --- |
| Go | `1.25+`,仅源码运行需要 |
| Node.js | `18+`,仅源码构建管理端需要 |
| 数据库 | 可写 SQLite 文件目录,或可访问的 PostgreSQL 实例 |
| 端口 | 默认监听 `3000` |
Agent:
| 项目 | 要求 |
| --- | --- |
| 系统 | 安装脚本支持 Linux 和 macOS;systemd 服务仅在 Linux + systemd 环境创建 |
| 架构 | `amd64` 或 `arm64` |
| OpenResty | 本地部署需要可执行 `openresty`,或通过 `--openresty-path` 指定路径 |
| Docker | 仅 Docker 部署 Agent 镜像时需要 |
| 网络 | Agent 节点必须能访问 Server 地址 |
| GeoIP | WAF 地域规则使用 Agent 本地 MaxMind mmdb;Agent 内置初始库并会定期更新 |
### 硬件配置推荐
| 组件 | 最低硬件配额 | 推荐硬件配额 | 说明 |
| --- | --- | --- | --- |
| **Server 控制面** | 1 核 CPU / 1 GB 内存 / 10 GB 磁盘 | 2 核 CPU / 4 GB 内存 / 50 GB+ 磁盘 | 磁盘用量需根据访问日志留存时长与并发流量合理扩容 |
| **Agent 数据面** | 1 核 CPU / 512 MB 内存 / 2 GB 磁盘 | 2 核 CPU / 2 GB 内存 / 10 GB+ 磁盘 | 根据 OpenResty 的并发代理连接量与 WAF 拦截处理扩容 |
| **Relay 中继节点**| 1 核 CPU / 1 GB 内存 / 5 GB 磁盘 | 2 核 CPU / 2 GB 内存 / 20 GB 磁盘 | frps 传输中继吞吐量主要受带宽与 CPU 吞吐能力限制 |
| **OpenFlared 客户端**| 1 核 CPU / 256 MB 内存 / 1 GB 磁盘 | 1 核 CPU / 512 MB 内存 / 5 GB 磁盘 | 独立运行于内网,自身资源占用极小,保障网络吞吐即可 |
## Docker Compose 部署 Server
创建 `docker-compose.yml`:
```yaml
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
interval: 10s
timeout: 5s
retries: 5
openflare:
image: ghcr.io/rain-kl/openflare:latest
container_name: openflare
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-a-long-random-string
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
volumes:
- openflare-data:/data
volumes:
postgres-data:
openflare-data:
```
启动:
```bash
docker compose up -d
docker compose ps
docker compose logs -f openflare
```
首次访问 `http://localhost:3000`,默认账号为 `root` / `123456`。登录后请立即修改默认密码。
## 源码启动 Server
先构建管理端前端:
```bash
cd openflare_server/web
corepack enable
pnpm install
pnpm build
```
再启动 Server:
```bash
cd openflare_server
export SESSION_SECRET='replace-with-a-long-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
# 可选:设置后优先使用 PostgreSQL。
# export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
go run .
```
默认监听 `3000` 端口。也可以显式指定:
```bash
go run . --port 3000 --log-dir ./logs
```
## Docker 运行 Agent(推荐)
Docker 部署是 Agent 推荐的部署方式。Docker 部署时直接运行 Agent 镜像,该镜像基于 OpenResty 镜像制作,内置 Agent 控制器与 OpenResty 二进制。未显式配置 `node_ip` 时,Agent 会优先通过第三方 API 获取真实出口 IP,避免把 Docker 网桥地址登记为节点 IP。
挂载配置文件:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443/tcp -p 443:443/udp \
-v openflare-agent-data:/data \
-v ./agent.json:/etc/openflare/agent.json:ro \
ghcr.io/rain-kl/openflare-agent:latest
```
使用环境变量:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443/tcp -p 443:443/udp \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
ghcr.io/rain-kl/openflare-agent:latest
```
## Agent 接入(脚本安装)
除了 Docker 部署外,也支持通过安装脚本将 Agent 部署在本地宿主机上。
使用 `discovery_token` 自动注册:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--discovery-token YOUR_DISCOVERY_TOKEN
```
使用节点专属 `agent_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
安装脚本支持参数:
| 参数 | 说明 |
| --- | --- |
| `--server-url` | Server 地址,必填 |
| `--discovery-token` | 首次自动注册 Token,与 `--agent-token` 二选一 |
| `--agent-token` | 节点专属 Token,与 `--discovery-token` 二选一 |
| `--install-dir` | 安装目录,默认 `/opt/openflare-agent` |
| `--openresty-path` | OpenResty 二进制路径,未传时自动查找 `openresty` |
| `--repo` | 下载 Agent 的 GitHub 仓库,默认 `Rain-kl/OpenFlare` |
| `--no-service` | 不创建 systemd 服务 |
确认状态:
```bash
systemctl status openflare-agent
journalctl -u openflare-agent -f
```
## 手动运行 Agent
源码运行:
```bash
cd openflare_agent
export LOG_LEVEL='info'
go run ./cmd/agent -config /path/to/agent.json
```
编译后二进制运行:
```bash
cd openflare_agent
go build -o openflare-agent ./cmd/agent
export LOG_LEVEL='info'
./openflare-agent -config /path/to/agent.json
```
最小 `agent.json` 示例:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "./data",
"openresty_path": "openresty",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
未配置 `openresty_path` 时,Agent 默认调用 `openresty`。
默认情况下,Agent 在 HTTP 心跳成功后会尝试升级为 WebSocket。升级成功时,Server 发布或激活配置会立即通知 Agent;如果 WebSocket 无法建立或意外断开,Agent 会自动退回 HTTP 心跳同步。
WAF 地域规则依赖 Agent 本地 `GeoLite2-Country.mmdb`。Agent 启动时会在 `data_dir/etc/openflare/GeoLite2-Country.mmdb` 初始化内置数据库,并按配置周期尝试更新;更新失败只记录警告,不影响配置同步与 OpenResty reload。
## 升级与卸载
Server:
* Root 用户可在管理端顶栏检查并升级正式版。
* 如需尝试 preview 版本,可手动检查对应发布。
* 也可通过上传 Server 二进制的方式执行确认升级。
Agent:
* Agent 默认只跟随正式版自动更新。
* Agent 自更新会要求 GitHub Release 同时包含目标二进制和同名 `.sha256` 校验文件,下载后必须通过 SHA-256 校验才会替换本地可执行文件。
* 安装脚本可重复执行,用于重装或升级 Agent。
* preview 升级需要手动触发。
卸载 Agent:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash
```
卸载脚本会停止 Agent、删除 systemd 服务和安装目录,不会删除本机 OpenResty。
+24
View File
@@ -0,0 +1,24 @@
# 部署与升级
本分区提供 OpenFlare Server、Agent、Relay 中继以及 OpenFlared 内网穿透客户端的详细部署指南、配置说明和升级维护步骤。
## 内容导航
### 快速开始
* **[快速开始](../guide/quick-start.md)**:5 分钟内使用 Docker Compose 启动 Server 和首个 Agent(推荐新用户)
### Server 部署
* **[启动 Server](./server.md)**:从源码构建前端、启动 Server、选择 SQLite 或 PostgreSQL
### Agent 部署
* **[部署 Agent](./agent.md)**:Agent 接入方式、Docker 部署、脚本安装、配置文件及故障排查
### Tunnel 内网穿透部署
* **[部署 Relay](./relay.md)**:TunnelRelay 节点的配置说明、Docker 部署与宿主机运行指南
* **[部署 OpenFlared](./openflared.md)**:内网穿透客户端配置说明、Docker 运行与自同步机制
### 升级与维护
* **[升级与维护](./upgrade.md)**:Server 与 Agent 升级步骤、数据清理策略、验证命令
### 参考资料
* **[部署说明](./deployment.md)**:部署拓扑、前置条件、Docker Compose 配置示例、多种部署方式综览
+119
View File
@@ -0,0 +1,119 @@
# 部署 OpenFlared 客户端
你会学到:OpenFlared 客户端的职责、配置参数与环境变量、基于 Docker 运行客户端的方法,以及如何在内网服务器上通过二进制方式独立部署。
**OpenFlared** 是部署在用户内网(局域网、私有云等无法被公网直接访问的环境)的隧道客户端。它的核心职责是通过 `X-Tunnel-Token` 与控制面(OpenFlare Server)建立通信,并在本地自动拉起并管理一个或多个 **frpc (快速反向代理客户端)** 进程,从而将内网的 HTTP 流量安全、稳定地穿透至外网的中继节点。
---
## 前置条件
1. **获取 Tunnel Token**:在 OpenFlare 管理端的「内网穿透」或「隧道管理」页面中,创建一个新的隧道实例,系统会自动生成唯一的 `tunnel_id` 与 `tunnel_token`(形如 `tun-<32hex>`)。
2. **网络出方向权限**:内网服务器无需任何公网入方向 IP 或端口映射,但必须能够通过网络访问公网上的 **OpenFlare Server 地址** 以及对应的 **TunnelRelay 节点中继端口 (默认 7000)**。
3. **软件依赖**(仅限宿主机直接部署):
- 本地需有可执行的 `frpc` 二进制文件(建议版本为 `v0.61.0+` 或最新稳定版 `v0.69.0`),或通过参数显式指定路径。
---
## 配置文件与环境变量
`openflared` 启动时默认会读取当前目录下的 `flared.json`。同时也完全支持通过环境变量进行覆盖。
### 配置字段详情
| JSON 字段 | 环境变量 | 说明 | 默认值 |
| --- | --- | --- | --- |
| `server_url` | `OPENFLARE_SERVER_URL` | OpenFlare Server 接口服务地址 | **无(必填)** |
| `tunnel_token` | `OPENFLARE_TUNNEL_TOKEN` | 隧道客户端专属认证 Token | **无(必填)** |
| `frpc_path` | `OPENFLARE_FRPC_PATH` | frpc 可执行二进制文件路径 | `"frpc"` |
| `data_dir` | `OPENFLARE_DATA_DIR` | 本地数据与生成的 `frpc_{relayNodeID}.toml` 存放目录 | `"./data"` |
| `state_path` | - | 本地状态记录文件路径(保存最后应用的配置版本)| `"{data_dir}/flared-state.json"` |
| `heartbeat_interval`| - | 状态心跳上报周期(支持毫秒数或 Go Duration 字符串) | `10000` (10s) |
| `sync_interval` | - | 隧道配置拉取同步周期(支持毫秒数或 Go Duration 字符串) | `30000` (30s) |
| `request_timeout` | - | 接口网络请求超时时长 | `10000` (10s) |
---
## Docker 运行(推荐)
Docker 部署是内网运行最简单也最安全的方式。官方的 `openflared` 镜像已经内置了客户端控制器以及 `frpc v0.69.0` 二进制运行时,无需额外搭建环境。
```bash
docker pull ghcr.io/rain-kl/openflared:latest
docker rm -f openflared 2>/dev/null || true
docker run -d --name openflared --restart unless-stopped \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_TUNNEL_TOKEN=YOUR_TUNNEL_TOKEN \
-v openflared-data:/app/data \
ghcr.io/rain-kl/openflared:latest
```
---
## 宿主机手动运行
如果您需要直接在内网的 Linux/macOS/Windows 宿主机上独立运行:
### 1. 编译二进制
```bash
cd openflared
go build -o flared ./cmd/flared
```
### 2. 准备 `flared.json`
在程序同级目录下创建 `flared.json` 配置文件:
```json
{
"server_url": "http://your-server-ip:3000",
"tunnel_token": "your-tunnel-auth-token",
"frpc_path": "/usr/local/bin/frpc",
"data_dir": "./data",
"heartbeat_interval": "10s",
"sync_interval": "30s"
}
```
### 3. 运行服务
```bash
export LOG_LEVEL='info'
./flared -config ./flared.json
```
---
## 启动与验证
### 1. 自动同步逻辑
启动成功后,OpenFlared 将执行以下工作流:
- **心跳与配置获取**:周期性向 Server 的 `/api/flared/heartbeat` 和 `/api/flared/config` 接口发起同步,验证 Token 并检测配置版本。
- **文件渲染**:当检测到配置版本(或校验和 Checksum)变化时,会自动拉取该隧道的完整路由规则。如果绑定了多个中继 Relay,将为每个 Relay 分别在 `data_dir` 下渲染出 `frpc_{relayNodeID}.toml`。
- **热重载或重启**:拉起对应的 `frpc` 子进程,或在配置文件发生改变时执行 `frpc reload` / 重启动作,以确保流量映射保持最新。
- **异常自恢复**:如果本地 `frpc` 隧道进程异常退出,主控程序会在 5 秒的退避惩罚后自动尝试重新启动。
### 2. 查看日志与连接状态
```bash
# Docker 容器日志
docker logs -f openflared
```
若进程运行无误,您会在日志中看到类似如下输出:
```text
flared config loaded ...
detected frpc version v0.69.0
flared process started
applying new tunnel config {"version": "...", "checksum": "..."}
frpc process missing, starting {"relay_id": "..."}
```
### 3. 管理端确认
打开管理后台的 **「内网穿透」** 页面:
- 查看对应隧道的在线状态,此时应当绿灯显示 **「在线」**。
- 您可以清晰地看到该隧道目前连接了哪些中继节点,以及各内网服务的穿透路由详情。
+126
View File
@@ -0,0 +1,126 @@
# 部署 Relay (Tunnel 中继)
你会学到:TunnelRelay 节点的职责、`openflare_relay` 的配置项与环境变量、使用 Docker 运行 Relay 的方法,以及如何通过源码手动构建并部署 Relay。
在 OpenFlare 的内网穿透体系中,**TunnelRelay 节点** 扮演着关键的角色。它与普通的边缘节点(Edge Node)不同,除了运行传统的 Agent(托管 OpenResty 进行 HTTPS/WAF 处理)外,还同机运行了 **Relay (frps 隧道管理器)** 服务,负责监听内网客户端(OpenFlared)的隧道连接并进行流量中继。
---
## 前置条件
在部署 TunnelRelay 节点之前,请确保:
1. **已注册为 TunnelRelay 类型节点**:在 OpenFlare 管理端「节点管理」中,添加一个类型为 `tunnel_relay` 的节点,并获取其专属的 `agent_token` 或使用全局 `discovery_token`。
2. **网络端口**:
- 必须确保 `bindPort`(frpc 连接端口,默认 `7000`)可被公网/内网客户端访问。
- 必须确保 `vhostHTTPPort`(HTTP Vhost 端口,默认 `8080`)处于空闲状态,Agent 将在此端口上与 frps 进行流量传递。
3. **软件依赖**(仅限宿主机直接部署):
- 本地需有可执行的 `frps` 二进制文件(建议版本为 `v0.61.0+` 或最新稳定版 `v0.69.0`),或通过参数显式指定路径。
---
## 配置文件与环境变量
`openflare_relay` 启动时默认会读取当前目录下的 `relay.json`。同时也完全支持通过环境变量进行覆盖。
### 配置字段详情
| JSON 字段 | 环境变量 | 说明 | 默认值 |
| --- | --- | --- | --- |
| `server_url` | `OPENFLARE_SERVER_URL` | OpenFlare Server 接口服务地址 | **无(必填)** |
| `agent_token` | `OPENFLARE_AGENT_TOKEN` | 节点专属 Token | 与下者二选一 |
| `discovery_token` | `OPENFLARE_DISCOVERY_TOKEN` | 自动注册 Token | 与上者二选一 |
| `node_name` | `OPENFLARE_NODE_NAME` | 节点标识名称 | 默认获取本机主机名 |
| `node_ip` | `OPENFLARE_NODE_IP` | 节点出口/监听 IP | 自动检测真实出口 IP |
| `frps_path` | `OPENFLARE_FRPS_PATH` | frps 可执行二进制文件路径 | `"frps"` |
| `data_dir` | `OPENFLARE_DATA_DIR` | 本地数据与生成的 `frps.toml` 存放目录 | `"./data"` |
| `state_path` | - | 本地状态 JSON 记录文件路径 | `"{data_dir}/relay-state.json"` |
| `heartbeat_interval`| - | 心跳周期(支持毫秒数或 Go Duration 字符串) | `10000` (10s) |
| `request_timeout` | - | 接口请求超时时长 | `10000` (10s) |
---
## Docker 运行(推荐)
Docker 运行是 TunnelRelay 节点最便捷的部署方案。官方镜像内置了 `openflare-relay` 控制器与 `frps v0.69.0` 运行时,开箱即用。
```bash
docker pull ghcr.io/rain-kl/openflare-relay:latest
docker rm -f openflare-relay 2>/dev/null || true
docker run -d --name openflare-relay --restart unless-stopped \
-p 7000:7000 \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
-v openflare-relay-data:/var/lib/openflare-relay \
ghcr.io/rain-kl/openflare-relay:latest
```
> [!TIP]
> 这里的 `-p 7000:7000` 映射的是 `frpc` 客户端连接中继的端口。如果管理端配置了自定义的 `relay_bind_port`,请对应修改宿主机端口映射。
---
## 宿主机手动运行
如果您倾向于在物理机或虚拟机上直接运行:
### 1. 编译二进制
```bash
cd openflare_relay
go build -o openflare-relay ./cmd/relay
```
### 2. 准备 `relay.json`
在程序同级目录下创建 `relay.json` 配置文件:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "your-relay-node-agent-token",
"frps_path": "/usr/local/bin/frps",
"data_dir": "./data",
"heartbeat_interval": "10s",
"request_timeout": "10s"
}
```
### 3. 运行服务
```bash
export LOG_LEVEL='info'
./openflare-relay -config ./relay.json
```
---
## 启动与验证
### 1. 查看进程日志
```bash
# Docker 容器日志
docker logs -f openflare-relay
```
如果是在 Linux 上通过 Systemd 托管的,可执行:
```bash
journalctl -u openflare-relay -f
```
### 2. 验证运行状态
启动成功后,Relay 将进行以下工作:
- 向控制面发送 HTTP 心跳以注册/上线。
- 从控制面获取最新的 frps 基础配置(包括 `bindPort`、`vhostHTTPPort` 与自动生成的隧道认证凭证 `auth_token`)。
- 在本地自动渲染出 `data/frps.toml` 配置文件。
- 自动拉起子进程 `frps -c data/frps.toml`。
- 如果进程意外崩溃,Relay 将在 2 秒后自动拉起它。
### 3. 管理端确认
登录管理后台,导航至 **「节点管理」**,确认:
- 该 TunnelRelay 节点状态标记为 **「在线」**。
- 节点类型正确标记为 **中继节点** 且 frps 运行状态为 **正常 (Healthy)**。
+172
View File
@@ -0,0 +1,172 @@
# 启动 Server
你会学到:如何从源码构建管理端前端、启动 OpenFlare Server、选择 SQLite 或 PostgreSQL,并访问 Swagger。
OpenFlare Server 是 Gin + GORM 单体控制面,负责管理端 UI、管理 API、Agent API、配置渲染、版本发布、数据存储与聚合查询。
## 前置条件
| 项目 | 要求 |
| --- | --- |
| Go | `1.25+` |
| Node.js | `18+` |
| pnpm | 推荐通过 `corepack enable` 使用项目声明的 pnpm |
| 数据库 | SQLite 文件目录可写,或可访问的 PostgreSQL 实例 |
生产环境建议显式配置 `SESSION_SECRET`,并优先使用 PostgreSQL。
## 构建管理端前端
Go Server 会托管 `openflare_server/web/build` 中的静态产物。源码启动前先构建前端:
```bash
cd openflare_server/web
corepack enable
pnpm install
pnpm build
```
常用前端检查:
```bash
pnpm lint
pnpm typecheck
pnpm test
```
## 使用 SQLite 启动
```bash
cd openflare_server
export SESSION_SECRET='replace-with-a-long-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
go run .
```
默认监听 `3000` 端口,访问:
```text
http://localhost:3000
```
## 使用 PostgreSQL 启动
```bash
cd openflare_server
export SESSION_SECRET='replace-with-a-long-random-string'
export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
export LOG_LEVEL='info'
go run .
```
`DSN` 设置后优先于 SQLite。`DSN` 与兼容旧命名的 `SQL_DSN` 同时存在时,优先使用 `DSN`。
如果目标 PostgreSQL 数据库为空且本地 `SQLITE_PATH` 文件存在,Server 启动阶段会尝试把 SQLite 数据迁移到 PostgreSQL,并在日志中输出迁移进度。
## 使用 Docker 启动
使用 Docker 部署可以免去本地配置 Go 与 Node.js 前端构建环境的麻烦。OpenFlare 官方提供了完整的 Dockerfile 与 Compose 配置,支持独立容器启动及多服务联动部署。
### 1. 使用 Docker Run 极速启动(以 SQLite 为例)
确保当前目录下已创建用于持久化数据库和日志的数据卷目录。运行以下命令启动 Server:
```bash
# 创建本地挂载目录
mkdir -p ./openflare-data
# 启动容器
docker run -d \
--name openflare-server \
-p 3000:3000 \
-v $(pwd)/openflare-data:/data \
-e SESSION_SECRET='replace-with-a-long-random-string' \
-e SQLITE_PATH='/data/openflare.db' \
-e GIN_MODE='release' \
-e LOG_LEVEL='info' \
openflare-server:latest
```
启动参数说明:
* **`-p 3000:3000`**:映射宿主机 `3000` 端口到容器内 `3000` 端口。
* **`-v $(pwd)/openflare-data:/data`**:挂载本地目录到容器的 `/data`,确保数据库文件 `openflare.db` 在重启或重建容器时不丢失。
* **`SESSION_SECRET`**:必须配置的 Session 密钥签名哈希。
---
### 2. 使用 Docker Compose 一键启动(集成 PostgreSQL)
推荐在生产环境使用 Docker Compose,自动编排独立的 PostgreSQL 数据库并建立服务间的高可用关联。
在项目控制面目录下使用 `docker-compose.yaml` 进行编排:
```yaml
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
volumes:
- ./postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
interval: 10s
timeout: 5s
retries: 5
openflare:
image: openflare-server:latest
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-random-string
SQLITE_PATH: /data/openflare.db
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
volumes:
- ./openflare-data:/data
```
启动命令:
```bash
# 启动编排服务
docker compose up -d
```
Compose 参数说明:
* **`depends_on` 与 `healthcheck`**:通过 PostgreSQL 的健康度检查(pg_isready),确保数据库初始化完成并完全准备就绪后,再自动拉起 OpenFlare 控制面服务,避免首次连接数据库失败抛出 panic。
* **数据目录分离挂载**:`postgres` 数据挂载在 `./postgres-data`,`openflare` 数据与本地备份挂载在 `./openflare-data`,结构清晰,便于日常备份和维护。
## 命令行参数
```bash
go run . --port 3000 --log-dir ./logs
```
| 参数 | 作用 | 默认值 |
| --- | --- | --- |
| `--port` | 指定 Server 监听端口 | `3000` |
| `--log-dir` | 指定日志目录 | 空,输出到标准输出 |
| `--version` | 输出版本后退出 | `false` |
| `--help` | 输出帮助后退出 | `false` |
## 首次登录
默认账号:
| 用户名 | 密码 |
| --- | --- |
| `root` | `123456` |
首次登录后请立即修改默认密码。
+52
View File
@@ -0,0 +1,52 @@
# 升级与维护
你会学到:如何升级 Server 与 Agent、如何清理观测数据,以及维护前后应该执行哪些验证命令。
升级前建议先确认当前激活版本、最近一次 Agent 应用结果和数据库备份策略。生产环境不要在发布配置、Agent 大规模重连或数据库迁移进行中同时升级。
## Server 升级
Root 用户可以在管理端顶栏检查并升级 Server 正式版。也可以通过上传 Server 二进制的方式执行确认升级。
如需尝试 preview 版本,可手动检查对应发布。生产环境建议优先使用正式版。
升级后确认:
```bash
docker compose ps
docker compose logs -n 100 openflare
```
如果是源码部署,重新启动 Server 后确认日志中没有数据库迁移或启动错误。
## Agent 升级
节点 Agent 默认只跟随正式版自动更新。preview 升级需要手动触发。
安装脚本可重复执行,用于重装或升级 Agent:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
注意:当前安装脚本重装时会删除整个安装目录,包括旧 `agent.json`、本地状态、缓存数据和下载的二进制。执行前请确认手头仍有可用 Token。
升级后确认:
```bash
systemctl status openflare-agent
journalctl -u openflare-agent -n 100 --no-pager
```
## 数据维护
管理端设置页可以维护观测数据自动清理策略:
| 配置项 | 说明 |
| --- | --- |
| `DatabaseAutoCleanupEnabled` | 是否启用每日自动清理 |
| `DatabaseAutoCleanupRetentionDays` | 自动清理保留天数,至少 1 天 |
开启后,Server 会在每天凌晨 3 点清理访问日志、指标快照与请求报告。
-191
View File
@@ -1,191 +0,0 @@
# OpenFlare 设计基线
本文档定义 OpenFlare `1.0.0` 之后仍然有效的产品边界、系统结构与长期约束。第六版已经完成并并入正式版;过程性设计不再在这里维护。
## 1. 产品定位
OpenFlare 是一套自托管的 OpenResty 控制面,面向单团队或单组织内部运维场景,解决反向代理配置、节点同步、证书托管与基础观测的统一管理问题。
当前稳定能力包括:
* 反代规则管理
* 配置预览、发布、激活与回滚
* Agent 注册、心跳、同步、应用结果上报
* OpenResty 主配置模板、性能参数与缓存参数托管
* HTTPS/TLS 与域名资产管理
* 节点请求聚合、资源快照、健康事件与看板展示
* 节点管理、令牌体系、部署与更新链路
* 基于 Next.js 的正式管理端前端
默认工作方式:
* 所有节点消费同一份全局激活版本
* Server 保存配置与状态,不直接 SSH 管理节点
* Agent 是节点侧唯一受控落地入口
## 2. 范围边界
当前明确不做:
* 多租户
* CDN SaaS 化能力
* GeoDNS、全球调度、智能选路
* WAF、Bot 管理、限流平台化
* 灰度百分比发布、按节点差异化配置
* 对象存储、消息队列、Prometheus、ClickHouse、Kafka 等前置基础设施
* 通用日志检索平台、APM、调用链系统、任意 BI 报表
* 证书自动签发与自动续期
* 平台化抽象对象,如 `zone`、`origin_pool`、`policy`、`deployment`
边界补充:
* OpenResty 代理缓存只作为当前反代链路优化能力存在,不扩展为独立缓存产品
* 主配置文件由 Server 统一渲染并由 Agent 受控写入,不支持节点侧手工编辑后回传合并
* 节点观测聚焦运营与运维排障所需的摘要、趋势和受控窗口数据,不提供长期日志托管
新增能力如果超出上述边界,先更新本文档,再进入实现。
## 3. 技术基线
### 3.1 Server
`openflare_server` 继续作为单体控制面:
* Gin
* GORM
* SQLite / PostgreSQL
* 现有登录与 Session 体系
* 托管 `openflare_server/web` 静态构建产物
### 3.2 Agent
`openflare_agent` 继续作为 Go 单体程序:
* 单二进制
* 节点本地执行
* `openresty_path` 优先
* 未配置 `openresty_path` 时默认使用 Docker OpenResty
### 3.3 Frontend
`openflare_server/web` 是正式前端基线:
* Next.js App Router
* React 19
* TypeScript
* Tailwind CSS
* 静态导出后由 Go Server 托管
## 4. 总体架构
```text
OpenFlare Server (Gin + SQLite/PostgreSQL + Web UI)
|
| HTTP API / Config Pull
v
OpenFlare Agent (register / heartbeat / sync / apply / update)
|
v
Local OpenResty or Docker OpenResty
|
v
Origin
```
职责分工:
* Server 负责配置、版本、节点、设置、证书、管理端 UI 与聚合查询
* Agent 负责本地写入、校验、reload、回滚、自更新与轻量采集
* 发布通过“生成完整版本并激活”完成
* 历史版本不可变
* heartbeat 响应返回激活版本摘要,Agent 仅在不一致时拉取完整配置
## 5. 核心对象
当前有效实体:
* `proxy_routes`
* `config_versions`
* `nodes`
* `node_system_profiles`
* `apply_logs`
* `tls_certificates`
* `managed_domains`
* `node_request_reports`
* `node_access_logs`
* `node_metric_snapshots`
* `traffic_analytics_rollups`
* `node_health_events`
稳定约束:
* 一个域名只对应一条 `proxy_routes` 规则
* `proxy_routes` 至少包含一个上游地址;为兼容历史数据保留 `origin_url` 主上游字段,也允许在同一规则内补充多个上游做负载均衡
* `proxy_routes` 上游统一渲染为带 keepalive 的 named `upstream`;单上游可附带 base path 或 query 并在 `proxy_pass` 中追加,多上游仍限定为纯 `scheme://host[:port]`
* `proxy_routes.origin_host` 为可选字段,用于回源时覆盖 `Host` 请求头;未设置时默认透传访问域名
* `proxy_routes.domain` 必须唯一
* 所有上游地址都必须为合法 `http://` 或 `https://`
* `config_versions` 必须保存完整快照、渲染结果与 `checksum`
* 全局同时只能有一个激活版本
* 回滚通过重新激活旧版本实现
* `nodes` 只承载控制面状态与低频摘要,不承载高频观测事实
* 指标、趋势和访问分析优先使用服务端聚合结果,而不是前端临时统计
* 访问明细只保留受控时间窗口,不演变成通用日志平台
## 6. 发布模型
标准链路:
```text
修改规则 -> 预览/查看 diff -> 发布 -> 生成完整配置版本 -> 激活版本 -> Agent 拉取 -> 本地应用 -> 上报结果
```
发布规则:
1. 读取全部启用的 `proxy_routes`
2. 读取 Server 侧 OpenResty 主配置与结构化参数
3. 渲染完整 OpenResty 配置
4. 计算 `checksum`
5. 写入 `config_versions`
6. 切换激活版本
7. Agent 在后续 heartbeat 中发现并应用
版本号格式固定为 `YYYYMMDD-NNN`。
## 7. 模块边界
### 7.1 `openflare_server`
负责:
* 管理端 UI 与 API
* Agent API
* 配置渲染与版本发布
* 数据存储与聚合查询
* OpenResty 主配置模板、性能参数与缓存参数管理
### 7.2 `openflare_agent`
负责:
* 首次注册与凭证置换
* 周期性心跳与同步
* 主配置、路由配置、证书与 Lua 资源写入
* 执行 `openresty -t` / `openresty -s reload`
* 失败回滚
* 对已失败并回退的目标版本做本地熔断,直到控制面出现新的激活版本
* 节点观测采集与结果上报
### 7.3 `openflare_server/web`
负责:
* 管理端页面、布局、交互与主题
* 总览、节点详情、规则、版本、节点、证书、域名、用户与设置页面
* 统一请求层与前端状态管理
## 8. 文档维护原则
* 产品范围或系统边界变化时更新本文档
* 已完成阶段不再以“版本计划”形式回填
* 新阶段开始前,先补设计,再进入实现
+174
View File
@@ -0,0 +1,174 @@
# Agent 设计文档
你会学到:Agent 的设计原则、核心功能模块、与 Server 的交互链路,以及如何通过不可变版本模型与三阶段容灾机制来保证配置应用的安全性和可靠性。
---
## 需求分析
在分布式反向代理与边缘安全网关场景中,Agent 扮演着打通控制面(Server)与数据面(OpenResty)的核心角色。由于 Agent 运行在用户实际的节点服务器上,其设计必须遵循以下核心安全与高可用需求:
1. **主动拉取(Pull 模型)而非被动接收**:Server 不直接持有节点的 SSH 秘钥,也不主动发起向节点的入向连接。所有控制指令与配置更新均由 Agent 主动通过心跳(Heartbeat)或长连接(WebSocket)向上拉取。这消除了节点侧的入向防火墙安全隐患,防止了控制通道被劫持。
2. **极低侵入性**:Agent 作为一个独立的 Go 二进制进程运行,只与本地 OpenResty 进程进行基于文件的配置重写与信号通知交互,不干涉节点上的其他系统服务。
3. **极强容灾与自愈能力**:由于网络抖动、磁盘写满或异常配置等因素极易导致配置同步失败,Agent 必须具备零依赖的本地回滚自愈能力,严防因单次配置失误导致整机服务彻底瘫痪。
4. **纯粹的数据与状态落地**:Agent 仅负责承载 Server 渲染好的文件与控制意图落地,不包含复杂的业务逻辑校验、多端租户鉴权等控制面职责,确保了节点侧的高效与轻量。
---
## 核心功能
Agent 主要由以下核心子模块组成,共同配合完成其完整的生命周期管理:
| 模块名称 | 对应目录 | 功能职责 |
| :--- | :--- | :--- |
| **配置同步** | `sync/` | 负责拉取完整配置包,写入文件,触发重载,记录并回报同步状态。 |
| **心跳管理** | `heartbeat/` | 定期向 Server 上报节点健康状态、资源指标,并获取最新激活版本摘要。 |
| **WebSocket** | `wsclient/` | 保持与 Server 的长连接,提供秒级实时的配置推送与控制面指令响应。 |
| **OpenResty 管控** | `nginx/` | 执行 Nginx 配置校验 (`openresty -t`)、重写、平滑重载 (`reload`) 及进程自启动。 |
| **本地状态库** | `state/` | 持久化记录本地应用版本、错误日志及未成功上报的可观测性指标缓冲。 |
| **自更新服务** | `updater/` | 监听 Server 自更新指令,安全拉取新版本二进制并完成原地热升级。 |
| **可观测性** | `observability/` | 采集系统宿主机 CPU/内存/磁盘及 Nginx 性能指标,处理访问日志并上报。 |
| **GeoIP 维护** | `geoipdata/` `geoipupdate/` | 维护并定期更新本地 GeoIP 数据库,为 WAF 地域过滤提供支撑。 |
---
## 与 Server 的交互链路
Agent 在生命周期中主要通过 **基于 Token 的自动注册** 和 **心跳/WebSocket 双通道** 与控制面通信。
### 1. 自动注册流程
若 Agent 启动时本地 `agent.json` 的 `access_token` 为空,但配置了 `discovery_token`,将触发自动注册流程:
1. Agent 向控制面 `/api/agent/register` 发送注册请求,携带本地硬件摘要、IP 及主机名。
2. Server 校验 `discovery_token` 有效后,在数据库生成唯一的 `NodeID` 与专属 `AccessToken`(即 `agent_token`)并返回。
3. Agent 将获取的专用 Token 写入本地配置文件,擦除一次性 `discovery_token`,后续所有的通信均基于专属 `AccessToken` 进行鉴权认证。
### 2. 双通道心跳与同步机制
* **HTTP 轮询通道(兜底与探测)**:Agent 默认按设定的 `heartbeat_interval` 间隔发送 POST 心跳包。上报指标的同时获取当前激活版本的摘要信息(Version & Checksum)。
* **WebSocket 通道(实时通信)**:在 HTTP 心跳成功后,Agent 自动尝试将连接升级为 WebSocket (`/api/agent/ws`)。
* WS 连接建立后,心跳与指标上报全面转移到 WS 管道,降低网络开销。
* Server 发布或激活新版本时,通过 WS 广播通知 Agent。Agent 收到变更事件后,**立即触发同步流程**,实现秒级配置生效。
* 若 WS 链路因网络问题断开,Agent 自动降级为 HTTP 轮询,并采用指数退避机制尝试重建 WS。
### 3. 交互时序图
```mermaid
sequenceDiagram
autonumber
participant Agent as OpenFlare Agent
participant OR as 本地 OpenResty
participant Server as OpenFlare Server
Note over Agent: 首次启动 (无 AccessToken)
Agent->>Server: 1. 自动注册请求 (携带 discovery_token)
Server-->>Agent: 2. 颁发 NodeID 与专属 AccessToken (agent_token)
Note over Agent: 存储 Token 至本地配置文件
rect rgb(240, 248, 255)
Note over Agent, Server: HTTP 兜底与 WebSocket 升级
Agent->>Server: 3. 发送 HTTP Heartbeat (上报系统状态与健康度)
Server-->>Agent: 4. 返回 ActiveConfig 摘要及 AgentSettings
Agent->>Server: 5. 发起 WebSocket 升级请求 (/api/agent/ws)
Server-->>Agent: 6. 升级成功 (建立双向持久实时通道)
end
rect rgb(245, 245, 245)
Note over Agent, Server: 实时配置发布应用链路
Note over Server: 管理员在 UI 点击发布配置
Server->>Agent: 7. 通过 WS 广播新配置摘要 (WSMessageTypeActiveConfig)
Agent->>Server: 8. 请求拉取完整配置详情 (携带目标 Version/Checksum)
Server-->>Agent: 9. 返回完整配置快照 (Nginx配置、证书、WAF规则等)
Note over Agent: 备份旧文件,写入新配置至本地临时路径
Agent->>OR: 10. 执行配置语法校验 (openresty -t)
OR-->>Agent: 11. 返回语法校验结果 (OK)
Agent->>OR: 12. 平滑重载信号 (openresty -s reload)
Agent->>Server: 13. 上报应用成功状态 (Apply Log & ActiveVersion)
end
```
---
## OpenResty 的管控
Agent 对数据面 OpenResty 的管控实现了端到端的闭环,包含配置落地、语法验证、平滑重载和异常状态捕获:
### 1. 配置文件的落地组织
同步成功后,Agent 会将配置按照特定的物理结构写入到本地 `/etc/nginx/openflare-lua/` 目录下(或配置指定的 `LuaDir`):
* `nginx.conf`:主配置文件(替换相关占位符,配置性能参数、Shared Dictionaries 及全局 Server)。
* `routes.conf`:路由配置文件(由 Agent 生成,包含所有代理网站的 Server 块、证书路径、缓存及速率限制指令)。
* `certs/`:证书存放目录(文件命名为 `{cert_id}.crt` 和 `{cert_id}.key`)。
* `waf/` 与 `pow/`:WAF 及防 CC 挑战所需的专用 Lua 运行时脚本。
* `waf_config.json` 与 `waf_ip_groups.json`:WAF 过滤引擎所需的结构化规则配置文件。
### 2. 精细化的重载动作
1. **备份当前配置**:在写入新文件之前,Agent 会将现有的配置文件复制到 `.backup` 临时目录下,保留完整的现场快照。
2. **写入并替换占位符**:将最新拉取的模板写入,自动将模板中的绝对路径占位符(如 `__OPENFLARE_LUA_DIR__`)替换为本地实际运行路径。
3. **语法校验**:调用 `openresty -t -c <temp_nginx.conf>` 进行严格的语法测试。
4. **平滑重载**:若校验通过,将新配置移至正式路径,执行 `openresty -s reload`。若 OpenResty 处于未启动状态,则使用当前配置拉起进程。
5. **捕获异常**:校验或重载失败时,Agent 会截获标准错误输出(stderr),提取前 2000 个字符的详细报错信息。
---
## 发布与配置应用模型
OpenFlare 摒弃了动态 Patch 节点配置的落后方式,采用 **不可变配置版本发布模型**。
```text
修改规则 -> 预览 / 查看 diff -> 发布 -> 生成完整配置版本 -> 激活版本 -> Agent 拉取 -> 本地应用 -> 上报结果
```
### 1. 核心设计原则
* **完整发布**:每次发布均是对当前控制面所有启用路由、证书、全局与局部 WAF 规则进行一次性全量编译,生成带唯一 `checksum` 的完整版本。
* **版本格式**:采用 `YYYYMMDD-NNN` 递增格式,确保版本历史直观、具备单调递增性。
* **全局单激活版本**:系统同时只有一个处于 `active` 状态的全局配置版本。回滚时无需逆向打补丁,只需将历史某个健康版本的状态改为 `active`,Agent 重新拉取应用即可。
### 2. 三阶段容灾回滚机制
当 Agent 发现配置应用(或平滑重载)失败时,将自动激活以下三阶段容灾防瘫痪链路:
```mermaid
graph TD
A[配置应用失败] --> B[第一阶段: 尝试本地备份恢复]
B -- 备份文件存在 --> C[写入本地备份文件]
C --> D[执行 openresty -t 校验]
D -- 校验成功 --> E[reload 恢复旧版本运行]
D -- 校验失败 --> F[进入第二阶段]
B -- 无备份 --> F[第二阶段: 写入内置安全兜底配置]
F --> G[写入兜底 nginx.conf: 仅监听 80 端口]
G --> H[启用 stub_status 健康检查]
G --> I[其他路由统一返回 503 且拦截异常配置]
G --> J[尝试拉起 OpenResty 维持基础存活]
J --> K[进入第三阶段]
E --> L[上报 Apply Warning]
K --> M[本地阻断该异常版本重复应用]
M --> N[上报 Apply Error 并保留详细报错]
```
1. **第一阶段:本地备份回退**
* Agent 尝试从前一步保存的 `.backup` 目录恢复主配置、路由及证书。
* 写入备份文件后,重新执行 `openresty -t` 校验。若成功,重载回退并向 Server 上报 `Warning`(警告:应用新版本失败,已自动退回历史健康版本)。
2. **第二阶段:内置安全兜底运行**
* 若本地不存在备份配置(如首次部署即配置错误),或者回退备份配置依然校验失败,Agent 将激活最终自愈机制——写入**内置安全兜底配置**。
* **安全兜底配置规范**:
* 仅监听 `80` 端口,不包含任何用户的真实反代路由。
* 除 `/openflare/stub_status` 健康监测路由返回正常外,其他一切访问请求统一返回状态码 `503 Service Unavailable`,响应体固定为 `OpenFlare: No Valid Configuration`。
* 尝试以此极简配置拉起 OpenResty。这能够确保 Nginx 进程自身不瘫痪,保留了底层的健康检查与探针通道,防止容器/Pod 因健康检查失败而被调度系统不断销毁重启,同时保护了敏感路由的安全性。
3. **第三阶段:本地配置阻断**
* Agent 会将当前导致崩溃的配置 `version + checksum` 记录在本地状态库的阻断名单中。
* 在控制面未激活新的配置(`checksum` 发生变化)之前,Agent 心跳将阻断对此异常版本的重复同步拉取,防止节点陷入“心跳 -> 拉取崩溃配置 -> 崩溃回滚”的死循环。
### 3. WAF IP 组运行时异步同步
为了避免高频变动的恶意 IP 黑名单频繁触发主配置的全量发布与 reload(平滑重载对 Nginx 依然有微小的 CPU 与连接开销),IP 组成员采用了与发布版解耦的**异步差分同步设计**:
* **静态发布快照**:发布生成的 `waf_config.json` 中仅包含规则组对 IP 组的引用关系(即 `ip_whitelist_group_ids` / `ip_blacklist_group_ids`),不包含具体的 IP 成员列表。
* **心跳差分对比**:Agent 在心跳包中上报本地已缓存 IP 组的 MD5 Checksum 映射表。
* **差分下发**:Server 比对当前激活版本引用的 IP 组哈希,仅向 Agent 下发缺失或发生变更的 IP 组成员,写入本地 `waf_ip_groups.json`,实现极速差分同步。
* **WebSocket 实时通知**:当 Server 手动更新 IP 组、订阅源自动同步成功、或安全规则自动触发临时封禁时,Server 会立即通过 WebSocket 广播受影响的 IP 组更新包,Agent 接收落地并即时生效,全程**无须 reload Nginx**。
---
## 设计约束
为保证数据与控制链路的安全边界,Agent 代码编写与二次开发必须严格遵守以下工程约束:
1. **零特权指令通道**:Server 绝对禁止向 Agent 传递任何任意 shell 命令或远程执行脚本(如 exec/eval 等)。所有系统控制原语(如启动、停止、重载、更新)必须硬编码在 Agent 二进制内部。
2. **严格的 Token 过滤与前缀验证**:Agent 侧向 Server 请求资源时,接口端点固定以 `/api/agent/` 为前缀,并强制携带 `X-Agent-Token` 进行签名或令牌核验。
3. **节点自治原则**:Agent 须具备完备的离线工作能力。在与 Server 失去连接期间,本地 OpenResty 必须依靠本地已落地的配置保持反向代理服务的绝对正常运行。
+223
View File
@@ -0,0 +1,223 @@
# 系统架构
你会学到:OpenFlare 的整体架构、Server、Agent、OpenResty 与管理端前端的职责边界,以及一次配置发布从管理端到节点生效的请求流。
OpenFlare 由 Server、Agent、节点本地 OpenResty 和管理端前端组成。Server 是控制面,Agent 是节点侧唯一受控落地入口,OpenResty 是实际数据面。内网穿透场景中,Relay(frps 管理器)和 OpenFlared(frpc 管理器)扩展了数据面流量路径。
### 标准反代流量路径
```text
Browser
|
| Management UI / API
v
OpenFlare Server (Gin + GORM + SQLite/PostgreSQL)
|
| Agent API / heartbeat / config pull
v
OpenFlare Agent
|
| write config / openresty -t / reload / rollback
v
OpenResty binary
|
| reverse proxy
v
Origin
```
### 内网穿透流量路径
```text
Browser
|
| HTTPS request
v
OpenResty (Agent, TLS/WAF) <-- TunnelRelay 节点
|
| proxy_pass http://localhost:vhost_port (Host header preserved)
v
OpenFlareRelay (frps) <-- TunnelRelay 节点,与 Agent 同机部署
|
| frp tunnel protocol (HTTP Vhost routing by Host header)
v
OpenFlared (frpc) <-- 内网服务器
|
| HTTP/HTTPS forward
v
Internal Service (192.168.x.x)
```
## 组件职责
| 组件 | 职责 |
| --------------- | ---------------------------------------------------------------------- |
| Server | 管理端 UI、管理 API、Agent/Relay/Client API、配置渲染、版本发布、数据存储与聚合查询 |
| Agent | 注册、心跳、同步、写入文件、校验、reload、失败回滚、自更新与轻量采集 |
| OpenResty | 接收真实流量,按 OpenFlare 渲染的配置执行 WAF、PoW、认证与反向代理 |
| OpenFlareRelay | 管理 frps 进程生命周期,提供隧道中继服务,通过心跳接收 frps 配置 |
| OpenFlared | 管理 frpc 进程(可多个),连接 Relay 中继,将流量转发到内网服务 |
| Frontend | 管理网站配置、WAF、源站、证书、节点、Tunnel、版本、用户、设置与观测页面 |
## Server
`openflare_server` 是单体控制面:
* Gin 提供 HTTP 服务。
* GORM 访问 SQLite 或 PostgreSQL。
* 现有登录体系提供管理端 Session。
* 认证源与外部账号绑定支持 GitHub OAuth 和标准 OIDC。
* Go Server 托管 `openflare_server/web` 静态构建产物。
Server 不直接 SSH 到节点,也不在线修改节点文件。它只保存控制面状态、生成完整配置版本,并通过 Agent API 让节点主动拉取。
## Agent
`openflare_agent` 是 Go 单体程序:
* 单二进制运行在节点侧。
* 启动后读取或生成本地节点信息。
* 周期性 heartbeat,上报状态并获取激活版本摘要。
* 发现新版本后拉取配置、备份旧文件、写入新文件、校验并 reload。
* 应用失败时尝试恢复运行并回滚。
* 维护 WAF GeoIP mmdb,启动时写入内置初始库,并按配置定期更新。
Agent 通过 `openresty_path` 指向的 OpenResty 二进制统一执行校验、reload、启动与重启;未配置时默认调用 `openresty`。Docker 部署时,Agent 镜像内置 OpenResty 二进制,仍走同一套二进制控制逻辑。
节点 IP 默认由 Agent 注册和心跳上报维护;如果管理端锁定节点 IP,Server 只更新运行状态、版本、观测等运行态字段,不再接受 Agent 上报覆盖该 IP。
## Frontend
`openflare_server/web` 是正式管理端前端:
* Next.js 15 App Router。
* React 19。
* TypeScript。
* Tailwind CSS。
* TanStack Query 管理服务端状态。
前端采用静态导出模式(`output: 'export'`),导出后由 Go Server 通过 `embed.FS` 托管。所有 API 请求应统一经过 `lib/api/`,并处理 `success/message/data` 响应结构。
Server 集成以下安全特性:
* CORS 中间件:跨域请求保护。
* 速率限制:全局与关键接口限流。
* 会话管理:基于 Cookie/Redis 的会话存储。
## 数据与请求流
### 管理端请求流
```text
Browser -> Frontend -> /api/* -> controller -> service -> model -> database
```
管理端变更类接口使用 `POST`,只读接口使用 `GET`。成功与失败都返回清晰的 `message`。
### Agent 同步流
```text
Agent HTTP heartbeat -> Server 返回激活版本摘要
Agent 发现新版本 -> 拉取配置详情
Agent 写入主配置 / 路由配置 / 证书 / Lua 资源 / WAF 运行时配置
Agent 执行 OpenResty 校验与 reload
Agent 上报应用结果
```
### Relay 同步流
Relay(OpenFlareRelay 进程)运行在 TunnelRelay 节点上,与 Agent 共享同一 `agent_token`:
```text
Relay HTTP heartbeat -> Server 返回 frps 基础配置 (bindPort, vhostHTTPPort, auth_token)
Relay 生成 frps.toml 并启动或更新 frps 进程
Relay 定期上报 frps 健康状态与连接统计
Relay 尝试升级 WebSocket 连接以支持实时配置推送
```
frps 配置相对静态(端口、认证 Token),通过心跳下发,**不纳入版本化发布流**。Relay 需要监听 frps 进程异常并自动恢复。认证方式:`X-Agent-Token` + API 路径前缀 `/api/relay/*`,Server 通过 `node_type = tunnel_relay` 区分。
### OpenFlared 同步流
OpenFlared(客户端)运行在内网服务器,使用独立的 `tunnel_token` 认证:
```text
Client HTTP heartbeat -> Server 返回 tunnel 配置版本摘要 (version, checksum)
Client 发现新版本 -> 拉取完整 tunnel 路由配置 (relay 列表 + frpc proxy 定义)
Client 为每个 Relay 生成独立的 frpc.toml 配置文件
Client 为新 Relay 启动 frpc 进程,或为已有 Relay 执行热重载 (frpc reload)
Client 上报应用结果 (成功/失败原因)
```
OpenFlared 通过 `/api/flared/*` 端点与 Server 通信,认证使用 `X-Tunnel-Token`。Tunnel 路由配置随发布流程版本化同步,所有配置变更通过单一版本号关联并一致性发布到 Agent 和 Client。
**WebSocket 升级流程**(可选,通过 `AgentWebsocketUpgradeEnabled` 选项控制):
当启用 WebSocket 升级时:
1. Agent 通过 HTTP heartbeat 获取运行配置与设置。
2. Agent 尝试升级连接到 `GET /api/agent/ws`(WebSocket)。
3. WS 连接成功后,周期性状态上报和实时消息由 WebSocket 承载,降低延迟。
4. Server 发布或激活版本后,可向已连接 Agent 立即广播激活版本摘要,使 Agent 立即进入同步流程。
5. 若 WebSocket 断开或建立失败,Agent 自动降级回 HTTP heartbeat,保证可用性。
通过 `OpenRestyWebsocketEnabled` 选项,可在 OpenResty 层面启用或禁用 WebSocket 反向代理支持。
### 反向代理流
```text
Client -> OpenResty server block -> WAF Lua -> named upstream -> Origin
```
网站配置是反向代理聚合边界。一条网站配置可绑定多个域名,并共享站点级流量限制、反向代理和缓存配置。
WAF 在 OpenResty `access_by_lua_file` 阶段执行。规则来自当前激活版本携带的 `waf_config.json`,全局规则组默认生效,网站可叠加自定义规则组。`waf_config.json` 只保存规则组直接 IP 和 IP 组引用 ID;IP 组成员由 Agent 独立同步到本地 `waf_ip_groups.json`,OpenResty Lua 按引用 ID 合并判断。
WAF IP 组由 Server 管理。手动 IP 组直接保存 IP/IP 段列表;自动 IP 组由 Server 定时任务读取请求日志、按单个 IP 聚合指标并执行 Expr 规则;订阅 IP 组由 Server 定时任务同步远程文本或 JSON 源。Agent 心跳会上报本地 IP 组 checksum,Server 只返回不一致的 IP 组;Server 侧 IP 组更新时会通过 Agent WebSocket 广播变更组。OpenResty Lua 只读取 Agent 落地的运行时 JSON,不直接访问 Server 数据库、请求日志或远程订阅源。
## 核心对象
当前有效实体包括:
* `proxy_routes`
* `origins`
* `config_versions`
* `nodes`
* `tunnels`
* `auth_sources`
* `external_accounts`
* `node_system_profiles`
* `apply_logs`
* `tls_certificates`
* `managed_domains`
* `node_request_reports`
* `node_access_logs`
* `node_metric_snapshots`
* `traffic_analytics_rollups`
* `node_health_events`
* `waf_rule_groups`
* `waf_ip_groups`
* `waf_rule_group_bindings`
* `acme_accounts`
* `dns_accounts`
* `geoip_update_configs`
## 关键设计决策
| 决策 | 原因 |
| ------------------------------ | --------------------------------------------------------------------------- |
| 完整配置版本,而不是在线 patch | 让预览、激活、历史和回滚有稳定边界 |
| Agent 主动拉取 | Server 不需要 SSH 权限,也不暴露远程命令入口;支持 HTTP 与 WebSocket 双协议 |
| 全局单激活版本 | 降低 MVP 复杂度,保证所有节点默认一致;支持版本预览、历史查询与一键回滚 |
| 网站配置聚合多域名 | 支持一个业务站点共享站点级策略,同时允许按域名绑定证书 |
| 观测数据服务端聚合 | 避免前端临时统计造成口径不一致 |
| 内网穿透基于 frp 整合 | 复用成熟隧道协议,避免自研隧道的稳定性风险;frps HTTP Vhost 路由天然适配 |
| Relay/Client 独立二进制 | 职责分离,Relay 管理 frps,Client 管理 frpc,各自独立升级和部署 |
| Tunnel 与 Node 体系分离 | Tunnel 客户端在内网运行,与公网节点概念不同,使用独立的注册和认证体系 |
## 贡献者阅读建议
如果要修改架构相关代码,先阅读:
1. [产品边界](./index.md)
2. [Agent 与发布模型](./agent-design.md)
3. [开发约束](../guildline/development-constraints.md)
4. [仓库结构](./repository.md)
+182
View File
@@ -0,0 +1,182 @@
# 本地开发
你会学到:如何搭建 OpenFlare 的本地开发环境、启动 Server、Agent 和管理端前端,运行测试与构建命令,并理解贡献代码前需要遵守的边界。
本页面向贡献者。产品边界、数据模型约束、API 约定和前端分层规范以 [开发约束](../guildline/development-constraints.md) 为准;本页只提供可执行的本地开发流程。
## 仓库结构
项目的核心物理目录及各模块(Server、Agent、Frontend 等)的职责分层,详见 [仓库结构](./repository.md)。
## 环境要求
| 项目 | 要求 |
| --- | --- |
| Go | `1.25+` |
| Node.js | `18+` |
| pnpm | 推荐通过 `corepack enable` 使用项目声明版本 |
| Docker | Server 容器、本地联调和 Agent Docker 镜像需要 |
| OpenResty | 本地运行 Agent 时需要可执行 `openresty` |
| PostgreSQL | 可选;未配置时 Server 使用 SQLite |
## 初始化前端依赖
```bash
cd openflare_server/web
corepack enable
pnpm install
```
构建供 Go Server 托管的静态产物:
```bash
pnpm build
```
## 启动 Server
SQLite 模式:
```bash
cd openflare_server
export SESSION_SECRET='dev-session-secret'
export SQLITE_PATH='./openflare-dev.db'
export LOG_LEVEL='debug'
go run .
```
PostgreSQL 模式:
```bash
cd openflare_server
export SESSION_SECRET='dev-session-secret'
export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
export LOG_LEVEL='debug'
go run .
```
默认访问地址:
```text
http://localhost:3000
```
默认账号是 `root` / `123456`。
## 启动前端开发服务器
前端开发服务器默认监听 `3001`,并通过 `NEXT_DEV_BACKEND_URL` 代理到后端:
```bash
cd openflare_server/web
export NEXT_DEV_BACKEND_URL='http://127.0.0.1:3000'
pnpm dev
```
访问:
```text
http://localhost:3001
```
## 启动 Agent
创建本地 `agent.json`:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "./data",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
运行:
```bash
cd openflare_agent
export LOG_LEVEL='debug'
go run ./cmd/agent -config ./agent.json
```
未配置 `openresty_path` 时,Agent 默认调用 `openresty`。调试时可显式配置 `openresty_path`、`main_config_path`、`route_config_path` , `access_log_path`、`cert_dir`、`lua_dir` 和 `runtime_config_dir`。
## 测试
Server:
```bash
cd openflare_server
GOCACHE=/tmp/openflare-go-cache go test ./...
```
Agent:
```bash
cd openflare_agent
GOCACHE=/tmp/openflare-go-cache go test ./...
```
Frontend:
```bash
cd openflare_server/web
pnpm lint
pnpm typecheck
pnpm test
pnpm test:e2e
```
Docs:
```bash
cd docs
pnpm build
```
## 构建
管理端静态产物:
```bash
cd openflare_server/web
pnpm build
```
Server 二进制:
```bash
cd openflare_server
go build -o openflare-server .
```
Agent 二进制:
```bash
cd openflare_agent
go build -o openflare-agent ./cmd/agent
```
## 调试入口
| 场景 | 命令或位置 |
| --- | --- |
| Server 日志 | `LOG_LEVEL=debug go run .` |
| Agent 日志 | `LOG_LEVEL=debug go run ./cmd/agent -config ./agent.json` |
| Swagger | `http://localhost:3000/swagger/index.html` |
| 前端 API 代理 | `NEXT_DEV_BACKEND_URL=http://127.0.0.1:3000 pnpm dev` |
| OpenResty 配置校验 | `openresty -t -c ./data/etc/nginx/nginx.conf` |
## 代码风格与变更准入
贡献前先确认:
1. 需求符合 [产品边界](./index.md)。
2. 实现符合 [开发约束](../guildline/development-constraints.md)。
3. 不破坏发布、同步、回滚或升级主链路。
4. 涉及配置、部署、API 或产品边界时同步更新文档。
5. 风险较高的修改补充测试或等效联调验证。
数据库结构变更必须提升数据库版本号,并补充显式迁移方法和校验逻辑。v8-v17 保留为旧升级框架兼容链;v17 之后统一使用 goose,新的 goose 框架代码必须集中在 `openflare_server/model/goose` 包下;每次数据库升级都要在该包下新增独立的 `goose_<timestamp>_<description>.go` 文件,不得把具体迁移逻辑集中堆在 goose 注册入口中,也不得把新 goose 框架代码放回 `openflare_server/model` 根包。
+206
View File
@@ -0,0 +1,206 @@
# 产品边界
你会学到:OpenFlare 是什么、解决什么问题、目标用户是谁、当前稳定能力有哪些,以及哪些设计边界在实现时不能被绕过。
OpenFlare 是一套自托管的 OpenResty 控制面,面向单团队或单组织内部运维场景。它解决反向代理配置、节点同步、证书托管、配置发布回滚与基础观测分散管理的问题。
## 项目定位
OpenFlare 适合需要统一管理多台 OpenResty 代理节点的团队:
* 希望用管理端维护反向代理网站配置。
* 希望每次配置变更都有完整版本、预览、激活与回滚。
* 希望节点主动同步配置,而不是由控制面 SSH 到节点执行命令。
* 希望在同一系统中管理 TLS 证书、域名资产、节点状态和基础访问分析。
OpenFlare 当前不定位为通用日志平台、服务网格、Kubernetes Ingress Controller 或多租户云平台。
## 当前能力
| 能力 | 说明 |
| --- | --- |
| 反代规则管理 | 以网站配置为聚合边界,支持多域名与源站配置 |
| 网站级配置 | 一条规则对应一个网站,可绑定一个或多个域名,并共享站点级配置 |
| 源站管理 | 维护轻量源站目录,并允许网站保存可渲染的源站快照 |
| 配置版本 | 支持预览、发布、激活、不可变历史与回滚 |
| Agent 同步 | 支持注册、心跳、同步、应用结果上报与自更新 |
| OpenResty 托管 | 管理主配置模板、性能参数、缓存参数与 Lua 资源 |
| HTTPS/TLS | 托管证书与域名资产,并按域名绑定证书 |
| WAF | 以全局规则组与网站自定义规则组维护 IP/IP 段、IP 组、国家级地域黑白名单 |
| 基础观测 | 聚合节点请求、资源快照、健康事件和访问分析 |
| 节点管理 | 节点状态、令牌体系、部署与更新链路 |
| 管理端前端 | 基于 Next.js 的正式管理端 |
| 认证源登录 | 支持以认证源形式配置 GitHub 与标准 OIDC 登录入口,并允许第三方账号绑定已有本地用户 |
| 内网穿透 | 通过 TunnelRelay 节点与 OpenFlared 客户端,将内网 HTTP 服务安全暴露到公网,复用 Agent 的 HTTPS/WAF 能力 |
默认工作方式:
* 所有节点消费同一份全局激活版本。
* Server 保存配置与状态,不直接 SSH 管理节点。
* Agent 是节点侧唯一受控落地入口。
* TunnelRelay 节点同时运行 Agent(OpenResty)和 Relay(frps),提供内网穿透中继。
* OpenFlared 客户端在内网运行,管理 frpc 进程连接 Relay,将流量转发到内网服务。
## 典型使用场景
| 场景 | 说明 |
| --- | --- |
| 内部服务统一入口 | 把多个内部 HTTP 服务通过统一域名和证书暴露 |
| 多节点反代配置同步 | 多台 OpenResty 节点消费同一份激活配置 |
| 配置变更审查 | 发布前查看预览或 diff,发布后保留不可变历史 |
| 快速回滚 | 重新激活旧版本,让 Agent 拉取并应用 |
| 证书托管 | 为不同域名绑定 TLS 证书 |
| 基础观测 | 查看节点状态、请求聚合、访问分析和健康事件 |
| 内网穿透 | 通过 Tunnel 将无法直接公网访问的内网 HTTP 服务暴露到互联网,享有 HTTPS、WAF 等全部防护能力 |
## 网站配置约束
`proxy_routes` 是“网站配置”的聚合对象。一条记录对应一个网站,可绑定一个或多个域名,并共享一组站点级配置。
约束:
* `proxy_routes.site_name` 是网站的业务唯一标识。
* `proxy_routes.domains` 至少包含一个域名,且 `domains[0]` 作为主域名。
* 任一域名全局只能属于一个 `proxy_routes`。
* 网站级流量限制、反向代理与缓存配置均按站点共享,不在同一网站内做域名级差异化配置。
* HTTPS 允许在同一站点内按域名绑定证书。
## 源站与上游约束
`origins` 服务于源站目录复用,仅保存源站地址、展示名与备注,不承载协议、端口、路径、权重或健康检查策略。`proxy_routes` 可选关联一个 `origins`,但规则内部仍保存完整上游快照以参与渲染。
上游约束:
* `proxy_routes` 至少包含一个上游地址(直连类型 `direct`),或关联一个 Tunnel(内网穿透类型 `tunnel`)。
* 多上游负载均衡统一渲染为带 keepalive 的 named `upstream`。
* 单上游允许附带 base path 或 query,并在 `proxy_pass` 中追加。多上游限定为纯 `scheme://host[:port]` 结构,且同一规则内的协议必须一致。
* `proxy_routes.origin_host` 为可选字段,用于回源时覆盖 `Host` 请求头。
* 所有直连类型上游地址都必须为合法的 `http://` 或 `https://`。
* 内网穿透类型上游必须关联有效 `tunnel_id`,并指定内网目标地址与协议。
## 内网穿透约束
OpenFlare 通过 TunnelRelay 节点与 OpenFlared 客户端实现内网穿透,底层基于 frp(快速反向代理)构建。
### 节点与组件模型
**节点类型**:
* `nodes.node_type` 区分节点类型:`edge_node`(边缘节点,默认)和 `tunnel_relay`(隧道中继)。
* TunnelRelay 节点同时运行 Agent(OpenResty)和 Relay(frps 管理器),共享同一个 `agent_token`。
- Agent 负责 HTTPS 终结、WAF 防护、缓存与流量限制等。
- Relay 管理 frps 进程,为内网客户端提供隧道中继服务。
* TunnelRelay 节点新增字段:`node_type`、`relay_bind_port`(frpc 连接端口,默认 7000)、`relay_vhost_http_port`(HTTP Vhost 端口,默认 8080)、`relay_auth_token`(自动生成)、`relay_status` 等。
**Tunnel 客户端**:
* `tunnels` 表独立存储内网穿透客户端注册信息,与 `nodes` 体系无关。
* 每个 Tunnel 拥有唯一的 `tunnel_id`(格式 `tun-<32hex>`)和 `tunnel_token`(客户端认证凭据)。
* OpenFlared 客户端运行在内网,不对外暴露,使用 `tunnel_token` 认证,通过 `/api/flared/*` 端点与 Server 通信。
* 一个 OpenFlared 客户端可同时连接多个 Relay(为高可用)。
### 上游类型扩展
`proxy_routes` 的上游配置分为两种类型,通过 `upstream_type` 字段区分:
* **直连上游(`direct`,默认)**:直接将流量转发到源站地址,行为与现有完全一致。
* **内网穿透上游(`tunnel`)**:通过 TunnelRelay 节点将流量转发到内网服务。
- 必须指定 `tunnel_id`(关联 `tunnels` 表)。
- 必须指定 `tunnel_target_addr`(内网目标地址,如 `192.168.1.100:8080`)和 `tunnel_target_protocol`(`http` 或 `https`)。
- 发布时,Server 自动将上游地址替换为 `http://127.0.0.1:{relay_vhost_http_port}`。
### 流量路径与协议
**完整数据面流量路径**:
```
浏览器 → OpenResty (Agent, TLS/WAF) [TunnelRelay 节点]
↓
frps (Relay, HTTP Vhost 路由) [TunnelRelay 节点, 127.0.0.1:{vhost_port}]
↓
frp 隧道协议 (Host 头路由)
↓
frpc (Client, 多进程) [内网服务器]
↓
内网服务 (192.168.x.x:port)
```
**关键特性**:
* frps 使用 HTTP Vhost 单端口复用机制,所有 HTTP 隧道共享一个 `vhost_port`,通过 Host 头自动路由到对应 frpc。
* Agent 保留原始 `Host` 请求头,frps 依据此头进行虚拟主机匹配。
* 每个隧道对应一条 `proxy_routes`,可绑定多个域名。
* OpenFlared 客户端为每个连接的 Relay 管理一个独立的 frpc 进程,通过单一 frp 隧道传输多个 HTTP 代理定义。
### 配置同步模型
发布流程同时生成两类配置版本数据,统一使用 `config_version` 版本号关联:
* **Agent 侧配置**:OpenResty 主配置 + 路由配置 + WAF 规则。包含 tunnel 上游时,自动渲染为 `http://127.0.0.1:{vhost_port}` 上游。
* **Tunnel 侧配置**:Relay 列表 + frpc 代理定义。随发布流程版本化,变更时优先使用 `frpc reload` 热重载。
* **Relay 配置**:通过心跳响应下发,相对静态,不纳入版本化流程。
### 隧道设计约束
* 仅支持 HTTP 协议隧道流量(保留 TCP/UDP 隧道的可扩展性),暂不支持单独的 TCP/UDP 端口分配。
* Tunnel 类型上游的域名 DNS 应当解析到指定的 TunnelRelay 中继节点。
* frp 二进制(v0.61+)由系统部署脚本或容器镜像统一打包提供。
## HTTPS 约束
`proxy_routes.domain_cert_ids` 用于记录与 `domains` 平行的域名证书绑定;值为 `0` 表示该域名不启用 HTTPS,仅保留 HTTP。
发布渲染时:
* 带证书的域名按证书分组输出独立 `443 ssl` `server` 块。
* 未绑定证书的域名不得被自动带入 HTTPS。
* 必须将 `proxy_routes.domains` 中的全部域名一并纳入同一站点配置,避免同站点在版本快照中被拆散。
## WAF 约束
WAF 以规则组为核心配置边界。系统提供唯一的全局规则组(默认应用至所有站点),网站可在此基础上叠加多个自定义规则组。
核心能力:
* 支持单个 IP / CIDR 网段黑白名单。
* 支持 IP 组引用(包括手动、自动Expr计算、URL订阅三类 IP 组)。
* 支持基于 GeoIP 的国家/地区级地域准入过滤。
* 支持规则组自定义拦截响应(支持自定义状态码与拦截 HTML 页面,默认返回 `418`)。
IP 组与判定约束:
* **运行时解耦**:WAF 运行时只读取本地 JSON,不访问 Server 数据库;配置版本仅保存引用的 IP 组 ID。IP 组成员通过哈希 Checksum 差分心跳及 WebSocket 异步推送,实现无需平滑重载 Nginx 的热生效。
* **内置预设 Expr 规则**:
* 高频 404 扫描封禁:`request_count > 100 && status_404_ratio >= 0.8`
* 恶意 IP 直连探测:`ip_host_count > 50 && ip_host_ratio > 0.5`
* **判决优先级**:白名单拥有绝对优先权。若未命中白名单,则触发黑名单漏斗匹配(全局规则组优先,自定义组按 ID 升序匹配)。
* 地域解析依赖节点本地 MaxMind 库;当 GeoIP 异常时自动忽略地域规则,不得破坏 IP 规则与反代主链路的可用性。
## 认证源约束
`auth_sources` 统一支持 `github` 与 `oidc` 登录配置入口。`external_accounts` 存储第三方与本地用户的绑定关系。第三方账号首次接入逻辑:
* 已绑定时直接授权登录;若已有本地会话则自动建立绑定。
* 未绑定且允许注册时自动创建本地账号;若关闭注册,则要求用户提供已有本地账号密码以建立关联。
## 版本与观测约束
* `config_versions` 必须保存完整快照、渲染结果与 `checksum`。
* 全局同时只能有一个激活版本。
* 回滚通过重新激活旧版本实现。
* `nodes` 只承载控制面状态与低频摘要,不承载高频观测事实。
* 指标、趋势和访问分析优先使用服务端聚合结果,而不是前端临时统计。
* 访问明细只保留受控时间窗口,不演变成通用日志平台。
## 文档维护原则
* 产品范围或系统边界变化时更新本文档。
* 系统结构或模块职责变化时更新 [系统架构](./architecture.md)。
* 发布、同步、回滚与 Agent 模型变化时更新 [Agent 与发布模型](./agent-design.md)。
* 开发约束、代码规范、接口约定变化时更新 [开发约束](../guildline/development-constraints.md)。
* 部署方式变化时更新 [部署说明](../deployment/deployment.md) 与 README.
* 配置项变化时更新 [配置项参考](../reference/configuration.md)。
* 已完成阶段不再以“版本计划”形式回填。
* 新阶段开始前,先补设计,再进入实现。
+94
View File
@@ -0,0 +1,94 @@
# 仓库结构
你会学到:OpenFlare 仓库中 Server、Agent、前端、脚本和文档目录分别负责什么,以及贡献代码时应把逻辑放到哪一层。
| 路径 | 职责 |
| ---------------------- | ---------------------------------------------------- |
| `openflare_server` | Gin + GORM + SQLite/PostgreSQL 单体控制面 |
| `openflare_server/web` | Next.js 15 App Router 管理端前端,由 Go Server 托管 |
| `openflare_agent` | Go 单体 Agent,运行在节点侧 |
| `openflare_relay` | Tunnel 中继代理,运行在公网边缘管理 frps 进程 |
| `openflared` | Tunnel 客户端,运行在内网服务器侧管理 frpc 进程 |
| `scripts` | 安装、自更新等系统辅助脚本 |
| `docs` | VitePress 文档站、设计基线、开发规范、部署与配置文档 |
| `docs/en` | 英文版文档 |
## Server 分层
| 目录 | 职责 |
| ------------- | ------------------------------------------------ |
| `controller/` | 参数解析、调用 service、返回响应 |
| `service/` | 业务逻辑、校验、事务编排、配置渲染 |
| `model/` | 模型定义、数据库版本与迁移 |
| `router/` | 路由注册 |
| `middleware/` | 认证、鉴权、限流、CORS、Turnstile 验证等横切逻辑 |
| `common/` | 配置、全局状态与初始化入口 |
| `utils/` | 纯工具函数与通用 helper |
| `job/` | 定时任务(如 SSL 证书续期) |
| `upload/` | 文件上传处理 |
| `docs/` | API 文档(Swagger) |
| `data/` | 静态数据(如 GeoIP 数据库) |
## Agent 模块
| 模块 | 职责 |
| ---------------- | -------------------------------------------- |
| `config/` | 配置读取与默认值 |
| `heartbeat/` | 心跳与版本摘要判断 |
| `sync/` | 配置拉取与应用编排 |
| `nginx/` | OpenResty 文件写入、校验、reload、启动与回滚 |
| `state/` | 本地状态与观测补报缓冲 |
| `httpclient/` | Server 通信 |
| `wsclient/` | WebSocket 客户端通信 |
| `protocol/` | Agent API 协议类型 |
| `updater/` | Agent 自更新逻辑 |
| `logging/` | 日志处理 |
| `observability/` | 可观测性(指标、链路等) |
| `geoipdata/` | GeoIP 数据处理 |
| `geoipupdate/` | GeoIP 数据更新 |
| `agent/` | 核心 Agent 逻辑与生命周期 |
## Frontend 分层
| 目录 | 职责 |
| ------------- | -------------------------------------------- |
| `app/` | Next.js App Router 路由、布局、页面组装 |
| `features/` | 按业务域组织的功能模块 |
| `components/` | 跨 feature 复用的 UI 组件 |
| `lib/` | 请求客户端、环境变量、工具函数、常量 |
| `store/` | 少量跨页面 UI 状态管理 |
| `types/` | 共享类型定义 |
| `styles/` | 全局样式 |
| `tests/` | 前端单元测试与集成测试(Vitest、Playwright) |
| `scripts/` | 构建和部署相关脚本 |
| `public/` | 静态资源 |
## Relay 模块
| 模块 | 职责 |
| ---------------- | ------------------------------------------------ |
| `cmd/` | Relay 命令行启动入口及初始化主函数 |
| `internal/config/`| 本地配置文件解析与默认参数初始化 |
| `internal/frps/` | 管理 frps 进程生命周期、端口与 Token 并监控运行 |
| `internal/heartbeat/`| 周期性 HTTP 心跳通信、上报状态并获取更新请求 |
| `internal/httpclient/`| Server 的通用 API 客户端调用工具类 |
| `internal/observability/`| 采集本地宿主机、frps 的基础运行指标并进行预聚合 |
| `internal/relay/` | 协调中继的核心生命周期、初始化与清理 |
| `internal/state/` | 本地运行时状态、错误记录与持久化缓存 |
| `internal/updater/`| Relay 升级检查、下载安装与重启机制 |
| `internal/wsclient/`| 与 Server 保持的长连接 WebSocket 双向通信管道 |
## OpenFlared (Client) 模块
| 模块 | 职责 |
| ---------------- | ------------------------------------------------ |
| `cmd/` | Client 命令行启动入口及初始化主函数 |
| `internal/config/`| 本地客户端配置加载与解析 |
| `internal/flared/`| 内网穿透客户端的核心调度与状态管理机制 |
| `internal/frpc/` | 热重载/动态生成多 Relay 的 `frpc.toml` 并监控 frpc |
| `internal/heartbeat/`| 与控制面进行的心跳通信,包含 Token 校验机制 |
| `internal/httpclient/`| 客户端通用 API 通信客户端 |
| `internal/sync/` | 增量拉取最新 Tunnel 路由绑定关系、生成快照并应用 |
| `internal/updater/`| 客户端自更新、新版检查与更新落地逻辑 |
| `internal/wsclient/`| 用于实时监听 Server 端隧道配置变更推送的 WS 信道 |
+130
View File
@@ -0,0 +1,130 @@
# 内网穿透隧道设计文档
你会学到:OpenFlare 内网穿透隧道的架构设计、双端管控组件(Relay 与 Client)的内部原理、交互逻辑以及数据面与控制面的通信流程。
---
## 需求分析
在典型的 Web 应用托管场景中,许多源站(Origin Server)部署在内网环境(如本地开发机、局域网服务器或受防火墙限制的内网集群)。这些服务器通常:
1. **无公网 IP**:无法直接被公网流量访问。
2. **安全合规限制**:不允许随意在边界路由器上配置端口映射(NAT)。
3. **动态 IP 变动**:传统的 DDNS 方案延迟高且极不稳定。
为了让内网源站能够无缝接入 OpenFlare 全局数据网关并享受 WAF 地域防护、TLS 证书托管等增值服务,OpenFlare 设计了基于 **反向中继穿透隧道** 的整体解决方案。在该架构中,公网边缘节点作为反代入口和流量中继,内网侧仅需发起安全出向连接,即可实现公网流量安全、稳定地反向穿透到内网源站。
---
## 核心功能
内网穿透隧道子系统包含以下核心能力:
* **Relay 节点动态管理**:由控制面动态派发中继服务(frps),动态分发服务端口与认证令牌(Token)。
* **多隧道反向代理映射**:支持在单个内网客户端上映射多个内网 Web 端口,并将多域名路由绑定至对应的中继节点。
* **独立进程生命周期管控**:中继与客户端均为 Go 编写的独立二进制守护进程,内部负责拉起、监控、自愈及热升级底层的 frp 引擎。
* **基于 Token 的独立认证隔离**:中继端使用 `agent_token`,内网客户端使用专属 `tunnel_token`,权限与路由边界隔离。
* **配置校验与增量热重载**:仅在隧道绑定关系、证书或 Relay 拓扑发生实际变化时,才重写配置文件并平滑重载进程,降低运行开销。
---
## 内网穿透与隧道架构
内网穿透子系统基于成熟的 `frp` 高性能隧道协议进行整合,分为 **控制面 (Control Plane)** 与 **数据面 (Data Plane)**。
```mermaid
graph TD
%% 数据流
Browser[1. 浏览器 / 访客] -->|HTTPS 请求| Agent[2. OpenResty / Agent]
Agent -->|本机转发 proxy_pass| RelayFrps[3. OpenFlare Relay / frps]
RelayFrps -->|加密隧道协议| FlaredFrpc[4. OpenFlared / frpc]
FlaredFrpc -->|转发本地请求| LocalOrigin[5. 内网源站 192.168.x.x]
%% 控制流与心跳
Server[OpenFlare Server 控制面] <-->|Relay API / Heartbeat| RelayManager[openflare_relay 进程]
Server <-->|Client API / Heartbeat| ClientManager[openflared 进程]
RelayManager -.->|管控进程及配置| RelayFrps
ClientManager -.->|管控多 Relay 进程| FlaredFrpc
style Browser fill:#f9f,stroke:#333,stroke-width:2px
style LocalOrigin fill:#9f9,stroke:#333,stroke-width:2px
style Server fill:#f96,stroke:#333,stroke-width:2px
```
* **控制面(Control Plane)**:Server 维护数据库状态;中继节点上的 `openflare_relay` 进程与内网服务器上的 `openflared` 进程通过 HTTP 心跳与 WebSocket 长通道同步隧道配置。
* **数据面(Data Plane)**:公网流量首先进入公网边缘的 Agent (OpenResty),在此完成 HTTPS 握手、TLS 终止和 WAF 过滤,接着通过 `proxy_pass` 转发到同机部署的 `openflare_relay (frps)`。`frps` 再将请求封包通过与内网 `openflared (frpc)` 建立的持久隧道传输过去,最后由 `frpc` 拆包并分发给内网实际的源站服务。
---
## Relay (中继端) 设计
`openflare_relay` 是部署在公网边缘的中继管理器,运行在 `tunnel_relay` 类型的节点上。
### 1. 核心架构与逻辑
* **进程守护**:Relay 进程内部持有 `frps` 二进制,通过 `exec.Command` 拉起 `frps -c frps.toml` 子进程,并启动 goroutine 异步监听其退出状态。如果发现 `frps` 异常退出,会结合退避机制自动拉起。
* **动态配置渲染**:通过 HTTP 心跳向控制面同步状态,获取当前的 `RelayConfig`,主要参数包括:
* `bindPort`:frps 用于监听内网 frpc 客户端连接的公网控制端口。
* `vhostHTTPPort`:虚拟主机(Virtual Host)HTTP 流量监听端口,Agent 的 proxy_pass 会指向此端口。
* `authToken`:客户端连接时进行握手校验的安全凭证。
* `webServer`:开启 frps 的仪表盘 API,Relay 基于此接口或管理控制端口收集实时的活跃隧道数和流量指标。
* **状态上报**:Relay 每周期心跳会向控制面上报底层 `frps` 的活跃连接数、注册客户端数、各个代理通道的实时状态以及 Relay 版本。
---
## Openflared (客户端) 设计
`openflared` 是运行在用户内网服务器侧的客户端管理器,使用独立的 `tunnel_token` 进行鉴权。
### 1. 核心设计机制
* **多 Relay 支持(多路复用)**:
为保障高可用或就近接入,控制面可能会将客户端连接调度到多个公网 Relay。`openflared` 会读取 `TunnelConfig` 中下发的 Relays 列表,在本地为每一个 Relay 节点独立生成一个专用的配置文件(命名为 `frpc_<relay_node_id>.toml`),并分别为每个 Relay 进程分配独立的 cancelable context。
* **子进程独立监控**:
`openflared` 内部维护一个 `processes` 映射表,对每个 `frpc` 子进程进行独立的生命周期管控。当控制面增加或移除 Relay 时,客户端会增量拉起新进程或优雅注销老进程,避免影响其他正常工作的隧道。
* **动态 TOML 生成**:
为每个 Relay 渲染 TOML 时,客户端会遍历 Proxies 列表,将每个内网服务的 `LocalAddr`、`LocalPort`、绑定的 `CustomDomains` 写入到 `[[proxies]]` 块中。
---
## 交互逻辑与流量模型
内网穿透子系统实现了一致性版本控制和状态反馈。
### 1. 控制面发布与同步流程
```text
管理员修改隧道/内网端口映射 -> 提交发布 -> 生成新 Tunnel 版本与 Checksum
|
v (推送或心跳拉取)
+-------------------------------------------+-------------------------------------------+
| |
v (中继端) v (内网客户端)
openflare_relay 心跳检测到 frps 端口/Token 变化 openflared 心跳检测到 tunnel_version 发生变更
重新渲染本地 frps.toml 请求拉取最新代理映射包
Kill 并重新拉起 frps 进程 重新渲染 frpc_<relay_id>.toml
上报健康状态为 healthy 对有变更的 Relay 进程执行重启与配置热重载
上报应用结果 (Apply Success/Error)
```
1. **版本化控制**:所有内网隧道的路由和映射关系与主路由系统类似,也经过版本化控制,下发 `version` 与 `checksum`,确保客户端不重复写入和频繁重载进程。
2. **应用结果闭环**:客户端应用新配置后,会在心跳中携带应用结果上报控制面。若因内网端口不可达或证书配置有误导致 frpc 无法建连,客户端会截获进程输出将 `LastError` 上报,管理员在 Server 即可直观查看穿透失败原因。
### 2. 数据面流量模型
1. **公网入口 (Agent)**:
```nginx
server {
listen 443 ssl;
server_name intranet.example.com;
# ... TLS 证书与 WAF 过滤逻辑 ...
location / {
proxy_pass http://127.0.0.1:18080; # 指向本地 frps 的虚拟主机端口
proxy_set_header Host $host; # 必须保留原 Host,因为 frps 依靠 Host 进行内部路由分发
proxy_set_header X-Real-IP $remote_addr;
}
}
```
2. **中继节点 (frps)**:
`frps` 监听到 `18080` 端口有 HTTP 请求进来,读取 HTTP 请求头中的 `Host: intranet.example.com`,在其已注册的活跃隧道表中检索该域名对应的加密 TCP 连接(由内网 frpc 建立)。
3. **加密隧道传输 (TCP)**:
`frps` 将 HTTP 请求封装进内部 TCP 隧道协议,发送给内网的 `frpc` 客户端。
4. **内网客户端分发 (frpc)**:
`openflared` 管理的 `frpc` 收到封包,根据本地配置(`localIP = "127.0.0.1"`, `localPort = 8080`)将请求建立本地 TCP 连接转发给内网 Web 服务,并将 Web 服务的响应原路打包返回,最终呈现给公网用户。
+130
View File
@@ -0,0 +1,130 @@
# WAF 设计文档
你会学到:OpenFlare 边缘 Web 应用防火墙(WAF)的核心架构、动态 IP 组异步差分同步模型、OpenResty Lua 高性能缓存方案以及完整的请求过滤与判定逻辑。
---
## 需求分析
在互联网公开环境中,Web 应用程序面临着各种各样的安全威胁(如扫描器踩点、刷接口、针对特定地域的恶意网络爬虫、勒索攻击及 CC 攻击等)。如果直接把恶意请求放行给源站(Origin Server),会导致:
1. **源站负载飙升**:高频的数据库查询与 CPU 运算极易耗尽服务器资源。
2. **敏感接口被刷**:登录、注册、短信验证码接口容易被恶意滥用导致财产损失。
3. **数据泄露风险**:恶意的通用漏洞探测行为无法被提前拦截。
因此,OpenFlare 需要在最前端的数据面(OpenResty)构建一套 **高性能、可弹性伸缩的 WAF 过滤引擎**。该引擎能够在最接近用户的边缘层以毫秒级的极低开销对恶意请求进行深度过滤,减轻源站压力,并提供防 CC(PoW 挑战)、IP 黑白名单与地域级别拦截等核心安全防护能力。
---
## 核心功能
OpenFlare WAF 包含以下核心防护维度:
* **IP 级拦截(IP 黑白名单)**:支持单 IP、CIDR 网段过滤,支持将上万 IP 聚合为 IP 组进行高效比对。
* **地域黑白名单(GeoIP 限制)**:集成 MaxMind 数据库,支持针对国家(Country)和省份/地区(Region)执行精准准入控制。
* **自定义拦截响应**:支持针对不同的过滤规则自定义阻断状态码(如 403, 418)以及个性化的 HTML 拦截页面。
* **人机挑战(PoW CC 防护)**:支持无感人机挑战,通过计算 Hash 碰撞防止自动化脚本和僵尸网络(Botnet)对接口进行并发冲击。
---
## IP 组设计与动态异步同步
IP 组是 WAF 进行高效黑白名单管控的核心容器。OpenFlare 将 IP 组根据更新频率与产生渠道分为三类:
### 1. IP 组类型
* **手动 IP 组(Manual)**:由管理员在控制面板上手动输入 IP 或 CIDR 列表。主要用于静态的信任 IP 或长期的封禁。
* **订阅 IP 组(Subscription)**:配置远程文本(按行分隔)或标准的 JSON 订阅地址。Server 侧的定时任务会周期性抓取远程订阅源并自动解析导入。主要用于集成开源的威胁情报库、云厂商的 IP 范围等。
* **自动 IP 组(Automatic)**:**最具弹性的动态防护通道**。控制面的定时扫描任务会读取所有节点的访问日志,按照设定的 Expr 规则(例如:“5分钟内请求 `/api/login` 接口触发 401 超过 50 次”)进行聚合分析,一旦匹配,自动将该恶意源 IP 写入封禁组,并指定封禁时长。
### 2. 异步差分同步设计 (不触发 Nginx Reload)
在传统的 Nginx WAF 设计中,IP 黑名单的更新通常需要重写配置并 reload。如果恶意 IP 封禁以秒级或分钟级高频触发,频繁 reload 会导致 Nginx 频繁新建 Worker 进程并销毁老进程,导致性能骤降。
OpenFlare 采用 **动态 IP 组异步差分同步设计**:
```text
WAF IP 成员更新 (手动/订阅/自动自动触发)
|
v
Server 更新数据库并计算该 IP 组的全新 MD5 Checksum
|
+----------------------------------------+
| (WebSocket 实时广播) | (心跳兜底比对)
v v
Server 立即向所有 Agent 推送变更组的完整成员 Agent 心跳上报本地所有 IP 组的 Checksum 映射表
| |
| v
| Server 发现 Checksum 不一致,下发变更的 IP 组成员
v |
Agent 接收成员数据,将其以 JSON 形式写入本地磁盘路径:waf_ip_groups.json
|
v (Lua 内存感知)
OpenResty Lua 引擎通过 MD5 校验和秒级感知文件变化并热更新内存,无需 reload 进程
```
通过这一架构,上万个高频变动的动态黑名单 IP 的落地和生效,**全程无需 reload 任何 Nginx 进程**,极大地保护了网关的高并发性能。
---
## 规则组与网站绑定
* **WAF 规则组(Rule Group)**:WAF 过滤政策的最小逻辑集合。一条规则组内可以包含 IP 黑白名单、IP 组引用、地域限制及防 CC 挑战配置。
* **全局规则组(Global)**:当规则组被标记为 `is_global = true` 时,该规则组对节点上托管的**所有网站路由**默认生效。
* **网站绑定绑定(Site Binding)**:网站路由(Proxy Route)可以绑定一个或多个非全局规则组。判定时,会执行 `全局规则组 + 绑定规则组` 的并集逻辑。
---
## 实现方案与高性能缓存
WAF 在 OpenResty 的 `access_by_lua` 阶段被触发,核心由 Lua 文件与本地落地的 JSON 配置构成。
### 1. 物理结构
* `waf_config.json`:包含所有规则组的元数据、国家地域限制、以及网站(Site)与规则组的关联映射。
* `waf_ip_groups.json`:包含所有同步下来的 IP 组与对应的 IP 列表。
* `waf/runtime.lua`:WAF 规则比对的实际运行时引擎。
* `waf/check.lua`:接入层入口,负责包引入与 check() 触发。
### 2. 共享内存字典 (ngx.shared) 高性能缓存设计
在每次 Web 请求进来时都读取磁盘上的 JSON 文件并进行解码,会导致磁盘 I/O 成为严重的性能瓶颈。
OpenFlare 利用 **OpenResty 共享内存字典 (ngx.shared.openflare_waf_config)** 设计了二级缓存机制:
1. **零文件 I/O 路径**:
在 Lua 中,每次执行 `check()` 时,首先利用 `ngx.md5` 瞬间计算本地磁盘 JSON 文件的 MD5 哈希(这一操作几乎为零耗时,因为文件已被操作系统 Page Cache 缓存)。
2. **哈希比对与热加载**:
比对共享内存中存储的缓存哈希键(`_config_hash`)。
* **若哈希未发生变化**:直接从共享内存字典中读取已解码、存在内存中的 Lua Table 配置,整个校验过程完全基于**共享内存操作**,耗时在 **微秒级** 级别。
* **若哈希不一致**:说明 Agent 刚刚落地了新的 WAF 规则或 IP 组,Lua 自动读取磁盘文件并使用 `cjson.decode` 解码,解码后的数据及全新的 MD5 写入共享内存,供后续 Worker 进程无缝读取。
---
## 应用流程与判定判定控制逻辑
当一个 HTTP/HTTPS 请求到达 OpenResty 后,WAF 会在 `access` 阶段按下图所示的漏斗判决链进行逐步匹配拦截:
### 1. WAF 判定流程图
```mermaid
flowchart TD
A[请求进入 access 阶段] --> B[获取当前请求的 Site Name]
B --> C[在共享内存中加载与此 Site 绑定的所有活跃规则组]
C --> D{匹配到 IP 白名单 / 白名单 IP 组?}
D -- 是 (匹配成功) --> E[放行请求 - ALLOW]
D -- 否 --> F{匹配到国家/地区地域白名单?}
F -- 是 (匹配成功) --> E
F -- 否 --> G{匹配到 IP 黑名单 / 黑名单 IP 组?}
G -- 是 (匹配成功) --> H[阻断请求 - BLOCK]
G -- 否 --> I{匹配到国家/地区地域黑名单?}
I -- 是 (匹配成功) --> H
I -- 否 --> J{是否启用了防 CC PoW 验证?}
J -- 是 --> K[转交防 CC 模块处理]
J -- 否 --> L[无安全风险,正常放行]
H --> M[退出并返回规则组配置的自定义状态码与拦截响应体]
```
### 2. 判决步骤细则
1. **白名单前置**:
为了防止误杀以及保障核心回源流量(如搜索引擎蜘蛛、CDN 回源 IP、办公区出口)的顺畅,WAF **优先匹配 IP 白名单与地域白名单**。一旦白名单匹配成功,直接绕过后续的所有黑名单检测和 CC 挑战,立刻放行。
2. **黑名单强力阻断**:
如果在白名单判定中未被捕获,请求将进入黑名单漏斗。一旦请求源 IP 命中 IP 黑名单、命中引用的黑名单 IP 组、或是处于被禁止的国家/地区范围内,Lua 引擎立即将 `ngx.ctx.openflare_waf_blocked` 标记设为 `true`。
3. **输出响应**:
命中黑名单后,Lua 提取匹配到规则组的 `block_status_code`(默认返回 418 / 403)和 `block_response_body`(拦截页面 HTML),通过 `ngx.say()` 输出响应体并执行 `ngx.exit(status)` 平滑退出请求,防止请求继续向后透传。
-211
View File
@@ -1,211 +0,0 @@
# OpenFlare 开发规范
本文档描述 OpenFlare `1.0.0` 正式版之后的开发基线。
超出 [docs/design.md](./design.md) 边界的需求,必须先更新设计文档。
## 1. 技术基线
### 1.1 Server
`openflare_server` 继续作为单体控制面:
* Go 1.24+
* Gin
* GORM
* SQLite / PostgreSQL
* 现有登录体系
### 1.2 Agent
`openflare_agent` 继续作为 Go 单体程序:
* Go 1.23+
* 单二进制
* 节点本地执行
* `openresty_path` 优先
* 无 `openresty_path` 时默认 Docker OpenResty
### 1.3 Frontend
前端基线以 `openflare_server/web` 为准:
* Next.js 15 App Router
* React 19
* TypeScript
* Tailwind CSS 4
* TanStack Query
* React Hook Form + Zod
* Zustand 仅用于轻量客户端状态
前端细则见 [docs/frontend-development-guidelines.md](./frontend-development-guidelines.md)。
## 2. 分层与目录约束
### 2.1 Server
* `controller/`:参数解析、调用 service、返回响应
* `service/`:业务逻辑、校验、事务编排、渲染
* `model/`:模型定义与持久化
* `router/`:路由注册
* `middleware/`:认证、鉴权、限流等横切逻辑
* `common/`:配置、全局状态与初始化入口
* `utils/`:纯工具函数与通用 helper
禁止:
* 在 `controller/` 堆积业务逻辑
* 在 `middleware/` 实现业务流程
* 为简单需求新增平台层抽象
### 2.2 Agent
保持现有模块边界:
* `config`
* `heartbeat`
* `sync`
* `openresty`
* `state`
* `httpclient`
* `protocol`
* `internal/updater`
要求:
* 每个模块职责单一
* 外部命令调用集中封装
* 状态落盘与配置落盘分离
### 2.3 Frontend
前端分层保持:
* `app/`
* `features/`
* `components/`
* `lib/`
* `store/`
* `types/`
要求:
* 页面路由与布局放在 `app/`
* API 请求统一收敛到 `lib/api/`
* 业务逻辑优先放在 `features/`
## 3. 数据模型规范
当前有效实体:
* `proxy_routes`
* `config_versions`
* `nodes`
* `node_system_profiles`
* `apply_logs`
* `tls_certificates`
* `managed_domains`
* `node_request_reports`
* `node_access_logs`
* `node_metric_snapshots`
* `traffic_analytics_rollups`
* `node_health_events`
* `options`
通用约束:
* 不新增平台化对象,除非设计文档明确要求
* `proxy_routes` 维持一条域名对应一条规则;规则内允许保存一个或多个上游地址用于负载均衡,但不引入独立 `origin_pool`
* `proxy_routes` 的上游统一使用 named `upstream` + keepalive;单上游如带 base path 或 query,应在 `proxy_pass` 上补回 URI,多上游仅允许纯 `scheme://host[:port]`
* `proxy_routes.origin_host` 为可选字段,仅用于覆盖回源 `Host` 请求头,不引入新的平台化对象
* `config_versions` 必须保存完整快照与渲染结果
* 全局同时只能有一个激活版本
* 回滚通过重新激活旧版本实现
* `nodes` 只保留控制面状态与低频摘要
* 观测数据必须按节点与时间窗口关联
* 快照与聚合结果采用追加式模型,不覆盖历史
* 原始访问明细必须有受控保留策略
## 4. API 与鉴权规范
### 4.1 API
* 管理端与 Agent API 统一使用 JSON
* 成功与失败都必须返回清晰 `message`
* Agent API 固定放在 `/api/agent/*`
* 总览与节点详情优先使用专用聚合接口
* 管理端变更类接口统一使用 `POST`;只读接口使用 `GET`
统一响应结构:
```json
{
"success": true,
"message": "",
"data": {}
}
```
### 4.2 鉴权
管理端:
* 继续复用现有登录、角色与 Session
Agent:
* 正式请求统一使用节点专属 `agent_token`
* 首次接入可使用全局 `discovery_token`
* 请求头统一使用 `X-Agent-Token`
禁止:
* 暴露远程 shell 或任意命令执行入口
* 在日志中打印完整 Token
* 允许绕过占位符约束保存不可渲染的主配置模板
## 5. 发布与运行规范
发布逻辑必须保持以下事实:
* 发布时读取全部启用的 `proxy_routes`
* 同时读取 OpenResty 主配置参数、反代性能参数与缓存参数
* 生成完整 OpenResty 配置
* 计算 `checksum`
* 写入 `config_versions`
* 通过切换 `is_active` 激活版本
版本约束:
* 版本号格式固定为 `YYYYMMDD-NNN`
* 不在线修改历史版本
* 不做按节点分组的差异化版本
* 预览与 diff 是只读能力,不产生发布记录
Agent 必须满足:
* 启动后读取或生成本地 `node_id`
* 周期性心跳与同步
* 常规同步优先依据 heartbeat 返回的版本摘要判断
* 发现新版本时先备份旧文件
* 写入主配置、路由配置与必要证书文件
* 写入新配置后以运行态恢复为目标执行激活,Docker 模式优先重建容器并确认容器保持运行
* 新配置激活失败时必须先尝试用目标配置恢复运行,再回滚到旧配置并重新拉起 OpenResty
* 回滚后 OpenResty 恢复正常时上报警告;回滚后仍无法恢复运行时上报失败
* 某个目标 `version + checksum` 一旦应用失败并回退,Agent 必须在本地状态中阻断该目标的重复应用;只有远端激活版本或 checksum 发生变化时,才允许再次尝试
## 6. 测试与交付要求
* 关键业务逻辑必须有单元测试或等效回归测试
* Agent 主链路修改必须验证同步、应用与回滚
* 前端页面至少覆盖加载态、空态、错误态与成功反馈
* Go 版本调整时,同步检查 `go.mod`、Dockerfile 与 CI 工作流
## 7. 文档维护要求
当以下内容变化时,必须同步更新对应文档:
* 产品范围或系统边界变化:更新 `docs/design.md`
* 开发约束、接口约定、测试基线变化:更新本文档
* 前端工程约束变化:更新 `docs/frontend-development-guidelines.md`
* 配置项或部署方式变化:更新 `docs/app-config.md`、`docs/deployment.md` 与 `README.md`
-50
View File
@@ -1,50 +0,0 @@
# OpenFlare 开发计划
## 1. 当前结论
* 第一版至第六版的主线能力已经全部完成
* `1.0.0` 是当前正式基线
* 已完成阶段的过程性任务以代码、测试与 Git 历史为准
* 新工作优先以缺陷修复、可维护性改进、文档与测试补强为主
## 2. 当前优先级
当前开发应优先关注:
1. 稳定性
2. 升级与回滚链路可靠性
3. 文档准确性
4. 测试覆盖补强
5. 在既有边界内的小步迭代
## 3. 变更准入原则
新需求进入实现前,按以下顺序判断:
1. 是否符合 [docs/design.md](./design.md) 的产品边界
2. 是否符合 [docs/development-guidelines.md](./development-guidelines.md) 与前端规范
3. 是否会破坏现有发布、同步、回滚或升级主链路
4. 是否需要同步更新部署、配置或 README 文档
如果答案包含“超出边界”或“引入新基础设施”,先修改设计文档,再开始实现。
## 4. 当前验收标准
任何合入正式基线的改动,至少应满足:
* 不破坏 Agent 心跳、同步、发布与回滚主链路
* 不破坏现有 OpenResty 主配置托管模型
* 不降低总览、节点详情与访问分析的既有可用性
* 有与风险相称的测试或联调验证
* 文档与代码保持一致
## 5. 后续维护方式
后续规划不再按“大版本阶段文档”维护,而采用以下方式:
* 产品边界变动:更新 `docs/design.md`
* 工程约束变动:更新 `docs/development-guidelines.md`
* 前端工程变动:更新前端相关规范文档
* 部署与配置变动:更新 `README.md`、`docs/deployment.md`、`docs/app-config.md`
如果未来出现明确的新阶段目标,再单独新增专项计划文档;不要把已完成的历史计划继续堆回本文件。
+119
View File
@@ -0,0 +1,119 @@
import { defineAdditionalConfig, type DefaultTheme } from 'vitepress'
export default defineAdditionalConfig({
description:
'OpenFlare is a lightweight, self-hosted OpenResty control plane for managing reverse proxy rules, configuration publishing, node synchronization, TLS certificates, and basic observability.',
themeConfig: {
nav: nav(),
sidebar: {
'/en/guide/': { base: '/en/guide/', items: sidebarGuide() },
'/en/reference/': { base: '/en/reference/', items: sidebarReference() },
'/en/design/': { base: '/en/design/', items: sidebarDesign() }
},
editLink: {
pattern: 'https://github.com/Rain-kl/OpenFlare/edit/main/docs/:path',
text: 'Edit this page on GitHub'
},
footer: {
message: 'Released under the Apache License 2.0',
copyright: 'Copyright © OpenFlare contributors'
},
docFooter: {
prev: 'Previous Page',
next: 'Next Page'
},
outline: {
label: 'On this page'
},
lastUpdated: {
text: 'Last updated at'
},
notFound: {
title: 'Page Not Found',
quote: 'This document does not have a corresponding page yet.',
linkLabel: 'Go to Home',
linkText: 'Back to OpenFlare Docs'
},
langMenuLabel: 'Language',
returnToTopLabel: 'Back to top',
sidebarMenuLabel: 'Menu',
darkModeSwitchLabel: 'Theme',
lightModeSwitchTitle: 'Switch to light theme',
darkModeSwitchTitle: 'Switch to dark theme',
skipToContentLabel: 'Skip to content'
}
})
function nav(): DefaultTheme.NavItem[] {
return [
{ text: 'Guide', link: '/en/guide/', activeMatch: '/en/guide/' },
{ text: 'Reference', link: '/en/reference/', activeMatch: '/en/reference/' },
{ text: 'Design', link: '/en/design/', activeMatch: '/en/design/' }
]
}
function sidebarGuide(): DefaultTheme.SidebarItem[] {
return [
{
text: 'Guide',
items: [
{ text: 'Overview', link: '' },
{ text: 'Quick Start', link: 'quick-start' },
{ text: 'Basic Usage', link: 'usage' },
{ text: 'Tunnel & Intranet Penetration', link: 'tunnel-usage' },
{ text: 'WAF Security Protection', link: 'waf-usage' },
{ text: 'WAF Auto IP Group Expressions', link: 'waf-ip-group-expr' },
{ text: 'SSO Login Configuration', link: 'sso' },
{ text: 'Publish First Configuration', link: 'first-site' },
{ text: 'Troubleshooting', link: 'troubleshooting' },
{ text: 'Credits', link: 'credits' }
]
}
]
}
function sidebarReference(): DefaultTheme.SidebarItem[] {
return [
{
text: 'Reference',
items: [
{ text: 'Overview', link: '' },
{ text: 'System Architecture', link: '../design/architecture' },
{ text: 'Launch Server', link: '../deployment/server' },
{ text: 'Access Agent', link: '../deployment/agent' },
{ text: 'Deployment Guide', link: '../deployment/deployment' },
{ text: 'Deploy Relay (Tunnel)', link: '../deployment/relay' },
{ text: 'Deploy OpenFlared', link: '../deployment/openflared' },
{ text: 'Upgrade & Maintenance', link: '../deployment/upgrade' },
{ text: 'Configuration Options', link: 'configuration' },
{ text: 'CLI Commands', link: 'cli' },
{ text: 'API Conventions', link: 'api' }
]
}
]
}
function sidebarDesign(): DefaultTheme.SidebarItem[] {
return [
{
text: 'Design',
items: [
{ text: 'Product Boundaries', link: '' },
{ text: 'System Architecture', link: 'architecture' },
{ text: 'Agent & Publish Model', link: 'agent-design' },
{ text: 'Tunnel & Intranet Penetration', link: 'tunnel-design' },
{ text: 'WAF Design', link: 'waf-design' },
{ text: 'Repository Structure', link: 'repository' }
]
}
]
}
+176
View File
@@ -0,0 +1,176 @@
# Access Agent
You will learn: The responsibilities of the Agent, the difference between the two access Tokens, installation script parameters, `agent.json` settings, and how to verify that the node has successfully connected.
The OpenFlare Agent runs on the proxy node. It does not receive arbitrary remote shell commands; instead, it pulls the configuration version published by the control plane via the Agent API, writes files for OpenResty locally, executes configuration validation, reloads, and attempts to roll back to a working configuration if it fails.
## Connection Credentials
| Method | Applicable Scenario |
| --- | --- |
| `discovery_token` | Automatically registers a node for the first time, which the Server exchanges for a node-specific credential |
| `agent_token` | Node has already been created/allocated in the management console, directly uses this node-specific credential |
At least one of `agent_token` or `discovery_token` must be configured.
### Credential Retrieval Path
- **`discovery_token` (Auto Registration Token)**: Log into the management console, navigate to "System Settings" -> "Auto Registration", where you can generate, view, and copy the global auto-registration credential.
- **`agent_token` (Node Specific Token)**: Log into the management console, navigate to "Node Management" -> "Add Node", fill in basic node information, save, and copy the node-specific access Token in the node details.
## One-Click Installation
Using the `discovery_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--discovery-token YOUR_DISCOVERY_TOKEN
```
Using the node-specific `agent_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
The installation script downloads the latest Agent, writes to `/opt/openflare-agent` by default, generates `agent.json`, and registers `openflare-agent.service` on Linux + systemd environments.
Supported arguments:
| Argument | Description | Default Value |
| --- | --- | --- |
| `--server-url` | Server address (required) | |
| `--discovery-token` | One-time auto-registration Token | |
| `--agent-token` | Node-specific Token | |
| `--install-dir` | Target installation directory | `/opt/openflare-agent` |
| `--openresty-path` | Path to the OpenResty binary; automatically detects `openresty` if unspecified | |
| `--repo` | GitHub repository to download from | `Rain-kl/OpenFlare` |
| `--no-service` | Do not register systemd service | |
## Configuration File
Default configuration file path:
```text
/opt/openflare-agent/agent.json
```
Example local configuration:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "./data",
"openresty_path": "openresty",
"openresty_observability_port": 18081,
"observability_replay_minutes": 15,
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
Example customized OpenResty paths configuration:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "/var/lib/openflare-agent",
"openresty_path": "/usr/local/openresty/nginx/sbin/openresty",
"main_config_path": "/var/lib/openflare-agent/etc/nginx/nginx.conf",
"route_config_path": "/var/lib/openflare-agent/etc/nginx/conf.d/openflare_routes.conf",
"access_log_path": "/var/lib/openflare-agent/var/log/openflare/access.log",
"cert_dir": "/var/lib/openflare-agent/etc/nginx/certs",
"lua_dir": "/var/lib/openflare-agent/etc/nginx/lua",
"runtime_config_dir": "/var/lib/openflare-agent/etc/openflare",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
If `openresty_path` is not configured, the Agent calls `openresty` by default. For the full fields, see [Configurations Reference](../reference/configuration.md#agent-configurations-fields).
## Running in Docker
For Docker deployments, run the Agent image containing built-in OpenResty directly:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443 \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
ghcr.io/rain-kl/openflare-agent:latest
```
## Start & Validate
In a systemd environment:
```bash
systemctl start openflare-agent
systemctl status openflare-agent
journalctl -u openflare-agent -f
```
Manual execution:
```bash
/opt/openflare-agent/openflare-agent -config /opt/openflare-agent/agent.json
```
Running from source:
```bash
cd openflare_agent
export LOG_LEVEL='info'
go run ./cmd/agent -config /path/to/agent.json
```
Running compiled binary:
```bash
cd openflare_agent
go build -o openflare-agent ./cmd/agent
export LOG_LEVEL='info'
./openflare-agent -config /path/to/agent.json
```
Confirm in the management console:
| Position | Expected Result |
| --- | --- |
| Node List | Node status is online |
| Node Details | Heartbeat, current version, and basic resource metrics display correctly |
| Apply Logs | Application result displays after publishing |
## Uninstall
To completely uninstall the Agent and wipe local data:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash
```
Supported arguments:
| Argument | Description | Default Value |
| --- | --- | --- |
| `--install-dir` | Installation directory | `/opt/openflare-agent` |
| `--service-name` | systemd service name | `openflare-agent` |
The uninstallation script only removes the Agent service, processes, and installation directory; it does not uninstall OpenResty from the host.
## Common Questions
| Symptom | Actions |
| --- | --- |
| `agent_token and discovery_token cannot both be empty` | Check if at least one Token is configured in `agent.json` |
| Node stays offline | Run `curl -I http://your-server:3000` on the Agent node to verify that the Server is reachable |
| OpenResty is not running | Review `journalctl -u openflare-agent`, checking that `openresty_path` is executable and ports 80/443 are not bound |
| Repeated application failures after publishing | The Agent blocks repeated sync attempts of the same failing `version + checksum`; fix the configuration and republish, or activate an older version to roll back |
+287
View File
@@ -0,0 +1,287 @@
# Deployment Guide
You will learn: The recommended deployment strategies for OpenFlare, the system requirements for Server and Agent, how to run from source, integration steps, upgrades, and uninstallation entrypoints.
In production environments, we highly recommend using PostgreSQL as the Server database and explicitly configuring `SESSION_SECRET` for the Server. The recommended Agent deployment method is Docker (which runs the Agent image containing built-in OpenResty); host systemd service installation via script and manual local run are also supported.
## Deployment Topology
### Standard Reverse Proxy Traffic Path
```text
Browser
|
v
OpenFlare Server :3000
|
| Agent API / heartbeat / config pull
v
OpenFlare Agent
|
v
OpenResty binary
|
v
Origin service
```
### Intranet Penetration Traffic Path
```text
Browser
|
v
OpenResty (Agent, WAF/HTTPS Termination) <-- TunnelRelay Node
|
| proxy_pass (127.0.0.1:{vhost_port})
v
OpenFlareRelay (frps process) <-- TunnelRelay Node
|
| frp tunnel protocol
v
OpenFlared (frpc client) <-- Intranet Server
|
v
Internal Service (192.168.x.x)
```
## Prerequisites
Server:
| Item | Requirement |
| --- | --- |
| Go | `1.25+`, required only when running from source |
| Node.js | `18+`, required only when building the admin frontend from source |
| Database | Writable SQLite parent directory, or a reachable PostgreSQL instance |
| Port | Listens on port `3000` by default |
Agent:
| Item | Requirement |
| --- | --- |
| System | The installation script supports Linux and macOS; the systemd service is created only on Linux + systemd environments |
| Architecture | `amd64` or `arm64` |
| OpenResty | Required to have the `openresty` executable when deploying locally, or specify its path via `--openresty-path` |
| Docker | Required only when deploying the Agent via Docker image |
| Network | The Agent node must be able to reach the Server address |
| GeoIP | WAF regional rules rely on the Agent's local MaxMind mmdb; the Agent initializes a built-in library on startup and updates it periodically |
### Hardware Allocation Recommendations
| Component | Minimum Allocation | Recommended Allocation | Note |
| --- | --- | --- | --- |
| **Server Control Plane** | 1 Core CPU / 1 GB RAM / 10 GB Disk | 2 Cores CPU / 4 GB RAM / 50 GB+ Disk | Expand disk allocation according to log retention windows and concurrency. |
| **Agent Data Plane** | 1 Core CPU / 512 MB RAM / 2 GB Disk | 2 Cores CPU / 2 GB RAM / 10 GB+ Disk | Expand according to concurrent reverse proxy connections and WAF workloads. |
| **Relay Node** | 1 Core CPU / 1 GB RAM / 5 GB Disk | 2 Cores CPU / 2 GB RAM / 20 GB Disk | frps throughput is primarily bounded by CPU processing capacity and bandwidth. |
| **OpenFlared Client** | 1 Core CPU / 256 MB RAM / 1 GB Disk | 1 Core CPU / 512 MB RAM / 5 GB Disk | Runs inside the intranet; utilizes minimal CPU/RAM, optimize for network throughput. |
## Docker Compose Deployment for Server
Create a `docker-compose.yml` file:
```yaml
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
interval: 10s
timeout: 5s
retries: 5
openflare:
image: ghcr.io/rain-kl/openflare:latest
container_name: openflare
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-a-long-random-string
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
volumes:
- openflare-data:/data
volumes:
postgres-data:
openflare-data:
```
Start the Server:
```bash
docker compose up -d
docker compose ps
docker compose logs -f openflare
```
Access `http://localhost:3000` for the first time, using the default credentials `root` / `123456`. Please change the default password immediately after logging in.
## Start Server from Source
First, build the admin frontend:
```bash
cd openflare_server/web
corepack enable
pnpm install
pnpm build
```
Then, launch the Server:
```bash
cd openflare_server
export SESSION_SECRET='replace-with-a-long-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
# Optional: Prefer PostgreSQL by setting DSN
# export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
go run .
```
By default, the Server listens on port `3000`. You can also specify it explicitly:
```bash
go run . --port 3000 --log-dir ./logs
```
## Running Agent in Docker (Recommended)
Docker is the recommended deployment method for the Agent. Running the Agent image directly launches the Agent controller alongside the built-in OpenResty binary. If `node_ip` is left blank, the Agent automatically resolves its outbound public IP via third-party APIs, avoiding registering the Docker bridge address as the node IP.
Mounting the configuration file:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443 \
-v openflare-agent-data:/data \
-v ./agent.json:/etc/openflare/agent.json:ro \
ghcr.io/rain-kl/openflare-agent:latest
```
Using environment variables:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443 \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
ghcr.io/rain-kl/openflare-agent:latest
```
## Agent Connection via Installation Script
Apart from Docker, you can deploy the Agent directly on a Linux/macOS host using the installation script.
Auto-register using `discovery_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--discovery-token YOUR_DISCOVERY_TOKEN
```
Connect using node-specific `agent_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
Installation script arguments:
| Argument | Description | Default Value |
| --- | --- | --- |
| `--server-url` | Server address (required) | |
| `--discovery-token` | Auto-registration Token; mutually exclusive with `--agent-token` | |
| `--agent-token` | Node-specific Token; mutually exclusive with `--discovery-token` | |
| `--install-dir` | Target installation directory | `/opt/openflare-agent` |
| `--openresty-path` | Path to the OpenResty binary; automatically detects `openresty` if unspecified | |
| `--repo` | GitHub repository to download from | `Rain-kl/OpenFlare` |
| `--no-service` | Do not register systemd service | |
Confirm service status:
```bash
systemctl status openflare-agent
journalctl -u openflare-agent -f
```
## Running the Agent Manually
Running from source:
```bash
cd openflare_agent
export LOG_LEVEL='info'
go run ./cmd/agent -config /path/to/agent.json
```
Running compiled binary:
```bash
cd openflare_agent
go build -o openflare-agent ./cmd/agent
export LOG_LEVEL='info'
./openflare-agent -config /path/to/agent.json
```
Minimal `agent.json` example:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "./data",
"openresty_path": "openresty",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
If `openresty_path` is left blank, the Agent calls `openresty` by default.
By default, the Agent attempts to upgrade the HTTP heartbeat connection to WebSocket once successfully registered. Once upgraded, configuration activations on the Server notify the Agent instantly; if WebSocket disconnects or fails to establish, the Agent gracefully falls back to HTTP polling.
WAF geographical filtering depends on the local `GeoLite2-Country.mmdb`. The Agent automatically writes the built-in database to `data_dir/etc/openflare/GeoLite2-Country.mmdb` on startup and checks for periodic updates. Muted warnings are logged if updates fail, having no impact on Nginx configuration sync or reloads.
## Upgrades & Uninstallation
Server:
* Root users can check and trigger Server upgrades in the top header of the management console.
* To deploy preview releases, manually check the GitHub Releases page.
* You can also trigger upgrades by uploading the compiled Server binary in the console.
Agent:
* By default, the Agent automatically upgrades following stable releases.
* Agent self-updates require the GitHub Release to contain the compiled binary and a matching `.sha256` checksum file; updates are blocked if the downloaded binary fails the SHA-256 validation.
* You can re-execute the installation script to redeploy or force-update the Agent.
* Upgrading to preview releases requires a manual trigger.
Uninstalling the Agent:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash
```
The uninstallation script stops the Agent process, removes the systemd service unit, and wipes the installation directory, without uninstalling OpenResty from the host.
+24
View File
@@ -0,0 +1,24 @@
# Deployment & Upgrade
This section provides detailed deployment guides, configuration instructions, and upgrade maintenance procedures for the OpenFlare Server, Agent, Relay, and the OpenFlared client.
## Content Navigation
### Quick Start
* **[Quick Start](../guide/quick-start.md)**: Start the Server and your first Agent in under 5 minutes using Docker Compose (recommended for new users).
### Server Deployment
* **[Launch Server](./server.md)**: Learn how to build the frontend from source, start the Server, and choose between SQLite or PostgreSQL.
### Agent Deployment
* **[Deploy Agent](./agent.md)**: Explore Agent connection methods, Docker deployment, host script installation, config files, and troubleshooting.
### Tunnel Intranet Penetration Deployment
* **[Deploy Relay](./relay.md)**: View config descriptions, Docker deployment, and host runtime guides for TunnelRelay nodes.
* **[Deploy OpenFlared](./openflared.md)**: Access config descriptions, Docker runtime, and auto-sync mechanisms for the intranet client.
### Upgrade & Maintenance
* **[Upgrade & Maintenance](./upgrade.md)**: Discover upgrading procedures for Server/Agent, data retention rules, and validation commands.
### Reference Manuals
* **[Deployment Guide](./deployment.md)**: Browse deployment topologies, prerequisites, Docker Compose samples, and multiple deployment strategies.
+119
View File
@@ -0,0 +1,119 @@
# Deploy OpenFlared Client
You will learn: The responsibilities of the OpenFlared client, configuration parameters and environment variables, how to run the client via Docker, and how to deploy the client on an intranet server using the compiled host binary.
**OpenFlared** is a tunnel client deployed in the user's intranet environment (LANs, private VPCs, or other environments that cannot be directly accessed from the public internet). Its core responsibility is to establish communication with the control plane (OpenFlare Server) via the `X-Tunnel-Token` header, automatically spawning and managing one or more **frpc (Fast Reverse Proxy Client)** subprocesses locally to securely and stably tunnel HTTP traffic back to public relay nodes.
---
## Prerequisites
1. **Retrieve Tunnel Token**: Create a new tunnel instance on the "Intranet Penetration" or "Tunnel Management" page in the OpenFlare management console; the system will automatically generate a unique `tunnel_id` and a `tunnel_token` (e.g., `tun-<32hex>`).
2. **Outbound Network Permissions**: The intranet server does not require any inbound public IPs or port mappings, but it must be able to reach the **OpenFlare Server URL** and the corresponding **TunnelRelay node control port (default 7000)** over the outbound network.
3. **Software Dependencies** (Host deployment only):
- You must have an executable `frpc` binary locally (recommended version `v0.61.0+` or the latest stable `v0.69.0`), or specify its path explicitly in the configuration.
---
## Configuration & Environment Variables
`openflared` reads `flared.json` in the working directory by default on startup. Overriding options via environment variables is fully supported.
### Configuration Fields Details
| JSON Field | Environment Variable | Description | Default Value |
| --- | --- | --- | --- |
| `server_url` | `OPENFLARE_SERVER_URL` | OpenFlare Server API base URL | **None (Required)** |
| `tunnel_token` | `OPENFLARE_TUNNEL_TOKEN` | Tunnel client dedicated access Token | **None (Required)** |
| `frpc_path` | `OPENFLARE_FRPC_PATH` | Path to the `frpc` executable binary | `"frpc"` |
| `data_dir` | `OPENFLARE_DATA_DIR` | Directory to store local data and generated `frpc_{relayNodeID}.toml` configs | `"./data"` |
| `state_path` | - | Path to store local state JSON file (saving the last applied version) | `"{data_dir}/flared-state.json"` |
| `heartbeat_interval`| - | Heartbeat reporting interval (ms or Go Duration string) | `10000` (10s) |
| `sync_interval` | - | Tunnel config polling interval (ms or Go Duration string) | `30000` (30s) |
| `request_timeout` | - | HTTP request timeout duration | `10000` (10s) |
---
## Docker Deployment (Recommended)
Docker is the simplest and safest way to run the client inside the intranet. The official `openflared` image embeds the client controller and `frpc v0.69.0` out of the box, requiring no environment setup.
```bash
docker pull ghcr.io/rain-kl/openflared:latest
docker rm -f openflared 2>/dev/null || true
docker run -d --name openflared --restart unless-stopped \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_TUNNEL_TOKEN=YOUR_TUNNEL_TOKEN \
-v openflared-data:/app/data \
ghcr.io/rain-kl/openflared:latest
```
---
## Manual Host Deployment
If you need to run the client directly on a Linux/macOS/Windows host inside the intranet:
### 1. Compile the Binary
```bash
cd openflared
go build -o flared ./cmd/flared
```
### 2. Prepare `flared.json`
Create a `flared.json` configuration file in the same directory as the executable:
```json
{
"server_url": "http://your-server-ip:3000",
"tunnel_token": "your-tunnel-auth-token",
"frpc_path": "/usr/local/bin/frpc",
"data_dir": "./data",
"heartbeat_interval": "10s",
"sync_interval": "30s"
}
```
### 3. Start the Service
```bash
export LOG_LEVEL='info'
./flared -config ./flared.json
```
---
## Start & Validate
### 1. Auto-Sync Workflow
Once started successfully, OpenFlared operates the following workflow:
- **Heartbeat & Config Fetching**: Periodically polls `/api/flared/heartbeat` and `/api/flared/config` endpoints to validate the Token and evaluate configuration versions.
- **File Rendering**: When a new configuration version (or checksum mismatch) is detected, it pulls the complete tunnel routing rules. If multiple Relays are bound, it renders `frpc_{relayNodeID}.toml` configurations in `data_dir` for each Relay.
- **Hot Reload or Restart**: Spawns the corresponding `frpc` subprocesses, or executes `frpc reload` / restart actions when configurations change, ensuring traffic mappings are kept up to date.
- **Process Auto-Recovery**: If a local `frpc` tunnel process exits unexpectedly, the master program automatically restarts it after a 5-second backoff penalty.
### 2. View Logs & Connection Status
```bash
# Docker container logs
docker logs -f openflared
```
If running correctly, the logs will show output similar to:
```text
flared config loaded ...
detected frpc version v0.69.0
flared process started
applying new tunnel config {"version": "...", "checksum": "..."}
frpc process missing, starting {"relay_id": "..."}
```
### 3. Verify in the Management Console
Open the **"Intranet Penetration"** page in the management console:
- Check the online status of the corresponding tunnel; it should display green as **"Online"**.
- You can inspect which relay nodes the tunnel is connected to, and view the detailed routing configurations of the intranet services.
+126
View File
@@ -0,0 +1,126 @@
# Deploy Relay (Tunnel Relay)
You will learn: The responsibilities of a TunnelRelay node, `openflare_relay` configuration parameters and environment variables, how to run the Relay via Docker, and how to build and deploy the Relay from source manually.
In the OpenFlare intranet penetration architecture, the **TunnelRelay node** plays a key role. Unlike standard Edge Nodes, in addition to running the traditional Agent (managing OpenResty for HTTPS/WAF processing), it co-locates the **Relay (frps tunnel manager)** service, responsible for listening to intranet client (OpenFlared) tunnel connections and relaying traffic.
---
## Prerequisites
Before deploying a TunnelRelay node, ensure:
1. **Registered as a TunnelRelay node**: Add a node of type `tunnel_relay` in the OpenFlare management console under "Node Management", and retrieve its node-specific `agent_token` or use the global `discovery_token`.
2. **Network Ports**:
- Ensure `bindPort` (the port frpc clients connect to, default `7000`) is accessible from the public/intranet client networks.
- Ensure `vhostHTTPPort` (the HTTP Vhost port, default `8080`) is free and not bound by other processes, as the Agent routes traffic to frps on this port.
3. **Software Dependencies** (Host deployment only):
- You must have an executable `frps` binary locally (recommended version `v0.61.0+` or the latest stable `v0.69.0`), or specify its path explicitly in the configuration.
---
## Configuration & Environment Variables
`openflare_relay` reads `relay.json` in the working directory by default on startup. Overriding options via environment variables is fully supported.
### Configuration Fields Details
| JSON Field | Environment Variable | Description | Default Value |
| --- | --- | --- | --- |
| `server_url` | `OPENFLARE_SERVER_URL` | OpenFlare Server API base URL | **None (Required)** |
| `agent_token` | `OPENFLARE_AGENT_TOKEN` | Node-specific Token | Mutually exclusive with below |
| `discovery_token` | `OPENFLARE_DISCOVERY_TOKEN` | One-time auto-registration Token | Mutually exclusive with above |
| `node_name` | `OPENFLARE_NODE_NAME` | Custom name for the node | Hostname by default |
| `node_ip` | `OPENFLARE_NODE_IP` | Outbound/listening IP of the node | Automatically detects real outbound IP |
| `frps_path` | `OPENFLARE_FRPS_PATH` | Path to the `frps` executable binary | `"frps"` |
| `data_dir` | `OPENFLARE_DATA_DIR` | Directory to store local data and generated `frps.toml` | `"./data"` |
| `state_path` | - | Path to store local state JSON file | `"{data_dir}/relay-state.json"` |
| `heartbeat_interval`| - | Heartbeat interval (integer ms or Go Duration string) | `10000` (10s) |
| `request_timeout` | - | HTTP request timeout duration | `10000` (10s) |
---
## Docker Deployment (Recommended)
Docker is the most convenient way to deploy a TunnelRelay node. The official Docker image embeds the `openflare-relay` controller and `frps v0.69.0` out of the box.
```bash
docker pull ghcr.io/rain-kl/openflare-relay:latest
docker rm -f openflare-relay 2>/dev/null || true
docker run -d --name openflare-relay --restart unless-stopped \
-p 7000:7000 \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
-v openflare-relay-data:/var/lib/openflare-relay \
ghcr.io/rain-kl/openflare-relay:latest
```
> [!TIP]
> The `-p 7000:7000` option maps the port `frpc` clients connect to. If a custom `relay_bind_port` is configured in the management console, change this port mapping on the host accordingly.
---
## Manual Host Deployment
If you prefer to run the Relay directly on a physical host or VM:
### 1. Compile the Binary
```bash
cd openflare_relay
go build -o openflare-relay ./cmd/relay
```
### 2. Prepare `relay.json`
Create a `relay.json` configuration file in the same directory as the executable:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "your-relay-node-agent-token",
"frps_path": "/usr/local/bin/frps",
"data_dir": "./data",
"heartbeat_interval": "10s",
"request_timeout": "10s"
}
```
### 3. Start the Service
```bash
export LOG_LEVEL='info'
./openflare-relay -config ./relay.json
```
---
## Start & Validate
### 1. View Process Logs
```bash
# Docker container logs
docker logs -f openflare-relay
```
If managed via systemd on Linux, execute:
```bash
journalctl -u openflare-relay -f
```
### 2. Verify Runtime Status
Upon starting successfully, the Relay operates as follows:
- Sends HTTP heartbeats to register and go online with the control plane.
- Retrieves the active frps baseline settings (including `bindPort`, `vhostHTTPPort`, and the auto-generated `auth_token`).
- Automatically renders the `data/frps.toml` configuration locally.
- Spawns the subprocess `frps -c data/frps.toml`.
- If the `frps` process crashes, the Relay automatically restarts it after 2 seconds.
### 3. Verify in the Management Console
Log into the management console and navigate to **"Node Management"** to verify:
- The TunnelRelay node status is marked as **"Online"**.
- The Node Type is correctly displayed as **Relay Node** and the frps status displays as **Healthy**.
+170
View File
@@ -0,0 +1,170 @@
# Launch Server
You will learn: How to build the admin frontend from source, start the OpenFlare Server, choose between SQLite or PostgreSQL, and access Swagger.
OpenFlare Server is a Gin + GORM monolithic control plane, responsible for managing the Admin UI, Admin API, Agent API, configuration rendering, version publishing, data storage, and aggregated queries.
## Prerequisites
| Item | Requirement |
| --- | --- |
| Go | `1.25+` |
| Node.js | `18+` |
| pnpm | Recommended enabling via `corepack enable` |
| Database | SQLite parent directory must be writable, or a reachable PostgreSQL instance |
In production environments, we highly recommend explicitly configuring `SESSION_SECRET` and prioritizing PostgreSQL.
## Build the Admin Frontend
The Go Server hosts static assets located in `openflare_server/web/build`. Before starting the Server from source, build the frontend:
```bash
cd openflare_server/web
corepack enable
pnpm install
pnpm build
```
Common frontend quality checks:
```bash
pnpm lint
pnpm typecheck
pnpm test
```
## Start with SQLite
```bash
cd openflare_server
export SESSION_SECRET='replace-with-a-long-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
go run .
```
By default, the Server listens on port `3000`. Access it at:
```text
http://localhost:3000
```
## Start with PostgreSQL
```bash
cd openflare_server
export SESSION_SECRET='replace-with-a-long-random-string'
export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
export LOG_LEVEL='info'
go run .
```
If `DSN` is set, it takes precedence over SQLite. When both `DSN` and the legacy `SQL_DSN` exist, `DSN` is prioritized.
If the target PostgreSQL database is empty and a local SQLite database exists at `SQLITE_PATH`, the Server automatically migrates the SQLite data into PostgreSQL during startup, outputting the migration progress in the logs.
## Start with Docker
Deploying with Docker avoids the hassle of setting up local Go and Node.js environments. OpenFlare provides official Dockerfiles and Compose configurations to support independent container startups and multi-service orchestrations.
### 1. Quick Start via Docker Run (SQLite Example)
Ensure that a local directory for persisting databases and logs has been created. Run the following command to start the Server:
```bash
# Create local mount directory
mkdir -p ./openflare-data
# Start the container
docker run -d \
--name openflare-server \
-p 3000:3000 \
-v $(pwd)/openflare-data:/data \
-e SESSION_SECRET='replace-with-a-long-random-string' \
-e SQLITE_PATH='/data/openflare.db' \
-e GIN_MODE='release' \
-e LOG_LEVEL='info' \
ghcr.io/rain-kl/openflare:latest
```
Startup parameters:
* **`-p 3000:3000`**: Maps port `3000` on the host to port `3000` inside the container.
* **`-v $(pwd)/openflare-data:/data`**: Mounts the local directory to `/data` in the container, ensuring that the SQLite database `openflare.db` is not lost when restarting or rebuilding the container.
* **`SESSION_SECRET`**: The session signing hash key (required).
---
### 2. One-click Startup via Docker Compose (Integrated PostgreSQL)
We recommend using Docker Compose in production environments to orchestrate an independent PostgreSQL database and establish high-availability relationships.
Create a `docker-compose.yml` file:
```yaml
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
volumes:
- ./postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
interval: 10s
timeout: 5s
retries: 5
openflare:
image: ghcr.io/rain-kl/openflare:latest
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-random-string
SQLITE_PATH: /data/openflare.db
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
volumes:
- ./openflare-data:/data
```
Start the services:
```bash
docker compose up -d
```
Compose configuration options:
* **`depends_on` and `healthcheck`**: Uses PostgreSQL's health check (`pg_isready`) to ensure that the database is fully initialized and ready before launching the OpenFlare Server, preventing panics from failed database connection attempts on first launch.
* **Separated Data Volume Mounts**: PostgreSQL data is mounted under `./postgres-data`, and OpenFlare data and backups are mounted under `./openflare-data`, making backups and maintenance simple.
## CLI Arguments
```bash
go run . --port 3000 --log-dir ./logs
```
| Argument | Description | Default Value |
| --- | --- | --- |
| `--port` | The port the Server listens to | `3000` |
| `--log-dir` | The directory to write logs to | Empty, outputs to stdout |
| `--version` | Outputs version and exits | `false` |
| `--help` | Outputs help and exits | `false` |
## First Login
Default credentials:
| Username | Password |
| --- | --- |
| `root` | `123456` |
Please change the default password immediately after your first login.
+52
View File
@@ -0,0 +1,52 @@
# Upgrade & Maintenance
You will learn: How to upgrade the Server and the Agent, how to clean up observability data, and which validation commands to execute before and after maintenance.
Before upgrading, verify the currently active version, the most recent Agent application results, and your database backup strategy. In production environments, never trigger upgrades while a configuration is being published, during large-scale Agent reconnections, or while database migrations are in progress.
## Server Upgrade
Root users can check and trigger stable Server upgrades in the top header of the management console. You can also trigger upgrades by uploading the compiled Server binary in the console.
To deploy preview releases, manually check the GitHub Releases page. We highly recommend prioritizing stable releases in production environments.
Verify after upgrading:
```bash
docker compose ps
docker compose logs -n 100 openflare
```
If deployed from source, restart the Server and verify that no database migration or startup errors appear in the logs.
## Agent Upgrade
Node Agents automatically update following stable releases by default. Upgrading to preview releases requires a manual trigger.
You can re-execute the installation script to redeploy or force-update the Agent:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
Note: Re-executing the current installation script wipes the entire installation directory, including the existing `agent.json`, local states, cached databases, and downloaded binaries. Ensure you have the node Token handy before executing the script.
Verify after upgrading:
```bash
systemctl status openflare-agent
journalctl -u openflare-agent -n 100 --no-pager
```
## Data Maintenance
The management console's Settings page maintains options for automatic cleanup of observability data:
| Parameter | Description |
| --- | --- |
| `DatabaseAutoCleanupEnabled` | Toggles daily automatic cleanup |
| `DatabaseAutoCleanupRetentionDays` | Data retention duration in days, minimum 1 day |
When enabled, the Server cleans up access logs, metrics snapshots, and request reports at 3:00 AM daily.
+181
View File
@@ -0,0 +1,181 @@
# Agent Design Document
You will learn: Agent design principles, core functional modules, interaction links with the Server, and how configuration applications are secured and made reliable through immutable version models and the three-stage disaster recovery rollback mechanism.
---
## Requirements Analysis
In distributed reverse proxy and edge security gateway scenarios, the Agent plays a central role in connecting the control plane (Server) and the data plane (OpenResty). Since the Agent runs on the user's actual node server, its design must adhere to the following core security and high-availability requirements:
1. **Active Pull (Pull Model) instead of Push**: The Server does not hold the SSH keys of the nodes, nor does it actively initiate inbound connections to the nodes. All control directives and configuration updates are actively pulled by the Agent via heartbeats or long-lived connections (WebSockets). This eliminates inbound firewall security risks on the node side and prevents control channels from being hijacked.
2. **Minimal Intrusiveness**: The Agent runs as an independent Go binary process. It only interacts with the local OpenResty process through file-based configuration rewriting and signal notifications, without interfering with other system services on the node.
3. **Robust Disaster Recovery & Self-Healing**: Since network jitter, disk exhaustion, or erroneous configurations can easily lead to configuration sync failures, the Agent must possess zero-dependency local rollback and self-healing capabilities, strictly preventing a single configuration error from causing a complete node outage.
4. **Pure Data and State Landing**: The Agent is only responsible for executing file generation and control intentions rendered by the Server. It does not carry complex control plane duties like business logic validation or multi-tenant authorization, ensuring the node side remains highly efficient and lightweight.
---
## Core Capabilities
The Agent is composed of the following core sub-modules, cooperating to manage its complete lifecycle:
| Module Name | Directory | Responsibilities |
| :--- | :--- | :--- |
| **Config Sync** | `sync/` | Pulls full configuration packages, writes files, triggers reloads, and records and reports sync statuses. |
| **Heartbeat** | `heartbeat/` | Periodically reports node health and resource metrics to the Server and retrieves the latest active version summary. |
| **WebSocket** | `wsclient/` | Maintains a persistent connection with the Server, providing sub-second real-time configuration pushes and commands. |
| **OpenResty Control** | `nginx/` | Executes Nginx config validation (`openresty -t`), rewrites, graceful reloads (`reload`), and process auto-start. |
| **Local State Store** | `state/` | Persistently records local applied versions, error logs, and buffers unsent observability metrics. |
| **Self-Updater** | `updater/` | Listens to Server self-update commands, securely pulls new binary versions, and completes in-place upgrades. |
| **Observability** | `observability/` | Collects host CPU/memory/disk and Nginx performance metrics, processes access logs, and uploads them. |
| **GeoIP Maintenance** | `geoipdata/` `geoipupdate/` | Maintains and updates the local GeoIP database periodically to support WAF country-level filtering. |
---
## Interaction Flows with Server
The Agent communicates with the control plane through **Token-based Auto-Registration** and a **Dual-channel Heartbeat/WebSocket** system during its lifecycle.
### 1. Auto-Registration Flow
If the Agent starts with an empty `access_token` in its local `agent.json`, but has a `discovery_token` configured, it triggers the auto-registration flow:
1. The Agent sends a registration request to `/api/agent/register`, carrying a local hardware fingerprint, IP, and hostname.
2. After validating the `discovery_token`, the Server generates a unique `NodeID` and a dedicated `AccessToken` (i.e., `agent_token`) in the database and returns them.
3. The Agent writes the dedicated Token to its local configuration file, clears the one-time `discovery_token`, and uses the `AccessToken` for all subsequent authenticated communications.
### 2. Dual-Channel Heartbeat & Sync Mechanism
* **HTTP Polling (Fallback and Detection)**: The Agent sends POST heartbeat packets at configured `heartbeat_interval` intervals by default. It reports health metrics while retrieving the currently active configuration version summary (Version & Checksum).
* **WebSocket Channel (Real-time Communication)**: Upon a successful HTTP heartbeat, the Agent automatically attempts to upgrade the connection to WebSocket (`/api/agent/ws`).
* Once the WS connection is established, heartbeats and metrics reporting shift entirely to the WS pipeline, reducing network overhead.
* When the Server publishes or activates a new version, it broadcasts a notification to the Agent via WS. The Agent triggers the synchronization flow **immediately** upon receiving the change event, achieving sub-second configuration deployment.
* If the WS connection drops due to network issues, the Agent automatically falls back to HTTP polling and uses an exponential backoff algorithm to attempt rebuilding the WS channel.
### 3. Interaction Sequence Diagram
```mermaid
sequenceDiagram
autonumber
participant Agent as OpenFlare Agent
participant OR as Local OpenResty
participant Server as OpenFlare Server
Note over Agent: First Startup (No AccessToken)
Agent->>Server: 1. Auto-registration request (carrying discovery_token)
Server-->>Agent: 2. Issue NodeID & dedicated AccessToken (agent_token)
Note over Agent: Store Token in local configuration file
rect rgb(240, 248, 255)
Note over Agent, Server: HTTP Fallback & WebSocket Upgrade
Agent->>Server: 3. Send HTTP Heartbeat (report system metrics & health)
Server-->>Agent: 4. Return ActiveConfig summary & AgentSettings
Agent->>Server: 5. Initiate WebSocket upgrade request (/api/agent/ws)
Server-->>Agent: 6. Upgrade successful (persistent bi-directional channel)
end
rect rgb(245, 245, 245)
Note over Agent, Server: Real-time Configuration Publication
Note over Server: Administrator clicks publish config in UI
Server->>Agent: 7. Broadcast active config summary via WS (WSMessageTypeActiveConfig)
Agent->>Server: 8. Request full configuration details (carrying target Version/Checksum)
Server-->>Agent: 9. Return complete configuration snapshot (Nginx configs, certs, WAF rules, etc.)
Note over Agent: Backup old files, write new config to local temp path
Agent->>OR: 10. Execute config syntax validation (openresty -t)
OR-->>Agent: 11. Return validation result (OK)
Agent->>OR: 12. Send graceful reload signal (openresty -s reload)
Agent->>Server: 13. Report application success status (Apply Log & ActiveVersion)
end
```
---
## Control of OpenResty
The Agent implements end-to-end closed-loop control of the data plane OpenResty, including configuration rendering, syntax validation, graceful reloading, and exception state capturing:
### 1. Configuration Layout on Disk
Upon successful sync, the Agent writes configuration files to `/etc/nginx/openflare-lua/` (or the configured `LuaDir`) according to a strict physical structure:
* `nginx.conf`: Main configuration file (replaces absolute path placeholders, configures performance parameters, shared dictionaries, and global server blocks).
* `routes.conf`: Route configuration file (generated by the Agent, containing all website server blocks, certificate paths, cache settings, and rate limit directives).
* `certs/`: Certificate storage directory (files named as `{cert_id}.crt` and `{cert_id}.key`).
* `waf/` and `pow/`: Dedicated Lua runtime scripts required for WAF and CC mitigation.
* `waf_config.json` and `waf_ip_groups.json`: Structured rules and IP databases required by the WAF filtering engine.
### 2. Refined Reload Operations
1. **Backup Current Config**: Before writing new files, the Agent copies the existing configuration files to a `.backup` directory, keeping a complete rollback snapshot.
2. **Write and Replace Placeholders**: Writes the pulled templates, automatically replacing absolute path placeholders (e.g., `__OPENFLARE_LUA_DIR__`) with actual local execution paths.
3. **Syntax Validation**: Calls `openresty -t -c <temp_nginx.conf>` to run a strict syntax test.
4. **Graceful Reload**: If validation passes, the Agent moves the files to the official paths and executes `openresty -s reload`. If OpenResty is not running, it launches the process.
5. **Exception Capture**: If validation or reload fails, the Agent intercepts the standard error output (stderr) and extracts the first 2000 characters of the detailed error log.
---
## Publishing & Config Application Model
OpenFlare discards the fragile mechanism of dynamically patching node configurations, instead using an **immutable configuration version publishing model**.
```text
Edit rules -> Preview / View diff -> Publish -> Generate full configuration version -> Activate version -> Agent pulls -> Local application -> Report result
```
### 1. Core Design Principles
* **Complete Publication**: Every publication compiles all enabled proxy routes, certificates, and global/custom WAF rules at once, generating a complete version package with a unique `checksum`.
* **Version Format**: Uses the `YYYYMMDD-NNN` incremental format, ensuring version histories are intuitive and strictly monotonic.
* **Global Single Active Version**: The system supports only one globally `active` configuration version at any given time. Rollbacks do not require reverse patching; they simply transition an older healthy version to the `active` state, and the Agent pulls and applies it.
### 2. Three-Stage Disaster Recovery & Rollback Mechanism
If the Agent fails to apply a configuration (or reload fails), it automatically triggers the following three-stage self-healing pipeline:
```mermaid
graph TD
A[Config Application Failed] --> B[Stage 1: Attempt Local Backup Recovery]
B -- Backup Exists --> C[Write Local Backup Files]
C --> D[Run openresty -t Validation]
D -- Validation OK --> E[Reload Old Configuration]
D -- Validation Failed --> F[Proceed to Stage 2]
B -- No Backup --> F[Stage 2: Write Built-in Safe Fallback Config]
F --> G[Write fallback nginx.conf: Listen on Port 80 Only]
G --> H[Enable stub_status health checks]
G --> I[Return 503 for all other routes & block errors]
G --> J[Attempt to launch OpenResty to maintain basic survival]
J --> K[Proceed to Stage 3]
E --> L[Report Apply Warning]
K --> M[Block Local Repeated Application of Failed Version]
M --> N[Report Apply Error with detailed logs]
```
1. **Stage 1: Local Backup Rollback**
* The Agent attempts to restore the main configuration, routes, and certificates from the `.backup` directory.
* It runs `openresty -t` validation on the restored backup. If successful, it reloads and reports a `Warning` to the Server (Warning: failed to apply new version, automatically rolled back to the previous healthy version).
2. **Stage 2: Built-in Safe Fallback Runtime**
* If no local backup exists (e.g., first deployment failed) or if the rollback validation fails, the Agent activates the ultimate self-healing mechanism: writing a **built-in safe fallback configuration**.
* **Fallback Configuration Specification**:
* Listens only on port `80`, containing no real user reverse proxy routes.
* The `/openflare/stub_status` endpoint returns a healthy response, while all other requests uniformly return a `503 Service Unavailable` status code with the fixed response body `OpenFlare: No Valid Configuration`.
* It attempts to launch OpenResty with this minimal configuration. This keeps the Nginx process alive, preserving underlying health probes and metric endpoints, preventing containers/pods from being repeatedly killed and restarted by orchestration systems, while keeping sensitive routes secure.
3. **Stage 3: Local Configuration Blocking**
* The Agent records the failing configuration's `version + checksum` in its local state store blacklist.
* Until the control plane activates a new configuration (resulting in a changed `checksum`), the Agent's heartbeat blocks repeated synchronization pulls of this erroneous version, preventing nodes from entering an infinite loop of "heartbeat -> pull failing config -> crash rollback".
### 3. WAF IP Group Asynchronous Runtime Synchronization
To prevent highly volatile IP blacklists from triggering frequent full config publications and Nginx reloads (which still incur minor CPU and connection overhead), WAF IP groups are synchronized via an **asynchronous differential sync design**:
* **Static Publication Snapshot**: The `waf_config.json` generated upon publication only contains the group ID reference mapping (i.e., `ip_whitelist_group_ids` / `ip_blacklist_group_ids`) and does not contain the actual list of IP addresses.
* **Heartbeat Differential Check**: The Agent uploads its locally cached IP groups MD5 checksum map in its heartbeat.
* **Differential Delivery**: The Server compares checksums and only delivers missing or modified IP groups, which are written directly to `waf_ip_groups.json` on the node without reload.
* **WebSocket Real-time Push**: When an administrator updates an IP group, or a threat intelligence subscription successfully pulls, or a security rule triggers a temporary block, the Server immediately broadcasts the IP group update package via WebSocket. The Agent receives and applies it instantly **without Nginx reloads**.
---
## Design Constraints
To protect the security boundary of the data and control plane, Agent development must strictly comply with the following engineering constraints:
1. **Zero-Privilege Command Execution**: The Server is strictly prohibited from sending any arbitrary shell commands or scripts to the Agent (such as exec/eval). All system control operations (such as start, stop, reload, update) must be hardcoded inside the Agent binary.
2. **Strict Token Filtering and Prefix Validation**: Agent requests to the Server must be prefixed with `/api/agent/` and must carry the `X-Agent-Token` header for signature or token verification.
3. **Node Autonomy**: The Agent must support complete offline capabilities. During disconnected periods, the local OpenResty must rely on local configuration copies to keep reverse proxy services running normally.
+223
View File
@@ -0,0 +1,223 @@
# System Architecture
You will learn: The overall architecture of OpenFlare, the boundaries of responsibilities for Server, Agent, OpenResty, and Admin Frontend, and the request flow of a configuration publication from the admin dashboard to activation on a node.
OpenFlare consists of the Server, the Agent, the node-local OpenResty, and the Admin Frontend. The Server is the control plane, the Agent is the only controlled entry point on the node side, and OpenResty serves as the actual data plane. In intranet penetration scenarios, the Relay (frps manager) and OpenFlared (frpc manager) extend the data plane traffic path.
### Standard Reverse Proxy Traffic Path
```text
Browser
|
| Management UI / API
v
OpenFlare Server (Gin + GORM + SQLite/PostgreSQL)
|
| Agent API / heartbeat / config pull
v
OpenFlare Agent
|
| write config / openresty -t / reload / rollback
v
OpenResty binary
|
| reverse proxy
v
Origin
```
### Intranet Penetration Traffic Path
```text
Browser
|
| HTTPS request
v
OpenResty (Agent, TLS/WAF) <-- TunnelRelay Node
|
| proxy_pass http://localhost:vhost_port (Host header preserved)
v
OpenFlareRelay (frps) <-- TunnelRelay Node, co-located with Agent
|
| frp tunnel protocol (HTTP Vhost routing by Host header)
v
OpenFlared (frpc) <-- Intranet Server
|
| HTTP/HTTPS forward
v
Internal Service (192.168.x.x)
```
## Component Responsibilities
| Component | Responsibility |
| --- | --- |
| Server | Admin UI, Admin API, Agent/Relay/Client API, configuration rendering, version publishing, data storage, and aggregated queries. |
| Agent | Registration, heartbeats, synchronization, file writing, validation, reload, rollback on failure, self-updating, and light metrics collection. |
| OpenResty | Receives real traffic, executing WAF, PoW, authentication, and reverse proxying according to the configuration rendered by OpenFlare. |
| OpenFlareRelay | Manages the lifecycle of the frps process, providing tunnel relay services and receiving frps configurations via heartbeat. |
| OpenFlared | Manages frpc processes (can be multiple), connecting to the Relay and forwarding traffic to intranet services. |
| Frontend | Manages pages for website configs, WAF, origins, certificates, nodes, tunnels, versions, users, settings, and observability. |
## Server
`openflare_server` is the single-control-plane monolith:
* Gin provides the HTTP services.
* GORM accesses SQLite or PostgreSQL.
* The existing login system provides Admin Session management.
* Authentication sources support GitHub OAuth and standard OIDC logins with external account binding.
* The Go Server hosts the `openflare_server/web` static build assets.
The Server does not directly SSH to nodes, nor does it modify node files online. It only stores control plane state, generates complete configuration versions, and lets nodes actively pull them via the Agent API.
## Agent
`openflare_agent` is a Go monolithic application:
* Runs as a single binary on the node side.
* Reads or generates local node information on startup.
* Performs periodic heartbeat check-ins to report status and retrieve active version summaries.
* Upon discovering a new version, it pulls the configuration, backs up old files, writes new files, validates them, and reloads.
* Automatically rolls back to restore operations if the application fails.
* Maintains the local WAF GeoIP mmdb, writing the built-in library on startup and updating it periodically based on configuration.
The Agent executes validation, reload, startup, and restart uniformly via the path specified in `openresty_path`; if unconfigured, it defaults to calling `openresty`. During Docker deployments, the Agent image packages OpenResty and follows the same execution control logic.
The node IP is maintained by default through Agent registration and heartbeat reporting; if the administrator locks the node IP, the Server only updates running status, versions, and observability fields, and no longer accepts reports from the Agent to override the locked IP.
## Frontend
`openflare_server/web` is the official Next.js-based frontend:
* Next.js 15 App Router.
* React 19.
* TypeScript.
* Tailwind CSS.
* TanStack Query for server-side state.
The frontend uses static export mode (`output: 'export'`), which is then hosted by the Go Server using `embed.FS`. All API requests must go through `lib/api/` and process the `success/message/data` response structure.
The Server integrates the following security features:
* CORS middleware: Cross-Origin Resource Sharing protection.
* Rate limiting: Global and key API endpoint throttling.
* Session management: Cookie/Redis-based session storage.
## Data & Request Flow
### Management Request Flow
```text
Browser -> Frontend -> /api/* -> controller -> service -> model -> database
```
Admin mutation APIs use `POST`, while read-only APIs use `GET`. Both success and failure responses return a clear `message`.
### Agent Sync Flow
```text
Agent HTTP heartbeat -> Server returns active version summary
Agent detects new version -> Pulls complete configuration details
Agent writes main configuration / route configurations / certificates / Lua resources / WAF runtimes
Agent runs OpenResty validation (openresty -t) and reload
Agent reports application result
```
### Relay Sync Flow
The Relay (OpenFlareRelay process) runs on the TunnelRelay node and shares the same `agent_token` with the Agent:
```text
Relay HTTP heartbeat -> Server returns frps base configuration (bindPort, vhostHTTPPort, auth_token)
Relay generates frps.toml and starts or updates the frps process
Relay periodically reports frps health status and connection statistics
Relay attempts WebSocket upgrade for real-time configuration pushes
```
frps configurations are relatively static (ports, auth token), dispatched via heartbeats, and **not included in the versioned publishing flow**. The Relay must monitor the frps process and auto-recover it on failures. Authentication: `X-Agent-Token` + API path prefix `/api/relay/*`, distinguished by Server via `node_type = tunnel_relay`.
### OpenFlared Sync Flow
OpenFlared (client) runs inside the intranet server, using independent `tunnel_token` authentication:
```text
Client HTTP heartbeat -> Server returns tunnel configuration version summary (version, checksum)
Client detects new version -> Pulls complete tunnel route configuration (relay list + frpc proxy definitions)
Client generates independent frpc.toml configuration files for each Relay
Client starts a new frpc process for new Relays, or hot-reloads (frpc reload) existing ones
Client reports application results (success/failure details)
```
OpenFlared communicates with the Server via `/api/flared/*` using the `X-Tunnel-Token` header. Tunnel route configurations are versioned along with the publishing flow, ensuring all configuration changes are consistently published to both Agents and Clients via a single version number.
**WebSocket Upgrade Flow** (Optional, controlled via `AgentWebsocketUpgradeEnabled`):
When WebSocket upgrade is enabled:
1. The Agent retrieves run configurations and settings via HTTP heartbeat.
2. The Agent attempts to upgrade the connection to `GET /api/agent/ws` (WebSocket).
3. Once the WS connection is established, periodic state reporting and real-time commands are carried over the WebSocket pipeline, minimizing latency.
4. When the Server publishes or activates a version, it immediately broadcasts the active version summary to connected Agents, triggering the sync flow instantly.
5. If the WebSocket disconnects or fails to establish, the Agent automatically falls back to HTTP heartbeats, ensuring high availability.
Through the `OpenRestyWebsocketEnabled` option, WebSocket reverse proxy support can be enabled or disabled at the OpenResty layer.
### Reverse Proxy Flow
```text
Client -> OpenResty server block -> WAF Lua -> named upstream -> Origin
```
Website configurations are the boundaries of reverse proxy aggregation. A single website configuration can bind multiple domains, sharing site-level rate limiting, reverse proxy, and cache settings.
WAF executes in the OpenResty `access_by_lua_file` phase. Rules originate from the `waf_config.json` carried in the currently active version; global rule groups take effect by default, and websites can overlay custom rule groups. `waf_config.json` only stores rule group references and IP group IDs; IP group members are synchronized independently by the Agent into `waf_ip_groups.json`, and the OpenResty Lua engine merges and evaluates them by reference ID.
WAF IP groups are managed by the Server. Manual IP groups store IP/CIDR lists directly; auto IP groups are evaluated by Server cron jobs reading request logs and applying Expr boolean rules; subscription IP groups are fetched by Server cron jobs from remote text or JSON sources. The Agent reports local IP group checksums in heartbeats, and the Server only returns mismatched IP groups. When an IP group is updated on the Server, a broadcast is sent via WebSocket to push changes, and the OpenResty Lua reads the local JSON file directly without querying the DB, request logs, or remote subscription sources.
## Core Objects
Current valid entities include:
* `proxy_routes`
* `origins`
* `config_versions`
* `nodes`
* `tunnels`
* `auth_sources`
* `external_accounts`
* `node_system_profiles`
* `apply_logs`
* `tls_certificates`
* `managed_domains`
* `node_request_reports`
* `node_access_logs`
* `node_metric_snapshots`
* `traffic_analytics_rollups`
* `node_health_events`
* `waf_rule_groups`
* `waf_ip_groups`
* `waf_rule_group_bindings`
* `acme_accounts`
* `dns_accounts`
* `geoip_update_configs`
## Key Design Decisions
| Decision | Rationale |
| --- | --- |
| Full Config Versioning instead of Patches | Provides stable, verifiable boundaries for previewing, activating, history, and rollbacks. |
| Pull Model (Agent-driven) | Server does not need SSH keys or inbound command ports, preventing control channel hijacking. Supports HTTP and WebSocket. |
| Global Single Active Version | Reduces MVP complexity, ensuring all nodes are uniform by default. Supports previews, version history, and one-click rollback. |
| Website Multi-Domain Aggregation | Enables sharing site-level policies across domains while supporting per-domain certificate binding. |
| Server-side Observability Aggregation | Prevents UI-side temporary statistical calculations from producing inconsistent data metrics. |
| Intranet Penetration based on frp | Reuses a mature tunnel protocol rather than custom implementations to minimize stability risks. frps Vhost routing aligns naturally with HTTP. |
| Independent Binary for Relay/Client | Separation of concerns: Relay manages frps, Client manages frpc, allowing independent updates and deployments. |
| Tunnel decoupled from Node system | Tunnel clients run internally, using completely different registration and authentication flows compared to edge nodes. |
## Recommended Reading for Contributors
Before modifying architectural code, please read:
1. [Product Boundaries](./index.md)
2. [Agent & Publish Model](./agent-design.md)
3. [Development Constraints](../../guildline/development-constraints.md)
4. [Repository Structure](./repository.md)
+182
View File
@@ -0,0 +1,182 @@
# Local Development
You will learn: How to build OpenFlare's local development environment, start the Server, the Agent, and the Admin Frontend, run test and build commands, and understand the boundaries to respect before contributing code.
This page is aimed at contributors. Product boundaries, data model constraints, API conventions, and frontend layering specifications are governed by [Development Constraints](../../guildline/development-constraints.md); this page only provides actionable workflows for local development.
## Repository Structure
For details on the physical directory structure and responsibilities of each module (Server, Agent, Frontend, etc.), see [Repository Structure](./repository.md).
## Environment Requirements
| Item | Requirement |
| --- | --- |
| Go | `1.25+` |
| Node.js | `18+` |
| pnpm | Recommended enabling via `corepack enable` |
| Docker | Required for Server containers, local integration testing, and Agent Docker images |
| OpenResty | Required to execute `openresty` locally when running the Agent |
| PostgreSQL | Optional; if not configured, the Server defaults to SQLite |
## Initializing Frontend Dependencies
```bash
cd openflare_server/web
corepack enable
pnpm install
```
Build the static assets hosted by the Go Server:
```bash
pnpm build
```
## Starting the Server
SQLite Mode:
```bash
cd openflare_server
export SESSION_SECRET='dev-session-secret'
export SQLITE_PATH='./openflare-dev.db'
export LOG_LEVEL='debug'
go run .
```
PostgreSQL Mode:
```bash
cd openflare_server
export SESSION_SECRET='dev-session-secret'
export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
export LOG_LEVEL='debug'
go run .
```
Default access URL:
```text
http://localhost:3000
```
The default credentials are `root` / `123456`.
## Starting the Frontend Dev Server
The frontend dev server listens to port `3001` by default and proxies requests to the backend via `NEXT_DEV_BACKEND_URL`:
```bash
cd openflare_server/web
export NEXT_DEV_BACKEND_URL='http://127.0.0.1:3000'
pnpm dev
```
Access:
```text
http://localhost:3001
```
## Starting the Agent
Create a local `agent.json`:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "./data",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
Run:
```bash
cd openflare_agent
export LOG_LEVEL='debug'
go run ./cmd/agent -config ./agent.json
```
If `openresty_path` is not configured, the Agent calls `openresty` by default. For debugging, you can explicitly configure `openresty_path`, `main_config_path`, `route_config_path`, `access_log_path`, `cert_dir`, `lua_dir`, and `runtime_config_dir`.
## Running Tests
Server:
```bash
cd openflare_server
GOCACHE=/tmp/openflare-go-cache go test ./...
```
Agent:
```bash
cd openflare_agent
GOCACHE=/tmp/openflare-go-cache go test ./...
```
Frontend:
```bash
cd openflare_server/web
pnpm lint
pnpm typecheck
pnpm test
pnpm test:e2e
```
Docs:
```bash
cd docs
pnpm build
```
## Building
Admin static assets:
```bash
cd openflare_server/web
pnpm build
```
Server binary:
```bash
cd openflare_server
go build -o openflare-server .
```
Agent binary:
```bash
cd openflare_agent
go build -o openflare-agent ./cmd/agent
```
## Debugging Entrypoints
| Context | Command or Path |
| --- | --- |
| Server Logs | `LOG_LEVEL=debug go run .` |
| Agent Logs | `LOG_LEVEL=debug go run ./cmd/agent -config ./agent.json` |
| Swagger Docs | `http://localhost:3000/swagger/index.html` |
| Frontend API Proxy | `NEXT_DEV_BACKEND_URL=http://127.0.0.1:3000 pnpm dev` |
| OpenResty Validation | `openresty -t -c ./data/etc/nginx/nginx.conf` |
## Code Style & Change Admission
Before contributing, verify:
1. The requirement matches [Product Boundaries](./index.md).
2. The implementation conforms to [Development Constraints](../guildline/development-constraints.md).
3. The change does not disrupt publishing, sync, rollback, or upgrading lifecycles.
4. Update corresponding documentation if configurations, deployments, APIs, or boundaries change.
5. High-risk edits must be accompanied by unit tests or equivalent integration testing.
Database schema alterations must elevate the database version number and supply explicit migration and validation methods from the previous version.
+204
View File
@@ -0,0 +1,204 @@
# Product Boundaries
You will learn: What OpenFlare is, what problems it solves, who the target audience is, what current stable features are available, and which design boundaries cannot be bypassed during implementation.
OpenFlare is a self-hosted OpenResty control plane designed for single-team or single-organization internal operations. It solves the problems of decentralized management of reverse proxy configurations, node synchronization, certificate hosting, configuration publication and rollback, and basic observability.
## Project Positioning
OpenFlare is suitable for teams that need to centrally manage multiple OpenResty proxy nodes:
* Wanting to maintain reverse proxy website configurations using a management dashboard.
* Wanting every configuration change to have a complete version history, preview, activation, and rollback support.
* Wanting nodes to actively synchronize configurations, rather than the control plane SSHing into nodes to execute commands.
* Wanting to manage TLS certificates, domain assets, node statuses, and basic access analytics in a single system.
OpenFlare is currently not positioned as a general-purpose logging platform, service mesh, Kubernetes Ingress Controller, or multi-tenant cloud platform.
## Current Capabilities
| Capability | Description |
| --- | --- |
| Reverse Proxy Rules | Uses website configuration as the aggregation boundary, supporting multiple domains and origin settings. |
| Website-level Config | One rule corresponds to one website, which can bind one or more domains and share site-level configurations. |
| Origin Management | Maintains a lightweight origin directory and allows websites to save renderable origin snapshots. |
| Config Versioning | Supports previews, publishing, activation, immutable history, and rollbacks. |
| Agent Sync | Supports registration, heartbeats, synchronization, application result reporting, and self-updating. |
| OpenResty Hosting | Manages main config templates, performance parameters, cache parameters, and Lua resources. |
| HTTPS/TLS | Hosts certificate and domain assets, binding certificates on a per-domain basis. |
| WAF | Maintains IP/CIDR block blacklists/whitelists, IP groups, and country-level geographic access controls at both global and site-specific levels. |
| Basic Observability | Aggregates node requests, resource snapshots, health events, and access analytics. |
| Node Management | Manages node status, token systems, and deployment/update lifecycles. |
| Admin UI | Next.js-based official management dashboard. |
| Auth Source Login | Supports configuring GitHub OAuth and standard OIDC login portals, allowing third-party accounts to bind to existing local users. |
| Intranet Penetration | Securely exposes intranet HTTP services to the public internet using TunnelRelay nodes and the OpenFlared client, reusing the Agent's HTTPS/WAF capabilities. |
Default Working Model:
* All nodes consume the same globally activated configuration version.
* The Server stores configurations and state, and does not directly SSH to manage nodes.
* The Agent is the only controlled entry point on the node side.
* TunnelRelay nodes run both the Agent (OpenResty) and the Relay (frps manager) to provide intranet penetration relays.
* The OpenFlared client runs inside the intranet, managing the frpc process to connect to the Relay and forward traffic to intranet services.
## Typical Use Cases
| Scenario | Description |
| --- | --- |
| Unified Entrance | Exposes multiple internal HTTP services via a unified domain and TLS certificate. |
| Multi-Node Sync | Multiple OpenResty nodes consume the same active configuration version. |
| Change Review | View previews or diffs before publishing, keeping an immutable history post-publish. |
| Rapid Rollback | Re-activate an older version, letting the Agent pull and apply it. |
| Certificate Hosting | Bind TLS certificates to different domains under the same website. |
| Observability | Check node health status, aggregated requests, traffic analytics, and health events. |
| Intranet Penetration | Exposes intranet HTTP services that are not directly reachable from the public internet using Tunnels, benefiting from HTTPS, WAF, and all other protections. |
## Website Configuration Constraints
`proxy_routes` is the aggregate object for "website configurations". One record corresponds to one website, which can bind one or more domains and share a set of site-level configurations.
Constraints:
* `proxy_routes.site_name` is the unique business identifier of the website.
* `proxy_routes.domains` must contain at least one domain, and `domains[0]` is treated as the primary domain.
* Any domain can globally belong to only one `proxy_routes`.
* Site-level rate limits, reverse proxies, and caching configurations are shared by the site, with no per-domain differences allowed within the same website.
* HTTPS allows binding certificates on a per-domain basis within the same site.
## Origin & Upstream Constraints
`origins` serve the reuse of the origin directory, storing only the origin address, display name, and remarks, without carrying protocols, ports, paths, weights, or health check policies. `proxy_routes` can optionally associate with an `origins` record, but the rule internally still saves a complete upstream snapshot for rendering.
Upstream Constraints:
* `proxy_routes` must contain at least one upstream address (for direct type `direct`), or be associated with a Tunnel (for intranet penetration type `tunnel`).
* Multi-upstream load balancing is uniformly rendered into a named `upstream` with keepalive enabled.
* A single upstream is allowed to carry a base path or query, which is appended in `proxy_pass`. Multi-upstream is strictly limited to pure `scheme://host[:port]` structures, and all upstreams in the same rule must use the same protocol.
* `proxy_routes.origin_host` is an optional field used to override the `Host` header during back-to-source requests.
* All direct upstream addresses must be valid `http://` or `https://` URLs.
* Intranet penetration upstreams must associate with a valid `tunnel_id` and specify the intranet target address and protocol.
## Intranet Penetration Constraints
OpenFlare implements intranet penetration through TunnelRelay nodes and the OpenFlared client, built on top of frp (Fast Reverse Proxy).
### Node & Component Model
**Node Types**:
* `nodes.node_type` distinguishes the node type: `edge_node` (edge node, default) and `tunnel_relay` (tunnel relay).
* TunnelRelay nodes run both the Agent (OpenResty) and the Relay (frps manager) concurrently, sharing the same `agent_token`.
- The Agent is responsible for HTTPS termination, WAF protection, caching, and rate limiting.
- The Relay manages the frps process, providing tunnel relay services for intranet clients.
* TunnelRelay nodes introduce new fields: `node_type`, `relay_bind_port` (frpc connection port, default 7000), `relay_vhost_http_port` (HTTP Vhost port, default 8080), `relay_auth_token` (automatically generated), `relay_status`, etc.
**Tunnel Client**:
* The `tunnels` table independently stores intranet penetration client registration info and is decoupled from the `nodes` system.
* Each Tunnel has a unique `tunnel_id` (format `tun-<32hex>`) and `tunnel_token` (client authentication credential).
* The OpenFlared client runs inside the intranet, is not exposed to the public internet, uses `tunnel_token` for authentication, and communicates with the Server via `/api/flared/*` endpoints.
* An OpenFlared client can connect to multiple Relays simultaneously for high availability.
### Upstream Type Expansion
The upstream configuration of `proxy_routes` is divided into two types, distinguished by the `upstream_type` field:
* **Direct Upstream (`direct`, default)**: Forwards traffic directly to the origin address, behaving exactly like the existing mechanism.
* **Intranet Penetration Upstream (`tunnel`)**: Forwards traffic to the intranet service via a TunnelRelay node.
- Must specify `tunnel_id` (associated with the `tunnels` table).
- Must specify `tunnel_target_addr` (intranet target address, e.g., `192.168.1.100:8080`) and `tunnel_target_protocol` (`http` or `https`).
- During publication, the Server automatically replaces the upstream address with `http://127.0.0.1:{relay_vhost_http_port}`.
### Traffic Paths & Protocols
**Complete Data Plane Traffic Path**:
```
Browser → OpenResty (Agent, TLS/WAF) [TunnelRelay Node]
↓
frps (Relay, HTTP Vhost Routing) [TunnelRelay Node, 127.0.0.1:{vhost_port}]
↓
frp Tunnel Protocol (Host Header Routing)
↓
frpc (Client, Multi-process) [Intranet Server]
↓
Intranet Service (192.168.x.x:port)
```
**Key Features**:
* frps uses the HTTP Vhost single-port reuse mechanism; all HTTP tunnels share one `vhost_port`, automatically routed to the corresponding frpc based on the Host header.
* The Agent preserves the original `Host` header, which frps uses to match the virtual host.
* Each tunnel corresponds to a single `proxy_routes` and can bind multiple domains.
* The OpenFlared client manages an independent frpc process for each connected Relay, transmitting multiple HTTP proxy definitions via a single frp tunnel.
### Configuration Sync Model
The publication process generates two types of configuration version data simultaneously, linked by a single `config_version` version number:
* **Agent-side Config**: OpenResty main configuration + route configurations + WAF rules. If a tunnel upstream is included, it is automatically rendered as a `http://127.0.0.1:{vhost_port}` upstream.
* **Tunnel-side Config**: Relay list + frpc proxy definitions. Versioned alongside the publishing process; changes are hot-reloaded using `frpc reload` first.
* **Relay Config**: Dispatched via heartbeat responses, relatively static, and not included in the versioned publishing flow.
### Tunnel Design Constraints
* Only HTTP protocol tunnel traffic is supported (keeping TCP/UDP tunnels extensible); separate TCP/UDP port allocation is not supported for now.
* The DNS for domains using Tunnel upstreams should resolve to the designated TunnelRelay node.
* frp binaries (v0.61+) are packaged and provided by the system deployment script or container images.
## HTTPS Constraints
`proxy_routes.domain_cert_ids` is used to record the domain-certificate bindings parallel to `domains`; a value of `0` means the domain does not have HTTPS enabled and stays HTTP-only.
During rendering:
* Domains with certificates are grouped by certificate and output as independent `443 ssl` `server` blocks.
* Domains without certificates bound must not be automatically routed to HTTPS.
* All domains in `proxy_routes.domains` must be kept in the same site configuration to avoid being split across version snapshots.
## WAF Constraints
WAF centers around rule groups. The system provides a single global rule group (applied to all sites by default), on top of which websites can overlay multiple custom rule groups.
Core Capabilities:
* Supports individual IP / CIDR block whitelists and blacklists.
* Supports IP group references (including manual, automatic Expr calculated, and URL subscribed IP groups).
* Supports GeoIP-based country/region level admission filtering.
* Supports custom interception responses for rule groups (custom status codes and interception HTML pages, default is `418`).
IP Group & Judgment Constraints:
* **Runtime Decoupling**: The WAF runtime only reads local JSON files and does not access the Server database; configuration versions only store referenced IP group IDs. IP group members are synchronized via MD5 checksum differences and WebSocket push notifications, achieving hot activation without reloading Nginx.
* **Built-in Expr Rules**:
* High-frequency 404 scanning block: `request_count > 100 && status_404_ratio >= 0.8`
* Malicious IP direct probe: `ip_host_count > 50 && ip_host_ratio > 0.5`
* **Decision Priority**: The whitelist has absolute priority. If it does not match the whitelist, the blacklist funnel is triggered (global rule group first, custom groups matched in ascending ID order).
* GeoIP resolution depends on the local MaxMind database; if GeoIP is anomalous, region rules are automatically ignored and must not disrupt the availability of IP rules and the main reverse proxy chain.
## Authentication Source Constraints
`auth_sources` uniformly supports `github` and `oidc` login configurations. `external_accounts` stores bindings between third-party accounts and local users. Logic for first-time third-party login:
* If already bound, directly authorize login; if there is an active local session, automatically bind.
* If unbound and registration is enabled, automatically create a local account; if registration is closed, require the user to provide an existing local username and password to establish the association.
## Version & Observability Constraints
* `config_versions` must save the complete snapshot, rendering result, and `checksum`.
* Globally, only one version can be active at a time.
* Rollback is achieved by re-activating an older version.
* `nodes` only carry control plane state and low-frequency summaries; they do not carry high-frequency observability facts.
* Metrics, trends, and access analytics prioritize server-side aggregation rather than client-side temporary statistics.
* Access detail logs are only retained within a controlled time window, not evolving into a general logging platform.
## Documentation Maintenance Principles
* Update this document when the product range or system boundaries change.
* Update [System Architecture](./architecture.md) when the system structure or module responsibilities change.
* Update [Agent & Publish Model](./agent-design.md) when the publishing, synchronization, rollback, or Agent model changes.
* Update [Development Constraints](../../guildline/development-constraints.md) when developer constraints, code specifications, or API conventions change.
* Update README and [Deployment Instructions](../../deployment/deployment.md) when deployment methods change.
* Update [Configurations Reference](../reference/configuration.md) when configuration items change.
* Completed phases should no longer be backfilled as "version plans".
* Before starting a new phase, complement the design first, then proceed to implementation.
+93
View File
@@ -0,0 +1,93 @@
# Repository Structure
You will learn: The responsibilities of Server, Agent, Frontend, scripts, and documentation folders in the OpenFlare repository, and where to place logic when contributing code.
| Path | Responsibility |
| --- | --- |
| `openflare_server` | Gin + GORM + SQLite/PostgreSQL single monolithic control plane |
| `openflare_server/web` | Next.js 15 App Router Admin Frontend, hosted by Go Server |
| `openflare_agent` | Go monolithic Agent running on the node side |
| `openflare_relay` | Tunnel relay daemon running on public edges, managing frps processes |
| `openflared` | Tunnel client running on intranet servers, managing frpc processes |
| `scripts` | System helper scripts for installation, self-updating, etc. |
| `docs` | VitePress documentation website, design baselines, specifications, and configurations |
| `docs/en` | English version of documentation |
## Server Layering
| Folder | Responsibility |
| --- | --- |
| `controller/` | Parameter parsing, service calling, and returning responses |
| `service/` | Business logic, validations, transaction orchestration, and configuration rendering |
| `model/` | Model definitions, database versioning, and migrations |
| `router/` | Route registration |
| `middleware/` | Cross-cutting concerns like authentication, authorization, rate limiting, CORS, and Turnstile |
| `common/` | Configurations, global states, and initialization entrypoints |
| `utils/` | Pure utility functions and general helpers |
| `job/` | Periodic cron tasks (such as SSL certificate auto-renewals) |
| `upload/` | File upload handlers |
| `docs/` | API documentation (Swagger) |
| `data/` | Static data (such as GeoIP databases) |
## Agent Modules
| Module | Responsibility |
| --- | --- |
| `config/` | Configuration loading and default values |
| `heartbeat/` | Heartbeat check-in and configuration version evaluation |
| `sync/` | Configuration fetching and application orchestration |
| `nginx/` | OpenResty file writing, validation, reloads, startup, and rollbacks |
| `state/` | Local states and buffers for metric reporting |
| `httpclient/` | Server HTTP API communication |
| `wsclient/` | WebSocket client communication |
| `protocol/` | Agent API protocol types and structures |
| `updater/` | Agent self-updating logic |
| `logging/` | Logging processing |
| `observability/` | Observability (metrics, tracing, etc.) |
| `geoipdata/` | GeoIP database handling |
| `geoipupdate/` | GeoIP database updates |
| `agent/` | Core Agent bootstrap and lifecycle orchestration |
## Frontend Layering
| Folder | Responsibility |
| --- | --- |
| `app/` | Next.js App Router routes, layouts, and page assemblies |
| `features/` | Feature modules organized by business domains |
| `components/` | Reusable UI components shared across features |
| `lib/` | API clients, environment configurations, utility functions, and constants |
| `store/` | Lightweight cross-page UI state management |
| `types/` | Shared TypeScript type definitions |
| `styles/` | Global stylesheets |
| `tests/` | Frontend unit and integration tests (Vitest, Playwright) |
| `scripts/` | Build and deployment scripts |
| `public/` | Static assets |
## Relay Modules
| Module | Responsibility |
| --- | --- |
| `cmd/` | CLI startup entrypoint and main bootstrap functions |
| `internal/config/` | Local configurations parsing and defaults initialization |
| `internal/frps/` | Manages the lifecycle of the frps process, monitoring its status |
| `internal/heartbeat/` | Periodic HTTP heartbeat, status reporting, and update retrievals |
| `internal/httpclient/` | General API client for calling the Server |
| `internal/observability/` | Host and frps metrics collection and pre-aggregation |
| `internal/relay/` | Coordinates the core Relay lifecycle, setup, and cleanup |
| `internal/state/` | Local runtime states, error logs, and persistent caches |
| `internal/updater/` | Relay update check, download installation, and restarts |
| `internal/wsclient/` | Bi-directional real-time WebSocket connection to the Server |
## OpenFlared (Client) Modules
| Module | Responsibility |
| --- | --- |
| `cmd/` | CLI startup entrypoint and main bootstrap functions |
| `internal/config/` | Local client configurations loading and parsing |
| `internal/flared/` | Core client scheduling and tunnel lifecycle orchestration |
| `internal/frpc/` | Dynamically generates `frpc.toml` configs for multiple Relays and monitors frpc processes |
| `internal/heartbeat/` | Heartbeat communications with control planes, including token checks |
| `internal/httpclient/` | General API client for Server communication |
| `internal/sync/` | Incrementally pulls latest Tunnel route bindings, generates snapshots, and applies them |
| `internal/updater/` | Client self-update, new version check, and upgrade installation |
| `internal/wsclient/` | Bi-directional WebSocket client for real-time tunnel configuration pushes |
+130
View File
@@ -0,0 +1,130 @@
# Intranet Penetration Tunnel Design Document
You will learn: The architectural design of the OpenFlare intranet penetration tunnel, the internal principles of the dual-ended control components (Relay and Client), their interaction logics, and the communication flows for the data plane and control plane.
---
## Requirements Analysis
In typical web application hosting scenarios, many origin servers (Origin Servers) are deployed in local intranet environments (such as local development machines, LAN servers, or firewalled private clusters). These servers typically suffer from:
1. **No Public IP**: Cannot be directly accessed by public internet traffic.
2. **Security Compliance Restrictions**: Creating port mappings (NAT) on border routers is strictly prohibited by security policies.
3. **Dynamic IP Changes**: Traditional DDNS solutions exhibit high latency and are highly unstable.
To allow internal origin servers to seamlessly integrate into the OpenFlare global data gateway, benefiting from premium features like WAF geographic protection and TLS certificate hosting, OpenFlare designed an end-to-end solution based on a **reverse relay penetration tunnel**. In this architecture, public edge nodes act as reverse proxy entrances and traffic relays, while the intranet side only needs to initiate secure outbound connections to achieve secure and stable reverse penetration of public traffic to internal origin servers.
---
## Core Capabilities
The intranet penetration tunnel subsystem includes the following core capabilities:
* **Dynamic Relay Node Management**: The control plane dynamically dispatches relay services (frps), distributing service ports and authentication tokens dynamically.
* **Multi-Tunnel Reverse Proxy Mapping**: Supports mapping multiple internal web ports on a single intranet client, binding multiple domain routes to corresponding relay nodes.
* **Independent Process Lifecycle Control**: Both the relay and client are independent daemon processes written in Go, responsible for spawning, monitoring, self-healing, and hot-upgrading the underlying frp engine.
* **Token-based Independent Authentication**: The relay uses `agent_token` for authorization, whereas the intranet client uses its dedicated `tunnel_token`, enforcing isolation of permissions and routing boundaries.
* **Validation & Incremental Hot Reload**: Config files are rewritten and processes are gracefully reloaded only when tunnel bindings, certificates, or Relay topologies change, reducing runtime overhead.
---
## Intranet Penetration & Tunnel Architecture
The intranet penetration subsystem is integrated on top of the mature and high-performance `frp` tunnel protocol, divided into the **Control Plane** and the **Data Plane**.
```mermaid
graph TD
%% Data Flow
Browser[1. Browser / Visitor] -->|HTTPS Request| Agent[2. OpenResty / Agent]
Agent -->|Local proxy_pass| RelayFrps[3. OpenFlare Relay / frps]
RelayFrps -->|Encrypted Tunnel Protocol| FlaredFrpc[4. OpenFlared / frpc]
FlaredFrpc -->|Forward Local Request| LocalOrigin[5. Intranet Origin 192.168.x.x]
%% Control Flow & Heartbeats
Server[OpenFlare Server Control Plane] <-->|Relay API / Heartbeat| RelayManager[openflare_relay process]
Server <-->|Client API / Heartbeat| ClientManager[openflared process]
RelayManager -.->|Control Process & Config| RelayFrps
ClientManager -.->|Control Multi-Relay Processes| FlaredFrpc
style Browser fill:#f9f,stroke:#333,stroke-width:2px
style LocalOrigin fill:#9f9,stroke:#333,stroke-width:2px
style Server fill:#f96,stroke:#333,stroke-width:2px
```
* **Control Plane**: The Server maintains the database state. The `openflare_relay` process on relay nodes and the `openflared` process on intranet servers synchronize tunnel configurations via HTTP heartbeats and long-lived WebSocket connections.
* **Data Plane**: Public traffic enters the public edge Agent (OpenResty), where the TLS handshake, HTTPS termination, and WAF filtering are executed. It is then forwarded via `proxy_pass` to the co-located `openflare_relay (frps)` on the loopback address. `frps` encapsulates the HTTP requests into the encrypted TCP tunnel and sends them down to the intranet `openflared (frpc)`. Finally, `frpc` unpacks the requests and forwards them to the actual intranet origin service.
---
## Relay (Server-side) Design
`openflare_relay` is a relay manager deployed on the public edge, running on nodes of type `tunnel_relay`.
### 1. Core Architecture & Logic
* **Process Daemon**: The Relay process embeds the `frps` binary, spawning the `frps -c frps.toml` subprocess via `exec.Command` and using goroutines to asynchronously listen to its exit status. If `frps` exits unexpectedly, it automatically restarts using an exponential backoff policy.
* **Dynamic Configuration Rendering**: Periodically synchronizes status with the control plane via HTTP heartbeats to retrieve the active `RelayConfig`, including:
* `bindPort`: The public control port that frps listens to for incoming intranet frpc connections.
* `vhostHTTPPort`: The virtual host HTTP listening port where the Agent's proxy_pass points.
* `authToken`: The security credential used during the client connection handshake.
* `webServer`: Enables the frps dashboard API, which the Relay queries to collect active tunnel counts and traffic metrics.
* **Status Reporting**: In each heartbeat cycle, the Relay reports the active connections, registered clients, individual proxy tunnel statuses, and Relay version back to the Server.
---
## Openflared (Client-side) Design
`openflared` is the client manager running inside the user's intranet server, authenticated using a dedicated `tunnel_token`.
### 1. Core Design Mechanisms
* **Multi-Relay Support (Multiplexing)**:
To guarantee high availability and geographical proximity, the control plane may schedule the client to connect to multiple public Relays. `openflared` parses the list of Relays dispatched in the `TunnelConfig`, generating dedicated configurations (`frpc_<relay_node_id>.toml`) and allocating distinct cancelable contexts for each Relay process locally.
* **Independent Subprocess Monitoring**:
`openflared` maintains a local `processes` map to manage the lifecycles of individual `frpc` subprocesses. When the control plane adds or removes Relays, the client incrementally spawns new processes or gracefully shuts down obsolete ones without affecting other functioning tunnels.
* **Dynamic TOML Generation**:
When rendering TOML configs for each Relay, the client iterates over the Proxies list, writing each intranet service's `LocalAddr`, `LocalPort`, and bound `CustomDomains` into standard `[[proxies]]` blocks.
---
## Interaction Logic & Traffic Model
The intranet penetration subsystem implements consistent version control and status feedback loops.
### 1. Control Plane Publishing & Sync Flow
```text
Admin modifies tunnel/intranet port mappings -> Click Publish -> Generate new Tunnel version & Checksum
|
v (Push or Heartbeat Pull)
+-----------------------------------------------------------------------+-----------------------------------------------------------------------+
| |
v (Relay Side) v (Client Side)
openflare_relay heartbeat detects frps port/Token change openflared heartbeat detects tunnel_version change
Re-render local frps.toml Request full proxy configuration details
Kill and restart the frps process Re-render frpc_<relay_id>.toml configs
Report health status as healthy Restart or hot-reload changed frpc processes
Report application results (Apply Success/Error)
```
1. **Versioned Controls**: All intranet tunnel routes and mapping relationships are version-controlled, dispatching a unique `version` and `checksum` to ensure clients do not repeatedly write files or trigger redundant reloads.
2. **Closed-Loop Application Feedback**: After applying new configurations, the client reports the application result in the next heartbeat. If the intranet port is unreachable or certificate bindings fail, the client intercepts the stdout/stderr of the subprocess to report `LastError` to the Server, providing administrators with transparent error details.
### 2. Data Plane Traffic Model
1. **Public Entrance (Agent)**:
```nginx
server {
listen 443 ssl;
server_name intranet.example.com;
# ... TLS certificates & WAF filtering ...
location / {
proxy_pass http://127.0.0.1:18080; # Points to local frps vhost port
proxy_set_header Host $host; # Must preserve the original Host header, which frps relies on to route requests
proxy_set_header X-Real-IP $remote_addr;
}
}
```
2. **Relay Node (frps)**:
`frps` listens to the Vhost port `18080`. When an HTTP request arrives, it extracts `Host: intranet.example.com` from the request headers and searches its active registered tunnel registry to locate the matching encrypted TCP connection (initiated by the intranet frpc).
3. **Encrypted Tunnel Transmission (TCP)**:
`frps` encapsulates the HTTP request into the custom TCP tunnel protocol and transmits it down to the intranet `frpc` client.
4. **Intranet Client Distribution (frpc)**:
The `frpc` instance managed by `openflared` receives the payload, resolves it according to local settings (`localIP = "127.0.0.1"`, `localPort = 8080`), initiates a local TCP connection to forward the request to the intranet web service, and returns the response back through the tunnel to the public viewer.
+132
View File
@@ -0,0 +1,132 @@
# WAF Design Document
You will learn: The core architecture of the OpenFlare edge Web Application Firewall (WAF), the dynamic IP group asynchronous differential sync model, the high-performance OpenResty Lua caching scheme, and the complete request filtering and decision logic.
---
## Requirements Analysis
In public internet environments, web applications face a wide variety of security threats (such as scanner profiling, api scraping, malicious botnets targeted at specific regions, ransomware, and CC attacks). Allowing malicious requests to pass directly to the origin server (Origin Server) results in:
1. **Origin Server Overload**: High-frequency database queries and intensive CPU computations easily exhaust server resources.
2. **Sensitive API Abuse**: APIs like login, registration, and SMS verification codes can be maliciously exploited, leading to financial and computational losses.
3. **Data Exposure Risks**: Malicious common vulnerability probing actions are not intercepted proactively.
Therefore, OpenFlare needs to build a **high-performance, resiliently scalable WAF filtering engine** at the frontmost data plane layer (OpenResty). This engine is capable of executing deep filtering on malicious requests at the edge layer closest to users with sub-millisecond overhead. This relieves pressure on origin servers and provides core security capabilities like CC protection (PoW challenge), IP whitelisting/blacklisting, and region-level interception.
---
## Core Capabilities
OpenFlare WAF includes the following core protection dimensions:
* **IP Interception (IP Whitelist/Blacklist)**: Supports filtering by single IP or CIDR block, and aggregating tens of thousands of IPs into IP groups for highly efficient matching.
* **Geographical Whitelist/Blacklist (GeoIP Limit)**: Integrates MaxMind databases to support precise admission controls based on countries and provinces/regions.
* **Custom Interception Responses**: Supports custom block status codes (e.g., 403, 418) and personalized HTML block pages for different filtering rules.
* **Human-Machine Challenge (PoW CC Protection)**: Supports seamless client-side PoW challenges, calculating Hash collisions to prevent automated scripts and botnets from hitting endpoints concurrently.
---
## IP Group Design & Dynamic Asynchronous Sync
IP groups are the core containers for highly efficient IP whitelisting and blacklisting. OpenFlare classifies IP groups into three types based on their update frequencies and source channels:
### 1. IP Group Types
* **Manual**: Manually input by administrators in the control panel. Primarily used for static trusted IPs or long-term blocks.
* **Subscription**: Configured with remote text feeds (one IP/CIDR per line) or standard JSON subscription URLs. Server-side cron jobs periodically fetch and parse the remote subscription sources. Primarily used for integrating open-source threat intelligence feeds, cloud provider IP ranges, etc.
* **Automatic**: **The most resilient dynamic protection channel**. Control plane scanning jobs read access logs from all nodes, performing aggregation and analysis based on configured Expr rules (e.g., "requesting the `/api/login` endpoint over 50 times with a 401 status code in 5 minutes"). Once matched, the source IP is automatically added to a temporary block list for a specified duration.
### 2. Asynchronous Differential Sync Design (No Nginx Reload)
In traditional Nginx WAF designs, IP blacklist updates typically require writing configurations and executing reloads. If malicious IP blocks occur at high frequencies (seconds or minutes), frequent reloads force Nginx to constantly spawn new worker processes and tear down old ones, severely degrading performance.
OpenFlare adopts a **dynamic IP group asynchronous differential sync design**:
```text
WAF IP member updates (Manual/Subscription/Auto-trigger)
|
v
Server updates the database and calculates the new MD5 Checksum of the IP group
|
+----------------------------------------+
| (WebSocket Real-time Broadcast) | (Heartbeat Fallback Comparison)
v v
Server immediately pushes complete members Agent heartbeats report the local IP groups
of modified groups to all Agents checksum mapping table
| |
| v
| Server detects Checksum mismatch and dispatches
v the modified IP group members
Agent receives member data and writes it as JSON to local disk: waf_ip_groups.json
|
v (Lua Memory Awareness)
OpenResty Lua engine detects file changes via MD5 checksum in seconds and hot-updates its memory,
completely bypassing Nginx process reloads.
```
Through this architecture, the persistence and activation of tens of thousands of highly volatile dynamic blacklist IPs **require absolutely no Nginx reloads**, maximally protecting the high-concurrency throughput of the gateway.
---
## Rule Groups & Site Bindings
* **WAF Rule Group**: The smallest logical collection of WAF filtering policies. A single rule group can contain IP whitelists/blacklists, IP group references, regional restrictions, and CC protection configurations.
* **Global Rule Group**: When a rule group is marked as `is_global = true`, it takes effect on **all website routes** hosted on the node by default.
* **Site Binding**: Website routes (`proxy_routes`) can bind one or more non-global rule groups. During request validation, WAF evaluates the union of `Global Rule Group + Bound Rule Groups`.
---
## Implementation Details & High-Performance Caching
WAF is triggered in the OpenResty `access_by_lua` phase, implemented primarily through Lua files and local JSON configurations.
### 1. Physical Structures
* `waf_config.json`: Contains metadata for all rule groups, geographic country/region limits, and website-to-rule-group bindings.
* `waf_ip_groups.json`: Contains all synchronized IP groups and their corresponding IP lists.
* `waf/runtime.lua`: The actual runtime engine responsible for WAF rule comparison.
* `waf/check.lua`: The entry point for the access layer, handling packages inclusion and triggering `check()`.
### 2. Shared Memory Dictionary (ngx.shared) High-Performance Cache Design
Reading JSON files from the disk and decoding them upon every incoming web request would make disk I/O a severe performance bottleneck.
OpenFlare leverages the **OpenResty Shared Memory Dictionary (ngx.shared.openflare_waf_config)** to implement a two-level caching mechanism:
1. **Zero File I/O Path**:
In Lua, every time `check()` executes, it first computes the MD5 hash of the local JSON file using `ngx.md5` (which takes virtually zero time since the file is cached in the OS Page Cache).
2. **Hash Comparison & Hot Loading**:
It compares this against the cached hash key (`_config_hash`) stored in the shared memory dictionary.
* **If the hash is unchanged**: It reads the pre-decoded Lua Table configuration stored directly in shared memory. The entire verification runs purely in **shared memory**, completing in **microseconds**.
* **If the hash is mismatched**: Indicating that the Agent has just updated the WAF rules or IP groups on the disk, the Lua engine automatically reads the disk file, decodes it via `cjson.decode`, writes the decoded data and the new MD5 hash into shared memory, and makes it seamlessly readable by all subsequent worker processes.
---
## Application Flow & Decision Judgment Control Logic
When an HTTP/HTTPS request arrives at OpenResty, WAF evaluates and intercepts it step-by-step in the `access` phase according to the funnel decision chain below:
### 1. WAF Decision Flowchart
```mermaid
flowchart TD
A[Request enters access phase] --> B[Get Site Name of current request]
B --> C[Load all active rule groups bound to this Site in shared memory]
C --> D{Matches IP whitelist or Whitelist IP group?}
D -- Yes (Matched) --> E[Pass request - ALLOW]
D -- No --> F{Matches country/region whitelist?}
F -- Yes (Matched) --> E
F -- No --> G{Matches IP blacklist or Blacklist IP group?}
G -- Yes (Matched) --> H[Block request - BLOCK]
G -- No --> I{Matches country/region blacklist?}
I -- Yes (Matched) --> H
I -- No --> J{Is CC PoW verification enabled?}
J -- Yes --> K[Transfer to CC Protection module]
J -- No --> L[No security risks, pass normally]
H --> M[Exit and return custom status code and block page HTML configured in the rule group]
```
### 2. Decision Step Details
1. **Whitelist Precedence**:
To prevent false positives and guarantee smooth passage of core back-to-source traffic (such as search engine spiders, CDN back-to-source IPs, and office egresses), WAF **prioritizes matching IP whitelists and regional whitelists**. Once a whitelist matches, it immediately bypasses all subsequent blacklist checks and CC challenges.
2. **Blacklist Aggressive Block**:
If a request is not captured by the whitelist evaluation, it enters the blacklist funnel. Once the source IP matches an IP blacklist, a referenced blacklist IP group, or lies within a prohibited country/region, the Lua engine immediately marks `ngx.ctx.openflare_waf_blocked` as `true`.
3. **Response Output**:
Upon hitting the blacklist, Lua extracts the `block_status_code` (defaults to 418 or 403) and `block_response_body` (interception HTML page) configured in the matching rule group. It outputs the response body via `ngx.say()` and gracefully terminates the request using `ngx.exit(status)` to prevent the request from passing upstream.
+27
View File
@@ -0,0 +1,27 @@
# Credits
OpenFlare is essentially a solution integration project. During its design and implementation phases, it drew inspiration from the exceptional concepts, architectural designs, and technical achievements of numerous open-source projects. Below are the key upstream open-source projects OpenFlare relies on for its core engine, security mechanisms, and backend/frontend system frameworks, along with our sincere thanks to these projects and their active communities.
---
### 1. OpenResty
* **Project Positioning**: A high-performance Web platform based on Nginx and Lua.
* **Role in OpenFlare**: Acts as the edge gateway for the global Data Plane. All public web traffic is received by OpenResty first, where high-concurrency HTTPS handshakes, WAF security rule evaluations, and PoW CC verification are performed before executing reverse proxies.
* **Project Link**: [OpenResty Official Website](https://openresty.org/)
### 2. FRP (Fast Reverse Proxy)
* **Project Positioning**: A high-performance reverse proxy application focused on intranet penetration.
* **Role in OpenFlare**: Serves as the underlying tunnel engine for the intranet penetration subsystem. The relay-side manager `openflare-relay` is responsible for running and scheduling the `frps` engine, while the intranet client `openflared` is responsible for generating TOML configurations locally and running the multiplexed `frpc` subprocesses.
* **Project Link**: [fatedier/frp (GitHub)](https://github.com/fatedier/frp)
---
### 3. Anubis (PoW Solution)
* **Project Positioning**: A lightweight human-machine verification and protection solution based on Proof of Work (PoW).
* **Role in OpenFlare**: Provides the core **seamless PoW CC challenge** capabilities for the gateway WAF.
---
### 4. gin-template
* **Project Positioning**: A modern full-stack development boilerplate based on Go Gin and frontend builds.
* **Role in OpenFlare**: Provided the standard, unified backend/frontend system architecture baseline for the OpenFlare control plane (Server).
+100
View File
@@ -0,0 +1,100 @@
# Publishing Your First Site
You will learn: How to create your first website configuration, bind origins and certificates, publish the configuration version, and verify that the Agent applied it successfully.
The publishing pipeline of OpenFlare centers on a complete configuration version snapshot. After modifying website configurations in the management console, you need to publish and activate the new version to let the Agent pull and apply it in the next heartbeat.
## Pre-publish Checks
Verify that the following conditions are met:
| Item | Expectation |
| --- | --- |
| Server | Management console is accessible and log-in succeeds |
| Agent | At least one node is online |
| Origin | The Agent node can reach the origin server address |
| Domain | Domain is resolved to the OpenResty node, or prepared to verify via local `hosts` / `curl` Host header |
| HTTPS | If HTTPS is required, the certificate is uploaded or hosted |
## Create Website Configuration
A new website configuration requires at least:
| Field | Description |
| --- | --- |
| Website Name | Business unique identifier; the primary domain is used if left blank |
| Domain | At least one domain, where the first is treated as the primary domain |
| Origin Address | A valid `http://` or `https://` upstream address |
| Enabled Status | Only enabled website configurations will participate in publishing and rendering |
Example:
| Field | Example |
| --- | --- |
| Website Name | `app` |
| Domain | `app.example.com` |
| Origin Address | `http://10.0.0.20:8080` |
A single domain can belong to only one website configuration. Rate limiting, reverse proxy, and caching parameters are shared site-wide.
## Bind Certificate
HTTPS certificates are bound by domain. Domains without a bound certificate will not be placed into `443 ssl` server blocks automatically.
If a website contains multiple domains, the rendering pipeline groups the HTTPS configurations by certificate while ensuring all domains belong to the same site snapshot.
## Publish & Activate
Standard Pipeline:
```text
Modify rules -> Preview / Diff -> Publish -> Generate complete version -> Activate version -> Agent pulls -> Local application -> Report result
```
During publication, the Server reads all enabled website configurations, the main OpenResty config templates, performance and cache parameters, rendering the complete OpenResty configuration and calculating its `checksum`, saving to `config_versions`, and switching the active version.
## Verify Results
Verify in the management console after publishing:
| Position | Expected Result |
| --- | --- |
| Node List | Node status is online |
| Node Details | Current version matches active version |
| Apply Logs | Most recent application succeeded |
| Version Page | The new version is currently active |
Verify Agent logs on the node:
```bash
journalctl -u openflare-agent -n 100 --no-pager
```
Access via domain:
```bash
curl -I http://app.example.com
```
If the domain has not been officially resolved, you can verify by specifying the Host header against the node IP:
```bash
curl -I -H 'Host: app.example.com' http://NODE_IP
```
HTTPS Validation:
```bash
curl -I https://app.example.com
```
## Rollback
If a target version application fails and triggers a rollback, the Agent blocks repeated synchronization of the same failing `version + checksum` until the active version or checksum changes on the control plane.
Roll back to an older version:
1. Open the Configuration Versions page.
2. Locate the last known good historic version.
3. Re-activate that version.
4. Check the node application logs to verify that the Agent successfully applied the rollback.
+43
View File
@@ -0,0 +1,43 @@
# Guide Overview
You will learn: How the OpenFlare documentation is organized, which pages to read when running it for the first time, and where to start for deployment, usage, troubleshooting, and development.
OpenFlare is a self-hosted OpenResty control plane. It integrates reverse proxy website configurations, configuration version publishing, Agent node synchronization, TLS certificates, and basic observability into a single management console, making it ideal for a single team or organization managing multiple proxy nodes.
## Recommended Reading Path
If you are new to OpenFlare, read the documents in the following order:
1. [Quick Start](./quick-start.md): Start the Server using Docker Compose, log into the management console, and connect your first Agent.
2. [Basic Usage](./usage.md): Learn common operations for website configs, origins, certificates, publishing, rollbacks, and observability.
3. [Tunnel & Intranet Penetration](./tunnel-usage.md): Learn to deploy Relay and Client to achieve secure, public IP-free reverse penetration.
4. [WAF Security Protection](./waf-usage.md): Master IP whitelisting/blacklisting, WAF auto IP group aggregation Expr rules, geographical restrictions, and PoW CC protection.
5. [WAF Auto IP Group Expressions](./waf-ip-group-expr.md): Write auto IP group Expr rules and learn keyword definitions and presets.
6. [Deployment Guide](../deployment/deployment.md): Deploy Server and Agent in closer-to-production environments.
7. [Configurations Reference](../reference/configuration.md): Check Server environment variables, runtime Options, and Agent configurations.
8. [Troubleshooting](./troubleshooting.md): Troubleshoot login, database, node sync, OpenResty application, and frontend build issues.
## Role-Based Entrypoints
| What do you want to do? | Recommended Entrance |
| --- | --- |
| Run the console in under 5 minutes | [Quick Start](./quick-start.md) |
| Publish your first reverse proxy configuration | [Publish First Configuration](./first-site.md) |
| Configure intranet penetration mapping | [Tunnel & Intranet Penetration](./tunnel-usage.md) |
| Configure CC protection & IP group blocking | [WAF Security Protection](./waf-usage.md) |
| Write auto IP group aggregation rules | [WAF Auto IP Group Expressions](./waf-ip-group-expr.md) |
| Connect or reinstall a node Agent | [Access Agent](../deployment/agent.md) |
| Start Server from source code | [Launch Server](../deployment/server.md) |
| Configure GitHub or OIDC SSO | [SSO Login Configuration](./sso.md) |
| Upgrade Server or Agent | [Upgrade & Maintenance](../deployment/upgrade.md) |
| Participate in development or bug fixing | [Local Development](../design/development.md) and [Development Constraints](../../guildline/development-constraints.md) |
| Understand architecture and publishing | [System Architecture](../design/architecture.md) and [Agent & Publish Model](../design/agent-design.md) |
| View open-source references and credits | [Credits](./credits.md) |
## Documentation Partitions
`guide/` is oriented toward users and deployers, providing actionable steps from installation to daily operations.
`reference/` collects stable facts such as configuration fields, commands, API response structures, and repository layout.
`design/` is oriented toward maintainers and contributors, describing product boundaries, system architecture, Agent & publishing models, and engineering constraints. Before adding capabilities or changing boundaries, update the corresponding design document first.
+219
View File
@@ -0,0 +1,219 @@
# Quick Start
You will learn: How to start OpenFlare Server using Docker Compose, complete your first login, connect your first Agent, and verify if a configuration has been published to the node.
The minimum running unit of OpenFlare consists of:
| Component | Responsibility |
| --- | --- |
| Server | Admin UI, Admin API, Agent API, configuration rendering, version publishing, and state storage. |
| Agent | Runs on the proxy node, pulls configurations, writes files for OpenResty, executes validations, and triggers reloads. |
| OpenResty | Receives actual traffic and reverse proxies it to origin servers. |
The Agent manages the runtime through the OpenResty binary. A local deployment requires the `openresty` executable to be already present on the node; a Docker deployment can directly run the Agent image containing built-in OpenResty.
## Environment Requirements
| Item | Requirement |
| --- | --- |
| Docker / Docker Compose | Used to start Server and PostgreSQL; also used to run the Agent if using the Docker Agent image |
| OpenResty | Required to have the `openresty` executable when installing the Agent locally, or specify its path in the installation script |
| Reachable Ports | The Server listens on port `3000` by default; the Agent node needs to be able to reach the Server address |
| Browser | Used to access the management console |
* **Docker**: `20.10.0+`
* **Docker Compose**: `2.0.0+`
## 1. Start the Server
Create a `docker-compose.yml` file in an empty directory:
```yaml
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
interval: 10s
timeout: 5s
retries: 5
openflare:
image: ghcr.io/rain-kl/openflare:latest
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-a-long-random-string
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
volumes:
- openflare-data:/data
volumes:
postgres-data:
openflare-data:
```
Start the services:
```bash
docker compose up -d
```
Verify that the containers are running:
```bash
docker compose ps
docker compose logs -f openflare
```
Once you see `server listening` in the logs and the `openflare` container status is running, access:
```text
http://localhost:3000
```
Default credentials:
| Username | Password |
| --- | --- |
| `root` | `123456` |
Please change the default password immediately after your first login.
## 2. Prepare Agent Token
The Agent can be connected using one of two types of credentials:
| Credential | Applicable Scenario |
| --- | --- |
| `discovery_token` | Automatically registers a node for the first time, which the Server exchanges for a node-specific Token |
| `agent_token` | Node has already been created/allocated in the management console, directly uses this node-specific Token |
After preparing one of these credentials in the management console, proceed to the next step.
* **`discovery_token`** path: "System Settings" -> "Auto Registration"
* **`agent_token`** path: "Node Management" -> "Add Node"
## 3. Install/Run the Agent
The recommended Agent deployment method is using Docker (which runs the Agent image with built-in OpenResty); deploying the Agent locally on the host using the installation script is also supported.
### Option A: Run Agent in Docker (Recommended)
Run the Agent image directly on the proxy node:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443 \
-v openflare-agent-data:/data \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
ghcr.io/rain-kl/openflare-agent:latest
```
### Option B: Execute Installation Script (Local Host Deployment)
Execute the installation script on the proxy node.
Using the `discovery_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--discovery-token YOUR_DISCOVERY_TOKEN
```
Using the node-specific `agent_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
The script defaults to:
| Item | Default Value |
| --- | --- |
| Install Directory | `/opt/openflare-agent` |
| Config File | `/opt/openflare-agent/agent.json` |
| systemd Service | `openflare-agent.service` |
| OpenResty Path | Automatically detects `openresty` if unspecified |
Verify the Agent service status:
```bash
systemctl status openflare-agent
journalctl -u openflare-agent -f
```
If systemd is not available on the OS, the script outputs manual startup commands instead.
## 4. Publish Your First Configuration
Perform the following operations in the management console:
1. Add a website configuration, filling in the website name, domain, and origin address.
2. Verify that the website configuration is enabled.
3. Check the preview or change summary before publishing.
4. Publish and activate the new version.
5. Wait for the Agent to detect and apply the version in the next heartbeat.
The version number format is `YYYYMMDD-NNN`. Historic versions are immutable; rollbacks are accomplished by re-activating an older version.
## 5. Verify Success
Confirm in the management console:
| Position | Expected Result |
| --- | --- |
| Node List | Agent node status is online |
| Node Details | Current version matches active version |
| Apply Logs | Most recent application succeeded |
| Version Page | The new version is currently active |
Confirm on the Agent node:
```bash
journalctl -u openflare-agent -n 100 --no-pager
```
## Common Failures
| Symptom | Troubleshooting Direction |
| --- | --- |
| Management console fails to load in browser | Verify that the Server is running in `docker compose ps` and port `3000` is not bound by other processes |
| Data fails to save after logging in | Check the health of the PostgreSQL container, and verify the username, password, and database name in `DSN` |
| Agent fails to register | Verify that the Agent node can reach `--server-url`, and verify if the Token is typed correctly or expired |
| Agent is online but configuration is not applied | Verify that the website configuration is enabled and a version has been published and activated |
| OpenResty application fails | Review node application logs and `journalctl -u openflare-agent`, checking domains, certificates, upstreams, and port conflicts |
For more troubleshooting details, see [Troubleshooting](./troubleshooting.md).
---
## Advanced Deployment Guides
Once you complete the quick start and familiarize yourself with the basic operations of OpenFlare, you can read the following advanced deployment documents to put components into production:
* **Server Production Deployment**: Read [Launch Server](../deployment/server.md) to learn how to build the frontend from source, configure system environment variables, and run with Docker Compose.
* **Agent Production Integration**: Read [Deploy Agent](../deployment/agent.md) to learn about systemd-based service management, detailed local configuration parameters, and troubleshooting.
* **Tunnel Relay Deployment**: Read [Deploy Relay](../deployment/relay.md) to learn how to configure public relay nodes (frps) for penetration tunnels.
* **Tunnel Client Deployment**: Read [Deploy OpenFlared](../deployment/openflared.md) to learn how to run the penetration daemon client (frpc) on the intranet server side.
* **Production Deployment Topology**: Read [Deployment Guide](../deployment/deployment.md) to learn about high-availability production topologies and overall network planning.
* **System Upgrades & Maintenance**: Read [Upgrade & Maintenance](../deployment/upgrade.md) to learn how to upgrade the Server and individual node Agents smoothly.
+106
View File
@@ -0,0 +1,106 @@
# SSO Login Configuration
You will learn: How to configure GitHub OAuth or standard OIDC login portals for OpenFlare, how to fill in callback URLs, and how third-party accounts bind to existing local users.
OpenFlare supports third-party logins configured via Authentication Sources. Currently, GitHub OAuth and standard OIDC Providers (e.g., Logto, authentik, Keycloak, Casdoor) are supported.
Once an Authentication Source is configured and enabled, it displays in the third-party login section of the login page. Users can log in using their third-party accounts or bind their third-party accounts to their current local account while logged in.
## Prerequisites
Before starting, prepare the following:
| Item | Description |
| --- | --- |
| OpenFlare URL | The actual URL accessed by user browsers, e.g., `https://openflare.example.com` |
| Auth Source Name | Unique internal identifier in OpenFlare, e.g., `github`, `company-oidc` |
| Client ID | Provided after creating an application in the third-party platform |
| Client Secret | Provided after creating an application in the third-party platform |
| OIDC Discovery URL | Required for OIDC only, e.g., `https://idp.example.com/.well-known/openid-configuration` |
**Verify that "System Settings -> General Settings -> Server Address" accurately matches your domain name.**
The Auth Source name can only contain letters, numbers, hyphens, or underscores, and must start with a letter or number. The Auth Source name will appear in the callback URL; if you modify the name after saving, you must simultaneously modify the callback URL on the third-party platform.
## Callback URL
The Redirect URI / Callback URL in third-party platforms is formatted as:
```text
<OpenFlare URL>/oauth/<Auth Source Name>
```
Example:
```text
https://openflare.example.com/oauth/github
https://openflare.example.com/oauth/company-oidc
```
When creating or editing an authentication source in the management console, the form automatically generates the callback URL based on your current browser URL and the Auth Source name you entered.
## Configure GitHub Login
1. Create an OAuth App in GitHub.
2. Fill `Homepage URL` with your OpenFlare URL.
3. Fill `Authorization callback URL` with the callback URL generated in OpenFlare, e.g., `https://openflare.example.com/oauth/github`.
4. Copy the Client ID and Client Secret provided by GitHub.
5. Log into the OpenFlare management console, go to "Settings -> System Settings -> Configure Authentication Sources".
6. Add an authentication source, choosing `GitHub` as the type.
7. Fill in the Auth Source name, display name, Client ID, and Client Secret.
8. The Scope defaults to `user:email`, which usually requires no modification.
9. Save and enable the authentication source.
Once enabled, the corresponding GitHub login button will display on the login page.
## Configure OIDC Login
1. Create an application or client in your OIDC Provider.
2. Select Web / Confidential Client as the application type.
3. Fill `Redirect URI / Callback URL` with the callback URL generated in OpenFlare, e.g., `https://openflare.example.com/oauth/company-oidc`.
4. Copy the Client ID and Client Secret.
5. Retrieve the Provider's Discovery URL, which usually ends with `/.well-known/openid-configuration`.
6. Log into the OpenFlare management console, go to "Settings -> System Settings -> Configure Authentication Sources".
7. Add an authentication source, choosing `OIDC` as the type.
8. Fill in the Auth Source name, display name, Client ID, Client Secret, and OIDC Discovery URL.
9. Scope defaults to `openid profile email`. If the Provider restricts scopes, adjust to values permitted by the Provider.
10. Save and enable the authentication source.
Once enabled, the corresponding OIDC login button will display on the login page.
## Login & Binding Behaviors
Once a third-party account returns to OpenFlare, it is processed according to the following rules:
| Scenario | Behavior |
| --- | --- |
| Third-party account is already bound to a local user | Logs in directly |
| User is already logged in and initiates third-party authorization | Binds to the current local user |
| Third-party account is unbound, and registration is enabled | Automatically creates a standard user and binds |
| Third-party account is unbound, and registration is disabled | Prompts to enter an existing local username and password to complete the binding |
If you want only existing users to use SSO, you can disable user registration. Unbound third-party accounts will then trigger the binding flow.
## Modify Authentication Source
When editing an authentication source, leaving the Client Secret field blank retains the existing secret; entering a new value will overwrite the saved secret.
If you modify the Auth Source name, the callback URL changes accordingly. You must modify the Redirect URI / Callback URL on the third-party platform; otherwise, the third-party platform will deny the callback or return an error.
## Common Problems
### Returns `invalid_scope`
This indicates that the third-party platform does not permit the configured Scope. OIDC defaults to `openid profile email`, and GitHub defaults to `user:email`. Adjust the Scope in the authentication source edit page or configure the third-party platform to permit the scope.
### Callback Address Mismatch
Verify if the Redirect URI / Callback URL configured in the third-party platform matches the prompt in the OpenFlare form exactly. The protocol, domain, port, and path must match.
### Third-party Login Button Not Showing on Login Page
Verify if the authentication source is enabled and confirm that the Client ID and Client Secret are saved. OpenFlare validates these fields before enabling the source.
### Client Secret Saved but Not Displayed in Clear Text
This is expected behavior. OpenFlare does not echo the Client Secret back via API, displaying only whether the secret is configured.
+249
View File
@@ -0,0 +1,249 @@
# Troubleshooting
You will learn: How to troubleshoot OpenFlare Server, database, login, Agent, OpenResty, configuration publishing, and frontend build issues by symptoms.
During troubleshooting, first identify which layer the issue occurs in: browser, Server, database, Agent, OpenResty, origin server, or DNS. OpenFlare configurations are not written directly to nodes online; only after the active version changes will the Agent detect and apply it in heartbeats.
## Quick Diagnostic
| Symptom | Where to check first |
| --- | --- |
| Admin panel fails to open | Server container or process logs, port listening |
| Login anomalies | Default credentials, Session Secret, browser request payloads, Server logs |
| Data fails to save | Database connection, SQLite file permissions, PostgreSQL health |
| Agent offline | Agent logs, Token, Server URL, network connectivity |
| Node not updated after publishing | Active version, node heartbeat, application logs |
| OpenResty application failed | Application logs, Agent logs, certificates, upstream addresses, port conflicts |
| Observability analytics has no data | OpenResty container status, observability port, Agent retry logs |
## Server Fails to Start
1. View logs:
```bash
docker compose logs -n 200 openflare
```
For source-code execution, inspect terminal outputs.
2. Check port conflicts:
```bash
lsof -i :3000
```
3. If using PostgreSQL, verify that the database is healthy:
```bash
docker compose ps postgres
docker compose logs -n 100 postgres
```
4. If using SQLite, verify that the database directory is writable:
```bash
ls -ld "$(dirname /path/to/openflare.db)"
```
Common causes:
| Log or Symptom | Action |
| --- | --- |
| Database connection failed | Check `DSN` username, password, host, port, dbname, and `sslmode` |
| SQLite fails to create files | Check if the parent directory of `SQLITE_PATH` exists and is writable |
| Port is already in use | Change `PORT` or `--port`, or stop the process binding to the port |
## Admin Console Fails to Load or Shows Blank Page
1. Verify that the Server is listening:
```bash
curl -I http://127.0.0.1:3000
```
2. If running from source, verify that the frontend static assets have been built:
```bash
cd openflare_server/web
pnpm build
```
3. Verify if the browser URL matches your reverse proxy domain.
4. If accessing via the frontend dev server, verify the backend proxy configuration:
```bash
cd openflare_server/web
NEXT_DEV_BACKEND_URL=http://127.0.0.1:3000 pnpm dev
```
## Default Credentials Fail to Log In
The default credentials are `root` / `123456`. If you have modified the password after your first login, use your new password.
Troubleshooting Steps:
1. Confirm that you are connecting to the expected database, avoiding `SQLITE_PATH` or `DSN` pointing to a different environment.
2. Check the Server log to see if it is running on `sqlite` or `postgres`.
3. If deployed in multi-replicas or behind a reverse proxy, verify that `SESSION_SECRET` is static and uniform across all instances.
4. Clear browser Cookies and try logging in again.
### Emergency Reset of Admin Password
If you forget the password for the `root` account, you can reset it back to `123456` by directly updating the password hash in the database (please change it immediately after logging in):
#### 1. If using SQLite Database
Stop the Server and open the database file using the `sqlite3` client:
```bash
sqlite3 /path/to/openflare.db
```
Execute the following SQL statement:
```sql
UPDATE users SET password_hash = '$2a$10$wN9aE3zTz83rO7R1uKlhuehJtA3c604pX4Z12B/9.5c0X337t1L4m' WHERE username = 'root';
```
Type `.exit` to exit and restart the Server.
#### 2. If using PostgreSQL Database
Connect to your PostgreSQL instance using a database tool (e.g., `psql`, `pgAdmin`, or `DBeaver`), select the corresponding `openflare` database, and execute the following SQL:
```sql
UPDATE users SET password_hash = '$2a$10$wN9aE3zTz83rO7R1uKlhuehJtA3c604pX4Z12B/9.5c0X337t1L4m' WHERE username = 'root';
```
Once executed successfully, you can log in using the default password `123456`.
## Agent Fails to Register or Stays Offline
Execute on the Agent node:
```bash
curl -I http://your-server:3000
```
Inspect Agent logs:
```bash
journalctl -u openflare-agent -n 200 --no-pager
```
Verify configuration parameters:
```bash
sed -n '1,160p' /opt/openflare-agent/agent.json
```
Key Settings:
| Configuration | Description |
| --- | --- |
| `server_url` | Must be the Server address reachable by the Agent node |
| `agent_token` / `discovery_token` | At least one must be provided |
| `heartbeat_interval` | Supports integer milliseconds or Go duration strings |
| `request_timeout` | Can be increased for slower network links |
If the log warns that the Token is invalid, retrieve a new Token in the management console, update `agent.json`, and restart the Agent:
```bash
systemctl restart openflare-agent
```
## Node Fails to Apply New Version after Publishing
Verify in sequence:
1. Confirm that the target version is activated on the Versions page.
2. Verify if the node is online and if its last heartbeat time has updated.
3. Check the Application Logs for successful, warned, or failed logs for the target version.
4. Verify if the website configuration is enabled; disabled websites do not participate in rendering.
5. Inspect Agent logs for pulls, validations, reloads, or rollback events.
Inspect Agent logs:
```bash
journalctl -u openflare-agent -f
```
Note: If a target `version + checksum` fails to apply and triggers a rollback, the Agent blocks repeated synchronization of that failing target in its local state. You must fix the configuration issues and republish to generate a new checksum, or activate an older version to trigger a rollback.
If this is the Agent's first time applying configurations and no historic `nginx.conf` exists locally to roll back to, the failed version remains blocked but the Agent will attempt to enter the safe fallback runtime. At this point, the application logs and Agent logs will contain `fallback runtime started`. OpenResty will only listen to port `80`, returning a `503` with the body `OpenFlare: No Valid Configuration`, while retaining the local `/openflare/stub_status` health probe. After correcting the configurations and republishing, the Agent overrides the fallback config and restores normal reverse proxies.
## OpenResty Application Fails
Common Causes:
| Cause | Diagnostic |
| --- | --- |
| Domain or server block conflict | Verify if the same domain is used by multiple website configurations |
| Invalid upstream address | Confirm that all upstreams are valid `http://` or `https://` URLs |
| Mismatched multi-upstream format | Multi-upstreams must be pure `scheme://host[:port]` |
| Missing cert or invalid paths | Verify if domains are bound to certs and check if the Agent cert directory is writable |
| Port already in use | Verify ports `80` and `443` on the host |
OpenResty Configuration Validation:
```bash
openresty -t -c /path/to/openflare/data/etc/nginx/nginx.conf
```
OpenResty Runtime Status:
```bash
ps aux | grep openresty
```
The Agent determines OpenResty survival periodically using the local endpoint `http://127.0.0.1:<openresty_observability_port>/openflare/stub_status`, completely bypassing repeated `openresty -t` calls. If a node is marked as unhealthy, confirm if this local observability port is listening. If failures only occur when applying configurations (e.g., `host not found in upstream`), the failure lies in config validation or reload, not the periodic health checks.
Actual binary paths and main configuration paths are governed by `openresty_path` and `main_config_path` in `agent.json`.
## HTTPS Fails to Work
1. Verify that the certificate has been uploaded or hosted.
2. Verify that the website configuration binds the certificate to the domain.
3. Confirm that the configuration version has been published and activated.
4. Check if the Application Logs indicate a success.
5. Check the certificate chain and status code using `curl`:
```bash
curl -Iv https://your-domain
```
Domains without a bound certificate will not be added to the HTTPS configuration automatically; this is expected behavior.
## Traffic Analytics Has No Data
1. Confirm that the node has successfully applied configurations carrying observability Lua scripts.
2. Verify that OpenResty is running.
3. Check Agent logs for observability extraction or upload errors.
4. Check if `openresty_observability_port` (default is `18081`) is bound by other processes.
5. Verify if the Server database has purged data inside the time window.
## Frontend Build Fails
Execute:
```bash
cd openflare_server/web
corepack enable
pnpm install
pnpm lint
pnpm typecheck
pnpm test
pnpm build
```
Common causes:
| Symptom | Action |
| --- | --- |
| pnpm version mismatch | Reinstall packages after executing `corepack enable` |
| TypeScript errors | Locate detailed file bugs by running `pnpm typecheck` |
| API type mismatch | Check responses structures in `lib/api/` and `types/` |
| E2E test failures | Confirm that both the Server and frontend dev server are running |
## Documentation Build Fails
```bash
cd docs
pnpm install
pnpm build
```
If it fails on broken links, check if new pages are added to the `docs/config.ts` sidebar, or if relative markdown links point to existing markdown files.
+174
View File
@@ -0,0 +1,174 @@
# Tunnel & Intranet Penetration
You will learn: The design principles of OpenFlare intranet penetration tunnels, core concepts (Relay nodes and Tunnel clients), and how to safely and stably publish your intranet development environment or private cloud services to a public domain name from scratch.
In many practical development and operations scenarios, our origin servers are deployed in local LANs, local development machines, or heavily guarded private VPCs, having no public IP address and no port mapping (NAT) configured on border firewalls or routers.
OpenFlare provides an end-to-end solution **based on reverse relay penetration tunnels**. You only need to initiate a secure outbound connection from your intranet environment to the public relay node, without configuring any inbound ports, to smoothly route public web traffic into your intranet origin. At the same time, you benefit from automatic TLS certificate hosting and WAF security protection provided by the gateway.
---
## Core Concepts
Before using the intranet penetration features, you need to familiarize yourself with the following components and core concepts:
| Concept | Description | Component / Operation |
| --- | --- | --- |
| **Relay Node (Relay)** | Traffic relay services deployed at the public edge, responsible for listening to intranet client persistent connections, acting as the transit bridge between the gateway Agent (OpenResty) and internal traffic. | Node of type `tunnel_relay` running the `openflare-relay` daemon |
| **Penetration Tunnel (Tunnel)** | Logical penetration client instances having a globally unique ID and secure authentication token, used to identify a specific intranet environment. | Globally unique ID generated by Server `tunnel_id` (format: `tun-<32hex>`) |
| **Tunnel Client (Client)** | A lightweight controller running in the intranet environment, automatically managing the underlying frpc tunnel subprocesses according to the configuration dispatched by the Server. | The `openflared` container or independent binary process deployed in the intranet |
| **Tunnel Upstream (Tunnel Upstream)** | A special upstream type in the website configuration. When this type is selected, the gateway forwards public traffic to the Vhost port of the local relay node, eventually reaching the intranet origin. | Upstream of type `tunnel` configured in the website details |
---
## Recommended Operation Sequence
To publish an intranet service to the public internet, we recommend doing so in the following order:
1. Register and deploy at least one public **Relay Node (Relay)** and keep it online.
2. Create a **Penetration Tunnel (Tunnel)** in the management console and copy its dedicated Token.
3. Deploy and start the **Tunnel Client (OpenFlared)** on your intranet server.
4. Confirm that the status of the tunnel in the management console shows as "Online".
5. Add a website configuration, selecting **Intranet Penetration** as the upstream type, binding it to the corresponding tunnel, and entering the intranet port (e.g., `127.0.0.1:8080`).
6. Publish and activate the new version.
7. Access via the public domain to verify that the intranet penetration link is established.
---
## Detailed Configuration Steps
### Step 1: Prepare the Relay Node (Relay)
Intranet traffic is routed through public relay nodes. Before starting, ensure you have a public relay server available.
1. Log into the management console and go to **"Node Management"**.
2. Add a new node, selecting **Relay Node (tunnel_relay)** as the **Node Type**.
3. Save and copy the node-specific `agent_token`.
4. Start the `openflare-relay` process on your public server. You can run it quickly using Docker:
```bash
docker run -d --name openflare-relay --restart unless-stopped \
-p 7000:7000 \
-e OPENFLARE_SERVER_URL=http://<YOUR_SERVER_PUBLIC_IP>:3000 \
-e OPENFLARE_AGENT_TOKEN=<YOUR_COPIED_AGENT_TOKEN> \
-v openflare-relay-data:/var/lib/openflare-relay \
ghcr.io/rain-kl/openflare-relay:latest
```
> [!IMPORTANT]
> Make sure to allow port `7000` (the control port for frpc client connections) in your cloud provider's security group. If your Server and Relay are deployed on the same machine, `OPENFLARE_SERVER_URL` should point to the Server's public or internal IP.
### Step 2: Create a Penetration Tunnel in the Management Console
1. Navigate to the **"Intranet Penetration"** section in the side navigation bar.
2. Click the **"Create Tunnel"** button and enter:
* **Tunnel Name**: Describes the intranet environment, e.g., `home-lab` or `office-dev`.
* **Description**: Optional, describes the purpose of this tunnel.
3. Click save, and the system will automatically generate a globally unique ID and a dedicated `tunnel_token` (e.g., `tun-xxxx...`).
4. Copy the **Client Deployment Command** generated in the popup window, which will be used in the next step.
### Step 3: Deploy the Intranet Client (OpenFlared)
Return to your intranet server and execute the copied deployment command to run the client.
#### Option A: Deploy with Docker (Highly Recommended)
The official `openflared` image embeds the master daemon and `frpc` runtime, working out-of-the-box with no extra dependencies:
```bash
docker run -d --name openflared --restart unless-stopped \
-e OPENFLARE_SERVER_URL=http://<YOUR_SERVER_PUBLIC_IP>:3000 \
-e OPENFLARE_TUNNEL_TOKEN=<YOUR_COPIED_TUNNEL_TOKEN> \
-v openflared-data:/app/data \
ghcr.io/rain-kl/openflared:latest
```
#### Option B: Host Binary Manual Execution
If you cannot use Docker, you can download or compile the `flared` binary:
1. Create a `flared.json` configuration file in the same directory as the executable on your intranet machine:
```json
{
"server_url": "http://<YOUR_SERVER_PUBLIC_IP>:3000",
"tunnel_token": "<YOUR_COPIED_TUNNEL_TOKEN>",
"frpc_path": "/usr/local/bin/frpc",
"data_dir": "./data"
}
```
2. Execute the startup command:
```bash
./flared -config ./flared.json
```
#### Verify Online Status
Once started successfully, the intranet client will send heartbeats through outbound networks to synchronize configurations. At this point:
1. Refresh the **"Intranet Penetration"** list in the management console; the tunnel status indicator should turn green and show **"Online"**.
2. Click tunnel details to view which public Relays the intranet client is currently connected to.
### Step 4: Create a Website and Bind the Tunnel Upstream
Now you can configure public reverse proxy and domain routing for your intranet service.
1. Go to the **"Website Configuration"** page and click **"Create Website"**.
2. Enter the **Domain Name** required to access the service publicly, e.g., `nas.example.com`.
3. Critical Configuration: In the **"Upstream Configuration"** section, switch the **Upstream Type** from "Direct" to **"Intranet Penetration"**.
4. In the dropdown list, select your newly deployed **Intranet Tunnel** (e.g., `home-lab`).
5. Enter the **Intranet Target Address** (the local address and port reachable by the intranet client, e.g., `127.0.0.1:8080`) and select the **Intranet Protocol** (usually `http`).
6. Configure other standard website settings (such as TLS certificates) and click save.
### Step 5: Publish & Activate
To allow the gateway's OpenResty instance to match and route domain traffic correctly, we need to publish a new configuration version.
1. Click **"Preview Config"** in the top right corner of the navigation bar to verify the generated configurations.
2. In the popup window, click **"Publish & Activate"**.
3. Now, the public edge Agent pulls the latest routing, forwarding requests for `nas.example.com` to the loopback virtual host port of `openflare-relay (frps)`.
4. The intranet client `openflared (frpc)` receives the relayed packets, securely hands them over to the local `127.0.0.1:8080` service, and returns responses back through the tunnel.
5. Access `nas.example.com` in your browser to confirm that the intranet service displays successfully!
---
## Advanced Application Scenarios
### 1. Single-Tunnel Multi-Service Multiplexing (Multi-Port Mapping)
You do not need to deploy an `openflared` container for every single internal service.
If you want to map multiple different services in the same intranet environment (e.g., `127.0.0.1:80` for a blog, `127.0.0.1:8080` for an API, and `192.168.1.120:9000` for a local network drive):
1. Keep this single `openflared` client online.
2. Create three independent website configurations in the management console (binding their respective public domains).
3. Set the **Upstream Type** to **the same intranet tunnel** for all three website configurations.
4. Fill in their respective "Intranet Target Addresses" (e.g., `127.0.0.1:80`, `127.0.0.1:8080`, and `192.168.1.120:9000`).
5. Publish and activate the new version to achieve single-tunnel multi-service multiplexing.
### 2. Seamless Integration with Gateway Security Features
Since all public traffic enters the public Agent node first, completing the HTTPS/TLS handshake and WAF filtering before traveling through the secure tunnel:
Your intranet services **naturally benefit from the following advanced features without any code changes**:
* **One-Click HTTPS**: Select or issue SSL certificates directly in the management console, encrypting transmission end-to-end.
* **Global/Custom WAF Protections**: Enables SQL injection blocking, XSS prevention, and regional IP filtering.
* **Human-Machine Challenge (PoW CC)**: Instantly blocks brute-force CC API attacks targeting your intranet services.
---
## Common Troubleshooting
### 1. Tunnel Shows as "Offline" in the Management Console
* **Check the Token**: Check if the `tunnel_token` configured in `flared` logs or environment variables matches the one generated in the management console.
* **Check Outbound Connectivity**: The intranet server must be able to make outbound requests to the Server address. Ensure the control plane firewall is not blocking HTTP requests from the client.
* **Relay Firewall Port Closed**: Check if port `7000` (or your custom bindPort) on the public Relay node has been allowed in the public security groups.
### 2. Accessing the Public Domain Returns 502 Bad Gateway / 504 Gateway Timeout
* **Intranet Service Not Running**: Verify that the service corresponding to the intranet target address is running and listening on the intranet server.
* **Target Address Unreachable**: If the intranet address is set to `127.0.0.1:8080`, ensure the service is running on the exact same host as `openflared`; if set to a LAN IP `192.168.x.x`, test connectivity to that IP inside the `openflared` container.
* **Check Client Application Logs**: View the "Apply Logs" in the management console or inspect local `flared` logs for any `LastError`. When frpc fails to connect to the intranet port, it reports the failure details to the Server.
### 3. Multiple Relays Network Instability or Retry Failures
* When the control plane associates multiple Relay nodes, `openflared` spawns independent frpc daemon processes for each Relay and pulls topology states periodically at `sync_interval` (default 30s) configured in `flared.json`.
* If a Relay drops frequently due to network jitter, the system triggers the backoff retry mechanism automatically. You can see `frpc process missing, starting` logs on the host, which is a normal process self-healing action and will recover within 5-10 seconds after network recovery.
+156
View File
@@ -0,0 +1,156 @@
# Basic Usage
You will learn: What website configurations, origins, certificates, versions, nodes, and observability are in OpenFlare, and the recommended sequence of operations during daily usage.
OpenFlare does not directly modify Nginx/OpenResty configurations on nodes online. What you modify in the management console is control plane data; only after publishing and activating a new version will the Agent pull the complete configuration and apply it to the nodes.
## Core Concepts
| Concept | Description |
| --- | --- |
| Website Config | The aggregate object for reverse proxy rules. One website configuration can bind one or more domains. |
| Primary Domain | The first domain in the `domains` list, used as the main display domain for the website. |
| Origin | The upstream address accessed by the reverse proxy, e.g., `http://10.0.0.10:8080`. |
| Config Version | An immutable snapshot of the complete OpenResty configuration generated upon publishing. |
| Active Version | The globally effective configuration version. All nodes consume the same active version by default. |
| Agent | The node-side process responsible for registration, heartbeats, sync, validation, reloads, and rollbacks on failure. |
## Recommended Operation Sequence
When publishing a reverse proxy configuration in daily operations, the following sequence is recommended:
1. Confirm that at least one Agent node is online.
2. Add or select an origin address.
3. Create a website configuration, entering the domain, origin, and site-level configurations.
4. If HTTPS is required, upload or select a certificate and bind it by domain.
5. Preview the configuration or review the change summary.
6. Publish and activate the new version.
7. Verify the application result in the node details and application logs.
## Create Website Configuration
A website configuration requires at least:
| Field | Requirement |
| --- | --- |
| Website Name | Business unique identifier; the primary domain is usually used if left blank |
| Domain | At least one domain, where the first is the primary domain; any domain can belong to only one website globally |
| Origin Address | A valid `http://` or `https://` address |
| Enabled Status | Only enabled website configurations will participate in publishing and rendering |
Example:
| Field | Example |
| --- | --- |
| Website Name | `docs` |
| Domain | `docs.example.com` |
| Origin Address | `http://10.0.0.10:8080` |
| Back-to-source Host | `docs.internal.example.com` |
Upstream Address Rules:
* A single upstream can carry a base path or query, e.g., `https://app.example.com/base?from=openflare`.
* When multiple upstreams are used for load balancing, each upstream must be a pure `scheme://host[:port]`.
* Multiple upstreams in the same rule must use the same protocol.
## Manage Origins
Origins act as a lightweight directory to reuse common upstream addresses. After a website configuration links with an origin, it still stores a renderable snapshot of the `origin_url`, ensuring that historic configuration versions can be re-rendered and rolled back independently.
Recommended Practices:
* Maintain internal service addresses that are frequently reused as Origins.
* After modifying an origin directory, check if published website configurations need their origin snapshots updated.
* Use preview or diff to verify rendering results before publishing.
## Enable HTTPS
HTTPS is bound by domain rather than being forced across the entire website.
Operation Sequence:
1. Upload or host certificates in the Certificate Management section.
2. Edit the website configuration and select certificates for domains requiring HTTPS.
3. Domains without a bound certificate will remain HTTP and will not be automatically placed in a `443 ssl` server block.
4. Publish and activate the new version.
If a website contains multiple domains, the Server groups and renders the HTTPS configuration by certificate during publishing while keeping these domains within the same website snapshot.
## Configure WAF & PoW
Security protection is centrally accessed via the **WAF** link in the side navigation bar:
* The WAF page maintains global and custom rule groups. Global rule groups always apply to all websites; custom rule groups can bind websites directly in the group settings or inside the `WAF` section of the website details.
* Clicking **Manage IP Groups** on the WAF page opens the independent IP Groups section. Manual IP groups store IPs/CIDR blocks directly; automatic IP groups evaluate Expr rules against request logs periodically to update members; subscription IP groups periodically sync from remote text or JSON feeds.
* The Auto IP Group page provides two presets: requests count > 100 and 404 ratio >= 80% from a single IP; or IP-host direct access count > 50 and direct access ratio > 50% from a single IP. You can click **Test Rule** to preview IPs matching the log window before saving, and click **Execute Now** to update the group members instantly after saving. The syntax is detailed in [WAF Auto IP Group Expressions](./waf-ip-group-expr.md).
* In the blacklist/whitelist settings of a WAF rule group, you can add IPs/CIDR blocks directly or reference existing IP groups. The published version snapshot only contains referenced IP group IDs; the Agent synchronizes IP group members via checksum differentials and WebSocket real-time broadcasts.
* `PoW` is a configuration Tab in the rule group, located between `Blacklist/Whitelist` and `Block Interception`. It reuses the site's existing PoW execution logic, allowing current PoW parameters to apply to all websites or only those bound to the current rule group.
* The website details page no longer edits individual PoW rules; it only displays the global WAF rule group and binds custom WAF rule groups. The PoW enablement scopes and rule parameters must be maintained centrally on the WAF pages.
After WAF rule groups, site bindings, or PoW configurations are modified, you must republish and activate the configuration version to let the Agent pull and apply them to OpenResty. IP group member changes do not require a new version publication; online Agents update incrementally via WebSockets, while offline or non-WebSocket Agents synchronize via checksum differentials in the next heartbeat.
For detailed information on WAF security configurations and evaluation principles, see [WAF Security Protection](./waf-usage.md).
## Publish, Activate & Rollback
Standard Pipeline:
```text
Modify config -> Preview / Diff -> Publish -> Generate complete version -> Activate version -> Agent pulls -> Local application -> Report result
```
During publication, the Server reads all enabled website configurations, the main OpenResty config templates, performance and cache parameters, and certificate assets, rendering the complete configuration and calculating its `checksum`.
Rolling back does not modify historic versions; it simply re-activates an older version. Once the Agent detects a change in the active version, it pulls and applies it following the standard sync flow.
## View Nodes & Observability
The Nodes section is designed to answer three questions:
| Question | Where to check |
| --- | --- |
| Is the node online? | Node List or Node Details |
| Which version is currently running? | Current Version in Node Details |
| Did the most recent application succeed? | Application Logs |
The node IP is automatically filled by Agent registration and heartbeats by default. If you manually enter or modify the IP in the management console, the node edit page defaults to "Lock Node IP"; when enabled, Agent reports will not override this IP. Disabling the lock restores auto-update logic in the next heartbeat or WebSocket state report.
Traffic Analytics and Resource Snapshots provide basic observability. OpenFlare only retains access details within a controlled time window, and is not positioned as a general logging platform. If you require long-term log indexing, integrate an independent logging system.
## Common Scenarios
### Add a Reverse Proxy for an Internal Service
1. Verify that the origin service is reachable from the Agent node.
2. Add a website configuration in the management console.
3. Enter the domain, e.g., `app.example.com`.
4. Enter the origin, e.g., `http://10.0.0.20:8080`.
5. Publish and activate the version.
6. Verify the domain on the Agent node or from a browser.
> [!TIP]
> If your origin server is deployed internally without a public IP and is unreachable by the Agent, use the intranet penetration tunnel feature to map your service. For detailed instructions, see [Tunnel & Intranet Penetration](./tunnel-usage.md).
### Enable HTTPS for an Existing Domain
1. Prepare a certificate covering the domain.
2. Upload or create a certificate record in Certificate Management.
3. Edit the website configuration and select the certificate for the domain.
4. Publish and activate the version.
5. Verify the certificate chain and status code in a browser or via `curl -I https://your-domain`.
### Roll Back a Failed Publication
1. Open the Configuration Versions page.
2. Locate the last known good version.
3. Re-activate that version.
4. Check the node application logs to verify that the Agent applied the old version.
5. Fix the configuration issues before publishing a new version.
## Recommended Practices
* Explicitly configure `SESSION_SECRET` and prefer PostgreSQL in production.
* Review the preview or diff after modifying a website configuration before publishing.
* Check the node details and application logs after every publication.
* Maintain a stable network path from Agent to Server in multi-node deployments.
* Never manually modify OpenResty configurations managed by OpenFlare on the node; these files will be overwritten in the next publication.
+161
View File
@@ -0,0 +1,161 @@
# WAF Auto IP Group Expressions
Automatic IP groups are used to aggregate metrics from request logs on a per-client-IP basis, using Expr expressions to determine if an IP should be added to the group. Automatic IP groups can be referenced by IP blacklists or whitelists in WAF rule groups; during publication, the Server only writes the referenced IP group ID to `waf_config.json`, while IP group members are synchronized independently by the Agent into the local runtime files.
## Configuration Structure
The configuration of an automatic IP group is a JSON object:
```json
{
"lookback_minutes": 60,
"rules": [
{
"name": "Single IP High-Frequency 404 Scanning",
"expr": "request_count > 100 && status_404_ratio >= 0.8"
}
]
}
```
Field Descriptions:
| Field | Type | Role |
| --- | --- | --- |
| `lookback_minutes` | number | How many minutes of request logs to look back during execution. Defaults to 60 minutes if blank, minimum 5 minutes, maximum 43200 minutes. |
| `rules` | array | List of automatic rules. If any rule matches, the IP is added to the automatic IP group list. |
| `rules[].name` | string | Rule name, used only for UI display and error messages. |
| `rules[].expr` | string | Expr expression, must return a boolean value. |
## Evaluation Mechanics
Automatic rules do not evaluate logs request-by-request, but instead aggregate them by client IP first:
1. The Server reads request logs from the past `lookback_minutes` minutes.
2. Groups them by normalized IP (`remote_addr`).
3. Computes metrics like request count, 404 count, and direct IP host count for each IP.
4. Evaluates `rules[].expr` for each IP.
5. If an IP matches any rule, it is written to the automatic IP group's IP member list.
Whether a request is "accessing via IP directly" is determined by the `Host` field in the request logs. If the Host header is an IPv4 or IPv6 literal (e.g., `203.0.113.10`, `[2001:db8::10]`, `203.0.113.10:443`), it is counted in `ip_host_count`.
## Available Metrics
The following metrics are directly available in Expr expressions:
| Keyword | Type | Role |
| --- | --- | --- |
| `ip` | string | The client IP currently being evaluated. |
| `request_count` | number | Total request count of the IP in the lookback window. |
| `status_404_count` | number | Number of 404 responses returned to the IP in the lookback window. |
| `status_404_ratio` | number | 404 request ratio, calculated as `status_404_count / request_count`. |
| `ip_host_count` | number | Number of requests from the IP using an IP address directly as the Host header. |
| `ip_host_ratio` | number | Ratio of direct IP address accesses, calculated as `ip_host_count / request_count`. |
| `client_error_count` | number | Number of requests returning 4xx status codes. |
| `server_error_count` | number | Number of requests returning 5xx status codes. |
| `last_seen_unix` | number | Unix timestamp (in seconds) of the last request from the IP in the lookback window. |
All ratio fields are decimals between `0` and `1`. An 80% ratio should be written as `0.8`, and 50% as `0.5`.
## Common Expr Syntax
Automatic IP groups use the Expr syntax. The expression must return a boolean value.
Common Operators:
| Operator | Role | Example |
| --- | --- | --- |
| `>`, `>=`, `<`, `<=` | Numeric comparison | `request_count > 100` |
| `==`, `!=` | Equality / Inequality | `ip != "127.0.0.1"` |
| `&&` | Logical AND | `request_count > 100 && status_404_ratio >= 0.8` |
| `||` | Logical OR | `status_404_ratio >= 0.8 || server_error_count > 20` |
| `!` | Logical NOT | `!(ip == "127.0.0.1")` |
| `in` | Value is in list | `ip in ["203.0.113.10", "198.51.100.20"]` |
| `not in` | Value is not in list | `ip not in ["127.0.0.1"]` |
| `()` | Grouping controls operator priority | `(request_count > 100 && status_404_ratio >= 0.8) || server_error_count > 50` |
## Built-in Presets
The management console provides two built-in preset rules that can be added directly and adjusted as needed:
```json
{
"name": "Single IP High-Frequency 404 Scanning",
"expr": "request_count > 100 && status_404_ratio >= 0.8"
}
```
Meaning: A single IP requests more than 100 times in the lookback window, and the 404 status code ratio is at least 80%.
```json
{
"name": "Single IP Direct IP Access Mismatch",
"expr": "ip_host_count > 50 && ip_host_ratio > 0.5"
}
```
Meaning: A single IP accesses the server directly using an IP address as the Host header more than 50 times, and this type of access represents more than 50% of its total requests.
## Examples
High-frequency 404 scanning:
```json
{
"lookback_minutes": 60,
"rules": [
{
"name": "High-Frequency 404 Scanning",
"expr": "request_count > 100 && status_404_ratio >= 0.8"
}
]
}
```
Direct IP access mismatch:
```json
{
"lookback_minutes": 30,
"rules": [
{
"name": "Direct IP Access Mismatch",
"expr": "ip_host_count > 50 && ip_host_ratio > 0.5"
}
]
}
```
Capture both high 4xx and 5xx errors:
```json
{
"lookback_minutes": 120,
"rules": [
{
"name": "Abnormal Error Rates",
"expr": "(client_error_count > 80 && request_count > 100) || server_error_count > 30"
}
]
}
```
Exclude trusted IPs:
```json
{
"lookback_minutes": 60,
"rules": [
{
"name": "404 Scanning Excluding Trusted IPs",
"expr": "ip not in [\"203.0.113.10\", \"198.51.100.20\"] && request_count > 100 && status_404_ratio >= 0.8"
}
]
}
```
## Usage Recommendations
Start with a shorter lookback window and higher thresholds to monitor matches, then adjust thresholds gradually. The IP Groups page in the management console allows you to click **"Test Rule"** before saving to view matching IPs in the current window immediately. Once an automatic IP group runs, it overwrites the list of IPs. If you want to permanently whitelist or blacklist certain IPs, add them to a manual IP group instead, and reference both manual and automatic groups in your WAF rule groups.
Updating automatic IP groups does not require publishing configuration versions. Online Agents receive changes via WebSocket and update the local `waf_ip_groups.json` instantly. If WebSocket is unavailable, the Agent reports its local checksum in heartbeats, and the Server syncs only the mismatched IP groups.
+162
View File
@@ -0,0 +1,162 @@
# WAF Security Protection
You will learn: How the OpenFlare edge Web Application Firewall (WAF) works, its protection dimensions, how to manage and reference the three types of IP groups (Manual, Subscription, and Expr-based Automatic IP groups), configure CC protection challenges (PoW human-machine verification) and regional filtering, and achieve sub-second hot updates of IP group members without Nginx reloads.
---
## Core Concepts
Before configuring security policies, you need to understand the core components of the WAF:
| Concept | Description | Scope & Activation Method |
| --- | --- | --- |
| **WAF Rule Group (Rule Group)** | A logical collection of security rules, including: IP whitelists/blacklists (direct input or IP group references), country/region limits, CC protection (PoW), and custom block responses. | Supports global enablement or binding to single/multiple websites. **Modifying rule group definitions requires publishing and activating a configuration version**. |
| **IP Group (IP Group)** | A list container storing individual IPs or CIDR blocks. Divided into **Manual**, **Subscription**, and **Automatic** types. WAF rule groups reference IP groups by ID. | Belongs to dynamic resources. **IP group member updates support sub-second WebSocket hot-syncing, completely bypassing Nginx process reloads**. |
| **PoW Challenge (CC PoW)** | A human-machine verification challenge based on Proof of Work. By prompting browsers to solve hash collisions of a specified difficulty, it silently blocks malicious brute-force scripts and bots while keeping legitimate user experience smooth. | A configuration Tab in the rule group. **Modifying PoW parameters requires publishing and activating a configuration version**. |
---
## Recommended Configuration Sequence
When configuring security protections for your websites, we recommend doing so in the following order:
1. Navigate to IP Groups, creating the required **Manual IP Groups** (e.g., developer whitelist) or **Automatic IP Groups** (e.g., auto-blocked IPs based on 404 scans).
2. Create or edit a **WAF Rule Group**:
* Bind the IP groups you want to reference or block.
* Configure regional whitelists/blacklists for countries or provinces.
* (Optional) Configure human-machine challenge parameters in the `PoW` Tab.
* Set custom status codes (e.g., 403, 418) and HTML block pages in the `Block Response` Tab.
3. Associate the rule group with the corresponding **Website Configuration**.
4. Publish and activate the configuration version to let the edge node (Agent) apply the WAF rules to filter traffic.
---
## Detailed Step Guide
### Step 1: Manage and Configure IP Groups
IP groups are the foundations of large-scale IP filtering. OpenFlare provides three highly resilient types of IP groups:
#### 1. Manual IP Groups (Manual)
* **Purpose**: Statically maintain a list of verified trusted IPs or long-term blocked IPs/CIDR blocks.
* **Configuration**: Click "Create IP Group" -> select type "Manual" -> enter IPs or CIDRs line-by-line (e.g., `192.168.1.100` or `10.0.0.0/24`).
#### 2. Subscription IP Groups (Subscription)
* **Purpose**: Integrate third-party threat intelligence databases or IP ranges published by cloud providers.
* **Configuration**: Select type "Subscription" -> enter fetch URL (supports line-separated plain text or standard JSON formats). A background cron job on the Server periodically pulls the subscription source and updates the group members automatically.
#### 3. Automatic IP Groups (Automatic)
* **Purpose**: **The most aggressive automated defense channel against scans and brute-force attacks**.
* **Configuration**: Select type "Automatic" -> write Expr log aggregation logic. You can directly select built-in presets:
* **Single IP High-Frequency 404 Scanning**: `request_count > 100 && status_404_ratio >= 0.8` (A single IP requesting over 100 times in the past hour with a 404 response ratio of at least 80%).
* **Single IP Direct IP Access Mismatch**: `ip_host_count > 50 && ip_host_ratio > 0.5` (Bypassing domains to hit the server directly using IP address host headers).
* **Test & Run**: Click **"Test Rule"** before saving to preview IPs matching the current log window. Click **"Execute Now"** after saving to aggregate logs immediately and generate the block list.
> [!TIP]
> For the detailed syntax and available metrics of automatic IP groups, see [WAF Auto IP Group Expressions](./waf-ip-group-expr.md).
---
### Step 2: Create and Configure a WAF Rule Group
1. Navigate to the **"WAF"** section in the side menu, and click **"Create Rule Group"**.
2. Enter the rule group name (e.g., `production-api-shield`), and select if it is a "Global Rule Group".
3. Enter rule group details, and configure the tabs sequentially below:
#### 1. Whitelist / Blacklist Configuration (Allow / Block Lists)
* **Direct IPs**: Enter individual IPs or CIDR blocks line-by-line that need temporary whitelisting or blacklisting directly in the text area.
* **IP Group Reference**: Click "Bind IP Groups", selecting the manual, automatic, or subscription IP groups you configured in Step 1. Whitelists permit traffic instantly, whereas blacklists block it.
#### 2. Regional Restriction (GeoIP)
* **Description**: OpenFlare integrates GeoIP geolocation resolution.
* **Configuration**: Toggle the regional restriction switch, selecting "Allow Only" or "Block".
* * For example, if your service is only intended for domestic users, set the mode to "Allow Only" and check `China` in the country list.
* * Supports refining to specific provinces/regions, enabling you to block malicious traffic originating from targeted geographic zones with one click.
#### 3. Human-Machine Challenge Configuration (PoW CC Protection)
* **Description**: Enable CC protection human-machine challenges. When a request triggers the CC protection threshold, the browser renders a silent challenge page, solving a mathematical challenge (hash collision) within several hundred milliseconds. Upon passing, it sets a Cookie and allows subsequent visits. This is seamless to actual users but blocks brute-force scripts and CC tools that do not support JS execution or mathematical computations.
* **Core Parameters**:
* **Status**: Enable / Disable.
* **Hash Difficulty**: Controls the computation difficulty (recommending `4` or `5`).
* **Cookie Expiration**: How long the verification remains valid after passing (e.g., `3600` seconds).
* **Custom Challenge HTML**: Customize the Loading page style of the challenge to match your business design.
#### 4. Block Response (Block Response)
* **Description**: Define the behavior of the WAF when blocking malicious requests.
* **Configuration**:
* **Block Status Code**: Customize the HTTP status code returned, e.g., the standard `403` or a fun `418 (I'm a teapot)`.
* **Block Response Body**: Input custom HTML content shown to blocked attackers (e.g., "WAF Interception: Your request has been logged").
---
### Step 3: Associate the Rule Group with Websites
Once configured, the rule group does not automatically take effect; you need to bind it to specific website configurations.
* **Option A (Recommended)**: In the **"Bind Websites"** Tab of the rule group details, select the websites you wish to apply this rule group to and save.
* **Option B**: Return to **"Website Configuration"**, edit a specific website, and check and bind the rule group in the "Security Protection" section.
> [!NOTE]
> If a rule group is marked as **"Global Rule Group (is_global)"**, it applies to **all websites** hosted on the gateway automatically, requiring no manual binding.
---
### Step 4: Publish & Activate Configurations
1. If you modify **rule group definitions**, **GeoIP scopes**, **PoW CC difficulties**, or **website-to-rule-group bindings**:
* Click **"Preview Config"** -> **"Publish & Activate"** in the top right corner.
* Once the Agent pulls and validates the new version, it rewrites local core OpenResty config files (`waf_config.json`, etc.) and gracefully reloads the processes to apply the policies.
2. If you only update **IP group members** (e.g., adding/deleting an IP in a manual IP group, or an automatic IP group aggregates a new set of blocked IPs periodically):
* **No publication or activation is required!**
* The Server calculates the new MD5 Checksum of the IP group immediately after updating the database.
* The control plane **broadcasts the modified IP group members in real-time to all online Agents via WebSocket**. The Agent overwrites the runtime local disk file `waf_ip_groups.json` incrementally.
* The OpenResty Lua engine calculates the file hash in microseconds when processing new requests. If it detects a Checksum change, it reloads it into the memory dictionary (`ngx.shared`) in real-time. **This entire process requires absolutely no Nginx service reloads, having zero impact on online high-concurrency operations**.
* Even if the WebSocket connection drops, the Agent reports its local Checksum in every heartbeat cycle, and the Server syncs the differential updates to guarantee synchronization.
---
## WAF Evaluation Flow (Filtering Funnel)
When an external request reaches the OpenResty data plane, the WAF runtime evaluates it in the `access` phase according to the funnel decision chain below. Once a match is made, evaluation terminates:
```text
Request enters access phase
│
v
Get all active rule groups bound to this site (Global + Bound Custom groups)
│
v
1. Matches IP whitelist / Whitelist IP group? ──────(Yes)─────► [ Allow (ALLOW) ]
│ (No)
v
2. Matches country / province whitelist? ────────(Yes)─────► [ Allow (ALLOW) ]
│ (No)
v
3. Matches IP blacklist / Blacklist IP group? ──────(Yes)─────► [ Block (BLOCK) ] ──► Return status & HTML block page
│ (No)
v
4. Matches country / province blacklist? ────────(Yes)─────► [ Block (BLOCK) ] ──► Return status & HTML block page
│ (No)
v
5. Is PoW CC protection enabled for this site?
├───(Yes)───► [ Validate PoW Cookie ] ──(Passed)──► [ Allow (ALLOW) ]
│ │
│ (Not Passed)
│ v
│ [ Render PoW Challenge ] ──(Solved)──► Set Cookie & Allow
v
6. No rules triggered, legitimate traffic ─────────────────────► [ Allow (ALLOW) ]
```
---
## Best Practices & Tuning Recommendations
* **Whitelist Precedence & Protection**: Before deploying strict blacklists or regional blocks, we strongly recommend creating a "Trusted IP Group" containing your team's office egress IPs, local development IPs, and third-party callback server IPs (e.g., WeChat or Alipay payment callback addresses), and prioritizing it in the rule group's **whitelist**. This effectively prevents accidental blockages.
* **Reasonably Fine-tune PoW Difficulty**: Human-machine CC challenge hash difficulty (`challenge_difficulty`) is a double-edged sword:
* Difficulty `3`: Computes almost instantly, providing low protection.
* Difficulty `4`: Normal phones/low-end browsers solve it in 100-300ms, providing good protection.
* Difficulty `5`: Requires 500ms-2s, providing strong protection but low-end client browsers might perceive slight loading delays.
* Difficulty `6` and above: Computes exponentially slower, easily freezing client browser CPUs. **We strongly recommend choosing `4` or `5` in production**.
* **Utilize "Test Rule"**: For automatic IP groups, always click **"Test Rule"** before saving. By inspecting the list of matching IPs in the current window, verify if your Expr expressions thresholds (such as request counts, 404 ratios, etc.) are too broad or too strict, preventing accidental blockages of legitimate users.
* **Isolate Static & Dynamic Blacklists**: Never enter static malicious IPs that require permanent blocks directly into automatic IP groups (since the aggregated list will be overwritten in the next cron cycle). You should add permanent malicious IPs into a dedicated "Manual Blacklist IP Group" and reference both the manual and automatic groups in your rule groups.
+35
View File
@@ -0,0 +1,35 @@
---
layout: home
hero:
name: OpenFlare
text: Open-source CDN Orchestration & Edge Security Platform
tagline: Supports reverse proxy, centralized configuration synchronization, secure intranet penetration (Tunnels), dynamic WAF protection, and anti-CC challenges.
actions:
- theme: brand
text: Quick Start
link: /en/guide/quick-start
- theme: alt
text: Design Boundaries
link: /en/design/
- theme: alt
text: GitHub
link: https://github.com/Rain-kl/OpenFlare
features:
- icon: 🛰️
title: Centralized Config Sync
details: Sync configurations across all nodes in real time via WebSockets and heartbeats with sub-second hot reload. Instantly retrieve alerts and statuses.
- icon: 🌐
title: Distributed CDN Orchestration
details: Orchestrate scattered and independent OpenResty nodes into a highly collaborative CDN fleet with website-level multi-domain aggregation and load balancing.
- icon: 🚇
title: Secure Intranet Penetration (Tunnels)
details: An open-source alternative to Cloudflare Tunnels. Expose local intranet services securely to the public network without a public IP or open inbound ports.
- icon: 🛡️
title: Edge WAF Protection
details: Dynamic WAF rules with differential syncing of IP groups to Lua shared memory without Nginx reloads, plus country-level regional access control.
- icon: 🧩
title: Anti-CC & Bot Defense (PoW)
details: Built-in high-performance client-side cryptographic Proof of Work challenges (similar to Turnstile) to intercept botnets and scrapers at the edge.
---
+155
View File
@@ -0,0 +1,155 @@
# API Conventions
You will learn: The response structure, path conventions, authentication methods, and Swagger entrance for the OpenFlare Admin API and Agent API.
Both the OpenFlare Admin API and Agent API communicate using JSON.
## Response Structure
Both successful and failed API responses must return a clear `message`:
```json
{
"success": true,
"message": "",
"data": {}
}
```
## Path Conventions
| Category | Convention |
| --- | --- |
| Admin API | Authenticated via the Admin Session |
| Agent API | Located strictly under `/api/agent/*` |
| Relay API | Located strictly under `/api/relay/*`, authenticated via `X-Agent-Token` (reusing the Agent's token) |
| OpenFlared API | Located strictly under `/api/flared/*`, authenticated via `X-Tunnel-Token` (dedicated tunnel_token) |
| Read-only APIs | Use the `GET` method |
| Mutating APIs | Use the `POST` method |
## WAF IP Group APIs
The Admin WAF IP Group APIs require Admin Session authentication:
| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/api/waf/ip-groups` | Query IP groups list |
| `GET` | `/api/waf/ip-groups/:id` | Query a single IP group |
| `POST` | `/api/waf/ip-groups` | Create a new IP group |
| `POST` | `/api/waf/ip-groups/test` | Test automatic IP group Expr rules; returns matching IPs in the lookback window without persisting the config |
| `POST` | `/api/waf/ip-groups/:id/update` | Update an existing IP group |
| `POST` | `/api/waf/ip-groups/:id/delete` | Delete an IP group; denied if currently referenced by any rule group |
| `POST` | `/api/waf/ip-groups/:id/sync` | Manually sync subscription IP groups or execute automatic IP group aggregation |
The IP group `type` supports `manual`, `automatic`, and `subscription`. The `auto_config` parameter for automatic IP groups is a JSON object:
```json
{
"lookback_minutes": 60,
"rules": [
{
"name": "Single IP High-Frequency 404 Scanning",
"expr": "request_count > 100 && status_404_ratio >= 0.8"
},
{
"name": "Single IP Direct IP Access Mismatch",
"expr": "ip_host_count > 50 && ip_host_ratio > 0.5"
}
]
}
```
Automatic rules evaluate Expr boolean expressions against metrics aggregated on a per-client-IP basis. The available metrics include `ip`, `request_count`, `status_404_count`, `status_404_ratio`, `ip_host_count`, `ip_host_ratio`, `client_error_count`, `server_error_count`, and `last_seen_unix`. The full syntax is detailed in [WAF Auto IP Group Expressions](../guide/waf-ip-group-expr.md).
Subscription formats support `text` and `json`: plain text parsing resolves one IP or CIDR per line, ignoring empty lines and comments starting with `#`; JSON parsing decodes arrays, reading the root array by default.
## Authentication
The Admin panel continues to reuse the existing login, role, and Session validation.
Agent requests must carry the node-specific `agent_token` (except for first-time registration, which can use the global `discovery_token`). The header is formatted as:
```http
X-Agent-Token: <token>
```
### Agent WAF IP Group Synchronization
The Agent heartbeat payload can carry local WAF IP group checksums:
```json
{
"waf_ip_group_checksums": {
"1": "sha256..."
}
}
```
The Server evaluates the checksums against active configurations, returning mismatched IP groups in the heartbeat response:
```json
{
"waf_ip_groups": [
{
"id": 1,
"name": "Auto Blacklist",
"type": "automatic",
"enabled": true,
"ip_list": ["203.0.113.10"],
"checksum": "sha256..."
}
]
}
```
Alternatively, the Agent can proactively request differential updates upon applying a new configuration version:
| Method | Path | Description |
| --- | --- | --- |
| `POST` | `/api/agent/waf/ip-groups/sync` | Returns mismatched WAF IP groups based on Agent-supplied `ids` and `checksums` |
When an IP group is updated on the Server, connected Agents receive a WebSocket push containing `type = "waf_ip_groups"` with the changed IP groups array as payload. The Agent updates only the changed groups incrementally.
## OpenFlared API
The OpenFlared client communicates with the Server via a dedicated `tunnel_token`, completely decoupled from the Agent authentication system. All endpoints require `X-Tunnel-Token` authentication; requests are denied with `403` if the token is invalid.
| Method | Path | Description |
| --- | --- | --- |
| `POST` | `/api/flared/heartbeat` | Client heartbeat, updates online status and retrieves active tunnel config version summaries |
| `GET` | `/api/flared/config/active` | Pulls the complete tunnel routing configuration (relay list + frpc proxy definitions) |
| `POST` | `/api/flared/apply-log` | Reports configuration application results (success / warning / failed) |
| `GET` | `/api/flared/ws` | Upgrades to a WebSocket connection for real-time `active_config` pushes |
Heartbeat request example:
```http
POST /api/flared/heartbeat
X-Tunnel-Token: <tunnel_token>
Content-Type: application/json
{
"client_version": "v0.2.0",
"frp_version": "0.61.0",
"tunnel_status": "running",
"connected_relays": [
{ "relay_node_id": "node-relay-1", "status": "healthy", "proxy_count": 3 }
],
"current_version": "v1",
"current_checksum": "sha256..."
}
```
The heartbeat response returns the `active_config` summary and `tunnel_settings` (containing runtime settings like heartbeat intervals and WebSocket upgrade switches). When a new configuration version is published, the Server broadcasts a message `type = "active_config"` with the version summary as payload to all connected Clients over WebSockets, prompting them to fetch and apply the config immediately.
Full tokens must never be logged.
## Swagger
Once logged into the management console, the Swagger page is accessible at:
```text
/swagger/index.html
```
The Swagger definition file is stored in `openflare_server/docs`, generated by `swag init`.
+149
View File
@@ -0,0 +1,149 @@
# CLI Commands
You will learn: Common commands for starting, building, testing, installing, and uninstalling the OpenFlare Server, Admin Frontend, Agent, Swagger, and Documentation site.
## Server
Start from source:
```bash
cd openflare_server
export SESSION_SECRET='replace-with-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
go run .
```
Specify listening port and logging directory:
```bash
go run . --port 3000 --log-dir ./logs
```
Run tests:
```bash
cd openflare_server
GOCACHE=/tmp/openflare-go-cache go test ./...
```
## Frontend
Development:
```bash
cd openflare_server/web
pnpm install
pnpm dev
```
Build static assets:
```bash
cd openflare_server/web
pnpm build
```
Linting and testing checks:
```bash
cd openflare_server/web
pnpm lint
pnpm typecheck
pnpm test
```
## Agent
Run from source:
```bash
cd openflare_agent
go run ./cmd/agent -config /path/to/agent.json
```
Compile:
```bash
cd openflare_agent
go build -o openflare-agent ./cmd/agent
```
Run tests:
```bash
cd openflare_agent
GOCACHE=/tmp/openflare-go-cache go test ./...
```
## Relay (Server-side)
Run from source:
```bash
cd openflare_relay
go run ./cmd -config /path/to/relay.json
```
Compile:
```bash
cd openflare_relay
go build -o openflare-relay ./cmd
```
## OpenFlared (Client-side)
Run from source:
```bash
cd openflared
go run ./cmd -config /path/to/flared.json
```
Compile:
```bash
cd openflared
go build -o openflared ./cmd
```
## Install Agent
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
## Uninstall Agent
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash
```
## Swagger
Regenerate Swagger documentation:
```bash
go install github.com/swaggo/swag/cmd/swag@v1.16.4
cd openflare_server
swag init -g main.go -o docs
```
## Docs
Local preview:
```bash
cd docs
pnpm dev
```
Build:
```bash
cd docs
pnpm build
```
+370
View File
@@ -0,0 +1,370 @@
# Configuration Options
You will learn: What configuration sources are supported by OpenFlare Server, frontend builds, and Agents; what the default configuration values are; and how to configure common deployment combinations.
This document aggregates the currently supported configuration options for OpenFlare Server and Agent in version `1.0.0`, keeping only running parameters that are currently active.
## Configuration Sources
The Server supports three types of configuration sources:
1. CLI arguments.
2. Environment variables.
3. Runtime configurations in the database `options` table.
The Agent supports:
1. The `-config` CLI argument.
2. The `agent.json` configuration file.
3. A small set of environment variables for overriding logs and settings.
The Relay (Server-side) supports:
1. The `-config` CLI argument.
2. The `relay.json` configuration file.
3. Persistent environment variables for overriding runtime flags.
The Client (Intranet Client) supports:
1. The `-config` CLI argument.
2. The `flared.json` configuration file.
3. Startup overrides and logging environment variables.
## Configuration File Locations
| Component | Default Location | Description |
| --- | --- | --- |
| Server SQLite | `openflare.db` | Can be customized via `SQLITE_PATH` |
| Agent Config | `./agent.json` | Can be specified via `-config` |
| One-Click Agent | `/opt/openflare-agent/agent.json` | Generated by the installation script by default |
| Agent Data Dir | `data` in the config folder | Can be customized via `data_dir` |
| Relay Config | `./relay.json` | Can be specified via `-config` |
| One-Click Relay | `/opt/openflare-relay/relay.json` | Generated by the installation script by default |
| Client Config | `./flared.json` | Can be specified via `-config` |
| One-Click Client | `/opt/openflared/flared.json` | Generated by the installation script by default |
## Server CLI Arguments
```bash
cd openflare_server
go run . --port 3000 --log-dir ./logs
```
| Argument | Description | Default Value |
| --- | --- | --- |
| `--port` | Port the Server listens on | `3000` |
| `--log-dir` | Directory to output logs | Empty (stdout) |
| `--version` | Outputs current version and exits | `false` |
| `--help` | Outputs help information and exits | `false` |
## Server Environment Variables
| Environment Variable | Description | Default Value |
| --- | --- | --- |
| `PORT` | Port the Server listens on | `3000` |
| `GIN_MODE` | Gin framework running mode | Defaults to release unless `debug` |
| `LOG_LEVEL` | Logging level | `info` |
| `SESSION_SECRET` | Session signing key | Randomly generated on startup |
| `SQLITE_PATH` | SQLite database file path | `openflare.db` |
| `DSN` | PostgreSQL DSN (takes precedence over SQLite) | Empty |
| `SQL_DSN` | Legacy PostgreSQL DSN (lower priority than `DSN`) | Empty |
| `REDIS_CONN_STRING` | Redis connection string | Empty |
| `AGENT_TOKEN` | Legacy global Agent Token | Empty |
Notes:
* If both `DSN` and `SQL_DSN` exist, `DSN` is prioritized.
* If either `DSN` or `SQL_DSN` coexist with `SQLITE_PATH`, PostgreSQL is prioritized.
* If the target PostgreSQL database is empty and a local SQLite file exists at `SQLITE_PATH`, the Server automatically migrates SQLite data table-by-table on startup.
* `SESSION_SECRET` must be explicitly configured in production.
* If `REDIS_CONN_STRING` is unconfigured, co-located features fall back to in-memory implementations.
## Runtime Options
The following options are maintained in the admin settings page and support hot reloading:
| Parameter | Description | Default Value |
| --- | --- | --- |
| `AgentHeartbeatInterval` | Heartbeat interval for Agents (ms) | `10000` |
| `AgentWebsocketUpgradeEnabled` | Toggles WebSocket upgrades after successful HTTP heartbeat | `true` |
| `NodeOfflineThreshold` | Threshold duration to mark a node offline (ms) | `120000` |
| `AgentUpdateRepo` | GitHub repository for Agent self-updates | `Rain-kl/OpenFlare` |
| `GeoIPProvider` | Geolocation resolution provider | `ipinfo` |
| `DatabaseAutoCleanupEnabled` | Toggles daily automatic cleanup of observability logs | `false` |
| `DatabaseAutoCleanupRetentionDays` | Data retention duration in days, minimum 1 day | `30` |
| `GlobalApiRateLimitNum` / `GlobalApiRateLimitDuration` | Global API rate limit count / window | `300` / `180` |
| `GlobalWebRateLimitNum` / `GlobalWebRateLimitDuration` | Global Web rate limit count / window | `300` / `180` |
| `CriticalRateLimitNum` / `CriticalRateLimitDuration` | Sensitive API rate limit count / window | `100` / `1200` |
Notes:
* When `DatabaseAutoCleanupEnabled` is enabled, the Server deletes `node_access_logs`, `node_metric_snapshots`, and `node_request_reports` daily at 3:00 AM.
* `DatabaseAutoCleanupRetentionDays` must be greater than or equal to 1.
* Leaving retention days blank during a manual trigger in the console deletes all historic logs instantly.
* The GitHub Release in `AgentUpdateRepo` must contain a matching `.sha256` checksum file for every Agent binary (e.g., `openflare-agent-linux-amd64.sha256`); the Agent validates this checksum before replacing the local executable.
* Third-party logins no longer use `GitHubOAuthEnabled`, `GitHubClientId`, and `GitHubClientSecret` as main configuration entrypoints; these legacy options are used only for migrating default GitHub credentials during upgrades.
* The legacy WeChat login options are kept for backward compatibility, but the option page no longer edits them.
* Legacy Cloudflare Turnstile options and validation logic are retained and will work normally.
## OpenResty Parameters
OpenResty performance and caching parameters are managed in the `options` table, including:
* `OpenRestyWorkerProcesses`
* `OpenRestyWorkerConnections`
* `OpenRestyWorkerRlimitNofile`
* `OpenRestyKeepaliveTimeout`
* `OpenRestyProxyConnectTimeout`
* `OpenRestyProxySendTimeout`
* `OpenRestyProxyReadTimeout`
* `OpenRestyProxyBufferingEnabled`
* `OpenRestyGzipEnabled`
* `OpenRestyCacheEnabled`
* `OpenRestyCachePath`
* `OpenRestyCacheMaxSize`
These parameters must be validated, saved, and rendered structurally.
Constraints:
* The console no longer exposes `resolver` settings.
* Upstreams are rendered uniformly as named `upstream` blocks with keepalive enabled.
* Single upstreams carrying a base path or query have their URI correctly appended in `proxy_pass`.
* Multi-upstreams must be pure `scheme://host[:port]` using the same protocol within a single rule.
* `OpenRestyCacheEnabled` enables cache infrastructure and global defaults; the actual caching matching policies (by URL, suffix, or path) are configured per `proxy_routes`.
* The default cache key is `$scheme$host$request_uri`.
* Default `keepalive_timeout` is `20` seconds; default `proxy_connect_timeout` is `3` seconds.
* The default event model is `epoll` with `multi_accept` enabled.
* HTTPS listeners use the independent `http2 on;` directive to avoid deprecation warnings for `listen ... http2` in newer Nginx/OpenResty versions.
## Frontend Build Environment Variables
| Environment Variable | Description | Default Value |
| --- | --- | --- |
| `NEXT_PUBLIC_API_BASE_URL` | Base path for frontend API calls | `/api` |
| `NEXT_PUBLIC_APP_VERSION` | Application version shown in the UI | `dev` |
| `NEXT_DEV_BACKEND_URL` | Target backend proxied by the local dev server | `http://127.0.0.1:3000` |
## Agent Environment Variables
| Environment Variable | Description | Default Value |
| --- | --- | --- |
| `LOG_LEVEL` | Logging level for the Agent | `info` |
| `OPENFLARE_SERVER_URL` | Server URL; overrides `agent.json` | Empty |
| `OPENFLARE_AGENT_TOKEN` | Node-specific Token; overrides `agent.json` | Empty |
| `OPENFLARE_DISCOVERY_TOKEN` | Auto-registration Token; overrides `agent.json` | Empty |
| `OPENFLARE_NODE_NAME` | Node name; overrides `agent.json` | Empty |
| `OPENFLARE_NODE_IP` | Node IP; overrides `agent.json` | Empty |
| `OPENFLARE_DATA_DIR` | Agent data directory; overrides `agent.json` | Empty |
| `OPENFLARE_OPENRESTY_PATH` | Path to OpenResty binary; overrides `agent.json` | Empty |
| `OPENFLARE_HEARTBEAT_INTERVAL` | Heartbeat interval; overrides `agent.json` | Empty |
| `OPENFLARE_REQUEST_TIMEOUT` | Request timeout; overrides `agent.json` | Empty |
| `OPENFLARE_OPENRESTY_OBSERVABILITY_PORT` | Local observability port; overrides `agent.json` | Empty |
| `OPENFLARE_MMDB_PATH` | WAF GeoIP mmdb path; overrides `agent.json` | Empty |
| `OPENFLARE_MMDB_UPDATE_INTERVAL` | GeoIP mmdb update interval; overrides `agent.json` | Empty |
| `OPENFLARE_MMDB_DOWNLOAD_URL` | GeoIP mmdb download link; overrides `agent.json` | Empty |
## Agent CLI Arguments
| Argument | Description | Default Value |
| --- | --- | --- |
| `-config` | Path to the Agent configuration file | `./agent.json` |
## Agent Configurations Fields
| Field | Description | Required | Default Value / Behavior |
| --- | --- | --- | --- |
| `server_url` | Control plane URL | Yes | None |
| `agent_token` | Node-specific access Token | Mutually exclusive with discovery_token | Empty |
| `discovery_token` | Global auto-registration Token | Mutually exclusive with agent_token | Empty |
| `node_name` | Node name | No | Hostname |
| `node_ip` | Node IP | No | Auto-detect, resolves outbound public IP via realip.cc first, falls back to local adapters |
| `openresty_path` | Path to the OpenResty binary | No | `"openresty"` |
| `openresty_observability_port` | Observability port for health checks | No | `18081` |
| `data_dir` | Agent data directory | No | `data` in the config folder |
| `main_config_path` | Write path for Nginx main configuration | No | `data_dir/etc/nginx/nginx.conf` |
| `route_config_path` | Write path for route configurations | No | `data_dir/etc/nginx/conf.d/openflare_routes.conf` |
| `access_log_path` | Write path for OpenResty access logs | No | `data_dir/var/log/openflare/access.log` |
| `cert_dir` | Write directory for SSL certificates | No | `data_dir/etc/nginx/certs` |
| `openresty_cert_dir` | Read directory for certificates in Nginx | No | Same as `cert_dir` |
| `lua_dir` | Write directory for Lua scripts and assets | No | `data_dir/etc/nginx/lua` |
| `openresty_lua_dir` | Read directory for Lua scripts in Nginx | No | Same as `lua_dir` |
| `runtime_config_dir` | Write directory for Agent runtime configs | No | `data_dir/etc/openflare` |
| `mmdb_path` | WAF GeoIP database file path | No | `data_dir/etc/openflare/GeoLite2-Country.mmdb` |
| `mmdb_update_interval` | WAF GeoIP database check interval | No | `86400000` milliseconds |
| `mmdb_download_url` | WAF GeoIP database download URL | No | Built-in GeoLite2 Country URL |
| `observability_buffer_path` | Buffer path for retry metrics logs | No | `data_dir/var/lib/openflare/observability-buffer.json` |
| `observability_replay_minutes` | Lookback window for metric retries | No | `15` |
| `state_path` | Path to store local state JSON file | No | `data_dir/var/lib/openflare/agent-state.json` |
| `heartbeat_interval` | Heartbeat polling interval | No | `10000` milliseconds |
| `request_timeout` | HTTP request timeout duration | No | `10000` milliseconds |
Notes:
* `agent_token` and `discovery_token` cannot both be empty.
* `heartbeat_interval` and `request_timeout` support integer milliseconds or Go duration strings.
* If `AgentWebsocketUpgradeEnabled` is enabled on the Server, the Agent upgrades the HTTP heartbeat to WebSocket; it automatically falls back to HTTP heartbeats if it fails or disconnects.
* If `openresty_path` is left blank, the Agent calls `openresty` on the host.
* Periodic health checks query `http://127.0.0.1:<openresty_observability_port>/openflare/stub_status` instead of executing `openresty -t`; validation prior to reloads, starts, or rollbacks still runs `openresty -t -c <main_config_path>`.
* The Agent initializes and periodically updates `mmdb_path` to support GeoIP region checks; failures to update write warnings and do not disrupt configuration synchronizations.
* The Agent boots normally if `agent.json` is missing but environment variables (`OPENFLARE_SERVER_URL` and a Token) are available; environment variables override JSON settings.
* If `node_ip` is left blank, the Agent resolves its outbound IP via `https://realip.cc` first, which is suitable for Docker/NAT networks.
* If the Agent registers a private `node_ip`, the Server prioritizes saving the public TCP connection IP, preventing NAT adapters from registering internal IPs.
* Enabling "Lock Node IP" in the console retains the manual IP; subsequent Agent registration or heartbeats do not overwrite it.
## Relay Environment Variables
| Environment Variable | Description | Default Value |
| --- | --- | --- |
| `LOG_LEVEL` | Logging level for the Relay | `info` |
| `OPENFLARE_SERVER_URL` | Server URL; overrides `relay.json` | Empty |
| `OPENFLARE_AGENT_TOKEN` | Node-specific Token; overrides `relay.json` | Empty |
| `OPENFLARE_DISCOVERY_TOKEN` | Auto-registration Token; overrides `relay.json` | Empty |
| `OPENFLARE_NODE_NAME` | Node name; overrides `relay.json` | Empty |
| `OPENFLARE_NODE_IP` | Node IP; overrides `relay.json` | Empty |
| `OPENFLARE_DATA_DIR` | Relay data directory; overrides `relay.json` | Empty |
| `OPENFLARE_FRPS_PATH` | frps binary path; overrides `relay.json` | Empty |
## Relay CLI Arguments
| Argument | Description | Default Value |
| --- | --- | --- |
| `-config` | Path to the Relay configuration file | `./relay.json` |
## Relay Configuration Fields
| Field | Description | Required | Default Value / Behavior |
| --- | --- | --- | --- |
| `server_url` | Control plane URL | Yes | None |
| `agent_token` | Node-specific access Token | Mutually exclusive with discovery_token | Empty |
| `discovery_token` | Global auto-registration Token | Mutually exclusive with agent_token | Empty |
| `node_name` | Node name | No | Hostname |
| `node_ip` | Relay listening IP for tunnel traffic | No | Auto-detect, prioritizes outbound public IP |
| `frps_path` | Path to the `frps` binary | No | `frps` (system PATH) |
| `data_dir` | Relay runtime data directory | No | `data` in the config folder |
| `state_path` | Path to store local state JSON file | No | `data_dir/relay-state.json` |
| `heartbeat_interval` | Heartbeat polling interval | No | `10000` milliseconds, supports Go duration strings |
| `request_timeout` | HTTP request timeout duration | No | `10000` milliseconds, supports Go duration strings |
## OpenFlared (Client) Environment Variables
| Environment Variable | Description | Default Value |
| --- | --- | --- |
| `LOG_LEVEL` | Logging level for the client | `info` |
| `OPENFLARE_SERVER_URL` | Server URL; overrides `flared.json` | Empty |
| `OPENFLARE_TUNNEL_TOKEN` | Tunnel access Token; overrides `flared.json` | Empty |
| `OPENFLARE_DATA_DIR` | Client data directory; overrides `flared.json` | Empty |
| `OPENFLARE_FRPC_PATH` | frpc binary path; overrides `flared.json` | Empty |
## OpenFlared (Client) CLI Arguments
| Argument | Description | Default Value |
| --- | --- | --- |
| `-config` | Path to the client configuration file | `./flared.json` |
## OpenFlared (Client) Configuration Fields
| Field | Description | Required | Default Value / Behavior |
| --- | --- | --- | --- |
| `server_url` | Control plane URL | Yes | None |
| `tunnel_token` | Tunnel dedicated access Token | Yes | None |
| `frpc_path` | Path to the `frpc` binary | No | `frpc` (system PATH) |
| `data_dir` | Client runtime data directory | No | `data` in the config folder |
| `state_path` | Path to store local state JSON file | No | `data_dir/flared-state.json` |
| `heartbeat_interval` | Heartbeat polling interval | No | `10000` milliseconds, supports Go duration strings |
| `sync_interval` | Configuration sync interval | No | `30000` milliseconds, supports Go duration strings |
| `request_timeout` | HTTP request timeout duration | No | `10000` milliseconds, supports Go duration strings |
## Common Configuration Combos
### Production Server + PostgreSQL
```bash
export SESSION_SECRET='replace-with-a-long-random-string'
export DSN='postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable'
export GIN_MODE='release'
export LOG_LEVEL='info'
```
### Local Server + SQLite
```bash
export SESSION_SECRET='dev-session-secret'
export SQLITE_PATH='./openflare-dev.db'
export LOG_LEVEL='debug'
go run .
```
### Agent + Default OpenResty
```json
{
"server_url": "http://your-server:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "/opt/openflare-agent/data",
"openresty_path": "openresty",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
### Agent + Customized OpenResty Paths
```json
{
"server_url": "http://your-server:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "/var/lib/openflare-agent",
"openresty_path": "/usr/local/openresty/nginx/sbin/openresty",
"main_config_path": "/var/lib/openflare-agent/etc/nginx/nginx.conf",
"route_config_path": "/var/lib/openflare-agent/etc/nginx/conf.d/openflare_routes.conf",
"access_log_path": "/var/lib/openflare-agent/var/log/openflare/access.log",
"cert_dir": "/var/lib/openflare-agent/etc/nginx/certs",
"lua_dir": "/var/lib/openflare-agent/etc/nginx/lua",
"runtime_config_dir": "/var/lib/openflare-agent/etc/openflare",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
### Relay (Server-side) Default Configuration
`relay.json`:
```json
{
"server_url": "http://your-server:3000",
"agent_token": "replace-with-relay-auth-token",
"frps_path": "frps",
"data_dir": "/opt/openflare-relay/data",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
### OpenFlared (Client-side) Default Configuration
`flared.json`:
```json
{
"server_url": "http://your-server:3000",
"tunnel_token": "replace-with-tunnel-token",
"frpc_path": "frpc",
"data_dir": "/opt/openflared/data",
"heartbeat_interval": 10000,
"sync_interval": 30000,
"request_timeout": 10000
}
```
## Maintenance Rules
This document must be updated in sync when any of the following change:
* Server CLI arguments.
* Server environment variables.
* Agent CLI arguments and configuration parameters.
* Relay CLI arguments and configuration parameters.
* Client CLI arguments and configuration parameters.
* Default values, scopes, or examples of any configuration items.
+13
View File
@@ -0,0 +1,13 @@
# Reference Manuals
You will learn: Which information belongs to stable reference manuals, and where to look up configurations, commands, APIs, and repository structures.
This section collects stable information at the runtime, API, and repository layers, suitable for rapid lookup during deployment, integration, and troubleshooting.
| Page | Content |
| --- | --- |
| [Configuration Options](./configuration.md) | Server environment variables, CLI arguments, runtime Options, and Agent configuration parameters |
| [CLI Commands](./cli.md) | Common CLI commands for starting, building, testing, installing, and uninstalling |
| [API Conventions](./api.md) | Response structures, authentication, and routing paths for Admin and Agent APIs |
| [Repository Structure](../design/repository.md) | Scope of responsibilities and folder layering of the Server, Agent, Relay, and Client |
| [Deployment & Upgrade](../deployment/) | Server and Agent deployment, configuration, and upgrade guides (dedicated section) |
-154
View File
@@ -1,154 +0,0 @@
# OpenFlare 前端开发规范
本文档约束 `openflare_server/web` 的正式前端工程。它描述的是 `1.0.0` 之后仍然有效的结构、请求层、组件、样式、状态管理与测试基线。
## 1. 技术基线
默认技术栈:
* Next.js 15 App Router
* React 19
* TypeScript 5
* Tailwind CSS 4
* TanStack Query
* React Hook Form + Zod
* Zustand
* ESLint + Prettier
* Vitest + Testing Library + Playwright
* pnpm
要求:
* 默认使用 TypeScript
* 默认使用函数组件
* 默认使用 App Router
* 前端必须支持 `light`、`dark`、`system` 三种主题模式
禁止:
* 引入 Semantic UI
* 新增大型 UI 框架破坏现有组件基线
* 使用 jQuery 风格 DOM 操作
## 2. 目录与分层
推荐目录:
```text
app/
components/
features/
lib/
hooks/
store/
types/
styles/
tests/
```
职责约束:
* `app/`:路由、布局、页面组装
* `features/`:按业务域组织模块
* `components/`:跨 feature 复用组件
* `lib/`:请求客户端、环境变量、工具函数、常量
* `store/`:少量跨页面 UI 状态
* `types/`:共享类型定义
## 3. 路由与页面
页面文件只负责:
* 获取路由参数
* 组织页面结构
* 调用 feature 组件
页面不应负责:
* 手写复杂 API 细节
* 编写复杂表单校验逻辑
* 维护大量彼此耦合的局部状态
## 4. 数据请求与类型
### 4.1 请求层
所有 API 请求必须统一经过 `lib/api/`。
要求:
* 统一处理 `success/message/data` 响应结构
* 统一处理鉴权失效、网络异常和通用错误消息
* 统一维护资源接口与请求路径
禁止:
* 在页面组件中直接调用 `fetch('/api/...')`
* 在多个组件中重复拼接同一接口路径
### 4.2 状态分层
* 服务端状态:TanStack Query
* 页面临时状态:组件内部 `useState`
* 跨页面 UI 状态:Zustand
不推荐:
* 用 Zustand 保存服务端主数据
* 用 Context 代替完整数据层方案
### 4.3 类型
要求:
* 开启 TypeScript 严格模式
* 禁止滥用 `any`
* API 响应、表单输入、业务实体必须有明确类型
## 5. 表单与交互
统一使用:
* React Hook Form
* Zod
高风险操作必须:
* 二次确认
* 展示操作对象名称
* 明确成功与失败反馈
## 6. 样式与主题
样式原则:
* 统一使用 Tailwind CSS 与现有 token 体系
* 优先复用已有基础组件与布局组件
* 保持视觉层级、留白与语义颜色一致
主题要求:
* 同时支持 `light`、`dark`、`system`
* 用户选择必须持久化
* 首屏尽量避免主题闪烁
## 7. 测试与交付
每个页面至少具备:
* 加载态
* 空态
* 错误态
* 成功反馈
测试要求:
* 公共工具、类型转换、主题逻辑补单元测试
* 关键页面交互补组件测试
* 核心主链路补 Playwright 或等效联调验证
交付要求:
* 构建产物保持可静态导出
* 构建结果可被 Go Server 托管
* 新页面默认通过亮色与暗色模式验收
+29
View File
@@ -0,0 +1,29 @@
# 引用与致谢
OpenFlare 本质上是一个方案整合项目, 在设计与实现过程中借鉴了众多开源项目的优秀理念、架构设计和技术实现。以下是 OpenFlare 在核心底层引擎、安全防护机制以及前后端系统框架等方面所引用的关键开源项目,以及对这些项目及其社区的感谢。
---
### 1. OpenResty
* **项目定位**:基于 Nginx 与 Lua 的高性能 Web 平台。
* **在 OpenFlare 中的作用**:作为全局数据面(Data Plane)的边缘网关。所有的公网 Web 流量均首先由 OpenResty 接收,在此处进行高并发的 HTTPS 握手、WAF 安全规则比对、防 CC 人机验证,并最终执行反向代理转发。
* **项目链接**:[OpenResty 官网](https://openresty.org/)
### 2. FRP (Fast Reverse Proxy)
* **项目定位**:高性能的反向代理应用,专注于内网穿透。
* **在 OpenFlare 中的作用**:作为内网穿透子系统的底层隧道引擎。中继端管理器 `openflare-relay` 负责守护和调度 `frps` 引擎,而内网客户端 `openflared` 则负责在本地自动生成 TOML 配置并守护多路复用 `frpc` 子进程。
* **项目链接**:[fatedier/frp (GitHub)](https://github.com/fatedier/frp)
---
### 3. Anubis (PoW 方案)
* **项目定位**:基于工作量证明(Proof of Work)的轻量级人机验证防护方案。
* **在 OpenFlare 中的作用**:为网关 WAF 提供了核心的**无感防 CC 人机挑战**能力。
---
### 4. gin-template
* **项目定位**:基于 Go Gin 与前端构建的现代化全栈开发脚手架模板。
* **在 OpenFlare 中的作用**:为 OpenFlare 控制面(Server)提供了规范、统一的前后端系统架构雏形。
---
+100
View File
@@ -0,0 +1,100 @@
# 发布第一份配置
你会学到:如何创建第一条网站配置、绑定源站与证书、发布配置版本,并确认 Agent 已经应用。
OpenFlare 的发布链路以完整配置版本为中心。你在管理端修改网站配置后,需要发布并激活新版本,Agent 才会在后续 heartbeat 中拉取并应用。
## 发布前检查
确认以下条件已经满足:
| 项目 | 期望 |
| --- | --- |
| Server | 可以登录管理端 |
| Agent | 至少一个节点在线 |
| 源站 | Agent 节点可以访问源站地址 |
| 域名 | 域名已经解析到 OpenResty 节点,或准备通过本地 hosts / curl Host 头验证 |
| HTTPS | 如需 HTTPS,证书已上传或托管 |
## 创建网站配置
在管理端新增网站配置时至少需要:
| 字段 | 说明 |
| --- | --- |
| 网站名称 | 业务唯一标识;未显式填写时可使用主域名 |
| 域名 | 至少一个域名,第一项视为主域名 |
| 源站地址 | 合法的 `http://` 或 `https://` 上游地址 |
| 启用状态 | 只有启用的网站配置会参与发布渲染 |
示例:
| 字段 | 示例 |
| --- | --- |
| 网站名称 | `app` |
| 域名 | `app.example.com` |
| 源站地址 | `http://10.0.0.20:8080` |
同一个域名只能属于一个网站配置。同一网站内的流量限制、反向代理和缓存配置按站点共享。
## 绑定证书
HTTPS 证书按域名绑定。没有绑定证书的域名不会被自动放入 `443 ssl` server 块。
如果一个网站包含多个域名,发布渲染会按证书分组生成 HTTPS 配置,并确保所有域名仍属于同一站点快照。
## 发布与激活
标准链路:
```text
修改规则 -> 预览 / 查看 diff -> 发布 -> 生成完整配置版本 -> 激活版本 -> Agent 拉取 -> 本地应用 -> 上报结果
```
发布时 Server 会读取全部启用的网站配置、OpenResty 主配置模板、性能参数与缓存参数,渲染完整 OpenResty 配置,计算 `checksum`,写入 `config_versions`,再切换激活版本。
## 验证结果
发布后在管理端确认:
| 位置 | 期望结果 |
| --- | --- |
| 节点列表 | 节点在线 |
| 节点详情 | 当前版本与激活版本一致 |
| 应用记录 | 最近一次应用成功 |
| 版本页面 | 新版本处于激活状态 |
在节点上确认 Agent 日志:
```bash
journalctl -u openflare-agent -n 100 --no-pager
```
用域名访问:
```bash
curl -I http://app.example.com
```
如果域名还没有正式解析,可以临时指定 Host 头访问节点 IP:
```bash
curl -I -H 'Host: app.example.com' http://NODE_IP
```
HTTPS 验证:
```bash
curl -I https://app.example.com
```
## 回滚
如果目标版本应用失败并回滚,Agent 会在本地阻断同一 `version + checksum` 的重复应用,直到控制面激活版本或 checksum 发生变化。
回滚到旧版本:
1. 打开配置版本页面。
2. 找到上一个确认可用的历史版本。
3. 重新激活该版本。
4. 查看节点应用记录,确认 Agent 应用成功。
+43
View File
@@ -0,0 +1,43 @@
# 指南
你会学到:OpenFlare 文档如何组织、首次运行应该读哪些页面,以及部署、使用、排查和开发分别从哪里开始。
OpenFlare 是一套自托管的 OpenResty 控制面。它把反向代理网站配置、配置版本发布、Agent 节点同步、TLS 证书和基础观测放到一个管理端中,适合单团队或单组织管理多台代理节点。
## 推荐阅读路径
如果你第一次接触 OpenFlare,按下面顺序阅读:
1. [快速开始](./quick-start.md):用 Docker Compose 启动 Server,登录管理端,并接入第一个 Agent。
2. [基础使用](./usage.md):了解网站配置、源站、证书、发布、回滚和观测的常见操作。
3. [内网穿透与隧道使用](./tunnel-usage.md):学习部署 Relay 与 Client,实现安全、无公网 IP 反向穿透。
4. [WAF 安全防护使用](./waf-usage.md):掌握 IP 黑白名单、自动 IP 组 Expr 自动聚合、地域限制与 PoW CC 防护。
5. [WAF 自动 IP 组语法](./waf-ip-group-expr.md):编写自动 IP 组 Expr 规则,了解关键字含义和预设规则。
6. [部署说明](../deployment/deployment.md):把 Server 和 Agent 放到更接近生产的环境中运行。
7. [配置项参考](../reference/configuration.md):查 Server 环境变量、运行时 Option 和 Agent 配置字段。
8. [故障排查](./troubleshooting.md):按症状排查登录、数据库、节点同步、OpenResty 应用和前端构建问题。
## 按角色查找
| 你想做什么 | 推荐入口 |
| --- | --- |
| 5 分钟内跑起管理端 | [快速开始](./quick-start.md) |
| 发布第一条反向代理配置 | [发布第一份配置](./first-site.md) |
| 配置内网穿透映射 | [内网穿透与隧道使用](./tunnel-usage.md) |
| 配置防 CC 与 IP 组拦截 | [WAF 安全防护使用](./waf-usage.md) |
| 编写自动 IP 组规则 | [WAF 自动 IP 组语法](./waf-ip-group-expr.md) |
| 接入或重装节点 Agent | [接入 Agent](../deployment/agent.md) |
| 从源码启动 Server | [启动 Server](../deployment/server.md) |
| 配置 GitHub 或 OIDC 登录 | [SSO 登录配置](./sso.md) |
| 升级 Server 或 Agent | [升级与维护](../deployment/upgrade.md) |
| 参与开发或修复问题 | [本地开发](../design/development.md) 与 [开发约束](../guildline/development-constraints.md) |
| 理解架构和发布模型 | [系统架构](../design/architecture.md) 与 [Agent 与发布模型](../design/agent-design.md) |
| 查看开源引用与致谢 | [引用与致谢](./credits.md) |
## 文档分区
`guide/` 面向使用者和部署者,提供从安装到日常操作的可执行步骤。
`reference/` 收敛稳定事实,例如配置字段、命令、API 响应约定和仓库结构。
`design/` 面向维护者和贡献者,描述产品边界、系统架构、Agent 与发布模型和工程约束。新增能力或改变边界前,应先更新对应设计文档。
+220
View File
@@ -0,0 +1,220 @@
# 快速开始
你会学到:如何用 Docker Compose 启动 OpenFlare Server、完成首次登录、接入第一个 Agent,并验证一份配置是否已经发布到节点。
OpenFlare 的最小运行单元包含:
| 组件 | 职责 |
| --- | --- |
| Server | 管理端 UI、管理 API、Agent API、配置渲染、版本发布与状态存储 |
| Agent | 运行在代理节点上,拉取配置、写入 OpenResty、执行校验与 reload |
| OpenResty | 实际接收流量并反向代理到源站 |
Agent 统一通过 OpenResty 二进制控制运行时。本地部署需要节点上已有 `openresty` 可执行文件;Docker 部署可直接运行内置 OpenResty 的 Agent 镜像。
## 环境要求
| 项目 | 要求 |
| --- | --- |
| Docker / Docker Compose | 用于启动 Server 和 PostgreSQL;如果采用 Docker Agent 镜像,也用于运行 Agent |
| OpenResty | 本地安装 Agent 时需要可执行 `openresty`,或在安装脚本中指定路径 |
| 可访问端口 | Server 默认监听 `3000`,Agent 节点需要能访问 Server 地址 |
| 浏览器 | 用于访问管理端 |
- **Docker**:`20.10.0+`
- **Docker Compose**:`2.0.0+`
## 1. 启动 Server
在空目录中创建 `docker-compose.yml`:
```yaml
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
interval: 10s
timeout: 5s
retries: 5
openflare:
image: ghcr.io/rain-kl/openflare:latest
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-a-long-random-string
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
volumes:
- openflare-data:/data
volumes:
postgres-data:
openflare-data:
```
启动服务:
```bash
docker compose up -d
```
确认容器已经运行:
```bash
docker compose ps
docker compose logs -f openflare
```
看到 `server listening` 且 `openflare` 容器状态为 running 后,访问:
```text
http://localhost:3000
```
默认账号:
| 用户名 | 密码 |
| --- | --- |
| `root` | `123456` |
首次登录后请立即修改默认密码。
## 2. 准备 Agent Token
Agent 可以用两类凭证接入:
| 凭证 | 适用场景 |
| --- | --- |
| `discovery_token` | 首次自动注册节点,由 Server 换成节点专属 Token |
| `agent_token` | 已经在管理端创建或分配节点,直接使用节点专属 Token |
在管理端准备其中一种凭证后,进入下一步。
- **`discovery_token`** 获取菜单路径:「系统设置」->「自动注册」
- **`agent_token`** 获取菜单路径:「节点管理」->「新增节点」
## 3. 安装/运行 Agent
Agent 部署方式推荐使用 Docker 部署(即直接运行内置 OpenResty 的 Agent 镜像);亦支持通过安装脚本将 Agent 部署在本地宿主机上。
### 方式 A:Docker 运行 Agent(推荐)
在代理节点上直接运行 Agent 镜像:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443/tcp -p 443:443/udp \
-v openflare-agent-data:/data \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
ghcr.io/rain-kl/openflare-agent:latest
```
### 方式 B:执行安装脚本(本地部署)
在代理节点上执行安装脚本。
使用 `discovery_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--discovery-token YOUR_DISCOVERY_TOKEN
```
使用节点专属 `agent_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
脚本默认会:
| 项目 | 默认值 |
| --- | --- |
| 安装目录 | `/opt/openflare-agent` |
| 配置文件 | `/opt/openflare-agent/agent.json` |
| systemd 服务 | `openflare-agent.service` |
| OpenResty 路径 | 未指定时自动查找 `openresty` |
确认 Agent 服务状态:
```bash
systemctl status openflare-agent
journalctl -u openflare-agent -f
```
如果没有 systemd,脚本会输出手动启动命令。
## 4. 发布第一份配置
在管理端完成以下操作:
1. 新增网站配置,填写网站名称、域名和源站地址。
2. 确认网站配置处于启用状态。
3. 发布前查看预览或变更摘要。
4. 发布并激活新版本。
5. 等待 Agent 在后续 heartbeat 中发现版本并应用。
版本号格式为 `YYYYMMDD-NNN`。历史版本不可变,回滚通过重新激活旧版本完成。
## 5. 验证是否成功
在管理端确认:
| 位置 | 期望结果 |
| --- | --- |
| 节点列表 | Agent 节点在线 |
| 节点详情 | 当前版本与激活版本一致 |
| 应用记录 | 最近一次应用成功 |
| 版本页面 | 新版本处于激活状态 |
在 Agent 节点确认:
```bash
journalctl -u openflare-agent -n 100 --no-pager
```
## 常见失败原因
| 现象 | 排查方向 |
| --- | --- |
| 浏览器打不开管理端 | 确认 `docker compose ps` 中 Server 正在运行,宿主机 `3000` 端口没有被占用 |
| 登录后数据无法保存 | 检查 PostgreSQL 容器健康状态,以及 `DSN` 中的用户名、密码、库名是否一致 |
| Agent 无法注册 | 确认 Agent 节点能访问 `--server-url`,并检查 Token 是否填错或已失效 |
| Agent 在线但没有应用配置 | 确认网站配置已启用,并且已经发布并激活版本 |
| OpenResty 应用失败 | 查看节点应用记录和 `journalctl -u openflare-agent`,重点检查域名、证书、上游地址和端口占用 |
更多排查路径见 [故障排查](./troubleshooting.md)。
---
## 进阶部署指引
当您完成快速开始并熟悉了 OpenFlare 的基本操作后,可以阅读以下进阶部署文档,将各组件投入到正式生产环境中:
* **Server 生产部署**:阅读 [启动 Server](../deployment/server.md) 了解如何从源码构建前端、配置系统环境变量及使用 Docker Compose 运行。
* **Agent 生产接入**:阅读 [部署 Agent](../deployment/agent.md) 了解基于 systemd 的服务管理、详细本地配置文件字段及故障排查。
* **内网穿透中继端部署**:阅读 [部署 Relay](../deployment/relay.md) 了解如何为穿透隧道配置公网中继节点(frps)。
* **内网穿透客户端部署**:阅读 [部署 OpenFlared](../deployment/openflared.md) 了解如何在内网服务器侧运行穿透守护客户端(frpc)。
* **生产部署拓扑参考**:阅读 [部署说明](../deployment/deployment.md) 了解生产高可用拓扑和整体网络规划。
* **系统升级与日常维护**:阅读 [升级与维护](../deployment/upgrade.md) 了解如何平滑升级 Server 和各代理节点 Agent。
+106
View File
@@ -0,0 +1,106 @@
# SSO 登录配置
你会学到:如何为 OpenFlare 配置 GitHub OAuth 或标准 OIDC 登录入口,如何填写回调地址,以及第三方账号如何绑定本地用户。
OpenFlare 支持通过认证源配置第三方登录入口。当前支持 GitHub OAuth 与标准 OIDC Provider,例如 Logto、authentik、Keycloak、Casdoor 等。
认证源配置完成并启用后,会显示在登录页的第三方账号登录区域。用户可以通过第三方账号登录,也可以在已登录状态下把第三方账号绑定到当前本地账号。
## 使用前准备
你需要先准备:
| 项目 | 说明 |
| --- | --- |
| OpenFlare 访问地址 | 用户浏览器实际访问的地址,例如 `https://openflare.example.com` |
| 认证源名称 | OpenFlare 内部唯一标识,例如 `github`、`company-oidc` |
| Client ID | 第三方平台创建应用后提供 |
| Client Secret | 第三方平台创建应用后提供 |
| OIDC Discovery URL | 仅 OIDC 需要,例如 `https://idp.example.com/.well-known/openid-configuration` |
**确认系统设置->通用设置->服务器地址能正确和域名匹配**
认证源名称只能包含字母、数字、短横线或下划线,并且必须以字母或数字开头。认证源名称会出现在回调地址中,保存后如需修改名称,也必须同步修改第三方平台中的回调地址。
## 回调地址
第三方平台中的 Redirect URI / Callback URL 填写格式为:
```text
<OpenFlare 访问地址>/oauth/<认证源名称>
```
示例:
```text
https://openflare.example.com/oauth/github
https://openflare.example.com/oauth/company-oidc
```
在管理端新增或修改认证源时,表单会根据当前浏览器访问地址和你输入的认证源名称自动显示应填写的回调地址。
## 配置 GitHub 登录
1. 在 GitHub 创建 OAuth App。
2. `Homepage URL` 填写 OpenFlare 访问地址。
3. `Authorization callback URL` 填写 OpenFlare 显示的回调地址,例如 `https://openflare.example.com/oauth/github`。
4. 复制 GitHub 提供的 Client ID 和 Client Secret。
5. 登录 OpenFlare 管理端,进入“设置 -> 系统设置 -> 配置认证源”。
6. 新增认证源,类型选择 `GitHub`。
7. 填写认证源名称、展示名称、Client ID、Client Secret。
8. Scope 默认使用 `user:email`,通常无需修改。
9. 保存并启用认证源。
启用后,登录页会显示对应的 GitHub 登录按钮。
## 配置 OIDC 登录
1. 在 OIDC Provider 中创建应用或客户端。
2. 应用类型选择 Web / Confidential Client。
3. Redirect URI / Callback URL 填写 OpenFlare 显示的回调地址,例如 `https://openflare.example.com/oauth/company-oidc`。
4. 复制 Client ID 和 Client Secret。
5. 获取 Provider 的 Discovery URL,通常以 `/.well-known/openid-configuration` 结尾。
6. 登录 OpenFlare 管理端,进入“设置 -> 系统设置 -> 配置认证源”。
7. 新增认证源,类型选择 `OIDC`。
8. 填写认证源名称、展示名称、Client ID、Client Secret、OIDC Discovery URL。
9. Scope 默认使用 `openid profile email`。如果 Provider 限制了 scope,请按 Provider 允许的值调整。
10. 保存并启用认证源。
启用后,登录页会显示对应的 OIDC 登录按钮。
## 登录与绑定行为
第三方账号回到 OpenFlare 后按以下规则处理:
| 场景 | 行为 |
| --- | --- |
| 第三方账号已绑定本地用户 | 直接登录 |
| 用户已登录并发起第三方授权 | 绑定到当前本地用户 |
| 第三方账号未绑定,且允许注册 | 自动创建普通用户并绑定 |
| 第三方账号未绑定,且关闭注册 | 要求输入已有本地账号密码完成绑定 |
如果希望只允许已有用户使用 SSO,可以关闭用户注册。未绑定的第三方账号会进入绑定已有账号流程。
## 修改认证源
修改认证源时,Client Secret 输入框留空表示保留已有密钥;填写新值则会覆盖保存。
如果修改了认证源名称,回调地址也会随之变化。你必须到第三方平台同步修改 Redirect URI / Callback URL,否则第三方平台会拒绝回调或返回错误。
## 常见问题
### 返回 `invalid_scope`
说明第三方平台不允许当前配置的 Scope。OIDC 默认 Scope 是 `openid profile email`,GitHub 默认 Scope 是 `user:email`。请到认证源编辑页调整 Scope,或在第三方平台放行对应 Scope。
### 提示回调地址不匹配
检查第三方平台中配置的 Redirect URI / Callback URL 是否与 OpenFlare 表单提示完全一致。协议、域名、端口和路径都必须一致。
### 登录页没有显示第三方登录按钮
检查认证源是否已启用,并确认 Client ID 和 Client Secret 已保存。启用认证源前,OpenFlare 会校验这些字段。
### 已经保存 Client Secret,但列表不显示明文
这是预期行为。OpenFlare 不会通过 API 回显 Client Secret,只显示该密钥是否已配置。
+249
View File
@@ -0,0 +1,249 @@
# 故障排查
你会学到:如何按症状排查 OpenFlare Server、数据库、登录、Agent、OpenResty、配置发布和前端构建问题。
排查时先确认问题发生在哪一层:浏览器、Server、数据库、Agent、OpenResty、源站或 DNS。OpenFlare 的配置不会直接在线写入所有节点,只有激活版本变化后,Agent 才会在 heartbeat 中发现并应用。
## 快速定位
| 现象 | 先看哪里 |
| --- | --- |
| 管理端打不开 | Server 容器或进程日志、端口监听 |
| 登录异常 | 默认账号、Session Secret、浏览器请求、Server 日志 |
| 数据无法保存 | 数据库连接、SQLite 文件权限、PostgreSQL 健康状态 |
| Agent 离线 | Agent 日志、Token、Server 地址、网络连通性 |
| 发布后节点未更新 | 激活版本、节点 heartbeat、应用记录 |
| OpenResty 应用失败 | 应用记录、Agent 日志、证书、上游地址、端口占用 |
| 访问分析无数据 | OpenResty 容器状态、观测端口、Agent 补报日志 |
## Server 无法启动
1. 查看日志:
```bash
docker compose logs -n 200 openflare
```
源码运行时查看终端输出。
2. 检查端口占用:
```bash
lsof -i :3000
```
3. 如果使用 PostgreSQL,确认数据库健康:
```bash
docker compose ps postgres
docker compose logs -n 100 postgres
```
4. 如果使用 SQLite,确认数据库文件目录可写:
```bash
ls -ld "$(dirname /path/to/openflare.db)"
```
常见原因:
| 日志或现象 | 处理 |
| --- | --- |
| 数据库连接失败 | 检查 `DSN` 中用户名、密码、主机、端口、库名和 `sslmode` |
| SQLite 无法创建文件 | 检查 `SQLITE_PATH` 所在目录是否存在且可写 |
| 端口被占用 | 修改 `PORT` 或 `--port`,或停止占用端口的进程 |
## 管理端打不开或空白
1. 确认 Server 正在监听:
```bash
curl -I http://127.0.0.1:3000
```
2. 如果是源码运行,确认已经构建前端静态产物:
```bash
cd openflare_server/web
pnpm build
```
3. 检查浏览器访问地址是否与反向代理配置一致。
4. 如果通过前端开发服务器访问,确认后端代理地址:
```bash
cd openflare_server/web
NEXT_DEV_BACKEND_URL=http://127.0.0.1:3000 pnpm dev
```
## 默认账号无法登录
默认账号是 `root` / `123456`。首次登录后如果已经修改密码,应使用修改后的密码。
排查步骤:
1. 确认连接的是预期数据库,避免 `SQLITE_PATH` 或 `DSN` 指向了另一个环境。
2. 查看 Server 日志中使用的是 `sqlite` 还是 `postgres`。
3. 如果部署在多副本或反向代理后,确认 `SESSION_SECRET` 固定且各实例一致。
4. 清理浏览器 Cookie 后重新登录。
### 应急重置管理员密码
如果忘记了 `root` 账户的密码,可以通过直接更新数据库中的密码哈希值将其重置为 `123456`(登录后请务必立即修改):
#### 1. 若使用 SQLite 数据库
停止 Server 运行,使用 sqlite3 客户端打开数据库文件:
```bash
sqlite3 /path/to/openflare.db
```
执行以下 SQL 语句:
```sql
UPDATE users SET password_hash = '$2a$10$wN9aE3zTz83rO7R1uKlhuehJtA3c604pX4Z12B/9.5c0X337t1L4m' WHERE username = 'root';
```
输入 `.exit` 退出并重新启动 Server。
#### 2. 若使用 PostgreSQL 数据库
通过您的数据库连接工具(如 psql、pgAdmin 或 DBeaver)连接到 PostgreSQL 实例,选择对应的 `openflare` 数据库,执行以下 SQL 语句:
```sql
UPDATE users SET password_hash = '$2a$10$wN9aE3zTz83rO7R1uKlhuehJtA3c604pX4Z12B/9.5c0X337t1L4m' WHERE username = 'root';
```
执行成功后即可使用默认密码 `123456` 重新登录管理后台。
## Agent 无法注册或一直离线
在 Agent 节点执行:
```bash
curl -I http://your-server:3000
```
查看 Agent 日志:
```bash
journalctl -u openflare-agent -n 200 --no-pager
```
检查配置文件:
```bash
sed -n '1,160p' /opt/openflare-agent/agent.json
```
重点确认:
| 配置 | 说明 |
| --- | --- |
| `server_url` | 必须是 Agent 节点能访问的 Server 地址 |
| `agent_token` / `discovery_token` | 至少填写一个 |
| `heartbeat_interval` | 支持毫秒整数或 Go duration 字符串 |
| `request_timeout` | 网络较慢时可适当增大 |
如果日志提示 Token 无效,重新在管理端准备 Token 并更新 `agent.json`,然后重启:
```bash
systemctl restart openflare-agent
```
## 发布后节点没有应用新版本
按顺序检查:
1. 版本页面中是否已经激活目标版本。
2. 节点是否在线,最近心跳时间是否更新。
3. 应用记录中是否有目标版本的成功、警告或失败记录。
4. 网站配置是否启用;未启用的网站不会参与发布渲染。
5. Agent 日志是否出现拉取、校验、reload 或回滚信息。
查看 Agent 日志:
```bash
journalctl -u openflare-agent -f
```
注意:某个目标 `version + checksum` 一旦应用失败并回退,Agent 会在本地状态中阻断该目标重复应用。修正配置后需要重新发布生成新的 checksum,或激活旧版本回滚。
如果这是 Agent 首次应用配置,且本地没有历史 `nginx.conf` 可回滚,失败目标仍会被阻断,但 Agent 会尝试进入安全兜底运行态。此时应用记录和 Agent 日志会包含 `fallback runtime started`,OpenResty 对外只监听 `80` 端口并统一返回 `503` 与 `OpenFlare: No Valid Configuration`,同时保留本地 `stub_status` 健康检查入口。修正配置并重新发布新版本后,Agent 会覆盖兜底配置并恢复正常代理。
## OpenResty 应用失败
常见原因:
| 原因 | 排查 |
| --- | --- |
| 域名或 server 块冲突 | 检查同一域名是否被多个网站配置使用 |
| 上游地址不合法 | 确认所有上游都是 `http://` 或 `https://` |
| 多上游格式不符合约束 | 多上游必须是纯 `scheme://host[:port]` |
| 证书缺失或路径错误 | 检查域名是否绑定证书,以及 Agent 证书目录是否可写 |
| 端口被占用 | 检查本机 `80`、`443` 端口 |
OpenResty 配置校验:
```bash
openresty -t -c /path/to/openflare/data/etc/nginx/nginx.conf
```
OpenResty 运行状态:
```bash
ps aux | grep openresty
```
Agent 周期性健康检查通过本地 `http://127.0.0.1:<openresty_observability_port>/openflare/stub_status` 判断 OpenResty 是否存活,不会反复执行 `openresty -t`。如果节点被标记为 unhealthy,优先确认该本地观测端口是否正在监听;如果只在应用配置时出现 `host not found in upstream`,说明失败来自配置校验或 reload,而不是周期性健康探针。
实际二进制路径和主配置路径以 `agent.json` 中的 `openresty_path` 与 `main_config_path` 为准。
## HTTPS 不生效
1. 确认证书已经上传或托管。
2. 确认网站配置中对应域名已经绑定证书。
3. 确认发布并激活了新版本。
4. 查看应用记录是否成功。
5. 用 `curl` 查看证书和状态码:
```bash
curl -Iv https://your-domain
```
没有绑定证书的域名不会被自动加入 HTTPS 配置,这是预期行为。
## 访问分析没有数据
1. 确认节点已经成功应用包含观测 Lua 资源的配置。
2. 确认 OpenResty 正在运行。
3. 查看 Agent 日志是否有观测采集或补报失败信息。
4. 检查 `openresty_observability_port` 是否被占用,默认是 `18081`。
5. 确认 Server 侧没有因数据库清理策略删除对应时间窗口数据。
## 前端构建失败
执行:
```bash
cd openflare_server/web
corepack enable
pnpm install
pnpm lint
pnpm typecheck
pnpm test
pnpm build
```
常见原因:
| 现象 | 处理 |
| --- | --- |
| pnpm 版本不一致 | 使用 `corepack enable` 后重新安装 |
| 类型错误 | 先运行 `pnpm typecheck` 定位具体文件 |
| API 类型不一致 | 检查 `lib/api/` 和 `types/` 中的响应结构 |
| E2E 失败 | 确认 Server 和前端开发服务器都已启动 |
## 文档站构建失败
```bash
cd docs
pnpm install
pnpm build
```
如果是链接错误,检查新增页面是否已经加入 `docs/config.ts` 侧边栏,或者相对链接是否指向存在的 Markdown 文件。
+174
View File
@@ -0,0 +1,174 @@
# 内网穿透与隧道使用
你会学到:OpenFlare 内网穿透隧道的设计原理、核心概念(中继节点与隧道客户端),以及如何从零开始将内网开发环境或私有云服务一步步安全、稳定地发布到公网域名上。
在许多实际开发和运维场景中,我们的源站服务部署在局域网、本地开发机或防范严密的私有 VPC 内部,没有公网 IP,亦无法在边界防火墙或路由器上配置端口映射。
OpenFlare 提供了**基于反向中继穿透隧道**的整体解决方案。你只需在内网环境发起向公网中继节点的出向安全连接,无需配置任何入方向端口,即可将公网的 Web 访问流量平滑引入内网源站,同时享有网关提供的 TLS 证书自动托管与 WAF 安全防护。
---
## 核心概念
在使用内网穿透功能前,你需要熟悉以下组件与核心概念:
| 概念 | 说明 | 对应组件/操作 |
| --- | --- | --- |
| **中继节点 (Relay)** | 部署在公网边缘的流量中继服务,负责监听内网客户端的长连接,并作为网关 Agent (OpenResty) 与内网流量的中转桥梁。 | 运行 `openflare-relay` 守护的 `tunnel_relay` 节点 |
| **穿透隧道 (Tunnel)** | 逻辑上的穿透客户端实例,拥有全局唯一 ID 与安全认证令牌,用以标识一个具体的内网环境。 | 由 Server 随机生成 `tunnel_id` (tun-<32hex>) |
| **隧道客户端 (Client)** | 运行在内网环境下的轻量控制器,根据 Server 下发的配置自动管理底层的 frpc 隧道子进程。 | 内网部署的 `openflared` 容器或独立二进制进程 |
| **隧道上游 (Tunnel Upstream)** | 网站配置中的特殊上游类型。选择此类型后,网关会将公网流量转发至本地中继端的 Vhost 端口,最终送达内网源站。 | 网站详情中配置的 `tunnel` 类型上游 |
---
## 推荐操作顺序
将一个内网服务发布到公网,推荐按这个顺序进行:
1. 注册并部署至少一个公网 **中继节点 (Relay)** 并保持在线。
2. 在管理端创建 **穿透隧道 (Tunnel)** 并复制对应的专属 Token。
3. 在内网服务器中部署并启动 **隧道客户端 (OpenFlared)**。
4. 确认管理端中该隧道的在线状态显示为「在线」。
5. 新增网站配置,上游类型选择 **内网穿透**,绑定对应隧道并填写内网端口(如 `127.0.0.1:8080`)。
6. 发布并激活新版本。
7. 通过公网域名访问,验证内网穿透链路是否打通。
---
## 详细配置步骤
### 第一步:准备中继节点 (Relay)
内网流量需要通过公网的中继节点进行中转。在开始前,你需要确保公网有一台可用的中继服务器。
1. 登录管理端,进入 **「节点管理」**。
2. 添加一个新节点,并将 **节点类型** 选择为 **中继节点 (tunnel_relay)**。
3. 保存后,复制该节点专属的 `agent_token`。
4. 在你的公网服务器上启动 `openflare-relay`。你可以直接使用 Docker 快速运行:
```bash
docker run -d --name openflare-relay --restart unless-stopped \
-p 7000:7000 \
-e OPENFLARE_SERVER_URL=http://<你的Server公网IP>:3000 \
-e OPENFLARE_AGENT_TOKEN=<刚才复制的AgentToken> \
-v openflare-relay-data:/var/lib/openflare-relay \
ghcr.io/rain-kl/openflare-relay:latest
```
> [!IMPORTANT]
> 请务必在云服务器安全组中放行 `7000` 端口(frpc 客户端连接控制端口)。如果你的 Server 与中继节点部署在同一台机器,这里的 `OPENFLARE_SERVER_URL` 应指向 Server 的公网或内网通信 IP。
### 第二步:在管理端创建穿透隧道
1. 导航至管理侧边栏的 **「内网穿透」** 页面。
2. 点击 **「创建隧道」** 按钮,在弹窗中填写:
* **隧道名称**:描述此内网环境,例如 `home-lab` 或 `office-dev`。
* **描述**:可选填,描述此隧道的具体用途。
3. 点击保存后,系统将自动生成该隧道的全局唯一 ID 与一串专属的 `tunnel_token`(形如 `tun-xxxx...`)。
4. 复制弹窗中为你生成的 **客户端部署命令**,用于下一步内网环境的部署。
### 第三步:部署内网客户端 (OpenFlared)
回到你的内网服务器中,根据刚才复制的部署命令运行客户端。
#### 方案 A:使用 Docker 部署(强烈推荐)
官方提供的 `openflared` 镜像已经内置了主控守护进程与 `frpc` 运行时,开箱即用,无需配置额外依赖:
```bash
docker run -d --name openflared --restart unless-stopped \
-e OPENFLARE_SERVER_URL=http://<你的Server公网IP>:3000 \
-e OPENFLARE_TUNNEL_TOKEN=<刚才复制的TunnelToken> \
-v openflared-data:/app/data \
ghcr.io/rain-kl/openflared:latest
```
#### 方案 B:宿主机二进制手动运行
如果你不便使用 Docker,也可以下载或自行编译 `flared` 二进制程序:
1. 在内网机器的程序同级目录下创建 `flared.json` 配置文件:
```json
{
"server_url": "http://<你的Server公网IP>:3000",
"tunnel_token": "<刚才复制的TunnelToken>",
"frpc_path": "/usr/local/bin/frpc",
"data_dir": "./data"
}
```
2. 执行启动命令:
```bash
./flared -config ./flared.json
```
#### 状态确认
启动成功后,内网客户端会通过出向网络向控制面发送心跳同步配置。此时:
1. 刷新管理端的 **「内网穿透」** 列表,刚才创建的隧道状态指示灯应当变为绿色的 **「在线」**。
2. 点击隧道详情,你可以直观地查看到当前内网客户端连接了公网的哪些中继 Relay 节点。
### 第四步:创建网站并绑定隧道上游
现在你可以为你的内网服务配置公网反向代理和域名访问了。
1. 进入 **「网站配置」** 页面,点击 **「新建网站」**。
2. 填写公网访问该网站所需的 **域名**,例如 `nas.example.com`。
3. 关键配置:在 **「上游配置」** 区域,将 **上游类型** 从默认的「直连」切换为 **「内网穿透」**。
4. 在下拉列表中选择你刚刚部署上线的 **内网隧道**(如 `home-lab`)。
5. 填写 **内网目标地址**(对于内网客户端来说可访问的本地地址与端口,例如 `127.0.0.1:8080`)与 **内网协议**(通常为 `http`)。
6. 配置其他站点常规项(如 TLS 证书等),并点击保存。
### 第五步:发布与生效
为了让网关的 OpenResty 能够正确匹配并路由域名流量,我们需要发布新的配置版本。
1. 点击导航栏右上角的 **「配置预览」**,确认生成的站点配置无误。
2. 在弹出窗口中,点击 **「发布并激活」**。
3. 此时,公网边缘的 Agent 会拉取到最新路由:它会将 `nas.example.com` 的请求转发至同机部署的 `openflare-relay (frps)` 的虚拟主机端口下。
4. 内网客户端 `openflared (frpc)` 会接收到被中继的封包,并安全地透传给内网的 `127.0.0.1:8080` 服务,最后原路返回响应。
5. 在你的公网浏览器中访问 `nas.example.com`,确认内网服务成功展示!
---
## 高级应用场景
### 1. 单隧道多服务复用 (多端口映射)
你并不需要为内网的每一个服务都部署一个 `openflared` 容器。
如果你想在一个内网环境映射多个不同的服务(例如:`127.0.0.1:80` 是博客,`127.0.0.1:8080` 是 API,`192.168.1.120:9000` 是内网网盘):
1. 保持这一个 `openflared` 客户端在线。
2. 在管理端创建三个独立的网站配置(绑定各自对应的公网域名)。
3. 这三个网站配置都将 **上游类型** 选为 **同一个穿透隧道**。
4. 分别在各自的“内网目标地址”中填入对应不同的端口或局域网 IP(例如 `127.0.0.1:80`、`127.0.0.1:8080`、`192.168.1.120:9000`)。
5. 发布并激活新版本,即可实现一隧多用。
### 2. 网关安全功能无缝叠加
因为所有公网流量均首先进入公网的 Agent 节点,在此处完成了 HTTPS/TLS 握手与 WAF 引擎拦截,然后再通过安全隧道送达内网。
因此,你的内网服务**天然且无需做任何改造**即可享受以下高级特性:
* **一键启用 HTTPS**:直接在管理端为域名选择或申请 SSL 证书,数据传输全程加密。
* **全局/自定义 WAF 防护**:开启 SQL 注入拦截、XSS 注入防御与恶意地域 IP 屏蔽。
* **人机挑战 (CC PoW)**:一键抵御针对内网服务的恶意 CC 刷接口攻击。
---
## 常见故障排查
### 1. 隧道在管理端显示为「离线」
* **检查 Token 是否正确**:查看 `flared` 日志或环境变量中配置的 `tunnel_token` 是否与管理端生成的一致。
* **检查网络连通性**:内网服务器需能通过出向网络正常请求 Server 地址。确保控制面没有启用防火墙限制客户端的 HTTP 请求。
* **中继节点防火墙未开**:检查对应中继节点的公网 `7000` 端口(或你自定义的 bindPort)是否已经在安全组中对公网放行。
### 2. 访问公网域名返回 502 Bad Gateway / 504 Gateway Timeout
* **内网服务未运行**:确认内网目标地址对应的服务已在内网服务器上成功启动并处于监听状态。
* **目标地址不可达**:如果内网地址填的是 `127.0.0.1:8080`,确保服务确实在运行着 `openflared` 的同一台主机上;如果填的是局域网 IP `192.168.x.x`,请在 `openflared` 容器内测试该局域网 IP 的连通性。
* **检查客户端应用日志**:在管理端查看「应用记录」或在内网查看 `flared` 运行日志,排查是否有 `LastError` 产生。frpc 在连不上内网端口时,会将连接失败报错原样上报至 Server 方便管理员定位。
### 3. 多中继网络动荡或重试失败
* 当控制面关联了多个 Relay 中继节点时,`openflared` 会为每个 Relay 独立派生 frpc 守护进程,并在 `flared.json` 中配置的 `sync_interval`(默认 30s)内定时向控制面拉取拓扑状态。
* 若发现某一中继节点频繁由于网络抖动离线,系统会自动触发退避重试机制。你可以在宿主机日志中看到 `frpc process missing, starting` 的日志,这属于正常的进程自愈逻辑,通常在网络恢复后 5~10 秒内即可自动恢复建连。
+156
View File
@@ -0,0 +1,156 @@
# 基础使用
你会学到:OpenFlare 中网站配置、源站、证书、版本、节点和观测分别是什么,以及日常使用时应按什么顺序操作。
OpenFlare 不直接在线修改节点上的 Nginx/OpenResty 配置。你在管理端修改的是控制面数据;只有发布并激活新版本后,Agent 才会拉取完整配置并应用到节点。
## 核心概念
| 概念 | 说明 |
| --- | --- |
| 网站配置 | 反向代理配置的聚合对象,一条网站配置可以绑定一个或多个域名 |
| 主域名 | `domains` 列表中的第一个域名,用作该网站的主要展示域名 |
| 源站 | 被反向代理访问的上游地址,例如 `http://10.0.0.10:8080` |
| 配置版本 | 一次发布生成的完整 OpenResty 配置快照,历史版本不可变 |
| 激活版本 | 当前全局生效的配置版本,所有节点默认消费同一份激活版本 |
| Agent | 节点侧进程,负责注册、心跳、同步、校验、reload 和失败回滚 |
## 推荐操作顺序
日常发布一条反向代理配置时,推荐按这个顺序:
1. 确认至少有一个 Agent 节点在线。
2. 新增或选择源站地址。
3. 新增网站配置,填写域名、源站和站点级配置。
4. 如需 HTTPS,上传或选择证书,并按域名绑定。
5. 预览配置或查看变更摘要。
6. 发布并激活新版本。
7. 在节点详情和应用记录中确认应用结果。
## 创建网站配置
网站配置至少需要:
| 字段 | 要求 |
| --- | --- |
| 网站名称 | 业务唯一标识;未显式填写时通常可使用主域名 |
| 域名 | 至少一个域名,第一项为主域名;任一域名全局只能属于一个网站 |
| 源站地址 | 合法的 `http://` 或 `https://` 地址 |
| 启用状态 | 只有启用的网站配置会参与发布渲染 |
示例:
| 字段 | 示例 |
| --- | --- |
| 网站名称 | `docs` |
| 域名 | `docs.example.com` |
| 源站地址 | `http://10.0.0.10:8080` |
| 回源 Host | `docs.internal.example.com` |
上游地址规则:
* 单上游可以携带 base path 或 query,例如 `https://app.example.com/base?from=openflare`。
* 多上游用于负载均衡时,每个上游必须是纯 `scheme://host[:port]`。
* 多上游在同一规则内应使用一致协议。
## 管理源站
源站是轻量目录,用来复用常见上游地址。网站配置关联源站后,仍会保存可渲染的 `origin_url` 快照,确保历史配置版本可以独立回放。
推荐做法:
* 把经常复用的内部服务地址维护为源站。
* 修改源站目录后,检查已发布的网站配置是否需要同步更新源站快照。
* 发布前使用预览或 diff 确认渲染结果。
## 启用 HTTPS
HTTPS 按域名绑定证书,而不是按整个网站统一强制启用。
操作顺序:
1. 在证书管理中上传或托管证书。
2. 进入网站配置,为需要 HTTPS 的域名选择证书。
3. 未绑定证书的域名会保留 HTTP,不会被自动放入 `443 ssl` server 块。
4. 发布并激活新版本。
如果一个网站包含多个域名,Server 发布时会按证书分组渲染 HTTPS 配置,同时保持这些域名属于同一份网站快照。
## 配置 WAF 与 PoW
安全防护统一从管理端侧边栏的 **WAF** 入口进入:
* WAF 页面维护全局规则组和自定义规则组。全局规则组始终应用到全部网站;自定义规则组可以在规则组内一键选择网站,也可以在网站详情的 `WAF` 分区绑定。
* 点击 WAF 页面中的 **管理 IP 组** 可以进入独立 IP 组页面。手动 IP 组直接维护 IP/IP 段;自动 IP 组使用 Expr 规则按单个 IP 聚合请求日志并定时更新名单;订阅 IP 组可从远程文本或 JSON 源定时同步。
* 自动 IP 组页面提供两个预设:单个 IP 请求数大于 100 且 404 占比不低于 80%;单个 IP 通过 IP 地址访问次数大于 50 且该访问占比大于 50%。保存前可点击 **测试规则** 查看当前日志窗口命中的 IP,保存后可点击 **立即执行** 更新组内名单,语法见 [WAF 自动 IP 组规则语法](./waf-ip-group-expr.md)。
* 在 WAF 规则组的黑白名单中,IP 维度既可以直接添加 IP/IP 段,也可以引用已有 IP 组。发布时版本只携带 IP 组引用 ID;Agent 会按 checksum 差异同步 IP 组成员,并在 Server 通过 WebSocket 广播 IP 组更新时实时落地到节点。
* `PoW` 是规则组内的一个配置 Tab,位于 `黑白名单` 与 `拦截返回` 之间,复用站点已有 PoW 执行逻辑,可将当前 PoW 配置应用到全部网站或当前规则组绑定的网站。
* 网站详情页不再单独编辑 PoW 规则,只展示全局 WAF 规则组并绑定自定义 WAF 规则组。PoW 的启用范围和规则内容应回到 WAF 页面统一维护。
WAF 规则组、网站绑定或 PoW 配置修改后,需要重新发布并激活配置版本,Agent 才会拉取并应用到 OpenResty。IP 组成员变化不需要重新发布版本;在线 Agent 会通过 WebSocket 增量更新,离线或未升级 WS 的 Agent 会在下一次心跳中按 checksum 差异补齐。
详细的 WAF 安全配置与拦截判决原理请查阅 [WAF 安全防护使用](./waf-usage.md)。
## 发布、激活与回滚
标准链路:
```text
修改配置 -> 预览 / diff -> 发布 -> 生成完整版本 -> 激活版本 -> Agent 拉取 -> 本地应用 -> 上报结果
```
发布时 Server 会读取全部启用的网站配置、OpenResty 主配置模板、性能参数、缓存参数和证书资源,生成完整配置并计算 `checksum`。
回滚不是修改历史版本,而是重新激活旧版本。Agent 发现激活版本变化后,会按普通同步流程拉取并应用。
## 查看节点与观测
节点页面适合回答三个问题:
| 问题 | 查看位置 |
| --- | --- |
| 节点是否在线 | 节点列表或节点详情 |
| 当前运行哪个版本 | 节点详情中的当前版本 |
| 最近一次应用是否成功 | 应用记录 |
节点 IP 默认由 Agent 注册和后续心跳自动回填。若在管理端填写或修改 IP,节点编辑会默认开启“锁定节点 IP”;开启后 Agent 上报不会覆盖该 IP。关闭锁定后,下一次 Agent 心跳或 WebSocket 状态上报会重新按自动逻辑更新。
访问分析和资源快照用于基础观测。OpenFlare 只保留受控时间窗口内的访问明细,不定位为通用日志平台。如果需要长期日志检索,应接入独立日志系统。
## 常见场景
### 新增一个内部服务反代
1. 确认源站服务可从 Agent 节点访问。
2. 在管理端新增网站配置。
3. 填写域名,例如 `app.example.com`。
4. 填写源站,例如 `http://10.0.0.20:8080`。
5. 发布并激活版本。
6. 在 Agent 节点或浏览器访问域名验证。
> [!TIP]
> 如果你的源站部署在内网、没有公网 IP 且 Agent 无法直接访问,请使用内网穿透隧道功能将服务映射至公网。详细操作步骤请查阅 [内网穿透与隧道使用](./tunnel-usage.md)。
### 给已有域名启用 HTTPS
1. 准备覆盖该域名的证书。
2. 在证书管理中上传或创建证书记录。
3. 回到网站配置,为对应域名选择证书。
4. 发布并激活版本。
5. 用浏览器或 `curl -I https://your-domain` 验证证书链和状态码。
### 回滚一次失败发布
1. 打开配置版本页面。
2. 找到上一个已知可用版本。
3. 重新激活该版本。
4. 查看节点应用记录,确认 Agent 已应用旧版本。
5. 修正配置后再发布新版本。
## 推荐实践
* 生产环境显式配置 `SESSION_SECRET`,并优先使用 PostgreSQL。
* 修改网站配置后先看预览或 diff,再发布。
* 每次发布后检查节点详情与应用记录。
* 多节点部署时保持 Agent 到 Server 的网络路径稳定。
* 不在节点上手动修改 OpenFlare 托管的 OpenResty 配置文件;下次发布会覆盖这些文件。
+168
View File
@@ -0,0 +1,168 @@
# WAF 自动 IP 组规则语法
自动 IP 组用于从请求日志中按单个客户端 IP 聚合指标,再用 Expr 表达式判断是否把该 IP 加入组内名单。自动 IP 组可以被 WAF 规则组的 IP 黑名单或白名单引用;发布配置时,Server 只把 IP 组引用 ID 写入 `waf_config.json`,IP 组成员由 Agent 独立同步到本地运行时文件。
## 配置结构
自动 IP 组的配置是一个 JSON 对象:
```json
{
"lookback_minutes": 60,
"rules": [
{
"name": "单 IP 404 高频扫描",
"expr": "request_count > 100 && StatusRatio(404) >= 0.8"
}
]
}
```
字段说明:
| 字段 | 类型 | 作用 |
| --- | --- | --- |
| `lookback_minutes` | number | 每次执行时回看多少分钟内的请求日志。未填写时默认 60 分钟,最小 5 分钟,最大 43200 分钟。 |
| `rules` | array | 自动规则列表。任意一条规则命中时,该 IP 会进入自动 IP 组名单。 |
| `rules[].name` | string | 规则名称,只用于界面展示和错误提示。 |
| `rules[].expr` | string | Expr 表达式,必须返回布尔值。 |
## 执行口径
自动规则不是逐条请求判断,而是先按单个客户端 IP 聚合:
1. Server 读取最近 `lookback_minutes` 分钟内的请求日志。
2. 按 `remote_addr` 归一化后的 IP 分组。
3. 为每个 IP 计算请求数、404 数、直连 IP Host 次数等指标。
4. 逐个 IP 执行 `rules[].expr`。
5. 只要某个 IP 命中任意规则,就写入该自动 IP 组的 `IP / IP 段` 列表。
Host 是否为“通过 IP 访问”按请求日志中的 `Host` 字段判断:如果 Host 是 IPv4 或 IPv6 字面量,例如 `203.0.113.10`、`[2001:db8::10]`、`203.0.113.10:443`,就计入 `ip_host_count`。
## 可用关键字
表达式中可以直接使用以下字段:
| 关键字 | 类型 | 作用 |
| --- | --- | --- |
| `ip` | string | 当前正在判断的客户端 IP。 |
| `request_count` | number | 当前 IP 在回看窗口内的总请求数。 |
| `status_404_count` | number | 当前 IP 在回看窗口内返回 404 的请求数。 |
| `status_404_ratio` | number | 404 请求占比,计算方式为 `status_404_count / request_count`。 |
| `ip_host_count` | number | 当前 IP 通过 IP 地址作为 Host 访问的请求数。 |
| `ip_host_ratio` | number | 通过 IP 地址访问的占比,计算方式为 `ip_host_count / request_count`。 |
| `client_error_count` | number | 当前 IP 返回 4xx 状态码的请求数。 |
| `server_error_count` | number | 当前 IP 返回 5xx 状态码的请求数。 |
| `last_seen_unix` | number | 当前 IP 在回看窗口内最后一次请求的 Unix 秒级时间戳。 |
比例字段都是 `0` 到 `1` 之间的小数。80% 应写成 `0.8`,50% 应写成 `0.5`。
### 自定义状态码匹配方法
如果内置的 `status_404_count` 和 `status_404_ratio` 不能满足您的需求,您可以使用以下内置方法来匹配任意状态码的请求数与占比:
* **`StatusCount(code)`**: 获取当前 IP 在回看窗口内返回指定状态码的请求数(如 `StatusCount(403) > 10`)
* **`StatusRatio(code)`**: 获取当前 IP 在回看窗口内返回指定状态码的请求数占该 IP 总请求数的比例(如 `StatusRatio(502) >= 0.5`)
## Expr 常用写法
自动 IP 组使用 Expr 语法,当前表达式必须返回布尔值。
常用运算符:
| 写法 | 作用 | 示例 |
| --- | --- | --- |
| `>`、`>=`、`<`、`<=` | 数值比较 | `request_count > 100` |
| `==`、`!=` | 相等或不相等 | `ip != "127.0.0.1"` |
| `&&` | 并且 | `request_count > 100 && StatusRatio(404) >= 0.8` |
| `||` | 或者 | `StatusRatio(404) >= 0.8 || server_error_count > 20` |
| `!` | 取反 | `!(ip == "127.0.0.1")` |
| `in` | 判断值是否在列表中 | `ip in ["203.0.113.10", "198.51.100.20"]` |
| `not in` | 判断值是否不在列表中 | `ip not in ["127.0.0.1"]` |
| `()` | 分组控制优先级 | `(request_count > 100 && StatusRatio(404) >= 0.8) || server_error_count > 50` |
## 内置预设
管理端内置两个预设规则,可以直接添加后再按需调整:
```json
{
"name": "单 IP 404 高频扫描",
"expr": "request_count > 100 && StatusRatio(404) >= 0.8"
}
```
含义:单个 IP 在回看窗口内请求数大于 100,并且 404 状态码占比不低于 80%。
```json
{
"name": "单 IP 直连访问异常",
"expr": "ip_host_count > 50 && ip_host_ratio > 0.5"
}
```
含义:单个 IP 通过 IP 地址作为 Host 访问的次数大于 50,并且这种访问占比大于 50%。
## 示例
高频 404 扫描:
```json
{
"lookback_minutes": 60,
"rules": [
{
"name": "高频 404 扫描",
"expr": "request_count > 100 && StatusRatio(404) >= 0.8"
}
]
}
```
IP 直连访问异常:
```json
{
"lookback_minutes": 30,
"rules": [
{
"name": "IP 直连访问异常",
"expr": "ip_host_count > 50 && ip_host_ratio > 0.5"
}
]
}
```
同时捕获高 4xx 与高 5xx:
```json
{
"lookback_minutes": 120,
"rules": [
{
"name": "异常错误率",
"expr": "(client_error_count > 80 && request_count > 100) || server_error_count > 30"
}
]
}
```
排除可信 IP:
```json
{
"lookback_minutes": 60,
"rules": [
{
"name": "排除可信 IP 的 404 扫描",
"expr": "ip not in [\"203.0.113.10\", \"198.51.100.20\"] && request_count > 100 && StatusRatio(404) >= 0.8"
}
]
}
```
## 使用建议
先用较短的回看窗口和较高阈值观察命中结果,再逐步调整阈值。管理端 IP 组页面支持在保存前点击 **测试规则**,直接查看当前回看窗口内命中的 IP;自动 IP 组真正执行后会覆盖该组的 IP 列表。如果要长期保留某些地址,建议放入手动 IP 组,并在 WAF 规则组中同时引用手动组和自动组。
自动 IP 组更新后不需要重新发布配置版本。在线 Agent 会通过 WebSocket 收到变更 IP 组并更新本地 `waf_ip_groups.json`;WebSocket 不可用时,Agent 会在下一次心跳中上报本地 IP 组 checksum,Server 只返回 checksum 不一致的 IP 组。
+162
View File
@@ -0,0 +1,162 @@
# WAF 安全防护使用
你会学到:OpenFlare 边缘 Web 应用防火墙 (WAF) 的工作原理、防护维度,如何管理与引用三类 IP 组(手动、订阅与基于 Expr 的自动 IP 组),配置防 CC 挑战(PoW 人机验证)与地域级拦截,以及如何在不 reload 进程的情况下实现 IP 组成员的秒级热更新。
---
## 核心概念
在配置安全策略前,你需要理解 WAF 的几个核心组成部分:
| 概念 | 说明 | 作用范围与生效方式 |
| --- | --- | --- |
| **WAF 规则组 (Rule Group)** | 安全规则的逻辑集合。包括:IP 黑白名单(直接录入或引用 IP 组)、国家/地区地域限制、防 CC 挑战(PoW)以及自定义拦截响应。 | 支持全局生效或绑定到单个/多个网站。**修改规则组定义必须发布并激活配置版本**。 |
| **IP 组 (IP Group)** | 存放单个 IP 或 CIDR 网段的列表容器。分为**手动**、**订阅**与**自动**三类。WAF 规则组可通过 ID 引用 IP 组。 | 属于动态资源。**IP 组成员的增减支持 WebSocket 秒级无缝热同步,无需 reload 进程**。 |
| **人机挑战 (CC PoW)** | 基于 Proof of Work (工作量证明) 的人机验证挑战。通过让浏览器计算特定难度的哈希碰撞,静默阻断恶意刷接口的自动化脚本与 Bot,保障正常用户体验。 | 位于规则组内的配置 Tab。**修改 PoW 参数必须发布并激活配置版本**。 |
---
## 推荐配置顺序
配置网站的安全防护时,推荐按这个顺序进行:
1. 进入 IP 组管理,创建所需的 **手动 IP 组** (如开发者白名单) 或 **自动 IP 组** (如根据 404 扫描自动封禁的 IP)。
2. 创建或编辑 **WAF 规则组**:
* 绑定需要引用或阻断的 IP 组。
* 配置国家或省份的地域黑白名单限制。
* (可选) 在 `PoW` 标签页配置人机挑战参数。
* 在 `拦截返回` 标签页设定自定义状态码(如 403, 418)和 HTML 拦截页。
3. 将规则组关联到对应的 **网站配置**。
4. 发布并激活配置版本,使边缘节点 (Agent) 开始应用 WAF 规则过滤流量。
---
## 详细步骤指南
### 第一步:管理与配置 IP 组
IP 组是进行大批量 IP 过滤的基石。OpenFlare 提供了极富弹性的三类 IP 组:
#### 1. 手动 IP 组 (Manual)
* **用途**:静态维护一些确定受信任或确定需长期拦截的 IP/网段。
* **配置**:点击「创建 IP 组」-> 类型选择「手动」-> 按行直接填入 IP 或 CIDR 格式(例如 `192.168.1.100` 或 `10.0.0.0/24`)。
#### 2. 订阅 IP 组 (Subscription)
* **用途**:接入第三方威胁情报库或云厂商公布的 IP 范围。
* **配置**:类型选择「订阅」-> 输入抓取 URL(支持按行分隔的文本文件或标准的 JSON 格式)。控制面板的定时任务会周期性拉取订阅源并自动同步至该组名单中。
#### 3. 自动 IP 组 (Automatic)
* **用途**:**最具杀伤力的防扫描、防爆破自动通道**。
* **配置**:类型选择「自动」-> 编写 Expr 日志聚合逻辑。你可以直接引用系统内置的预设:
* **单 IP 404 高频扫描**:`request_count > 100 && StatusRatio(404) >= 0.8` (单个 IP 最近一小时请求超 100 次且 404 响应占比超 80%)。
* **单 IP 直连访问异常**:`ip_host_count > 50 && ip_host_ratio > 0.5` (绕过域名直接通过 IP 地址进行高频请求)。
* **测试与立即执行**:保存前可点击 **「测试规则」** 按钮预览当前日志窗口被命中的 IP。保存后可点击 **「立即执行」** 直接聚合日志并生成封禁名单。
> [!TIP]
> 自动 IP 组的详细语法和可用指标请参阅 [WAF 自动 IP 组规则语法](./waf-ip-group-expr.md)。
---
### 第二步:创建与配置 WAF 规则组
1. 导航至左侧菜单 **「安全防护 (WAF)」**,点击 **「创建规则组」**。
2. 填写规则组名称(如 `production-api-shield`),选择是否为「全局规则组」。
3. 进入规则组详情,在下方几个配置 Tab 中依次设置:
#### 1. 黑白名单配置 (Allow / Block Lists)
* **直录 IP**:可直接在框内按行填入临时需要白名单放行或黑名单阻断的单个 IP 或网段。
* **IP 组引用**:点击「绑定 IP 组」,选择你在第一步中配置好的手动、自动或订阅 IP 组。白名单引用会直接放行,黑名单引用则直接阻断。
#### 2. 地域限制 (GeoIP)
* **说明**:OpenFlare 集成了 GeoIP 地理位置解析。
* **配置**:可开启地域限制开关,模式可选择「仅允许」或「禁止」。
* * 例如,若你的服务只服务于国内,可以将模式设为「仅允许」,并在国家列表中勾选 `中国`。
* * 支持细化到具体省份/地区(Region),一键拦截特定地理区域的恶意流量。
#### 3. 人机挑战配置 (PoW CC 防护)
* **说明**:开启防 CC 的人机挑战。当请求触发防CC机制时,浏览器会渲染一个静默挑战页面,并在几百毫秒内完成数学计算(哈希碰撞)。通过后会被写入 Cookie,后续访问直接放行。此过程对真实用户几乎无感,但能完美拦截不支持 JS/不具备计算能力的爆破脚本与 CC 僵尸工具。
* **核心参数**:
* **开启状态**:启用/禁用。
* **哈希难度**:控制碰撞难度(建议设定为 `4` 或 `5`)。
* **Cookie 有效期**:挑战通过后,在多长时间内免验证(例如 `3600` 秒)。
* **自定义挑战 HTML**:可定制挑战中的 Loading 页面风格,让其融入你的业务设计。
#### 4. 拦截返回 (Block Response)
* **说明**:设定 WAF 规则拦截恶意请求时的返回行为。
* **配置**:
* **拦截状态码**:可自定义拦截响应的 HTTP 状态码,例如标准的 `403`,或带有趣味性质的 `418 (I'm a teapot)`。
* **拦截响应体**:可在此输入自定义的 HTML 内容,展示给被拦截的攻击者(如:“WAF 拦截:你的请求已被记录”)。
---
### 第三步:将规则组关联到网站
规则组配置完成后,并不会自动生效,你需要将其与具体的网站配置绑定。
* **方案 A (推荐)**:在规则组详情页面的 **「绑定网站」** 选项卡中,一键勾选你希望启用此防护的网站并保存。
* **方案 B**:回到 **「网站配置」** 中编辑某个具体网站,在其「安全防护」配置区,勾选并绑定刚才创建的规则组。
> [!NOTE]
> 如果规则组被标记为 **「全局规则组 (is_global)」**,它将自动应用到网关上托管的**所有网站**,无需手动执行绑定。
---
### 第四步:发布并生效配置
1. 如果你修改了 **规则组定义**、**GeoIP 范围**、**PoW 防CC难度** 或 **网站的绑定关系**:
* 你需要点击管理端右上角的 **「配置预览」** -> **「发布并激活」**。
* Agent 拉取并校验新版本后,将重写本地 OpenResty 核心配置文件(`waf_config.json` 等)并平滑重载进程使策略生效。
2. 如果你只是更新了 **IP 组的成员名单**(如:在手动 IP 组中删减了一个 IP,或者自动 IP 组定时聚合出了一批新的封禁 IP):
* **不需要做任何发布操作!**
* Server 会在数据库更新后立即计算 IP 组全新的 Checksum 摘要。
* 控制面会通过 **WebSocket 长连接实时向所有在线的 Agent 广播** 变更的 IP 组成员,Agent 接收后会增量覆写到本地的运行时磁盘文件 `waf_ip_groups.json`。
* OpenResty Lua 引擎在处理新请求时,会在微秒级计算文件哈希,若发现 Checksum 变更则实时重载入内存字典(`ngx.shared`),**整个过程全程不需要 reload 任何 Nginx 服务,对线上高并发业务毫无影响**。
* 即使 WebSocket 连接意外中断,Agent 也会在每周期心跳中上报本地 Checksum,由 Server 差分补齐下发,确保万无一失。
---
## WAF 判定逻辑 (过滤漏斗)
当一个外部请求到达 OpenResty 数据面时,WAF 运行时引擎会以微秒级的极速开销进行如下判决流检测。只要判定出明确结果,即不再向下执行:
```text
请求进入 access 阶段
│
▼
获取当前请求绑定的所有规则组 (全局规则组 + 自定义规则组)
│
▼
1. 匹配 IP 白名单 / 白名单 IP 组? ──────(是)─────► [ 放行 (ALLOW) ]
│ (否)
▼
2. 匹配国家 / 省份地域白名单? ────────(是)─────► [ 放行 (ALLOW) ]
│ (否)
▼
3. 匹配 IP 黑名单 / 黑名单 IP 组? ──────(是)─────► [ 拦截 (BLOCK) ] ──► 返回自定义状态码与HTML拦截页
│ (否)
▼
4. 匹配国家 / 省份地域黑名单? ────────(是)─────► [ 拦截 (BLOCK) ] ──► 返回自定义状态码与HTML拦截页
│ (否)
▼
5. 该站点是否启用了 PoW CC 防护?
├───(是)───► [ 校验 PoW Cookie ] ──(验证通过)──► [ 放行 (ALLOW) ]
│ │
│ (未通过)
│ ▼
│ [ 渲染 PoW 挑战页 ] ──(计算正确)──► 写入 Cookie 并放行
▼
6. 未触发任何策略,属于正常业务流量 ───────────────► [ 放行 (ALLOW) ]
```
---
## 最佳实践与调优建议
* **白名单前置与保护**:在部署高强度黑名单或地域屏蔽前,建议首先创建一个「受信任 IP 组」,放入你团队的办公室出口 IP、本地开发 IP 以及可能访问你的第三方回调源站 IP(如微信、支付宝支付回调地址),并在规则组的**白名单**中优先引入。这可以有效防止误杀。
* **合理微调 PoW 难度**:人机 CC 挑战的哈希碰撞计算(`challenge_difficulty`)是一把双刃剑。
* 难度值 `3`:几乎瞬间完成计算,防 CC 强度低。
* 难度值 `4`:普通手机/低端浏览器在 100~300ms 内完成计算,防护性能良好。
* 难度值 `5`:需要 500ms~2s,防护性强,但低配端可能会感觉稍显卡顿。
* 难度值 `6` 及以上:计算量呈指数级上升,可能导致移动端用户浏览器 CPU 持续打满卡死。**因此强烈建议在生产环境选用 `4` 或 `5`**。
* **善用“测试规则”**:对于自动 IP 组,在点击保存之前务必点击 **「测试规则」**。通过分析当前窗口内被命中的 IP 列表,确认你的 Expr 表达式阈值(如请求数、404占比等)配置是否过宽或过紧,防止由于阈值配置不合理导致大面积误封正常用户。
* **分离静态与动态黑名单**:不要将需要长期封禁的静态恶意 IP 填入自动封禁组(因为自动聚合的名单随时会被新的执行窗口覆盖)。应该将确定的恶意 IP 录入到一个专门的「手动封禁 IP 组」中,并让规则组同时引用该手动组与自动组。
+188
View File
@@ -0,0 +1,188 @@
你是一个资深 Go 后端工程师,负责维护和开发一个长期演进的 Go 应用。
你的目标不是“尽快写完代码”,而是产出可维护、可测试、可演进、符合 Go 生态习惯的高质量代码。禁止为了完成任务而堆砌临时代码、过度抽象、重复逻辑或破坏现有架构。
在任何开发前,你必须先阅读并理解现有代码结构,包括:
- 项目目录结构
- 入口文件
- 配置管理方式
- 数据库/缓存/消息队列访问方式
- HTTP/RPC/API 层设计
- service/usecase/domain/repository 等分层方式
- 错误处理方式
- 日志方式
- 测试组织方式
- 依赖注入方式
- 现有编码风格
如果你不确定某个模块的职责,先通过代码上下文推断,不要随意新建重复模块。
开发原则:
1. 架构优先
- 优先融入现有架构,而不是另起炉灶。
- 不要随便新增 global variable、init 副作用、隐式依赖。
- 不要把业务逻辑写进 handler/controller。
- handler 只负责参数解析、鉴权上下文、调用 usecase/service、返回响应。
- service/usecase 负责业务编排。
- repository/dao 负责数据访问。
- domain/model 负责核心业务对象和规则。
- 基础设施代码与业务代码隔离。
2. Go 风格
- 使用清晰、直接、朴素的 Go 代码。
- 不要模仿 Java 式过度抽象。
- interface 应该由使用方定义,而不是提供方强行定义。
- 小接口优先。
- 命名要准确,不使用 Manager、Helper、Util 这类含糊名称,除非确实必要。
- 函数保持短小,单一职责。
- 不要为了“看起来高级”引入泛型、反射、复杂设计模式。
- 不要隐藏错误。
- error 必须带上下文信息,必要时使用 fmt.Errorf("...: %w", err)。
- 不要 panic,除非是程序启动阶段的不可恢复错误。
3. 可维护性
- 修改前先分析影响范围。
- 尽量最小改动,不做无关重构。
- 不改变公开 API、数据库结构、配置格式,除非任务明确要求。
- 如果必须改变,要说明兼容性影响和迁移方案。
- 删除代码前确认没有调用方。
- 避免复制粘贴已有逻辑,应抽取到合适位置,但不要过度抽象。
- 对复杂业务逻辑添加必要注释,解释“为什么”,不要注释显而易见的“是什么”。
4. 测试要求
- 新增业务逻辑必须补充单元测试。
- 修复 bug 必须补充回归测试。
- 测试应覆盖正常路径、异常路径、边界条件。
- 不要为了测试方便破坏业务代码结构。
- 外部依赖使用 mock/fake/stub 隔离。
- 测试命名清晰,例如 TestXXX_WhenYYY_ShouldZZZ。
- 表驱动测试优先,但不要为了表驱动牺牲可读性。
5. 并发与资源管理
- goroutine 必须有退出机制。
- 涉及 context 的地方必须正确传递 context.Context。
- 不要随意使用 context.Background() 替代上游 context。
- channel 必须明确关闭责任。
- 锁的范围要小,避免死锁。
- HTTP、数据库、文件、连接等资源必须正确关闭。
- 注意 race condition、goroutine leak、连接泄露。
6. 数据库与事务
- 数据库访问必须在 repository/dao 层。
- 事务边界应由业务用例层控制,而不是散落在多个底层函数中。
- 不要在循环中产生明显低效的 N+1 查询,除非数据量可控且有说明。
- SQL 要可读、参数化,禁止拼接不可信输入。
- schema 变更必须考虑迁移、回滚和兼容性。
7. API 设计
- 请求参数必须校验。
- 错误响应要稳定、清晰,不泄露内部敏感信息。
- 日志中不要打印密码、token、密钥、身份证号等敏感数据。
- 返回结构保持向后兼容。
- HTTP 状态码要语义正确。
8. 日志与可观测性
- 关键路径要有必要日志。
- 错误日志要包含排查所需上下文,但不要泄露敏感数据。
- 不要滥打日志。
- 不要在库代码里直接 fmt.Println。
- 如果项目已有 logger,要统一使用现有 logger。
9. 安全要求
- 所有外部输入都不可信。
- 不要硬编码密钥、token、密码。
- 不要把敏感配置提交到代码。
- 文件路径、URL、命令执行、SQL、模板渲染等位置必须注意注入风险。
- 鉴权和权限判断必须放在明确的位置,不能依赖前端或调用方自觉。
10. 性能要求
- 不要过早优化。
- 但不能写明显低效代码。
- 对热点路径要避免不必要的内存分配、大对象复制、重复解析。
- 大数据量处理应考虑分页、流式处理、批量操作。
- 如果引入缓存,必须说明一致性、过期策略和失效条件。
工作流程:
每次接到开发任务,你必须按以下步骤执行:
第一步:理解需求
- 用自己的话简要复述需求。
- 明确输入、输出、边界条件、异常情况。
- 如果需求含糊,列出你的合理假设,不要直接乱写。
第二步:阅读现有代码
- 找出相关模块、调用链、数据结构、接口、测试。
- 说明当前代码是如何工作的。
- 判断改动应该放在哪一层。
第三步:设计方案
- 给出最小可行修改方案。
- 说明为什么放在这些文件/模块中。
- 说明是否影响已有 API、数据库、配置、测试。
- 如果有多个方案,比较优缺点,选择更稳妥的方案。
第四步:编码
- 只修改与任务相关的代码。
- 保持现有代码风格。
- 不引入不必要的新依赖。
- 不制造重复逻辑。
- 不留下 TODO、临时代码、调试代码。
第五步:测试
- 补充或更新测试。
- 说明测试覆盖了哪些场景。
- 如果无法运行测试,要说明原因,并给出应该运行的命令。
第六步:交付说明
- 总结改了什么。
- 说明为什么这样改。
- 说明潜在风险。
- 给出验证方式。
- 如果存在未完成项,必须明确列出,不要假装完成。
输出格式:
你每次回复都应包含:
1. 需求理解
2. 现有代码分析
3. 修改方案
4. 具体改动
5. 测试与验证
6. 风险与注意事项
如果只是让我审查代码,则输出:
1. 问题列表
2. 严重程度:致命 / 高 / 中 / 低
3. 影响说明
4. 修改建议
5. 推荐改法示例
代码质量红线:
禁止出现以下行为:
- 为了完成需求复制粘贴大段重复代码
- 在 handler 中塞业务逻辑
- 到处传 map[string]interface{}
- 使用全局变量绕过依赖注入
- 随意新增 util/helper 垃圾桶包
- 忽略 error
- catch-all 式错误处理
- 函数超过合理长度仍继续堆逻辑
- 修改无关代码
- 未经说明改变已有行为
- 无测试地修改核心逻辑
- 引入大型依赖只为解决小问题
- 写完代码不说明验证方式
- 不理解现有架构就直接重构
当你发现现有代码已经比较混乱时:
- 不要一次性大重构。
- 先局部止血。
- 新代码尽量写在清晰边界内。
- 对旧代码只做必要改动。
- 如果需要重构,先提出分阶段计划。
请始终以“长期维护这个项目的人”的标准来写代码,而不是以“完成一次性任务”的标准来写代码。
+57
View File
@@ -0,0 +1,57 @@
# OpenFlare 特定项目开发准则 (Project Guidelines)
本文档定义了针对 **OpenFlare** 项目特定的后端开发约束、架构设计模式、GORM 数据库交互规范以及关键的 JSON 序列化避坑指南。所有参与项目后端开发的代码必须严格遵守。
---
## 1. 统一接口输入与响应处理(Controller 约束)
为了保证 API 的一致性,并消除控制器层中大量的样板代码,所有 Gin Controller 必须遵守以下规范:
### 1.1 参数解析与绑定
- **URL ID 参数解析**:必须调用统一的 `parseIDParam(c)` 辅助函数。严禁手写 `strconv.ParseUint(c.Param("id"), ...)`。
- **JSON 请求体绑定**:必须调用统一的 `bindJSON(c, &input)` 辅助函数。严禁手动调用 `c.ShouldBindJSON` 或 `json.NewDecoder` 并重复编写错误返回逻辑。
### 1.2 标准 API 响应
- 所有控制器方法的返回必须统一使用 `respondSuccess`、`respondFailure`、`respondBadRequest` 等标准方法。
- **严禁手写** `c.JSON(http.StatusOK, gin.H{...})`,以确保全局 API 响应字段结构(`success`/`message`/`data`)的百分之百一致。
> [!IMPORTANT]
> 接口的入参解析与响应统一规范定义在 [openflare_server/controller/response.go](file:///Users/ryan/DEV/Go/OpenFlare/openflare_server/controller/response.go) 中。
---
## 2. 纯净工具类与数据库逻辑完全隔离(Utils 约束)
为了确保代码的可测试性、高内聚和低耦合,`utils/` 目录下的工具包必须保持纯净性:
### 2.1 无副作用与解耦原则
- 所有底层客户端与外部服务对接包(如 `utils/acme` 证书操作、邮件发送、DNS 供应商对接等)**必须完全剥离数据库或 GORM 依赖**。
- 工具包中严禁导入 `openflare/model` 包或直接访问数据库连接。它们应当只接受基础数据类型(如 `string`、`[]byte` 等)或本地无依赖结构体作为输入,并返回纯粹的计算或请求结果。
### 2.2 业务服务层(Service)职责
- 业务服务层 `service/` 负责数据库实体的加载、组装、事务持久化,并将底层的具体网络或加密操作委托给 `utils/` 工具包。
- 这样不仅保证了底层工具类的百分之百可单元测试性,也维护了清晰的系统分层。
---
## 3. Go 泛型切片去重与 JSON 序列化陷阱(Slice 约束)
在进行切片操作和去重时,必须使用泛型辅助函数,并注意 Go Slice 的空/零值在 JSON 序列化中的表现。
### 3.1 避免重复编写 map-seen 逻辑
- 禁止在 `service/` 或 `model/` 中手写临时的 map-seen 去重样板代码。
- 必须统一调用基于 Go 泛型实现的 [openflare_server/utils/slice.go](file:///Users/ryan/DEV/Go/OpenFlare/openflare_server/utils/slice.go) 中的 `utils.Unique()` 辅助函数。
### 3.2 关键的 JSON 序列化规则(Nil vs. Empty Slice)
在 Go 中,未初始化的 `nil` 切片和已初始化的空切片 `[]T{}` 在内存中不同,它们在序列化为 JSON 时也有着决定性的区别:
- **`nil` 切片**:序列化为 JSON `null`。
- **空切片 (`make([]T, 0)`)**:序列化为 JSON `[]`。
> [!CAUTION]
> **开发避坑准则**:
> 1. GORM 数据库的很多 JSON/Array 字段(例如 `domain_cert_ids`、`upstreams` 等)或配置版本变更检测机制(如 `checksum` 计算和 `diff` 检测),要求空数组在 JSON 中必须表示为 `[]` 而非 `null`,否则会触发重复发布或解析失败的 bug。
> 2. `utils.Unique` 必须具备 **Nil-Preservation(空值保留)** 特性:
> - 如果传入的 Slice 是 `nil`,它必须返回 `nil`,以支持 `omitempty` 或在需要表示“缺失”的场景中输出 `null`。
> - 如果传入的 Slice 不是 `nil`(即使长度为 0 或去重后长度为 0),它必须返回非 nil 的空切片 `make([]T, 0)`,以确保序列化为 `[]`。
> 3. 所有类似的切片加工辅助函数都必须遵循此行为。
+289
View File
@@ -0,0 +1,289 @@
# 开发约束
你会学到:OpenFlare 代码修改的准入标准、后端/Agent/前端分层约束、数据模型边界、API 约定、数据库迁移要求和测试交付基线。
本文档融合原开发规范、前端规范与开发计划。
## 变更准入
新需求进入实现前,按以下顺序判断:
1. 是否符合 [产品边界](../design/index.md)。
2. 是否符合本文档的后端、Agent 与前端约束。
3. 是否会破坏现有发布、同步、回滚或升级主链路。
4. 是否需要同步更新部署、配置、README 或文档站页面。
如果需求超出边界或引入新基础设施,应先更新设计文档,再开始实现。
## 技术基线
Server:
* Go 1.25+
* Gin
* GORM
* SQLite / PostgreSQL
* 现有登录体系
Agent:
* 单二进制
* 节点本地执行
* 通过 `openresty_path` 或默认 `openresty` 控制 OpenResty 二进制
* Docker 部署使用内置 OpenResty 的 Agent 镜像,不由 Agent 再控制独立 OpenResty 容器
Frontend:
* Next.js 15 App Router
* React 19
* TypeScript 5
* Tailwind CSS 4
* TanStack Query
* React Hook Form + Zod
* Zustand 仅用于轻量客户端状态
* ESLint + Prettier
* Vitest + Testing Library + Playwright
* pnpm
## 工程分层约束
各组件和模块(Server、Agent、Frontend)的物理目录分层职责详见 [仓库结构](../design/repository.md)。在此结构下,开发必须遵守以下核心分层规则:
* **Server 开发规则**:禁止在 `controller/` 堆积业务逻辑,禁止在 `middleware/` 实现业务流程,禁止为简单需求新增平台层抽象。
* **Agent 开发规则**:每个模块职责单一,外部命令调用集中封装,状态落盘与配置落盘分离。
* **Frontend 开发规则**:页面文件只负责获取路由参数、组织页面结构、调用 feature 组件;不应手写复杂 API 细节、复杂表单校验逻辑或维护大量彼此耦合的局部状态。
## 数据模型规范
在定义和修改 Go/GORM 模型实体时,所有模型的业务边界与设计约束必须严格符合 [产品边界](../design/index.md)。
### 1. 当前有效实体
* **核心配置与反代**:`proxy_routes` (网站配置), `origins` (源站), `config_versions` (配置版本), `tls_certificates` (证书), `managed_domains` (托管域名).
* **节点与状态**:`nodes` (节点), `node_system_profiles` (系统概况), `apply_logs` (应用日志).
* **内网穿透**:`tunnels` (隧道客户端), `tunnel_tokens` (隧道认证令牌,可选持久化).
* **观测与分析**:`node_request_reports` (请求上报), `node_access_logs` (访问明细), `node_metric_snapshots` (指标快照), `traffic_analytics_rollups` (流量聚合), `node_health_events` (健康事件).
* **系统配置与第三方登录**:`options` (全局参数), `auth_sources` (第三方认证源), `external_accounts` (外部绑定账号).
* **安全与 WAF**:`waf_rule_groups` (WAF规则组), `waf_ip_groups` (WAF IP组), `waf_rule_group_bindings` (网站WAF绑定).
### 2. 底层数据库技术约束
在编写或修改模型时,必须严格遵守以下持久化与数据库设计准则:
* **禁止随意引入平台化新实体**:除非 [产品边界](../design/index.md) 设计发生调整并经评审。
* **业务唯一性保障**:
* `proxy_routes.site_name` 作为业务唯一主标识。
* `proxy_routes.domains` 中的各域名必须全局唯一,不可跨站点冲突,列表第一项视为主域名。
* `nodes.node_id` 唯一标识节点(自动生成或由用户指定)。
* `tunnels.tunnel_id` 唯一标识内网穿透客户端(格式 `tun-<32hex>`,自动生成)。
* **兼容字段处理**:遗留的 `proxy_routes.domain` 只能作为 `domains[0]` 的只读/兼容镜像,新代码不得以该字段为唯一业务输入。
* **多上游及 Keepalive**:单上游时应支持 base path/query 并在 `proxy_pass` 中正确补齐 URI;多上游负载均衡时仅允许纯 `scheme://host[:port]`。
* **证书映射**:证书绑定必须通过逐域名平行的 `domain_cert_ids` 字段精确保存,未绑定证书的域名不得参与 HTTPS 渲染。
* **版本快照一致性**:`config_versions` 必须保存版本发布时的完整快照及 checksum 校验码,确保渲染结果不可变且全局单激活版本。
* **外部账户唯一绑定**:第三方登录必须通过 `external_accounts` 映射至本地唯一用户,原 `users.github_id` 仅用于向后兼容迁移,任何新登录流程禁止以此为业务输入。
* **Tunnel 与上游关联**:
* `proxy_routes.upstream_type = 'tunnel'` 时,必须指定 `tunnel_id`(关联到 `tunnels` 表)。
* 必须指定 `tunnel_target_addr`(内网目标地址,如 `192.168.1.100:8080`)和 `tunnel_target_protocol`(`http` 或 `https`)。
* 发布配置时,Server 自动将此上游渲染为 `http://127.0.0.1:{relay_vhost_port}`,Agent 依据 Host 头由 frps 路由。
* **TunnelRelay 节点配置**:
* `nodes.node_type = 'tunnel_relay'` 时,新增字段 `relay_bind_port`、`relay_vhost_http_port`、`relay_auth_token` 必须有合理默认值。
* `relay_bind_port` 默认 7000,`relay_vhost_http_port` 默认 8080。
* `relay_auth_token` 由 Server 自动生成(32 位随机字符串),不由用户输入。
* 相对静态配置(如 `relay_agent_access_addr`、`relay_client_access_addr`)由 Relay 心跳下发,Server 可记录但不纳入版本化流。
* **Tunnel 客户端状态**:
* `tunnels.status` 记录客户端在线/离线/待激活状态。
* `tunnels.current_version` / `tunnels.current_checksum` 记录当前已应用的配置版本。
* `tunnels.connected_relays` 以 JSON 数组形式存储已连接 Relay 的信息(relay_node_id、连接状态等)。
* `last_seen_at`、`last_error` 用于调试和可观测性。
## 数据库迁移
任何涉及表结构、索引、列类型、分表规则或内部持久化元数据的修改,都必须同步提升数据库版本号。
数据库版本号定义在 `openflare_server/model`,不得只依赖 `AutoMigrate` 隐式升级存量数据库。
每次提升数据库版本号时,必须补充从上一版本升级到新版本的显式迁移方法。迁移方法必须包含升级后的校验逻辑;只有校验通过,才能写入新的数据库版本记录。
v1-v7 视为历史初始基线,不再维护逐版本升级文件。v8-v17 是旧升级框架的兼容迁移链,只保留在 `openflare_server/model/migrate` 目录中用于老库升级。旧库启动时必须先按旧框架升级到 `legacyMigrationTerminalVersion`,再桥接到 goose;不得为了整理文件而改变已发布 v8-v17 迁移的语义。
从 v17 之后,数据库升级统一使用 goose。新的 goose provider、桥接逻辑、注册入口和具体迁移文件必须全部放在 `openflare_server/model/goose` 包下,`openflare_server/model` 根包只保留纯净实体类、旧框架兼容适配和必要的上下文注入。每次新增数据库升级都必须新建一个单独的 Go 文件,文件名使用 `openflare_server/model/goose/goose_<timestamp>_<description>.go`,例如 `openflare_server/model/goose/goose_202606020001_add_node_capabilities_json.go`。迁移文件必须同时包含该版本的 goose migration 构造函数、升级逻辑和校验逻辑;`model/goose/migrations.go` 只能作为注册入口和公共构造工具,禁止把具体迁移逻辑集中堆放在该文件中。
执行数据库升级时必须按以下步骤完成:
1. 判断是否需要升级数据库版本:凡是新增/删除/重命名表、字段、索引、约束、列类型、分表规则,或改变持久化数据语义,都必须升级。
2. 新增 `openflare_server/model/goose/goose_<timestamp>_<description>.go`,其中 `<timestamp>` 为 goose 版本号。文件头部或迁移构造函数附近必须包含注释,说明本次升级了什么内容,以及为什么需要升级。
3. 在该文件中实现独立迁移构造函数,并返回通过 `newGORMMigration(...)` 创建的 migration;随后只在 `openflare_server/model/goose/migrations.go` 的 `registeredMigrations(...)` 中新增一条注册项。
4. 在同一个单独迁移文件中写入升级逻辑。可通过 goose `Context` 调用 `ApplyCurrentSchema`、历史 backfill、默认数据初始化等公共能力;复杂数据修复必须显式处理,不得只依赖 `AutoMigrate`。
5. 在同一个单独迁移文件中写入升级后的校验逻辑。校验至少要覆盖新增表/字段/索引是否存在、关键默认数据是否存在、必要的数据回填是否成功。
6. 如果新迁移需要新的公共 backfill 或校验辅助函数,优先放在该迁移文件中;只有多个迁移共同复用时,才放到 `openflare_server/model/goose` 包内的公共文件中。不要把新 goose 框架代码放回 `openflare_server/model` 根包。
7. 补充迁移测试:至少覆盖从旧框架终点或上一 goose 版本升级后 schema version、字段/表结构、关键数据回填和校验结果。还应保留旧库从 v15/v17 桥接到 goose 的回归覆盖。
8. 同步更新设计/开发文档;如果管理端 API、配置项或用户可见行为变化,还要同步更新对应指南、配置参考和 Swagger 文档。
新包启动后必须先检查数据库当前版本,再按顺序逐步升级到目标版本;禁止跳过中间升级步骤直接写目标版本。
空库初始化可以直接建立当前版本结构,但初始化完成后仍必须执行同版本校验,并落库当前数据库版本。
如果迁移失败或校验失败,启动流程必须中止,且不得提升数据库版本记录。涉及数据库版本变更的提交,必须补充对应的迁移测试或等效回归测试。
## API 与鉴权
管理端与 Agent/Relay/Client API 统一使用 JSON。成功与失败都必须返回清晰 `message`:
```json
{
"success": true,
"message": "",
"data": {}
}
```
约定:
* Agent API 固定放在 `/api/agent/*`,使用 `X-Agent-Token` 认证(节点专属 token)。
* **Relay API** 固定放在 `/api/relay/*`,使用 `X-Agent-Token` 认证(同 TunnelRelay 节点)。
- Server 通过 token + `/api/relay/*` 路径区分 Relay 请求。
- Relay 心跳返回 frps 配置(bindPort、vhostHTTPPort、authToken)。
- Relay 上报进程状态、连接数、proxy 列表等指标。
* **Tunnel Client API** 固定放在 `/api/flared/*`,使用 `X-Tunnel-Token` 认证(独立的 tunnel_token)。
- OpenFlared 使用 `tunnel_token` 与 Server 通信,独立于 Agent 认证体系。
- Client 心跳返回 tunnel 配置版本摘要。
- Client 可拉取完整配置(relay 列表 + frpc 代理定义)。
- Client 上报配置应用结果。
* **Admin Tunnel 管理 API** - `/api/tunnels/*`,要求 Admin Session。
- CRUD tunnel 实体(创建、查询、更新、删除)。
- Token 管理(生成、轮换)。
- 强制同步(触发 Client 立即拉取新配置)。
* 总览与节点详情优先使用专用聚合接口。
* 管理端变更类接口统一使用 `POST`;只读接口使用 `GET`。
* 管理端继续复用现有登录、角色与 Session。
* 第三方登录统一通过认证源 API 进入,认证源管理接口必须要求 Root Session。
* `/api/status` 只能返回已启用认证源的公开字段,不得返回 Client Secret。
* 第三方账号未绑定且注册关闭时,应提供绑定已有账号流程,不得自动创建用户。
* Agent/Relay/Client 正式请求统一使用对应的专属 token(`agent_token` / `relay_token`(即 agent_token) / `tunnel_token`)。
* 首次接入 Agent 可使用全局 `discovery_token`;首次接入 Client 由 Server 生成 tunnel_token,直接用于部署命令。
* Agent/Relay 请求头统一使用 `X-Agent-Token`;Client 请求头统一使用 `X-Tunnel-Token`。
禁止暴露远程 shell 或任意命令执行入口,禁止在日志中打印完整 Token,禁止绕过占位符约束保存不可渲染的主配置模板。
## 发布与运行
发布逻辑必须保持:
* 发布时读取全部启用的 `proxy_routes`。
* 同时读取 OpenResty 主配置参数、反代性能参数与缓存参数。
* 读取 WAF 规则组、规则组引用的 IP 组与网站绑定关系,并在发布快照中保存可回放数据。
* 自动型 WAF IP 组只能由 Server 定时任务读取请求日志并执行 Expr 布尔规则,OpenResty Lua 与 Agent 不得直接访问请求日志库或执行自动挖掘逻辑。
* 发布版本不得展开 WAF IP 组成员;Agent 必须通过独立的 IP 组 checksum 差异同步和 WebSocket 增量广播维护本地 `waf_ip_groups.json`。
* **内网穿透配置扩展**:区分上游类型,为 `upstream_type = 'tunnel'` 的代理规则生成独立的 tunnel 配置数据。
* OpenResty 侧:将 tunnel 上游自动渲染为 `http://127.0.0.1:{relay_vhost_port}`,必须保留原始 `Host` 请求头。
* Tunnel 侧:为每个 Client 生成完整的 relay 列表与 frpc 代理定义(frpc proxy 配置)。
* 生成完整 OpenResty 配置。
* 计算 `checksum`。
* 写入 `config_versions`(OpenResty 部分)+ 生成或更新 tunnel 配置版本数据。
* 通过切换 `is_active` 激活版本。
版本约束:
* 版本号格式固定为 `YYYYMMDD-NNN`。
* 同一版本号同时关联 OpenResty 配置与 Tunnel 配置,保证一致性。
* 不在线修改历史版本。
* 不做按节点分组的差异化版本。
* 预览与 diff 是只读能力,不产生发布记录。
Agent 必须满足:
* 启动后读取或生成本地 `node_id`。
* 周期性心跳与同步。
* 常规同步优先依据 heartbeat 返回的版本摘要判断。
* WS 连接升级开启且连接成功时,Agent 可通过 WS 接收激活版本摘要并立即同步;WS 失败或断开必须退回 HTTP heartbeat。
* 发现新版本时先备份旧文件。
* 写入主配置、路由配置与必要证书文件。
* 写入 WAF/PoW 运行时配置,并确保 WAF Lua 资源由 Agent 统一管理。
* WAF IP 组同步必须按组增量更新,不得在每次心跳或每次同步中传输全部 IP 组。
* 写入新配置后执行 `openresty -t -c <main_config_path>`,再 reload;reload 发现运行时未启动时允许直接启动 OpenResty。
* 周期性运行时健康检查不得调用 `openresty -t`,避免健康探针触发 upstream 域名同步解析;应优先请求本地 `openresty_observability_port` 上的 `/openflare/stub_status`,以 HTTP `200 OK` 作为 OpenResty 主进程和 worker 正在提供服务的判断依据。
* 新配置激活失败时必须先尝试用目标配置恢复运行,再回滚到旧配置并重新拉起 OpenResty。
* 回滚后 OpenResty 恢复正常时上报警告;如果本地没有历史主配置可恢复,必须允许写入内置安全兜底配置并拉起对外只监听 `80` 端口、统一返回 `503` 的 OpenResty 运行态;兜底配置仍需保留本地 `stub_status` 健康检查入口。
* 兜底运行态不得清除失败目标的阻断状态;应用记录必须能体现目标版本失败但 fallback runtime 已启动。存在历史主配置但回滚后仍无法恢复运行时上报失败。
* 某个目标 `version + checksum` 一旦应用失败并回退,Agent 必须在本地状态中阻断该目标的重复应用。
* Agent 维护本地 MaxMind mmdb 时,下载或刷新失败只能记录警告,不得阻断心跳、同步、配置应用或 OpenResty 健康检查。
OpenFlareRelay 必须满足:
* 启动后从 config 读取 Server 地址和 `agent_token`。
* 周期性向 Server 发送心跳,获取 frps 配置(bindPort、vhostHTTPPort、authToken)。
* 根据心跳响应生成 frps.toml,启动或更新 frps 进程。
* 上报 frps 进程健康状态、连接数、proxy 数等指标。
* frps 进程异常时自动重启,并上报失败信息。
* 可选支持 WebSocket 升级连接,接收实时配置推送。
OpenFlared 必须满足:
* 启动后从 config 读取 Server 地址和 `tunnel_token`。
* 周期性向 Server 发送心跳,获取 tunnel 配置版本摘要。
* 发现新版本后拉取完整 tunnel 配置(relay 列表 + frpc 代理定义)。
* 为每个 relay 生成独立 frpc.toml,启动新 frpc 进程或对已有进程执行热重载。
* 上报每个 frpc 进程的健康状态与连接情况。
* 配置应用失败时记录错误并上报,支持重试。
* 可选支持 WebSocket 升级连接,接收实时配置变更通知。
## 前端请求、状态与类型
所有 API 请求必须统一经过 `lib/api/`:
* 统一处理 `success/message/data` 响应结构。
* 统一处理鉴权失效、网络异常和通用错误消息。
* 统一维护资源接口与请求路径。
状态分层:
* 服务端状态:TanStack Query。
* 页面临时状态:组件内部 `useState`。
* 跨页面 UI 状态:Zustand。
要求开启 TypeScript 严格模式,禁止滥用 `any`,API 响应、表单输入、业务实体必须有明确类型。
## 表单、交互、样式与主题
表单统一使用 React Hook Form 与 Zod。
高风险操作必须二次确认、展示操作对象名称,并明确成功与失败反馈。
样式原则:
* 统一使用 Tailwind CSS 与现有 token 体系。
* 优先复用已有基础组件与布局组件。
* 保持视觉层级、留白与语义颜色一致。
主题要求:
* 同时支持 `light`、`dark`、`system`。
* 用户选择必须持久化。
* 首屏尽量避免主题闪烁。
## 测试与交付
* 关键业务逻辑必须有单元测试或等效回归测试。
* Agent 主链路修改必须验证同步、应用与回滚。
* 前端页面至少覆盖加载态、空态、错误态与成功反馈。
* Go 版本调整时,同步检查 `go.mod`、Dockerfile 与 CI 工作流。
## 后续维护方式
后续规划不再按“大版本阶段文档”维护,而采用以下方式:
* 产品边界变动:更新 [产品边界](../design/index.md)。
* 工程约束变动:更新本文档。
* 部署与配置变动:更新 [部署说明](../deployment/deployment.md)、[配置项](../reference/configuration.md) 与 README。
如果未来出现明确的新阶段目标,再单独新增专项计划文档;不要把已完成的历史计划继续堆回本文档。
当前专项“网站级规则与配置界面改造”的模型边界已纳入 [产品边界](../design/index.md),执行时仍按数据模型、接口、前端页面、迁移测试与文档联动的顺序推进。
+35
View File
@@ -0,0 +1,35 @@
---
layout: home
hero:
name: OpenFlare
text: 开源 CDN 编排与边缘安全平台
tagline: 支持反向代理、集中式配置同步、内网穿透(Tunnels)、动态 WAF 防护与人机防 CC 挑战。
actions:
- theme: brand
text: 快速开始
link: /guide/quick-start
- theme: alt
text: 设计边界
link: /design/
- theme: alt
text: GitHub
link: https://github.com/Rain-kl/OpenFlare
features:
- icon: 🛰️
title: 集中式配置同步
details: 通过 WebSocket 与心跳实现全网节点配置秒级同步下发与热生效,状态即时回收。
- icon: 🌐
title: 分布式 CDN 编排
details: 将独立的 OpenResty 编排为高度协同的分布式 CDN 舰队,支持源站多负载均衡。
- icon: 🚇
title: 安全内网穿透 (Tunnels)
details: 对标 Cloudflare Tunnels,无须公网 IP 或暴露入向端口,安全穿透本地服务至公网。
- icon: 🛡️
title: 边缘 WAF 安全防护
details: IP 组成员差分同步写入 Lua 共享内存,实现免 Nginx 重载的 WAF 热更新与 GeoIP 过滤。
- icon: 🧩
title: 防 CC 与人机挑战 (PoW)
details: 内置高性能客户端 Proof of Work 密码学挑战,网关边缘秒级拦截阻断僵尸网络与爬虫。
---
+31
View File
@@ -0,0 +1,31 @@
{
"$schema": "./node_modules/@lunariajs/core/config.schema.json",
"repository": {
"name": "Rain-kl/OpenFlare",
"rootDir": "docs"
},
"files": [
{
"location": "**/config.ts",
"pattern": "@lang/@path",
"type": "universal"
},
{
"location": "**/*.md",
"pattern": "@lang/@path",
"type": "universal"
}
],
"defaultLocale": {
"label": "简体中文",
"lang": "zh"
},
"locales": [
{
"label": "English",
"lang": "en"
}
],
"outDir": ".vitepress/dist/_translations",
"ignoreKeywords": ["lunaria-ignore"]
}
+20
View File
@@ -0,0 +1,20 @@
{
"name": "openflare-docs",
"private": true,
"type": "module",
"scripts": {
"dev": "vitepress dev",
"build": "vitepress build",
"preview": "vitepress preview",
"lunaria:build": "lunaria build",
"lunaria:open": "open-cli .vitepress/dist/_translations/index.html"
},
"devDependencies": {
"@lunariajs/core": "^0.1.1",
"markdown-it-mathjax3": "^4.3.2",
"open-cli": "^8.0.0",
"postcss-rtlcss": "^5.7.1",
"vitepress": "2.0.0-alpha.17",
"vitepress-plugin-llms": "^1.11.0"
}
}
+2940
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
allowBuilds:
esbuild: true
+137
View File
@@ -0,0 +1,137 @@
# API 约定
你会学到:OpenFlare 管理端 API 与 Agent API 的响应结构、路径约定、鉴权方式和 Swagger 入口。
OpenFlare 的管理端 API 与 Agent API 都使用 JSON。
## 响应结构
成功与失败都应返回清晰的 `message`:
```json
{
"success": true,
"message": "",
"data": {}
}
```
## 路径约定
| 类型 | 约定 |
| --- | --- |
| 管理端 API | 由管理端 Session 鉴权 |
| Agent API | 固定放在 `/api/agent/*` |
| Relay API | 固定放在 `/api/relay/*`,使用 `X-Agent-Token` 鉴权(与 Agent 复用同一 token) |
| OpenFlared API | 固定放在 `/api/flared/*`,使用 `X-Tunnel-Token` 鉴权(独立的 tunnel_token) |
| 只读接口 | 使用 `GET` |
| 变更类接口 | 使用 `POST` |
## WAF IP 组接口
管理端 WAF IP 组接口统一要求管理端 Session 鉴权:
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| `GET` | `/api/waf/ip-groups` | 查询 IP 组列表 |
| `GET` | `/api/waf/ip-groups/:id` | 查询单个 IP 组 |
| `POST` | `/api/waf/ip-groups` | 创建 IP 组 |
| `POST` | `/api/waf/ip-groups/test` | 测试自动 IP 组 Expr 规则,不保存配置,返回当前日志窗口内命中的 IP 列表 |
| `POST` | `/api/waf/ip-groups/:id/update` | 更新 IP 组 |
| `POST` | `/api/waf/ip-groups/:id/delete` | 删除 IP 组;已被规则组引用时会拒绝 |
| `POST` | `/api/waf/ip-groups/:id/sync` | 立即同步订阅型 IP 组或立即执行自动型 IP 组 |
IP 组 `type` 支持 `manual`、`automatic`、`subscription`。自动型 IP 组的 `auto_config` 是 JSON 对象
自动规则使用 Expr 语法,表达式必须返回布尔值。规则按单个 IP 的请求日志聚合指标计算,可用字段包括 `ip`、`request_count`、`status_404_count`、`status_404_ratio`、`ip_host_count`、`ip_host_ratio`、`client_error_count`、`server_error_count`、`last_seen_unix`。完整语法和字段含义见 [WAF 自动 IP 组规则语法](../guide/waf-ip-group-expr.md)。订阅格式支持 `text` 与 `json`:文本格式按行解析 IP/IP 段并忽略空行和 `#` 开头的注释;JSON 格式可通过映射规则选择数组,默认读取根数组。
## 鉴权
管理端继续复用现有登录、角色与 Session。
Agent 正式请求统一使用节点专属 `agent_token`,首次接入可使用全局 `discovery_token`。Agent 请求头固定为:
```http
X-Agent-Token: <token>
```
### Agent WAF IP 组同步
Agent 心跳 payload 可携带本地 WAF IP 组 checksum:
```json
{
"waf_ip_group_checksums": {
"1": "sha256..."
}
}
```
Server 会根据当前激活版本引用的 IP 组 ID 对比 checksum,并在心跳响应顶层返回差异组:
```json
{
"waf_ip_groups": [
{
"id": 1,
"name": "自动黑名单",
"type": "automatic",
"enabled": true,
"ip_list": ["203.0.113.10"],
"checksum": "sha256..."
}
]
}
```
Agent 也可以在应用新版本后主动请求差异同步:
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| `POST` | `/api/agent/waf/ip-groups/sync` | 根据 Agent 上报的 `ids` 与 `checksums` 返回不一致的 IP 组 |
当 Server 侧 IP 组更新时,已连接的 Agent WebSocket 会收到 `type = "waf_ip_groups"` 的消息,payload 为发生变化的 IP 组数组。Agent 应只更新收到的组,不要求 Server 每次下发全部 IP 组。
## OpenFlared API
OpenFlared 客户端用于内网穿透场景,通过 `tunnel_token` 与 Server 通信,独立于 Agent 认证体系。所有接口都使用 `X-Tunnel-Token` 鉴权,Server 会校验节点 `node_type = tunnel_client`,否则返回 `403`。
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| `POST` | `/api/flared/heartbeat` | 客户端心跳,刷新在线状态并返回 tunnel 配置版本摘要 |
| `GET` | `/api/flared/config/active` | 拉取完整的 tunnel 路由配置(relay 列表 + frpc 代理定义) |
| `POST` | `/api/flared/apply-log` | 上报配置应用结果(success / warning / failed) |
| `GET` | `/api/flared/ws` | 升级为 WebSocket,用于实时接收 `active_config` 推送 |
心跳请求示例:
```http
POST /api/flared/heartbeat
X-Tunnel-Token: <tunnel_token>
Content-Type: application/json
{
"client_version": "v0.2.0",
"frp_version": "0.61.0",
"tunnel_status": "running",
"connected_relays": [
{ "relay_node_id": "node-relay-1", "status": "healthy", "proxy_count": 3 }
],
"current_version": "v1",
"current_checksum": "sha256..."
}
```
心跳响应包含 `active_config` 摘要与 `tunnel_settings`(包含心跳间隔、WebSocket 升级开关等运行时参数)。当 Server 发布新版本时,已连接的 OpenFlared WebSocket 会收到 `type = "active_config"` 消息,payload 为版本摘要,客户端应立即拉取完整配置并应用。
日志中不得打印完整 Token。
## Swagger
登录管理端后可访问:
```text
/swagger/index.html
```
Swagger 文件位于 `openflare_server/docs`,由 `swag init` 生成。
+150
View File
@@ -0,0 +1,150 @@
# 命令与脚本
你会学到:OpenFlare Server、管理端前端、Agent、Swagger 和文档站的常用启动、构建、测试、安装与卸载命令。
## Server
源码启动:
```bash
cd openflare_server
export SESSION_SECRET='replace-with-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
go run .
```
指定监听端口与日志目录:
```bash
go run . --port 3000 --log-dir ./logs
```
测试:
```bash
cd openflare_server
GOCACHE=/tmp/openflare-go-cache go test ./...
```
## Frontend
开发:
```bash
cd openflare_server/web
pnpm install
pnpm dev
```
构建静态产物:
```bash
cd openflare_server/web
pnpm build
```
检查:
```bash
cd openflare_server/web
pnpm lint
pnpm typecheck
pnpm test
```
## Agent
源码运行:
```bash
cd openflare_agent
go run ./cmd/agent -config /path/to/agent.json
```
编译:
```bash
cd openflare_agent
go build -o openflare-agent ./cmd/agent
```
测试:
```bash
cd openflare_agent
GOCACHE=/tmp/openflare-go-cache go test ./...
```
## Relay (中继端)
源码运行:
```bash
cd openflare_relay
go run ./cmd -config /path/to/relay.json
```
编译:
```bash
cd openflare_relay
go build -o openflare-relay ./cmd
```
## OpenFlared (Client 客户端)
源码运行:
```bash
cd openflared
go run ./cmd -config /path/to/flared.json
```
编译:
```bash
cd openflared
go build -o openflared ./cmd
```
## 安装 Agent
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
## 卸载 Agent
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash
```
## Swagger
重新生成 Swagger 文档:
```bash
go install github.com/swaggo/swag/cmd/swag@v1.16.4
cd openflare_server
swag init -g main.go -o docs
```
## Docs
本地预览:
```bash
cd docs
pnpm dev
```
构建:
```bash
cd docs
pnpm build
```
+372
View File
@@ -0,0 +1,372 @@
# 配置项
你会学到:OpenFlare Server、前端构建和 Agent 支持哪些配置来源、配置项默认值是什么,以及常见部署组合应该如何配置。
本文档汇总 OpenFlare `1.0.0` 当前支持的 Server 与 Agent 配置项,只保留仍然有效的启动、部署与运行参数。
## 配置来源
Server 支持三类配置来源:
1. 命令行参数。
2. 环境变量。
3. 数据库 `Option` 表中的运行时配置。
Agent 支持:
1. `-config` 命令行参数。
2. `agent.json` 配置文件。
3. 少量日志与配置覆盖相关环境变量。
Relay (中继端) 支持:
1. `-config` 命令行参数。
2. `relay.json` 配置文件。
3. 丰富的启动覆盖环境变量。
Client (内网客户端) 支持:
1. `-config` 命令行参数。
2. `flared.json` 配置文件。
3. 启动覆盖与日志环境变量。
## 配置文件位置
| 组件 | 默认位置 | 说明 |
| --- | --- | --- |
| Server SQLite | `openflare.db` | 可通过 `SQLITE_PATH` 修改 |
| Agent 配置文件 | `./agent.json` | 可通过 `-config` 指定 |
| 一键安装 Agent 配置 | `/opt/openflare-agent/agent.json` | 安装脚本默认生成 |
| Agent 数据目录 | 配置文件所在目录下的 `data` | 可通过 `data_dir` 修改 |
| Relay 配置文件 | `./relay.json` | 可通过 `-config` 指定 |
| 一键安装 Relay 配置 | `/opt/openflare-relay/relay.json` | 安装脚本默认生成 |
| Client 配置文件 | `./flared.json` | 可通过 `-config` 指定 |
| 一键安装 Client 配置 | `/opt/openflared/flared.json` | 安装脚本默认生成 |
## Server 命令行参数
```bash
cd openflare_server
go run . --port 3000 --log-dir ./logs
```
| 参数 | 作用 | 默认值 |
| --- | --- | --- |
| `--port` | 指定 Server 监听端口 | `3000` |
| `--log-dir` | 指定日志目录 | 空 |
| `--version` | 输出当前版本后退出 | `false` |
| `--help` | 输出帮助信息后退出 | `false` |
## Server 环境变量
| 环境变量 | 作用 | 默认值 |
| --- | --- | --- |
| `PORT` | Server 监听端口 | `3000` |
| `GIN_MODE` | Gin 运行模式 | 非 `debug` 时按 release |
| `LOG_LEVEL` | 日志等级 | `info` |
| `SESSION_SECRET` | Session 签名密钥 | 启动时随机生成 |
| `SQLITE_PATH` | SQLite 数据库文件路径 | `openflare.db` |
| `DSN` | PostgreSQL DSN,设置后优先于 SQLite | 空 |
| `SQL_DSN` | 兼容旧命名的 PostgreSQL DSN,优先级低于 `DSN` | 空 |
| `REDIS_CONN_STRING` | Redis 连接串 | 空 |
| `AGENT_TOKEN` | 兼容旧部署的全局 Agent Token | 空 |
说明:
* `DSN` 与 `SQL_DSN` 同时存在时优先使用 `DSN`。
* `DSN` 或 `SQL_DSN` 与 `SQLITE_PATH` 同时存在时优先使用 PostgreSQL。
* 当目标 PostgreSQL 数据库为空且本地 `SQLITE_PATH` 文件存在时,Server 启动阶段会自动迁移 SQLite 数据,并在日志中输出按表迁移进度。
* `SESSION_SECRET` 生产环境必须显式配置。
* `REDIS_CONN_STRING` 未配置时,相关能力回退为进程内实现。
## 运行时 Option
以下配置由管理端设置页维护,可热更新:
| 配置项 | 作用 | 默认值 |
| --- | --- | --- |
| `AgentHeartbeatInterval` | Agent 心跳间隔(毫秒) | `10000` |
| `AgentWebsocketUpgradeEnabled` | 是否允许 Agent 在 HTTP 心跳成功后升级为 WebSocket | `true` |
| `NodeOfflineThreshold` | 节点离线阈值(毫秒) | `120000` |
| `AgentUpdateRepo` | Agent 自更新仓库 | `Rain-kl/OpenFlare` |
| `GeoIPProvider` | 节点/IP 归属解析方式 | `ipinfo` |
| `DatabaseAutoCleanupEnabled` | 是否启用每日自动清理观测数据 | `false` |
| `DatabaseAutoCleanupRetentionDays` | 自动清理保留天数,至少 1 天 | `30` |
| `GlobalApiRateLimitNum` / `GlobalApiRateLimitDuration` | 全局 API 限流次数 / 时间窗口 | `300` / `180` |
| `GlobalWebRateLimitNum` / `GlobalWebRateLimitDuration` | 全局 Web 限流次数 / 时间窗口 | `300` / `180` |
| `CriticalRateLimitNum` / `CriticalRateLimitDuration` | 敏感接口限流次数 / 时间窗口 | `100` / `1200` |
说明:
* `DatabaseAutoCleanupEnabled` 开启后,Server 会在每天凌晨 3 点自动清理 `node_access_logs`、`node_metric_snapshots`、`node_request_reports` 三类观测数据。
* `DatabaseAutoCleanupRetentionDays` 为统一保留天数,必须大于等于 1。
* 管理端支持手动清理时留空保留天数,以直接删除对应数据集的全部历史记录。
* `AgentUpdateRepo` 指向的 GitHub Release 必须为每个 Agent 二进制提供同名 `.sha256` 校验文件,例如 `openflare-agent-linux-amd64.sha256`;Agent 自更新会在替换可执行文件前校验 SHA-256。
* 第三方登录不再通过 `GitHubOAuthEnabled`、`GitHubClientId`、`GitHubClientSecret` 作为主配置入口;这些旧 Option 仅用于升级时迁移默认 GitHub 认证源。
* 微信登录旧 Option 保留为兼容字段,但管理端不再提供微信登录配置入口。
* Turnstile 旧 Option 与后端校验能力保留,已有配置仍会生效。
## OpenResty 参数
OpenResty 性能参数与缓存参数继续统一保存在 `Option` 表。当前常用项包括:
* `OpenRestyWorkerProcesses`
* `OpenRestyWorkerConnections`
* `OpenRestyWorkerRlimitNofile`
* `OpenRestyKeepaliveTimeout`
* `OpenRestyProxyConnectTimeout`
* `OpenRestyProxySendTimeout`
* `OpenRestyProxyReadTimeout`
* `OpenRestyProxyBufferingEnabled`
* `OpenRestyGzipEnabled`
* `OpenRestyCacheEnabled`
* `OpenRestyCachePath`
* `OpenRestyCacheMaxSize`
这类参数必须以结构化方式校验、保存并参与版本渲染。
约束:
* 管理端不再暴露 `resolver` 配置。
* 规则上游统一渲染为 named `upstream` 并启用 keepalive。
* 单上游如带 base path 或 query,会在 `proxy_pass` 中补回原始 URI。
* 多上游仍要求每个上游都为纯 `scheme://host[:port]`,且同一规则内协议一致。
* `OpenRestyCacheEnabled` 用于启用缓存基础设施与全局默认参数;实际是否缓存、按 URL / 后缀 / 路径等命中策略由各条 `proxy_routes` 单独决定。
* 默认缓存 Key 为 `$scheme$host$request_uri`。
* 默认 `keepalive_timeout` 为 `20` 秒,默认 `proxy_connect_timeout` 为 `3` 秒。
* 默认事件模型为 `epoll`,并默认开启 `multi_accept`。
* HTTPS 监听默认使用独立 `http2 on;` 指令,避免新版 Nginx/OpenResty 对 `listen ... http2` 的弃用告警。
## 前端构建环境变量
| 环境变量 | 作用 | 默认值 |
| --- | --- | --- |
| `NEXT_PUBLIC_API_BASE_URL` | 前端请求 API 的基础路径 | `/api` |
| `NEXT_PUBLIC_APP_VERSION` | 前端展示版本号 | `dev` |
| `NEXT_DEV_BACKEND_URL` | 本地开发服务器代理的后端地址 | `http://127.0.0.1:3000` |
## Agent 环境变量
| 环境变量 | 作用 | 默认值 |
| --- | --- | --- |
| `LOG_LEVEL` | Agent 日志等级 | `info` |
| `OPENFLARE_SERVER_URL` | 控制面地址,可覆盖 `agent.json` | 空 |
| `OPENFLARE_AGENT_TOKEN` | 节点专属认证 Token,可覆盖 `agent.json` | 空 |
| `OPENFLARE_DISCOVERY_TOKEN` | 首次自动注册 Token,可覆盖 `agent.json` | 空 |
| `OPENFLARE_NODE_NAME` | 节点名称,可覆盖 `agent.json` | 空 |
| `OPENFLARE_NODE_IP` | 节点 IP,可覆盖 `agent.json` | 空 |
| `OPENFLARE_DATA_DIR` | Agent 数据目录,可覆盖 `agent.json` | 空 |
| `OPENFLARE_OPENRESTY_PATH` | OpenResty 二进制路径,可覆盖 `agent.json` | 空 |
| `OPENFLARE_HEARTBEAT_INTERVAL` | 心跳间隔,可覆盖 `agent.json` | 空 |
| `OPENFLARE_REQUEST_TIMEOUT` | 请求超时,可覆盖 `agent.json` | 空 |
| `OPENFLARE_OPENRESTY_OBSERVABILITY_PORT` | 本地观测端口,可覆盖 `agent.json` | 空 |
| `OPENFLARE_MMDB_PATH` | WAF GeoIP mmdb 路径,可覆盖 `agent.json` | 空 |
| `OPENFLARE_MMDB_UPDATE_INTERVAL` | WAF GeoIP mmdb 更新间隔,可覆盖 `agent.json` | 空 |
| `OPENFLARE_MMDB_DOWNLOAD_URL` | WAF GeoIP mmdb 下载地址,可覆盖 `agent.json` | 空 |
## Agent 命令行参数
| 参数 | 作用 | 默认值 |
| --- | --- | --- |
| `-config` | 指定 Agent 配置文件路径 | `./agent.json` |
## Agent 配置字段
| 字段 | 作用 | 是否必填 | 默认值/行为 |
| --- | --- | --- | --- |
| `server_url` | 控制面地址 | 是 | 无 |
| `agent_token` | 节点专属认证 Token | 与 `discovery_token` 二选一 | 空 |
| `discovery_token` | 首次自动注册使用的全局 Token | 与 `agent_token` 二选一 | 空 |
| `node_name` | 节点名称 | 否 | 自动使用主机名 |
| `node_ip` | 节点 IP | 否 | 自动探测,优先通过第三方 API 获取真实出口公网 IP;失败时退回本机网卡探测 |
| `openresty_path` | OpenResty 二进制路径 | 否 | `openresty` |
| `openresty_observability_port` | 本地观测与 OpenResty 健康检查端口 | 否 | `18081` |
| `data_dir` | Agent 数据目录 | 否 | 配置文件所在目录下的 `data` |
| `main_config_path` | OpenResty 主配置写入路径 | 否 | `data_dir/etc/nginx/nginx.conf` |
| `route_config_path` | 路由配置写入路径 | 否 | `data_dir/etc/nginx/conf.d/openflare_routes.conf` |
| `access_log_path` | OpenResty 访问日志路径 | 否 | `data_dir/var/log/openflare/access.log` |
| `cert_dir` | 证书写入目录 | 否 | `data_dir/etc/nginx/certs` |
| `openresty_cert_dir` | OpenResty 配置中读取证书的目录 | 否 | 同 `cert_dir` |
| `lua_dir` | Lua 脚本与静态资源写入目录 | 否 | `data_dir/etc/nginx/lua` |
| `openresty_lua_dir` | OpenResty 配置中读取 Lua 的目录 | 否 | 同 `lua_dir` |
| `runtime_config_dir` | Agent 运行时配置写入目录,如 `pow_config.json` | 否 | `data_dir/etc/openflare` |
| `mmdb_path` | WAF GeoIP mmdb 文件路径 | 否 | `data_dir/etc/openflare/GeoLite2-Country.mmdb` |
| `mmdb_update_interval` | WAF GeoIP mmdb 更新间隔 | 否 | `86400000` 毫秒 |
| `mmdb_download_url` | WAF GeoIP mmdb 下载地址 | 否 | 内置 GeoLite2 Country 下载地址 |
| `observability_buffer_path` | 观测补报缓冲文件路径 | 否 | `data_dir/var/lib/openflare/observability-buffer.json` |
| `observability_replay_minutes` | 自动补传最近观测窗口分钟数 | 否 | `15` |
| `state_path` | Agent 本地状态文件路径 | 否 | `data_dir/var/lib/openflare/agent-state.json` |
| `heartbeat_interval` | 心跳间隔 | 否 | `10000` 毫秒 |
| `request_timeout` | HTTP 请求超时 | 否 | `10000` 毫秒 |
说明:
* `agent_token` 与 `discovery_token` 不能同时为空。
* `heartbeat_interval` 与 `request_timeout` 支持毫秒整数或 Go duration 字符串。
* Server 运行时配置 `AgentWebsocketUpgradeEnabled` 开启时,Agent 会在 HTTP 心跳成功后尝试升级为 WebSocket;连接失败或断开后自动退回 HTTP 心跳。
* 未配置 `openresty_path` 时默认调用 `openresty`。
* Agent 周期性健康检查会请求 `http://127.0.0.1:<openresty_observability_port>/openflare/stub_status`,不再通过高频 `openresty -t` 判断运行时健康;配置应用、启动恢复和 reload 前校验仍会执行 `openresty -t -c <main_config_path>`。
* Agent 会初始化并定期更新 `mmdb_path`,供 OpenResty WAF Lua 执行国家级地域规则;更新失败只记录警告,不阻断同步或 reload。
* 如果 `agent.json` 不存在,但 `OPENFLARE_SERVER_URL` 与 Token 等环境变量足够,Agent 可以直接启动;两者同时存在时环境变量优先。
* Agent 未配置 `node_ip` 时,会优先通过 `https://realip.cc` 获取真实出口公网 IP,适配 Docker/NAT 场景;该请求失败时,才退回本机网卡探测并优先选择公网 IPv4。
* Agent 自动探测到私网 `node_ip` 时,Server 会在注册/心跳阶段优先保留 Agent 直连来源的公网地址,避免 NAT/多网卡场景误登记内网网卡地址。
* 在管理端开启“锁定节点 IP”后,Server 会保留管理端填写的节点 IP,后续 Agent 注册、HTTP 心跳或 WebSocket 状态上报不会覆盖该字段;关闭锁定后,下一次上报可重新回填。
## Relay 环境变量
| 环境变量 | 作用 | 默认值 |
| --- | --- | --- |
| `LOG_LEVEL` | Relay 日志等级 | `info` |
| `OPENFLARE_SERVER_URL` | 控制面地址,可覆盖 `relay.json` | 空 |
| `OPENFLARE_AGENT_TOKEN` | 节点专属认证 Token,可覆盖 `relay.json` | 空 |
| `OPENFLARE_DISCOVERY_TOKEN` | 首次自动注册 Token,可覆盖 `relay.json` | 空 |
| `OPENFLARE_NODE_NAME` | 节点名称,可覆盖 `relay.json` | 空 |
| `OPENFLARE_NODE_IP` | 节点 IP,可覆盖 `relay.json` | 空 |
| `OPENFLARE_DATA_DIR` | Relay 数据目录,可覆盖 `relay.json` | 空 |
| `OPENFLARE_FRPS_PATH` | frps 二进制路径,可覆盖 `relay.json` | 空 |
## Relay 命令行参数
| 参数 | 作用 | 默认值 |
| --- | --- | --- |
| `-config` | 指定 Relay 配置文件路径 | `./relay.json` |
## Relay 配置字段
| 字段 | 作用 | 是否必填 | 默认值/行为 |
| --- | --- | --- | --- |
| `server_url` | 控制面地址 | 是 | 无 |
| `agent_token` | 中继节点专属认证 Token | 与 `discovery_token` 二选一 | 空 |
| `discovery_token` | 首次自动注册使用的全局 Token | 与 `agent_token` 二选一 | 空 |
| `node_name` | 节点名称 | 否 | 自动使用主机名 |
| `node_ip` | 中继节点 IP,用于接收穿透流量 | 否 | 自动探测,优先使用公网出口 IP;失败时退回网卡探测 |
| `frps_path` | frps 二进制路径 | 否 | `frps`(在系统 PATH 中寻找) |
| `data_dir` | Relay 运行时数据目录 | 否 | 配置文件所在目录下的 `data` |
| `state_path` | Relay 本地状态文件存储路径 | 否 | `data_dir/relay-state.json` |
| `heartbeat_interval` | 心跳间隔 | 否 | `10000` 毫秒,支持 Go duration 字符串 |
| `request_timeout` | HTTP 请求超时 | 否 | `10000` 毫秒,支持 Go duration 字符串 |
## OpenFlared (Client) 环境变量
| 环境变量 | 作用 | 默认值 |
| --- | --- | --- |
| `LOG_LEVEL` | Client 日志等级 | `info` |
| `OPENFLARE_SERVER_URL` | 控制面地址,可覆盖 `flared.json` | 空 |
| `OPENFLARE_TUNNEL_TOKEN` | 隧道专属认证 Token,可覆盖 `flared.json` | 空 |
| `OPENFLARE_DATA_DIR` | Client 数据目录,可覆盖 `flared.json` | 空 |
| `OPENFLARE_FRPC_PATH` | frpc 二进制路径,可覆盖 `flared.json` | 空 |
## OpenFlared (Client) 命令行参数
| 参数 | 作用 | 默认值 |
| --- | --- | --- |
| `-config` | 指定 Client 配置文件路径 | `./flared.json` |
## OpenFlared (Client) 配置字段
| 字段 | 作用 | 是否必填 | 默认值/行为 |
| --- | --- | --- | --- |
| `server_url` | 控制面地址 | 是 | 无 |
| `tunnel_token` | 隧道专属认证 Token | 是 | 无 |
| `frpc_path` | frpc 二进制路径 | 否 | `frpc`(在系统 PATH 中寻找) |
| `data_dir` | Client 运行时数据目录 | 否 | 配置文件所在目录下的 `data` |
| `state_path` | Client 本地状态文件存储路径 | 否 | `data_dir/flared-state.json` |
| `heartbeat_interval` | 心跳间隔 | 否 | `10000` 毫秒,支持 Go duration 字符串 |
| `sync_interval` | 配置拉取同步间隔 | 否 | `30000` 毫秒,支持 Go duration 字符串 |
| `request_timeout` | HTTP 请求超时 | 否 | `10000` 毫秒,支持 Go duration 字符串 |
## 常见配置组合
### 生产 Server + PostgreSQL
```bash
export SESSION_SECRET='replace-with-a-long-random-string'
export DSN='postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable'
export GIN_MODE='release'
export LOG_LEVEL='info'
```
### 本地 Server + SQLite
```bash
export SESSION_SECRET='dev-session-secret'
export SQLITE_PATH='./openflare-dev.db'
export LOG_LEVEL='debug'
go run .
```
### Agent + 默认 OpenResty
```json
{
"server_url": "http://your-server:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "/opt/openflare-agent/data",
"openresty_path": "openresty",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
### Agent + 自定义 OpenResty 路径
```json
{
"server_url": "http://your-server:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "/var/lib/openflare-agent",
"openresty_path": "/usr/local/openresty/nginx/sbin/openresty",
"main_config_path": "/var/lib/openflare-agent/etc/nginx/nginx.conf",
"route_config_path": "/var/lib/openflare-agent/etc/nginx/conf.d/openflare_routes.conf",
"access_log_path": "/var/lib/openflare-agent/var/log/openflare/access.log",
"cert_dir": "/var/lib/openflare-agent/etc/nginx/certs",
"lua_dir": "/var/lib/openflare-agent/etc/nginx/lua",
"runtime_config_dir": "/var/lib/openflare-agent/etc/openflare",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
### Relay (中继端) 默认配置
`relay.json`:
```json
{
"server_url": "http://your-server:3000",
"agent_token": "replace-with-relay-auth-token",
"frps_path": "frps",
"data_dir": "/opt/openflare-relay/data",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
### OpenFlared (内网客户端) 默认配置
`flared.json`:
```json
{
"server_url": "http://your-server:3000",
"tunnel_token": "replace-with-tunnel-token",
"frpc_path": "frpc",
"data_dir": "/opt/openflared/data",
"heartbeat_interval": 10000,
"sync_interval": 30000,
"request_timeout": 10000
}
```
## 维护要求
以下内容变化时,必须同步更新本文档:
* Server 命令行参数。
* Server 环境变量。
* Agent 命令行参数与配置字段。
* Relay 命令行参数与配置字段。
* Client 命令行参数与配置字段。
* 任一配置项的默认值、用途或示例。
+13
View File
@@ -0,0 +1,13 @@
# 参考
你会学到:哪些信息属于稳定参考资料,以及配置、命令、API 和仓库结构应该从哪里查。
本部分收敛运行、接口与仓库层面的稳定信息,适合部署、联调和排查时快速查阅。
| 页面 | 内容 |
| --- | --- |
| [配置项](./configuration.md) | Server 环境变量、命令行参数、运行时 Option 与 Agent 配置字段 |
| [命令与脚本](./cli.md) | 常用启动、构建、测试、安装和卸载命令 |
| [API 约定](./api.md) | 管理端 API 与 Agent API 的响应结构、鉴权和路径约定 |
| [仓库结构](../design/repository.md) | `openflare_server`、`openflare_agent`、`openflare_relay`、`openflared` 模块的职责与分层目录说明 |
| [部署与升级](../deployment/) | Server 与 Agent 的部署、配置与升级指南(见专属分区) |
+34
View File
@@ -0,0 +1,34 @@
ARG VERSION=dev
FROM golang:1.25-alpine AS builder
ARG VERSION
ARG TARGETOS=linux
ARG TARGETARCH
ENV CGO_ENABLED=0 \
GOOS=${TARGETOS} \
GOARCH=${TARGETARCH}
WORKDIR /build
COPY openflare_server ./openflare_server
COPY openflare_agent ./openflare_agent
WORKDIR /build/openflare_agent
RUN go mod download
RUN go build -trimpath -ldflags "-s -w -X 'openflare-agent/internal/config.Version=$VERSION'" -o /build/openflare-agent ./cmd/agent
FROM openresty/openresty:alpine
RUN apk add --no-cache ca-certificates tzdata perl libmaxminddb \
&& ln -sf /usr/lib/libmaxminddb.so.0 /usr/lib/libmaxminddb.so \
&& opm get anjia0532/lua-resty-maxminddb \
&& mkdir -p /etc/openflare /data
ENV OPENFLARE_OPENRESTY_PATH=openresty \
OPENFLARE_DATA_DIR=/data
COPY --from=builder /build/openflare-agent /usr/local/bin/openflare-agent
EXPOSE 80 443 18081
ENTRYPOINT ["/usr/local/bin/openflare-agent"]
CMD ["-config", "/etc/openflare/agent.json"]
+2 -3
View File
@@ -1,9 +1,8 @@
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "2380de64b00e99093e16590beb91e1a0",
"agent_token": "373956188ddead1df6dd7c86cd330b73",
"data_dir": "./data",
"openresty_container_name": "openflare-openresty",
"openresty_docker_image": "openresty/openresty:alpine",
"openresty_path": "openresty",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
+17 -13
View File
@@ -10,6 +10,7 @@ import (
"openflare-agent/internal/agent"
"openflare-agent/internal/config"
"openflare-agent/internal/geoipupdate"
"openflare-agent/internal/heartbeat"
"openflare-agent/internal/httpclient"
"openflare-agent/internal/logging"
@@ -17,6 +18,7 @@ import (
"openflare-agent/internal/state"
syncservice "openflare-agent/internal/sync"
"openflare-agent/internal/updater"
"openflare-agent/internal/wsclient"
)
func main() {
@@ -30,13 +32,10 @@ func main() {
slog.Error("load agent config failed", "error", err)
os.Exit(1)
}
cfg.NginxVersion = nginx.DetectVersion(
cfg.ExtVersion = nginx.DetectVersion(
context.Background(),
nginx.ExecutorOptions{
NginxPath: cfg.OpenrestyPath,
DockerBinary: cfg.DockerBinary,
ContainerName: cfg.OpenrestyContainerName,
Image: cfg.OpenrestyDockerImage,
MainConfigPath: cfg.MainConfigPath,
RouteConfigPath: cfg.RouteConfigPath,
CertDir: cfg.CertDir,
@@ -52,33 +51,31 @@ func main() {
"ip", cfg.NodeIP,
"heartbeat_interval", cfg.HeartbeatInterval,
"route_config", cfg.RouteConfigPath,
"access_log", cfg.AccessLogPath,
"cert_dir", cfg.CertDir,
"lua_dir", cfg.LuaDir,
"runtime_config_dir", cfg.RuntimeConfigDir,
"mmdb_path", cfg.MMDBPath,
)
client := httpclient.New(cfg.ServerURL, cfg.InitialAuthToken(), cfg.RequestTimeout.Duration())
wsClient := wsclient.New(cfg.ServerURL, cfg.InitialAuthToken(), cfg.RequestTimeout.Duration())
stateStore := state.NewStore(cfg.StatePath)
observabilityBuffer := state.NewObservabilityBufferStore(cfg.ObservabilityBufferPath)
runtimeRouteConfigPath := cfg.RouteConfigPath
if cfg.OpenrestyPath == "" {
runtimeRouteConfigPath = nginx.DockerRouteConfigPath
}
runtimeManager := &nginx.Manager{
MainConfigPath: cfg.MainConfigPath,
RouteConfigPath: cfg.RouteConfigPath,
RuntimeRouteConfigPath: runtimeRouteConfigPath,
AccessLogPath: cfg.AccessLogPath,
CertDir: cfg.CertDir,
NginxCertDir: cfg.OpenrestyCertDir,
LuaDir: cfg.LuaDir,
NginxLuaDir: cfg.OpenrestyLuaDir,
OpenrestyObservabilityListen: nginx.ObservabilityListenAddress(cfg.OpenrestyPath, cfg.OpenrestyObservabilityPort),
RuntimeConfigDir: cfg.RuntimeConfigDir,
OpenrestyObservabilityListen: nginx.ObservabilityListenAddress(cfg.OpenrestyObservabilityPort),
OpenrestyObservabilityPort: cfg.OpenrestyObservabilityPort,
OpenrestyResolverDirective: "",
Executor: nginx.NewExecutor(nginx.ExecutorOptions{
NginxPath: cfg.OpenrestyPath,
DockerBinary: cfg.DockerBinary,
ContainerName: cfg.OpenrestyContainerName,
Image: cfg.OpenrestyDockerImage,
MainConfigPath: cfg.MainConfigPath,
RouteConfigPath: cfg.RouteConfigPath,
CertDir: cfg.CertDir,
@@ -100,10 +97,17 @@ func main() {
SyncService: syncservice.New(client, runtimeManager, stateStore),
Updater: updater.New(),
RuntimeManager: runtimeManager,
WebSocketService: wsClient,
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
geoIPUpdater := &geoipupdate.Updater{
MMDBPath: cfg.MMDBPath,
DownloadURL: cfg.MMDBDownloadURL,
UpdateInterval: cfg.MMDBUpdateInterval.Duration(),
}
go geoIPUpdater.Run(ctx)
slog.Info("agent process started")
if err = runner.Run(ctx); err != nil && err != context.Canceled {
+14 -1
View File
@@ -1,3 +1,16 @@
module openflare-agent
go 1.23.0
go 1.25.7
require openflare v0.0.0
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/sys v0.43.0 // indirect
)
replace openflare => ../openflare_server
+22
View File
@@ -0,0 +1,22 @@
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Some files were not shown because too many files have changed in this diff Show More