Files
OpenFlare/docs/en/design/architecture.md
T
ryan dbaa3bf140 feat(cordis): add OpenFlare Cordis 架构改造设计
docs(changelog): 修正表述笔误

refactor(cordis): 磁盘缓存改用上上游能力并清理本地副本

按上游/下游归属规约:类型断言守卫已回流 Wavelet(f3d85d5,附回归用例),
本仓库删除 OpenFlare/plugins/server/pkg/cache 整包并改 import 到
Wavelet/pkg/cache/disk,同步后与上游零漂移。

验证:go build 通过;go test ./... exit 0(137 包 ok);256 条路由对拍与
232 条 swagger 操作均零差异;make build-all 四进制;前端零改动。

docs(cordis): 记录 T1 清理结果与五个复用阻塞点

refactor(cordis): server 复用上游 pkg 能力并删除等价本地副本

按上游/下游归属规约清理重复实现,删除 7 个与上游等价的本地包并改 import:
shared/response→pkg/response、pkg/{logger,mail,trace,httppool,cache/ram}→
上游同名包、infra/persistence/batchwriter→pkg/batchwriter。逐项核过差异:
httppool 逐字节相同;logger 的 Config 字段完全一致;response 的 7 个 Abort*
一致;cache/ram 换过去顺带把裸 go 变回带 panic 恢复的 util.Go。

两处非等价差异按语义处理:
- batchwriter.Stats 与 status DTO 原为类型别名,改为消费侧逐字段转换,
  避免 model 反向依赖基础设施类型;
- 上游 pkg/idgen 要求显式 Init(本地副本为懒加载自动初始化),本次保留本地
  副本,待与 infra 初始化一并迁移(已登记在清理计划)。

验证:go build 通过;go test ./... exit 0(138 包 ok);256 条路由对拍零差异;
make swagger 232 条操作零增减,且归一化后与旧文档深度相等——差异仅为
response.Any / logger.LogEntry 两个定义名随包路径改名,接口形状未变。

chore(cordis): 回流内核与 pkg/util 通用能力并清理 vendoring 污染

按新增的上游/下游归属规约:HandleRaw/BasePath 与版本比较、网络、格式化助手
属通用能力,已提交到 Wavelet 分支 feat/cordis-router-raw-routes,本仓库改为
纯同步获取(pkg/util 已零漂移),补丁登记保留至上游合并。

同时修掉我此前 git add -A 造成的污染:首次 vendoring 把上游工作区里被
gitignore 的运行期产物一起提交进来(upload 的 diskcache 缓存块 650 个与
driver_http/dist 前端构建物 380 个,共 12872 行/1030 文件)。sync-upstream.sh
现显式排除 uploads/dist/data/*.db,.gitignore 补上对应兜底规则。

AGENTS.md 增加上游/下游改动归属规约,并把仍指向前 Cordis 布局的硬性约束
(internal/router + Serve、internal/repository/logstore、internal/platform/bootstrap、
internal/cmd)改到当前插件路径。

验证:go build 通过;go test ./... exit 0(144 包 ok);make swagger 232 条
操作与基线逐条一致;make build-all 四进制;gofmt 干净。

feat(cordis): server 插件化并改由内核挂载控制面路由

新增 plugins/server/plugin.go:Apply 以 ctx.Router().Group(app.api_prefix)
声明根级与 /v1 全部路由;33 个注册函数由 *gin.RouterGroup 改为
core.RouterExtension,RegisterCollection 改用内核新增的 HandleRaw 保留
尾部斜杠变体,AdminMiddlewares 返回 []any(Go 不允许把 []T 展开为 ...any)。
删除 router.Serve 与 registerRoutes,装配根改为 core.App +
driver_http.New(WithEngine(router.BuildEngine())),监听、信号与优雅退出归内核;
前端 SPA 的 NoRoute 兜底因内核暂无贡献点而保留在引擎层。

路由保真证据:plugin_parity_test 对拍 baseline/routes-engine.txt 的 256 条
(方法 路径) 零差异;go test ./... exit 0(144 包 ok,含真实 handler 的
openflare/integration 用例走同一条挂载路径);make swagger 232 条操作与基线
逐条一致;golangci-lint 0 issues;make build-all 四进制;embed_frontend
标签编译通过;前端零改动。

已知待补:带 Redis 的实机 HTTP 冒烟(本机 6379 未启动,session store 与
改造前一样在建店阶段即 fatal),以及 bootstrap 的任务/设置/迁移注册迁入 Apply。

feat(core): RouterExtension 增加 HandleRaw 与 BasePath 以保真尾部斜杠路由

server 插件化的前置:Handle 经 cleanPath 会剥掉尾部斜杠,无法表达
/resource 与 /resource/ 两条不同路由,而 OpenFlare 有 20 个历史 list
端点两者都注册且部署关闭了 RedirectTrailingSlash,缺失即 404。新增
HandleRaw 与 BasePath(作用域包装器同样登记反注册),补 extpoints 用例;
并把 router.Serve 拆出 BuildEngine 以便交给 driver_http.WithEngine 复用,
新增路由表导出 harness,固化 256 条 (方法 路径) 基线供插件化对拍。
上游补丁登记于 backend/OpenFlare/upstream-patches.md,同步脚本改为按目录
前缀输出差异并在同步后提醒确认补丁是否仍在。

验证:go build 通过;go test ./... exit 0(143 包 ok);gofmt 干净。

docs(cordis): 记录 server 插件接入内核的可行路径与内核能力缺口

feat(cordis): agent/relay/flared 落地为内核驱动插件

三个边缘守护进程各新增 plugin.go,实现 core.Plugin + core.Driver
(自定义 DriverType 与同名 profile),装配与生命周期从 main 迁入
Apply/Start/Stop:Apply 负责 JSON 配置加载、运行环境与用户确保、
openresty/frps/frpc 管理器与各服务装配;Start 以 util.Go 拉起阻塞式
runner 与 GeoIP 周期更新;Stop 收敛主循环结果并在超时时报错而非静默。

入口改为 core.NewApp(core.WithProfile(...)) + Prepare/Run,保持
-config 旗标、默认路径、退出码与启动/停止日志不变。

验证:go build 通过;go test ./... exit 0(143 包 ok,含 3 个插件身份
与配置失败路径测试);make build-all 四进制产出;三进制实跑缺失配置
均 exit 1 且错误链保留 load {agent,relay,flared} config 原因;gofmt 干净。

refactor(cordis): 按功能职责拆分为 4 个插件与 share 共享层

backend/OpenFlare 不再平铺遗留分层,改为 plugins/{server,agent,relay,flared}
加 share/:控制面业务(openflare/admin/oauth/user/upload/cap/config/health 与
repository/model/infra/router 等支撑层)归 server;三个边缘守护进程各自成插件;
被两个以上插件消费的 protocol/geoip/wsclient/render/pagesarchive/edge 归 share。
同时把 pkg/util 与 buildinfo 合并回上游 pkg(上游已覆盖全部符号,仅 8 个函数与
2 个类型为 OpenFlare 独有,已一并迁入),装配根统一到 backend/cmd(含三个 daemon
入口),Dockerfile 与 release 工作流的构建路径和 -X 注入路径同步更新。

验证:go build 通过;go test ./... exit 0(141 包 ok);make swagger exit 0 且
232 条 API 操作与基线逐条一致;make build-all 产出 4 进制;-X 注入经二进制
strings 实测生效;日志后端直连门禁改写为按 server 插件业务域扫描并在扫描数为 0
时报错(防门禁静默失效);前端零改动。

feat(cordis): 落地 backend/share 共享层与上游同步脚本

跨插件共享资源(控制消息协议、GeoIP+iputil、边缘守护进程日志)从下游包
移入 backend/share,并声明其只能依赖 core/pkg 与标准/第三方库,禁止反向
引用下游业务与具体插件实现;新增 scripts/sync-upstream.sh 只覆盖
backend/{core,pkg,plugins},同步后 --check 报告零差异,证明与上游逐字一致。

go build 通过,go test ./... exit 0(142 包 ok),前端零改动。

refactor(cordis): 采用与 Wavelet 同构的单模块布局并引入上游内核

按上游结构落位:backend/{core,pkg,plugins} 为 Wavelet 上游拷贝,OpenFlare
全部业务收拢到上游 downstream 所对应的位置 backend/OpenFlare/,模块名保持
Wavelet 以保证上游 import 路径逐字一致、同步零改写;三个 daemon 入口移至
backend/OpenFlare/cmd,backend/cmd 与 main.go 作为控制面装配根。

行为不变:go build 通过,142 个测试包全绿(含上游插件测试),232 条 API
操作与改造前逐条一致,四进制产物正常,前端零改动。swagger 暂只扫描下游代码,
待 P4 挂载上游路由后再纳入 plugins/。

style: 修正模块路径改写导致的 import 分组排序漂移

refactor(layout): Go 代码迁入 backend/ 并将模块名简化为 OpenFlare

对齐上游 Wavelet 的仓库布局,为以第二 module 形态 vendoring Cordis 内核与
平台插件做准备:模块路径整体改写为 OpenFlare,Go 目标加 cd backend,
swaggo 产物移至 backend/docs 并把 json/yaml 复制回 docs/ 供站点消费,
Dockerfile 与 release 工作流的构建目录、ldflags 模块路径同步更新。

行为保持不变:232 条路由与改造前逐条一致,95 个测试包全绿,
四进制产物正常,前端零改动。

chore(cordis): 落地改造计划与 schema/路由基线

新增 legacy_dump_test 迁移快照 harness:在临时 sqlite 库上按生产顺序
(goose.UpTo → zone 导入 → goose.Up)跑完 76 个历史迁移并导出 schema 与
版本序列,作为改造前后一致性门禁的唯一事实来源。同时记录 232 条路由清单
与 foundation 实施计划。

docs(cordis): add OpenFlare Cordis 架构改造设计

明确上游以第二 module 形态 vendoring 进 backend/Wavelet、4 个插件
(server/agent/relay/flared) 全部装载内核,并规定保留 76 个历史 goose
迁移 + 一次性版本 stamp 桥接的迁移方案,配套三方 schema 一致性门禁,
确保已部署库不重跑历史、不丢数据。
2026-08-30 10:12:52 +08:00

15 KiB

System Architecture

You will learn: OpenFlare's overall architecture, the responsibility split of each core component (Server, Agent, OpenResty, Relay, Client), and the macro flow of the main data and request streams.

OpenFlare is a self-hosted OpenResty control plane. Physically it consists of the Server (control plane), the Agent (config landing), node-local OpenResty (data plane), intranet penetration components (Relay and OpenFlared, data-plane extensions), and the admin frontend.


Traffic Path Overview

Depending on the website upstream type, OpenFlare supports three data-plane traffic paths:

1. Standard Reverse Proxy Path

Browser
  |
  | HTTPS/HTTP request
  v
OpenResty (WAF, TLS, Rate Limit, optional origin error page)
  |
  | reverse proxy (proxy_pass)
  v
Origin Server (direct public/LAN upstream)

When the origin or gateway returns an error status in the configured list, a global custom/default HTML can be returned while keeping the real HTTP status; see Origin Error Page Design.

2. Intranet Penetration Path

For origin services on firewall-restricted intranet servers:

Browser
  |
  | HTTPS/HTTP request
  v
OpenResty (Agent host, TLS/WAF)
  |
  | proxy_pass http://localhost:vhost_port (Host header preserved)
  v
OpenFlareRelay (frps)              <-- same host as the Agent, provides relaying
  |
  | frp tunnel protocol (Host header routing)
  v
OpenFlared (frpc)                  <-- firewall-restricted intranet server
  |
  | HTTP/HTTPS forward
  v
Internal Service (192.168.x.x)

3. Pages Static Hosting Path

For pre-built SPAs or static site hosting:

Browser
  |
  | HTTPS/HTTP request
  v
OpenResty (Agent, TLS/WAF)
  |
  +---> [static serving] root/try_files ---> Agent local Pages deployment dir
  |
  +---> [API proxy] proxy_pass ---> backend API service (if API proxying enabled)

Component Responsibilities

Component Responsibility Detailed Design Reference
Server admin UI/API, control-plane state persistence, config compilation/rendering, release versioning, Pages deployment package storage, Cloudflare A-record pointing, access-log storage and business traffic aggregation, Uptime Kuma monitoring sync, login CAPTCHA protection Agent & Publish Model / Cloudflare DNS Pointing Design / Edge Observability & Business Traffic Stats / Uptime Kuma Sync Design / Login CAPTCHA Design
Agent periodic heartbeat & WS sync, static package pull/extraction, OpenResty config write/validate/reload and self-healing; observability reports only access details and host/health readings, no business pre-aggregation Agent & Publish Model / Edge Observability & Business Traffic Stats
OpenResty receives real traffic; executes WAF filtering, PoW protection, Basic Auth, static/reverse-proxy serving, and optional origin error pages WAF Design / Pages Design / Origin Error Page Design
Relay deployed on edge nodes; manages the frps daemon lifecycle and accepts heartbeat-dispatched penetration relay configs Tunnel Design
OpenFlared deployed in the intranet; manages the frpc process group, establishes reverse tunnels to multiple Relays, reports connection state Tunnel Design

Component Architecture and Division

1. Server (control plane)

The Go backend at the repo root (module OpenFlare) is the OpenFlare control plane, built on the Wavelet full-stack scaffold:

  • Provides admin REST APIs (/api/v1/d/*) authenticated via Session Cookie, with optional X-Access-Token.
  • Edge node protocols go through /api/v1/agent|relay|tunnel/*, authenticated with X-Agent-Token / X-Tunnel-Token respectively.
  • Contains the config Compiler, uniformly compiling DB rules, certs, and global params into immutable config snapshots and OpenResty physical config file text.
  • Uniformly receives Pages local uploads, Remote URLs, and public GitHub Release pre-built artifacts, completing source checks, restricted downloads, archive validation, and immutable deployments; manual uploads create candidates awaiting explicit activation, persistent-source sync creates-or-loads and atomically activates. The Server offers controlled latest-download endpoints to Agents; the internal scanner handles limited GitHub latest checks, lease recovery, optional auto-publish, and orphan upload compensation; the generic task management entry can't modify this schedule. Future repo source builds are extended by a standalone Server build executor; the Agent never executes third-party fetch or build commands.
  • Provides the optional Cloudflare DNS pointing control plane: maintains group desired state with ZoneDomains as members, idempotently syncing a single A record to the current active node IPv4 via Asynq; node IP changes only best-effort enqueue; no auto-failover in phase 1.
  • Backend integration with the Uptime Kuma monitoring sync service auto-maintains HTTP probe tasks for available sites.
  • Startup entry: root main.go + internal/cmd/ (api / worker / scheduler / all); OpenFlare business in internal/apps/openflare/, edge protocol handling in internal/apps/openflare/{agent,relay,flared}/.
  • See: Agent & Publish Model and Uptime Kuma Sync Design

2. Agent (config landing)

openflare-agent is the daemon running on the node:

  • Maintains periodic heartbeats with the control plane after startup, receiving real-time config release broadcasts via the optional WebSocket.
  • Pulls the latest active version's config files and certs, writes them locally, and performs safe validation via openresty -t before a smooth reload.
  • Handles Pages deployment package download, SHA-256 validation, and extraction switching locally.
  • See: Agent & Publish Model

3. OpenResty (data plane)

Receives visitor traffic and performs final business landing:

  • Traffic entry, supporting HTTP/2, HTTP/3 (QUIC), and dynamic TLS certificate binding.
  • Embeds Lua logic filtering WAF rules and verifying PoW challenges efficiently in the access_by_lua phase, followed by connection/rate limits and basic caching (policy in Edge Cache Strategy Design).
  • See: WAF Design and Pages Static Hosting Design

4. Relay and OpenFlared (tunnel components)

Extend data-plane reverse penetration:

  • openflare-relay guards the local frps, accepts Server config dispatch, and auto-updates the relay port.
  • openflared guards a group of frpc client processes in the intranet for nearest multi-relay connections and HA disaster recovery.
  • See: Tunnel Design

Data and Request Flow Overview

1. Config Release and Sync Flow

admin modifies config -> release new version -> generate globally unique Checksum active version
                                 |
              +------------------+------------------+
              | (WebSocket broadcast or periodic Heartbeat)      |
              v                                     v
       [edge node Agent]                        [intranet OpenFlared]
  pull latest OpenResty config/certs            pull latest Tunnel mapping config
  incrementally pull/extract Pages packages     generate/rewrite frpc.toml
  validate config and smooth reload             smooth reload or spawn frpc
  report apply state (Success / Error)          report tunnel connection state and metrics

2. Static Hosting and API Proxy Flow

  • Static assets are extracted to projects/{project_id}/current on the Agent node (pulled per project latest, only the newest package kept); OpenResty serves static resources at the edge via root/index/try_files.
  • With API proxying enabled, OpenResty rewrites and forwards (proxy_pass) API requests to the backend dynamic API based on the site's api_proxy_path (e.g. /api).
  • Admin operations and the internal scanner only generate constrained artifact candidates, reusing the unified inspect, upload.Ingest, and deployment pipeline. Manual uploads create a new inactive candidate; persistent-source sync/scanner creates-or-loads and atomically activates. A future repository build executor can only emit into the same artifact pipeline; the Agent is always just an active-deployment consumer.
  • Package validation, extraction escape defense, and Nginx rule rendering: Pages Static Hosting Design

3. WAF Security Filtering Flow

  • The WAF engine is embedded in the OpenResty request lifecycle.
  • WAF rules are orchestrated as a visual DAG on the control plane and compiled into a runtime graph at release; after an OpenResty reload each Worker loads it once, and subsequent requests only traverse the in-memory object.
  • Global rules always run first; route-bound rules execute in explicit order; reaching "pass" in the current rule continues to the next, reaching "block" immediately returns that node's configured block response.
  • IP group members hot-update independently: a coordinating worker checks the checksum every 5 seconds, loading the full snapshot only on change; each Worker's request path always reads the local in-memory object.
  • IP group sources and sync: WAF Design; graph model, execution semantics, release constraints: WAF Orchestration Rule Design

4. Edge Observability and Business Traffic Stats Flow

OpenResty access.log (business facts)
        |
        | Agent tails incremental details (no sum/count/uniq)
        v
Server stores via logstore (current log primary DB: PostgreSQL / SQLite / ClickHouse)
        |
        +---> global aggregation --> dashboard "data provided / requests / UV"
        +---> host∈Zone --> Zone "data provided" etc. (same semantics)
        +---> node_id filter --> node business volume

host /proc NIC, CPU etc. --> Agent reading snapshots --> host resource trends (displayed separately from business delivery)
OpenResty health and connections --> edge health (instant, not 24h business totals)

5. Cloudflare DNS Pointing Flow

admin configures connection/group/member -> Server persists desired state -> Asynq sync tasks
                                                        |
                                                        v
                                              Cloudflare Zone / DNS API
                                                        |
                                                        v
                                    single A record -> active_node IPv4

node IP manually updated or Agent heartbeat change --------------------> best-effort enqueue per node
  • The Cloudflare module only manages cached or taken-over uniquely-named A records; it doesn't extend the Zone core into an authoritative DNS control plane. On multiple same-name A records it stops syncing and asks the admin to clean up in Cloudflare.
  • Group backup/active nodes are reserved for later failover; phase 1 fixes the primary node and doesn't auto-switch on heartbeat offline.
  • Connection, model, idempotent sync, and phasing: Cloudflare DNS Pointing Design

Core Objects

Current core system entities include:

  • Reverse proxy & config: zones (root-domain management boundary), zone_domains (explicit domains with cert/route association), proxy_routes (route policy), origins, config_versions, tls_certificates. See Zone & Domain Resource Design.
  • Cloudflare DNS pointing: of_cf_connections (global connection), of_cf_pointing_groups (primary/backup/active nodes and default orange-cloud), of_cf_pointing_members (ZoneDomain members, record cache, sync state). See Cloudflare DNS Pointing Design.
  • Pages static hosting: of_pages_projects, of_pages_project_sources / of_pages_project_source_runtime (mutable source config and runtime), of_pages_deployments (immutable deployments), of_pages_deployment_files (deployment file manifests).
  • Nodes & tunnels: nodes, tunnels (tunnel clients), node_system_profiles, apply_logs.
  • WAF & security: waf_rule_groups, waf_ip_groups, waf_rule_group_bindings (site WAF bindings).
  • System & accounts: acme_accounts, dns_accounts, geoip_update_configs.

Key Design Decisions

Decision Reason
Full config versions instead of online patching stable boundaries for preview, activation, history, and rollback; consistent node state
Agent active pull the Server needs no SSH access, lowering security risk; supports HTTP/WebSocket dual-protocol switching
Globally single active version lowers control-plane complexity, keeps all nodes consistent by default; stable one-click second-level rollback
Zone domains separated from route policy Zones provide the root-domain entry and domain boundaries; routes still reuse the same site-level policy and bind certs per domain
Cloudflare pointing independent of the Zone core ZoneDomains only provide explicit FQDNs; the Cloudflare module drives single A records from DB desired state without widening Zones into a general DNS control plane
Intranet penetration integrated on frp reuses a mature tunnel protocol, avoiding self-built tunnel stability risks; its Vhost mechanism natively fits reverse-proxy routes
Runtime config decoupled from the control store WAF rules compile at release and load with the OpenResty reload; dynamic IP groups refresh independently via checksum-driven memory snapshots
Access logs as the single truth for business traffic the Agent forbids business pre-aggregation; dashboard and Zone share Server-side aggregation, avoiding openresty_tx vs bytes_sent dual tracks
Business delivery / edge health / host capacity layered data provided ≠ host NIC outbound ≠ OpenResty connections; UI and API name and section them separately
Pages artifacts separated from repo builds current sources only import pre-built artifacts; future checkout/build happens in a Server-isolated executor reusing the artifact pipeline; the Agent never runs third-party builds