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 一致性门禁,
确保已部署库不重跑历史、不丢数据。
This commit is contained in:
ryan
2026-08-29 19:28:39 +08:00
parent 9f79fb9969
commit dbaa3bf140
1327 changed files with 91634 additions and 4157 deletions
+8
View File
@@ -10,6 +10,14 @@ sidebar: false
## [Unreleased]
### 💄 其他/体验
- 后端代码整体迁入 `backend/`,与上游 Wavelet 的仓库布局对齐(Cordis 插件化改造第一阶段),模块名保持 `Wavelet` 以保证上游包路径逐字一致。构建、测试、镜像与发布链路已同步调整,`make build-all` / `make dev` / `make swagger` 等本地命令用法不变;HTTP 接口与控制台行为均无变化。
- 引入上游 Cordis 微内核与平台插件到 `backend/{core,pkg,plugins}`(与上游逐字一致,可用 `scripts/sync-upstream.sh` 重复同步),并新增 `backend/OpenFlare/share/` 承载多插件共享资源(控制消息协议、GeoIP、边缘日志)。此阶段仅落位结构与共享层,尚未改变运行时行为。
- 下游代码按功能职责拆为 `server`/`agent`/`relay`/`flared` 四个插件与 `backend/OpenFlare/share` 共享层;三个边缘守护进程改由 Cordis 内核装配启动(profile `agent`/`relay`/`flared`),`-config` 旗标、默认配置路径、退出码与启动日志保持原样。
- 控制台 API 改由 Cordis 内核提供服务:`server` 插件以声明式路由注册,HTTP 监听与优雅退出交给内核的 http 驱动。全部 256 条路由(含 20 条带尾部斜杠的历史列表接口)与改造前逐条一致,232 条对外 API 操作无变化。
- 清理与上游 Wavelet 重复的平台实现:响应封装、日志、邮件、链路追踪、HTTP 连接池、内存/磁盘缓存、批量写入等 8 个本地副本删除并改为使用上游能力(约 600 行重复代码消失,接口形状与文档定义完全一致);顺带把磁盘缓存的类型断言健壮性修复回流上游。
## [v3.5.4] - 2026-08-29
### ✨ 新功能
+1 -1
View File
@@ -78,7 +78,7 @@ OpenResty (Agent, TLS/WAF)
## 组件架构与分工
### 1. Server (控制面)
仓库根目录的 Go 后端(模块 `github.com/Rain-kl/Wavelet`)是 OpenFlare 控制面,基于 Wavelet 全栈脚手架构建:
仓库根目录的 Go 后端(模块 `OpenFlare`)是 OpenFlare 控制面,基于 Wavelet 全栈脚手架构建:
* 提供管理端 REST API(`/api/v1/d/*`),通过 **Session Cookie** 鉴权,可选 `X-Access-Token` 访问令牌。
* 边缘节点协议走 `/api/v1/agent|relay|tunnel/*`,分别使用 `X-Agent-Token` / `X-Tunnel-Token` 鉴权。
* 包含配置编译器(Compiler),将数据库中的规则、证书与全局参数统一编译为不可变的配置快照及 OpenResty 物理配置文件文本。
+1 -1
View File
@@ -70,7 +70,7 @@ OpenFlare 适合需要统一管理多台 OpenResty 代理节点的团队,具
## 仓库结构
OpenFlare 已收敛为**单 monorepo**(Go 模块 `github.com/Rain-kl/Wavelet`)。控制面 Server 与边缘组件(Agent、Relay、OpenFlared)共享同一仓库,业务代码按 Wavelet `internal/apps/` 领域模块组织。
OpenFlare 已收敛为**单 monorepo**(Go 模块 `OpenFlare`)。控制面 Server 与边缘组件(Agent、Relay、OpenFlared)共享同一仓库,业务代码按 Wavelet `internal/apps/` 领域模块组织。
在贡献代码时,请严格遵守以下物理分层与目录分工:
-21150
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -78,7 +78,7 @@ OpenResty (Agent, TLS/WAF)
## Component Architecture and Division
### 1. Server (control plane)
The Go backend at the repo root (module `github.com/Rain-kl/Wavelet`) is the OpenFlare control plane, built on the Wavelet full-stack scaffold:
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.
+1 -1
View File
@@ -70,7 +70,7 @@ When developing and contributing code, **you must strictly follow** these busine
## Repository Structure
OpenFlare has converged to a **single monorepo** (Go module `github.com/Rain-kl/Wavelet`). The control-plane Server and edge components (Agent, Relay, OpenFlared) share the repo, organized by Wavelet `internal/apps/` domain modules.
OpenFlare has converged to a **single monorepo** (Go module `OpenFlare`). The control-plane Server and edge components (Agent, Relay, OpenFlared) share the repo, organized by Wavelet `internal/apps/` domain modules.
When contributing code, strictly follow this physical layering and directory division:
@@ -0,0 +1,620 @@
# OpenFlare Cordis 基础改造实施计划(P0–P3)
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 把 OpenFlare 后端迁入 `backend/` 双 module 结构、以 vendoring 方式引入 Wavelet Cordis 上游与 `share` 共享层,并在不改动任何已部署数据库语义的前提下完成 per-plugin 迁移与版本 stamp 桥接。
**Architecture:** `backend/`(module `OpenFlare`)持有 4 个业务插件与装配根;`backend/Wavelet/`(module `Wavelet`)为上游原样拷贝,`replace` + `go.work` 实现单命令构建。迁移引擎自持 `w_schema_versions(plugin_id, version_id)`,历史 76 链以 plugin id `openflare/legacy` 保真注册,桥接逐行搬运 `goose_db_version` 状态,并保留 Go 侧 zone 导入钩子。
**Tech Stack:** Go 1.25、Gin、GORM、PostgreSQL/SQLite、ClickHouse(独立链,不动)、goose v3、Cobra、Viper。
**规格来源:** `docs/superpowers/specs/2026-08-29-openflare-cordis-refactor-design.md`
**后续计划:** P4(server 插件)、P5(agent/relay/flared 插件)、P6(平台分叉回流)、P7/P8(工具链与加固)各出独立 plan,依赖本 plan 产出的骨架与门禁。
---
## 进度与布局修正(2026-08-29 执行中更新)
**已完成**
- Task 1 基线:`docs/superpowers/specs/baseline/{schema-A.sql,versions-A.txt,routes.txt}`,由 `backend/OpenFlare/infra/persistence/migrator/legacy_dump_test.go` 产出(`OF_DUMP_SCHEMA` / `OF_DUMP_VERSIONS` 触发,A 路 43 表、版本止于 `202608090003`)。
- Task 2 布局:Go 树已入 `backend/`(提交 `f6dd7988`)。
**Task 3 / Task 4 作废原命令,改为**:与 Wavelet **同构的单 module**(提交 `9bf0b2de`)——`backend/{core,pkg,plugins}` 为上游拷贝,下游业务整体位于 `backend/OpenFlare/`(占据上游 `backend/downstream` 的位置),模块名保持 `Wavelet`。因此不再有 `go.work`、`replace`、第二 go.mod。
- Task 3 剩余动作:仅需 `scripts/sync-upstream.sh`(覆盖 `backend/{core,pkg,plugins}`,排除 `share`、`OpenFlare`)与上游 `scripts/check_cordis_architecture.sh` 接入。
- Task 4 改为:新建 `backend/share/`(import `Wavelet/share/...`)并写入所有权声明;把跨插件共享资源 `pkg/{protocol,geoip,wsclient}`、`apps/edge/logging` 移入;退役与上游重复的 `OpenFlare/pkg/{logger,trace,mail,httppool}` 与 `OpenFlare/{buildinfo,testhelper}`(已核实上游覆盖除 `util` 与 `testhelper.SetupLogStoresForTest` 外的全部符号);`OpenFlare/pkg/util` 的 8 个仅存函数与 `IdentifiableTimeRecord`/`VersionInfo` 合并进上游 `pkg/util`。
- 已实测门禁:`go build ./...` 通过、`go test ./...` exit 0(142 包 ok)、`make swagger` exit 0(232 条操作与基线逐条一致)、`make build-all` 四进制产出、`frontend/` 零改动。
- 遗留待办:swaggo 暂 `--exclude plugins,core`,Task 4/P4 需连带解除该排除。
## 关键事实(执行前必读,均已核实)
- 历史链:`internal/infra/persistence/migrator/goose/{postgres,sqlite}` 各 76 个文件,终版 `202608090003_add_log_indexes.sql`;版本表为 goose 默认 `goose_db_version`。ClickHouse 链独立:`goose/clickhouse` 14 文件 + `goose_clickhouse_version`。
- **历史链含 Go 侧数据迁移**:`migrator.Migrate()`(`internal/infra/persistence/migrator/migrator.go:91-103`)先 `goose.UpTo(202607120002)`,再在版本窗口 `[202607120002, 202607130001)` 内执行 `zone.ImportLegacyTx`,最后 `goose.Up()`。改造后该钩子必须仍然生效,且判断依据改为 `w_schema_versions` 中 `openflare/legacy` 的 max version。
- 另有 PG 序列修复 `resyncGooseVersionSequence`(同文件 `:158-183`)与迁移后 `clearSystemConfigCache`(`:185-189`),两者必须保留。
- 上游迁移引擎实现位于 `Wavelet/backend/cmd/app.go:112-360`(`sharedStore` + `gooseEngine`),`core.MigrationEngine` 为公开接口,OpenFlare 需自持一份实现(放 `OpenFlare/internal/platform/migration`),不可依赖上游 `cmd` 包内未导出符号。
- `MigrationEntry.Dir` 被上游引擎忽略;目录基名必须为 `postgres`/`sqlite`。goose 会给每个 plugin_id 插入哨兵 `version_id=0`。
- 内核:`core.Profile("agent")` 合法(未知 profile 原样透传);`App.Run` 始终阻塞在信号;长生命周期服务用 `ctx.RegisterDriver` + `ctx.OnDispose`。
- 全仓 476 个 `.go` 文件引用 `github.com/Rain-kl/Wavelet`,且**无任何非 import 出现**(已验证),模块路径替换可全局进行。
- 前端 `frontend/` 禁止改动;`//go:embed all:dist` 位于 `internal/router/root/frontend.go:18`,dist 由 `make build-embedded` 拷入。
## 文件结构(本计划涉及)
| 动作 | 路径 | 职责 |
| :-- | :-- | :-- |
| Create | `backend/`(git mv 而来) | Go 代码根 |
| Modify | `backend/go.mod` | `module OpenFlare` + `require/replace Wavelet` |
| Create | `backend/go.work` | 本地双 module 一体构建 |
| Create | `backend/Wavelet/**` | 上游拷贝(`core`、`plugins`、`pkg`、`scripts`、`go.mod`) |
| Create | `backend/Wavelet/share/{protocol,wsclient,geoip,edge}/` | 跨插件共享资源(OpenFlare 所有,同步排除) |
| Create | `backend/Wavelet/share/README.md` | 所有权与同步排除声明 |
| Create | `backend/internal/platform/migration/{store,engine,bridge,lock}.go` | 迁移引擎、桥接、锁 |
| Create | `backend/plugins/server/migrations/{postgres,sqlite}/*.sql` | 76×2 历史链原样保真 |
| Create | `backend/cmd/migrate-audit/main.go` | 三方 schema 一致性门禁工具 |
| Create | `scripts/sync-upstream.sh` | 上游同步(排除 `share/`) |
| Modify | `Makefile`、`docker/Dockerfile*`、`.github/workflows/*` | 路径切至 `backend/`、ldflags 模块路径 |
---
## Task 1: 基线快照(P0)
**Files:**
- Create: `docs/superpowers/specs/baseline/routes.txt`
- Create: `docs/superpowers/specs/baseline/go-test.txt`
- [ ] **Step 1: 导出改造前路由与测试基线**
```bash
cd /Users/ryan/Code/Go/OpenFlare-cordis
mkdir -p docs/superpowers/specs/baseline
python3 -c "import json;d=json.load(open('docs/swagger.json'));print('\n'.join(sorted(f'{m.upper()} {p}' for p,v in d.get('paths',{}).items() for m in v)))" > docs/superpowers/specs/baseline/routes.txt
go test ./... > docs/superpowers/specs/baseline/go-test.txt 2>&1; echo "exit=$?"
```
Expected: `exit=0`,`routes.txt` 非空。
- [ ] **Step 2: 生成 A 路 schema 基线(改造前二进制,供 Task 9 对拍)**
```bash
go build -o /tmp/of-baseline-server .
OF_DB_PATH=/tmp/of-baseline-a.db timeout 25 /tmp/of-baseline-server api >/tmp/of-a.log 2>&1 || true
sqlite3 /tmp/of-baseline-a.db "SELECT name,sql FROM sqlite_master WHERE type='table' ORDER BY name;" > docs/superpowers/specs/baseline/schema-A.sql
sqlite3 /tmp/of-baseline-a.db "SELECT version FROM goose_db_version WHERE is_applied=1 ORDER BY version;" > docs/superpowers/specs/baseline/versions-A.txt
```
Expected: `schema-A.sql` 含 `w_users`、`of_zones`;`versions-A.txt` 末行为 `202608090003`。
- [ ] **Step 3: 提交基线**
```bash
git add docs/superpowers/specs/baseline && git commit -m "chore(cordis): 记录改造前 schema 与路由基线"
```
## Task 2: Go 树迁入 `backend/` 并改 module 名(P1)
**Files:**
- Modify: `backend/**`(全部 Go 文件)、`Makefile`、`docker/*`、`.github/workflows/*`
- [ ] **Step 1: 移动文件(保留 git 历史)**
```bash
mkdir -p backend
git mv cmd internal pkg main.go go.mod go.sum backend/
```
- [ ] **Step 2: 改 module 名并重写全部 import 路径**
```bash
perl -pi -e 's{^module github\.com/Rain-kl/Wavelet$}{module OpenFlare}' backend/go.mod
grep -rl 'github.com/Rain-kl/Wavelet' --include='*.go' backend | xargs perl -pi -e 's{\bgithub\.com/Rain-kl/Wavelet\b}{OpenFlare}g'
grep -rl 'github.com/Rain-kl/Wavelet' --include='*.yml' --include='Dockerfile*' Makefile .github docker | xargs perl -pi -e 's{github\.com/Rain-kl/Wavelet}{OpenFlare}g'
```
- [ ] **Step 3: Makefile 全部 Go 目标切到 `backend/`(对齐 Wavelet 惯例)**
`MODULE := $(shell cd backend && go list -m)`;每个含 `go ` 命令的目标前缀 `cd backend &&`;`swagger` 输出目录仍为仓库根 `docs/`(`swag init -g backend/main.go -o docs --dir backend`),确保 `frontend` 消费路径不变。
- [ ] **Step 4: 修正内嵌与 ldflags 路径**
`build-embedded` 中前端导出物拷贝目标改为 `backend/internal/router/root/dist`;`.github/workflows/build-release.yml` 三处 `-X 'OpenFlare/plugins/{agent,relay,flared}/config.Version=...'` 在 Task 12 前暂以 `OpenFlare/internal/apps/...` 为准(P5 再随插件落位改一次),并全部加 `working-directory: backend`。
- [ ] **Step 5: 验证编译与测试未回归**
```bash
cd backend && go build ./... && go test ./... 2>&1 | tail -5
```
Expected: build 无输出;test 与 `docs/superpowers/specs/baseline/go-test.txt` 同结果(exit 0)。
- [ ] **Step 6: 验证 A 路 schema 仍一致(移动未改变行为)**
重跑 Task 1 Step 2(输出改 `schema-A2.sql`、`versions-A2.txt`)并 `diff -u` 两个 schema 文件。
Expected: diff 为空。
- [ ] **Step 7: 提交**
```bash
git add -A && git commit -m "refactor(layout): Go 代码迁入 backend/ 并将模块名简化为 OpenFlare"
```
## Task 3: Vendoring 上游为第二 module(P2)
**Files:**
- Create: `backend/Wavelet/**`、`backend/Wavelet/go.mod`、`backend/go.work`、`scripts/sync-upstream.sh`
- [ ] **Step 1: 拷入上游内核与插件(排除上游装配根与运行期产物)**
```bash
cd /Users/ryan/Code/Go/OpenFlare-cordis/backend
mkdir -p Wavelet
rsync -a --delete --exclude data --exclude uploads --exclude docs --exclude cmd --exclude main.go --exclude downstream \
/Users/ryan/Code/Go/Wavelet/backend/ Wavelet/
```
Expected: `Wavelet/core`、`Wavelet/plugins/{drivers,infra,domain}`、`Wavelet/pkg`、`Wavelet/scripts`、`Wavelet/go.mod` 存在。
- [ ] **Step 2: 建立双 module 关联**
```bash
printf 'go 1.25.7\n\nuse (\n\t.\n\t./Wavelet\n)\n' > go.work
perl -pi -e 's{^go \d.*$}{go 1.25.7\n\nrequire Wavelet v0.0.0\n\nreplace Wavelet => ./Wavelet}' go.mod
go work sync && go build ./... && cd Wavelet && go build ./... && cd ..
```
Expected: 两次 `go build ./...` 均无输出。若 `replace` 插入位置与 go.mod 现有语句冲突,手工将 `require Wavelet v0.0.0` / `replace Wavelet => ./Wavelet` 置于 `go` 指令之后。
- [ ] **Step 3: 写同步脚本(`share/` 永不覆盖)**
```bash
cat > ../scripts/sync-upstream.sh <<'SH'
#!/usr/bin/env bash
# 从 Wavelet 上游同步内核与插件;share/ 由 OpenFlare 拥有,显式排除。
set -euo pipefail
SRC="${1:-/Users/ryan/Code/Go/Wavelet/backend}"
DST="$(cd "$(dirname "$0")/../backend/Wavelet" && pwd)"
rsync -a --delete \
--exclude 'data' --exclude 'uploads' --exclude 'docs' --exclude 'cmd' \
--exclude 'main.go' --exclude 'downstream' --exclude 'share' \
"$SRC/" "$DST/"
echo "synced upstream -> $DST (share/ preserved)"
SH
chmod +x ../scripts/sync-upstream.sh
```
- [ ] **Step 4: 验证 `go.mod` 中上游依赖不丢失**
`go build ./...` 若报 `missing go.sum entry`,执行 `go mod tidy && cd Wavelet && go mod tidy && cd .. && go build ./...`。
Expected: 构建通过。
- [ ] **Step 5: 提交**
```bash
git add -A && git commit -m "feat(cordis): 以第二 module 形态 vendoring Wavelet 内核与平台插件"
```
## Task 4: `share` 共享层(P2,G3)
**Files:**
- Create: `backend/Wavelet/share/{protocol,wsclient,geoip,edge}/`、`backend/Wavelet/share/README.md`
- [ ] **Step 1: 建立所有权声明**
```bash
mkdir -p Wavelet/share
cat > Wavelet/share/README.md <<'MD'
# share — 跨插件共享资源层
本目录由 **OpenFlare 拥有**,不属于 Wavelet 上游同步范围(`scripts/sync-upstream.sh` 显式排除)。
用途:被 `server` 与 `agent`/`relay`/`flared` 中两个及以上插件共同消费、且无法放入
`Wavelet/pkg/`(禁止 import 业务代码)或插件内部(禁止插件互相 import)的资源。
MD
```
- [ ] **Step 2: 迁移四个真正跨插件的资源包**
```bash
mkdir -p Wavelet/share/{protocol,wsclient,geoip,edge}
git mv pkg/protocol Wavelet/share/protocol
git mv pkg/wsclient Wavelet/share/wsclient
git mv pkg/geoip Wavelet/share/geoip
git mv internal/apps/edge/logging Wavelet/share/edge/logging
grep -rl 'OpenFlare/pkg/protocol\|OpenFlare/pkg/wsclient\|OpenFlare/pkg/geoip\|OpenFlare/internal/apps/edge/logging' --include='*.go' . \
| xargs perl -pi -e 's{OpenFlare/pkg/(protocol|wsclient|geoip)}{Wavelet/share/$1}g; s{OpenFlare/internal/apps/edge/logging}{Wavelet/share/edge/logging}g'
go build ./...
```
Expected: 构建通过。
- [ ] **Step 3: 消除 `pkg` 与上游 `Wavelet/pkg` 的重复**
对每个 OpenFlare 保留包,先比对符号集,再决定归属:
```bash
for p in util logger trace cache mail response idgen ginutil httppool buildinfo testhelper; do
echo "=== $p: openflare-only symbols ==="
comm -23 <(grep -rhoE '^func [A-Z][A-Za-z0-9_]*' OpenFlare/pkg/$p/*.go 2>/dev/null | awk '{print $2}' | sort -u) \
<(grep -rhoE '^func [A-Z][A-Za-z0-9_]*' Wavelet/pkg/$p/*.go 2>/dev/null | awk '{print $2}' | sort -u)
done
```
- 上游同名包**已覆盖**的符号 → 删除 OpenFlare 副本,import 改指 `Wavelet/pkg/...`。
- 仅 OpenFlare 需要的符号(如 `util.Go`、`util.EscapeLike`、`util.DummyCheckPassword`)→ 追加到 `Wavelet/pkg/<p>/openflare.go`(OpenFlare 拥有该 fork,P6 回流上游),再删除 OpenFlare 副本。
- `pkg/cap`、`pkg/push`、`pkg/render`、`pkg/pagesarchive` → 与上游有交集者归 `Wavelet/pkg`,纯边缘共享者归 `Wavelet/share`。
每迁一包后 `go build ./... && go test ./...`,全部完成后提交。
- [ ] **Step 4: 验证共享层被真实使用并提交**
```bash
grep -rn 'Wavelet/share' --include='*.go' plugins internal cmd 2>/dev/null | head -5
go build ./... && git add -A && git commit -m "feat(cordis): 新增 Wavelet/share 跨插件共享层并收敛重复 pkg 实现"
```
Expected: 至少 server 与一个 daemon 路径 import `Wavelet/share/...`。
## Task 5: 迁移引擎骨架(P3)
**Files:**
- Create: `backend/internal/platform/migration/store.go`(自持 `sharedStore`)
- Create: `backend/internal/platform/migration/engine.go`(实现 `core.MigrationEngine`)
- Test: `backend/internal/platform/migration/store_test.go`
- [ ] **Step 1: 写失败测试——版本表 DDL 与上游逐字节一致**
```go
func TestSharedStoreDDLMatchesUpstream(t *testing.T) {
for _, dialect := range []string{"sqlite3", "postgres"} {
s := &sharedStore{pluginID: "t", dialect: dialect}
db, err := sql.Open("sqlite", ":memory:")
require.NoError(t, err)
t.Cleanup(func() { _ = db.Close() })
require.NoError(t, s.CreateVersionTable(context.Background(), db))
var got string
require.NoError(t, db.QueryRow("SELECT sql FROM sqlite_master WHERE name='w_schema_versions'").Scan(&got))
require.Contains(t, got, "plugin_id")
require.Contains(t, got, "version_id")
require.Contains(t, got, "PRIMARY KEY (plugin_id, version_id)")
}
}
```
- [ ] **Step 2: 运行验证失败**
Run: `cd backend && go test ./internal/platform/migration/ -run TestSharedStoreDDLMatchesUpstream -v`
Expected: FAIL(`sharedStore` 未定义)。
- [ ] **Step 3: 从上游移植 `sharedStore`**
将 `Wavelet/backend/cmd/app.go:142-240` 的 `sharedStore`(`Tablename`/`CreateVersionTable`/`Insert`/`Delete`/`GetMigration`/`GetLatestVersion`/`ListMigrations`/`placeholder`)整段复制到 `store.go`,仅改包名与注释;DDL 字符串**一字不动**。
Run: `go test ./internal/platform/migration/ -run TestSharedStoreDDLMatchesUpstream`
Expected: PASS。
- [ ] **Step 4: 写失败测试——历史链按 plugin id 独立计数**
```go
func TestLegacyPluginResumesFromStampedMax(t *testing.T) {
dir := t.TempDir()
db, err := sql.Open("sqlite", filepath.Join(dir, "t.db"))
require.NoError(t, err)
defer func() { _ = db.Close() }()
s := &sharedStore{pluginID: LegacyPluginID, dialect: "sqlite3"}
ctx := context.Background()
require.NoError(t, s.CreateVersionTable(ctx, db))
require.NoError(t, s.Insert(ctx, db, goosedb.InsertRequest{Version: 202608090003}))
got, err := s.GetLatestVersion(ctx, db)
require.NoError(t, err)
require.Equal(t, int64(202608090003), got)
}
```
Run: `go test ./internal/platform/migration/ -run TestLegacyPluginResumesFromStampedMax`
Expected: FAIL(`LegacyPluginID` 未定义)。
- [ ] **Step 5: 实现引擎**
```go
// LegacyPluginID 是 OpenFlare 76 个历史 goose 文件的归属标识。
const LegacyPluginID = "openflare/legacy"
const (
zoneImportSQLVersion int64 = 202607120002
zoneDropLegacySQLVersion int64 = 202607130001
)
```
`engine.go` 以上游 `gooseEngine.Migrate`(`cmd/app.go:243-306`)为基线,差异仅两处:
1. 遍历 `entries` 时,`entry.PluginID == LegacyPluginID` 走三段式:`provider.Up` 到 `zoneImportSQLVersion` → 若 `store.GetLatestVersion` 落在 `[zoneImportSQLVersion, zoneDropLegacySQLVersion)` 则在事务内执行 `zone.ImportLegacyTx` → 继续 `provider.Up` 至末尾;
2. 引擎入口先调用 `resyncGooseVersionSequence(sqlDB)`(自 `migrator.go:158-183` 原样移植,改收 `*sql.DB` 与方言参数),并在全部 entry 完成后调用 `clearSystemConfigCache`。
Run: `go test ./internal/platform/migration/ -v`
Expected: PASS。
- [ ] **Step 6: 提交**
```bash
git add backend/internal/platform/migration && git commit -m "feat(migration): 自持 w_schema_versions 引擎并保留 zone 导入钩子"
```
## Task 6: 历史链归属与注册(P3)
**Files:**
- Create: `backend/internal/platform/migration/legacy/migrations/{postgres,sqlite}`(76×2 文件)
- Modify: `backend/plugins/server/plugin.go`(`ctx.Migrations().Register(LegacyPluginID, legacyFS)`)
- [ ] **Step 1: 原样保真移动 SQL**
```bash
mkdir -p internal/platform/migration/legacy/migrations
git mv internal/infra/persistence/migrator/goose/postgres internal/platform/migration/legacy/migrations/postgres
git mv internal/infra/persistence/migrator/goose/sqlite internal/platform/migration/legacy/migrations/sqlite
git mv internal/infra/persistence/migrator/goose/clickhouse internal/platform/migration/legacy/migrations-clickhouse
```
Expected: `git diff --stat -M HEAD~1 -- '*/*.sql'` 显示纯 rename,零内容变更。
- [ ] **Step 2: embed 并注册**
```go
//go:embed legacy/migrations/postgres/*.sql legacy/migrations/sqlite/*.sql
var legacyFS embed.FS
```
ClickHouse 目录**不放**在 `migrations/` 下(避免被 `findMigrationFS` 的 `postgres|sqlite` 目录探测命中),继续由既有 `MigrateClickHouse` + `goose_clickhouse_version` 驱动,行为与改造前完全一致。
- [ ] **Step 3: 校验双方言文件集合一致**
```bash
diff <(ls internal/platform/migration/legacy/migrations/postgres) \
<(ls internal/platform/migration/legacy/migrations/sqlite)
```
Expected: 无差异(否则 `version_id` 会跨方言漂移)。
- [ ] **Step 4: 构建并提交**
`go build ./... && go test ./...` → PASS 后
```bash
git add -A && git commit -m "refactor(migration): 历史 goose 链原样归入 openflare/legacy 插件标识"
```
## Task 7: 版本 stamp 桥接(P3)
**Files:**
- Create: `backend/internal/platform/migration/bridge.go`
- Create: `backend/internal/platform/migration/lock.go`
- Test: `backend/internal/platform/migration/bridge_test.go`
- [ ] **Step 1: 写失败测试——老库逐行搬运且不重复执行**
```go
func TestBridgeCopiesLegacyVersions(t *testing.T) {
db, err := sql.Open("sqlite", filepath.Join(t.TempDir(), "old.db"))
require.NoError(t, err)
defer func() { _ = db.Close() }()
_, err = db.Exec(`CREATE TABLE goose_db_version (id INTEGER PRIMARY KEY AUTOINCREMENT,
version_id INTEGER NOT NULL, is_applied INTEGER NOT NULL, timestamp DATETIME)`)
require.NoError(t, err)
_, err = db.Exec(`INSERT INTO goose_db_version (version_id,is_applied) VALUES
(202606090001,1),(202607120002,1),(202608090003,1),(202608100001,0)`)
require.NoError(t, err)
n, err := Bridge(context.Background(), db, "sqlite3")
require.NoError(t, err)
require.Equal(t, 3, n) // 仅 is_applied=1 行被搬运
again, err := Bridge(context.Background(), db, "sqlite3")
require.NoError(t, err)
require.Equal(t, 0, again) // 幂等
var maxV int64
require.NoError(t, db.QueryRow(
"SELECT MAX(version_id) FROM w_schema_versions WHERE plugin_id='openflare/legacy'").Scan(&maxV))
require.Equal(t, int64(202608090003), maxV)
}
```
Run: `go test ./internal/platform/migration/ -run TestBridgeCopiesLegacyVersions`
Expected: FAIL(`Bridge` 未定义)。
- [ ] **Step 2: 实现 `Bridge`**
要点:以 `SELECT version_id FROM goose_db_version WHERE is_applied=1` 为源(表不存在则直接返回 0,视为新库);`INSERT INTO w_schema_versions (plugin_id, version_id) VALUES (?, ?) ON CONFLICT (plugin_id, version_id) DO NOTHING` 逐行写入 `openflare/legacy`,并以 `RowsAffected` 汇总真实新增数;同时补写哨兵 `0`。占位符沿用 `sharedStore.placeholder`。**禁止**读写任何业务表。
- [ ] **Step 3: 写失败测试——迁移期互斥锁**
```go
func TestMigrationLockIsExclusive(t *testing.T) {
db, err := sql.Open("sqlite", filepath.Join(t.TempDir(), "l.db"))
require.NoError(t, err)
defer func() { _ = db.Close() }()
unlock, err := LockMigration(context.Background(), db, "sqlite3")
require.NoError(t, err)
_, err2 := db.Exec("INSERT INTO w_schema_versions VALUES ('x',1,'2026-01-01')")
require.Error(t, err2) // 锁持有期间其他写者被阻塞/失败
require.NoError(t, unlock())
_, err3 := db.Exec("INSERT INTO w_schema_versions VALUES ('x',1,'2026-01-01')")
require.NoError(t, err3)
}
```
Expected: 先 FAIL(`LockMigration` 未定义),实现后 PASS。
- [ ] **Step 4: 实现 `LockMigration`**
PG:`SELECT pg_advisory_lock($1)` / `pg_advisory_unlock($1)`(固定 key 常量);SQLite:`PRAGMA busy_timeout` + 单写者事务(`BEGIN IMMEDIATE`)。返回 `func() error` 解锁,上游无 session locker,此处补齐多节点竞争保护。
- [ ] **Step 5: 接入装配根**
`Engine.Migrate` 开头:`unlock, err := LockMigration(...)`、`defer unlock()`;`Bridge(...)` 紧随 `CreateVersionTable` 之后、遍历 `entries` 之前。
- [ ] **Step 6: 全量测试并提交**
```bash
go test ./internal/platform/migration/ -race && go test ./... && git add -A && git commit -m "feat(migration): 一次性 goose_db_version 到 w_schema_versions 桥接与迁移互斥锁"
```
## Task 8: 上游插件初建与对齐(P3)
**Files:**
- Create: `backend/Wavelet/plugins/domain/*/migrations/{postgres,sqlite}/00002_openflare_align.sql`(仅在确有缺口时)
- Test: `backend/internal/platform/migration/parity_test.go`
- [ ] **Step 1: 列出对齐缺口(只读探测,不写库)**
```sql
-- 对 A 路基线库执行,逐表比对上游模型期望列
SELECT table_name, column_name FROM information_schema.columns
WHERE table_name LIKE 'w\_%' ORDER BY table_name, column_name;
```
Expected: 产出「上游需要但现库缺失」的列/表清单;`w_message_channels`、`w_message_bindings`、`w_message_pairing_codes` 应出现在“缺失表”中(OpenFlare 无 bot 渠道表)。
- [ ] **Step 2: 缺口只以追加式迁移补齐**
```sql
-- +goose Up
-- +goose StatementBegin
ALTER TABLE w_users ADD COLUMN IF NOT EXISTS need_change_password BOOLEAN NOT NULL DEFAULT FALSE;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
SELECT 1;
-- +goose StatementEnd
```
`Down` 一律写成 no-op(禁止 DROP,避免回滚破坏现网数据)。sqlite 方言无 `ADD COLUMN IF NOT EXISTS`,需拆为独立文件并按上游惯例手写 sqlite 版本(不接受条件 SQL)。
- [ ] **Step 3: 一致性检查(B 路 vs A 路,复用 Task 1 的 dump harness)**
`legacydump_test.go` 通过环境变量 `OF_DUMP_SCHEMA=<输出路径>` 触发,把「当前 embed 内的全部 SQL」应用到 `t.TempDir()` 的临时 sqlite 库并导出表/索引定义。B 路 = 迁移文件已全部就位后的 dump:
```bash
cd backend
OF_DUMP_SCHEMA=/tmp/of-b.sql go test ./internal/platform/migration/legacy -run DumpFreshSchema
diff -u docs/superpowers/specs/baseline/schema-A.sql /tmp/of-b.sql
```
Run: 上述 diff 命令
Expected: 初期 FAIL 并逐表列出漂移(缺失的 `ADD COLUMN`、被 align 迁移改动的列);补齐 align 迁移后 diff 为空。
- [ ] **Step 4: 提交**
```bash
git add -A && git commit -m "fix(migration): 追加式 align 迁移使全新安装与升级路径 schema 收敛"
```
## Task 9: 三方一致性门禁工具(P3 验收)
**Files:**
- Create: `backend/cmd/migrate-audit/main.go`
- Modify: `Makefile`(`migrate-audit` 目标)
- [ ] **Step 1: 实现只读 dump 与 diff CLI**
```go
// dump: sqlite -> SELECT name,sql FROM sqlite_master WHERE type IN ('table','index') ORDER BY name;
// postgres-> information_schema.columns + pg_indexes ORDER BY 1,2
// mode: A=基线文件 B=全新生成 C=恢复 A 后再跑一次
func main() {
mode := flag.String("mode", "B", "A|B|C")
dialect := flag.String("dialect", "sqlite3", "sqlite3|postgres")
out := flag.String("out", "", "dump 输出路径")
flag.Parse()
// 1) 临时目录一律 os.MkdirTemp("", "of-audit-"),禁止写源码树
// 2) B: 空库 -> bridge(no-op) -> Engine.Migrate(entries)
// 3) C: 复制 A 的 db 文件 -> bridge(stamp) -> Engine.Migrate -> 断言 results 为空
// 4) 输出规范化 dump 到 --out,交由 Makefile 的 diff 判定
}
```
- [ ] **Step 2: Makefile 目标与期望输出**
```make
migrate-audit:
cd backend && go run ./cmd/migrate-audit --mode B --out /tmp/of-b.sql
cd backend && go run ./cmd/migrate-audit --mode C --base docs/superpowers/specs/baseline/schema-A.sql --out /tmp/of-c.sql
diff -u docs/superpowers/specs/baseline/schema-A.sql /tmp/of-b.sql
@sqlite3 /tmp/of-c.db "SELECT count(*) FROM w_schema_versions" >/dev/null
@grep -q 'no-op' /tmp/of-c.log && echo "MIGRATION PARITY OK"
```
Expected: `make migrate-audit` 两个 diff 全空并打印 `MIGRATION PARITY OK`。
- [ ] **Step 3: 提交并在 changelog 记录**
`docs/changelog/index.md` 的 `[Unreleased]` 增补:迁移改为按插件版本表 `w_schema_versions` 管理,已部署库通过一次性桥接保持不重跑。
## Task 10: 架构门禁与 CI 收口(P3 收尾)
- [ ] **Step 1: 接入上游架构检查脚本**
`make arch-check` → `bash backend/Wavelet/scripts/check_cordis_architecture.sh`,并按 OpenFlare 布局增补 `backend/plugins` 与 `backend/Wavelet/share` 两条扫描根。
Expected: 首次运行对 P3 范围(尚无 4 插件)通过。
- [ ] **Step 2: 四二进制构建回归 + code-check + format**
```bash
make build-backend && make code-check && make format
cd backend && go test -race ./... && go test -shuffle=on ./...
```
Expected: 全部 exit 0;`-shuffle=on` 无顺序依赖失败。
- [ ] **Step 3: 前端零改动断言与提交**
```bash
git diff --name-only main...HEAD -- frontend/ | tee /tmp/fe.diff
test ! -s /tmp/fe.diff && echo "FRONTEND UNTOUCHED"
git add -A && git commit -m "chore(ci): cordis 布局路径门禁与迁移一致性检查接入"
```
Expected: 打印 `FRONTEND UNTOUCHED`。
---
## 完成定义(本计划)
- `backend/` 双 module 构建通过;`Wavelet/` 与上游差异仅 `share/` 与被显式记录的回流项。
- `make migrate-audit` 三方 diff 为空;已部署库升级演练零 SQL 变更。
- `go test -race ./...`、`-shuffle=on`、`make code-check` 全绿;`frontend/` 零改动。
- 4 个插件与 daemon 内核化由 P4/P5 承接,本计划不含业务路由迁移。
---
## 附录 A:server 插件接入内核的可行路径(P4 前置调查结论)
调查上游 `core` 与 `plugins/drivers/driver_http` 后确认:`ctx.Router().Use` **不会**作用到
gin engine(只把中间件摊平进 `RouteDefinition`),且以下 OpenFlare 现有 engine 级行为
在内核中没有插件级贡献点:
| 需求 | 现状 | 出处 |
| :-- | :-- | :-- |
| `RedirectTrailingSlash = false` | 内核无处设置(全仓 0 命中) | `router/router.go:42-43` |
| 自有 CORS / logger 分级 / session cookie 语义与 fatal 策略 | 硬编码在 `driver_http/engine.go` | `router.go:45-79` |
| Next.js 静态导出 NoRoute(含动态页 shell 回退) | 被 `registerFrontend` 占用且缺该回退 | `router/root/frontend.go:104-111` |
| `http.Server` 读写字节超时 / TLS | 仅 `ReadHeaderTimeout` | `router.go:83-87` |
| 绑定成功后回调 `onStarted` | 无驱动级回调 | `router.go:89-95` |
| 路由前缀取自 `app.api_prefix` | `ctx.Router()` 只收字面量 | `config.example.yaml:18` |
**结论:不改上游即可闭合——`driver_http.New(driver_http.WithEngine(自建 engine))` 已存在。**
装配根(`backend/cmd`)负责构造 gin engine(保留 Recovery/CORS/session/otelgin/
RedirectTrailingSlash/NoRoute 与超时),把 engine 交给 http 驱动;`server` 插件的
`Apply` 只做 `ctx.Router().Group(...)` 声明式路由注册。其余两点:
- `onStarted` 改由 `ctx.Events().On("app:ready")` + `ctx.Driver(core.DriverTypeHTTP).Addr()` 提供;
启动横幅在 `app.Run()` 前后打印,不再依赖回调。
- 免鉴权路径:**保留 OpenFlare 自有 `oauth.LoginRequired()`(不查白名单)**,语义与今天一致。
注意内核 `ctx.Router().RegisterWhitelist` 目前是**空转**(`driver_http.SetWhitelist`/
`IsPathWhitelisted` 无非测试调用方),不得依赖它放行——否则会 401。
待办:以上三项(engine 中间件贡献点、NoRoute 贡献点、白名单真正生效)应回流上游 Wavelet,
届时可去掉 `WithEngine` 例外。契约校验仍以 `docs/superpowers/specs/baseline/routes.txt`
的 232 条 **操作**(方法×路径,`.Any()` 计 7)为准,与代码内 215 个注册调用点不矛盾。
## 附录 B:server 插件化已就位件与剩余步骤
**已落地并验证**
- `router.BuildEngine()` 从 `Serve()` 中抽出(仅构造引擎与中间件,不监听),`Serve()` 复用它,行为不变。
- `router/routes_dump_test.go`:以 `OF_DUMP_ROUTES=<path>` 导出现存注册路径的 **(方法 路径) 全集**,
已固化基线 `docs/superpowers/specs/baseline/routes-engine.txt`:**256 条**(含 20 条尾部斜杠变体,
全为 `GET /api/v1/d/*`)。这是 server 插件化唯一的路由对拍基准(232 是 swagger 操作数,
二者差异来自斜杠变体与 `Any` 展开,不是矛盾)。
- 内核补丁:`RouterExtension` 新增 `HandleRaw`(保留尾部斜杠)与 `BasePath`,作用域包装器同步实现并登记反注册;
用例 `core/extpoints/router_raw_test.go` 覆盖;补丁登记在 `backend/OpenFlare/upstream-patches.md`,
`sync-upstream.sh` 同步后会打印需确认的补丁文件。
**剩余步骤(按序,每步都要过对拍)**
1. `openflare/apiutil.RegisterCollection` 改用 `route.Handle(method,"")` + `route.HandleRaw(method,"/")`
(取代 `route.BasePath()` 的 gin 用法与 `[]gin.HandlerFunc`→`[]any` 转换)。
2. `router/root`:`RegisterDefaultRootRoutes`/`RegisterCustomRootRoutes` 参数改 `core.RouterExtension`;
`RegisterFrontend`(`NoRoute` + 静态资源,含 Next.js 动态 shell 回退)保留在引擎层,由 `BuildEngine()` 调用。
3. 33 个 `*gin.RouterGroup` 注册函数改 `core.RouterExtension`(本轮已验证方法面只需
GET/POST/PUT/DELETE/Group/Use,且无人接收返回值),随后删除 `registerRoutes()`。
4. 新增 `OpenFlare/plugins/server/plugin.go`:`Name()="server"`,`Apply(ctx)` 内以
`ctx.Router().Group(config.Config.App.APIPrefix)` 复刻 `registerRoutes` 的树
(`/api` → `/v1` → v1/user/admin/openflare;根级 4 条走注册表绝对路径)。
5. 装配根切换:`cmd/{api,all}.go` 改为 `core.NewApp(core.WithProfile(...))` +
`app.Use(server.New())` + `app.Use(driver_http.New(driver_http.WithEngine(router.BuildEngine())))`,
启动横幅改挂 `ctx.Events().On("app:ready")` 并用 `httpDrv.Addr()` 取实际监听地址;
`otel_trace.Shutdown`/`bootstrap.Stop` 迁入 `ctx.OnDispose`,删除 `Serve()` 自带的信号循环。
6. 对拍:新测试把插件注册表产出的 (方法 路径) 集合与 `routes-engine.txt` 逐条比对,
差异必须为空;再实跑 server(sqlite)`curl` 健康检查、一个免鉴权端点与一个需鉴权端点(期望 401)。
@@ -0,0 +1,98 @@
# server 插件复用上游能力清理计划
**目标**:`backend/OpenFlare/plugins/server` 里大量能力上游 Wavelet 已提供,删除本地副本改为复用;
按规约判定归属——通用能力回流上游,OpenFlare 业务留在插件内。
**事实基线**:`server` 实测 **68,117** 行非测试 Go 代码(早先记的 29.6k 只是 `openflare/` 一个域)。
路由契约冻结基准:`docs/superpowers/specs/baseline/routes-engine.txt`(256 条 方法+路径)与
`routes.txt`(232 条 swagger 操作),由 `plugin_parity_test.go` 逐条把守。
## 关键防线(先读,否则会做错)
直接挂上游插件**会破坏契约**,以下差异必须逐项处理,不允许“上游有就换”:
1. **验证码门禁丢失**:OF 在 `POST /api/v1/user/{login,register,send-email-code}` 上挂了
`cap.VerifyMiddleware`(`router/v1/user.go:71-73`),上游 `domain/user` 没有该门禁
(`user/plugin.go:126-129`)。直接替换=安全回归。
2. **路径变化**:`/api/cap/{challenge,redeem}`(OF,挂在 `/api` 组)vs `/api/v1/cap/*`(上游,且多一个 `GET`);
`GET /api/health` vs `/healthz` + `/api/healthz`;`/api/v1/upload` 的 `GET /my`、`PUT /:id`、
`GET /download/:id`、`POST /download/batch` vs 上游 `GET ""`、`POST /batch-download`;
OF 独有 `GET /api/v1/user/self`。
3. **公共配置响应体不同**:`GET /api/v1/config/public` 路径相同但 JSON 字段不同,SPA 直接消费。
4. 上游额外需要表 `w_message_channels` / `w_message_bindings` / `w_message_pairing_codes`(OF 库中不存在)。
除此之外 13 张共享 `w_*` 表**列级零差异**;唯一缺口 `w_access_tokens.description/expires_at` 上游 Go 模型也未使用,非阻塞。
## 判定清单
| 包 | 行数 | 上游对应 | 判定 |
| :-- | --: | :-- | :-- |
| `shared/response` | 142 | `pkg/response` | 删(7/7 `Abort*` 逐字一致,仅风格差异) |
| `pkg/logger` | 382 | `pkg/logger` | 删(`Config` 字段完全一致) |
| `pkg/mail` | 266 | `pkg/mail` | 删 |
| `pkg/httppool` | 106 | `pkg/httppool` | 删(逐字节相同) |
| `pkg/trace` | 148 | `pkg/trace` | 删(差异仅默认 tracer 名,由 `app.app_name`/`otel.tracer_name` 决定) |
| `pkg/cache/ram` | 305 | `pkg/cache/ram` | 删(OF 用裸 `go`,上游用 `util.Go`,换过去是修复) |
| `infra/persistence/idgen` | 47 | `pkg/idgen` | 删(上游为超集) |
| `infra/persistence/batchwriter` | 337 | `pkg/batchwriter` | 删 |
| `listener` | 42 | `core/events.go Subscribe[T]` | 删,改事件订阅 |
| `testhelper` | 479 | `pkg/testhelper` | 合并,仅留 `SetupLogStoresForTest` |
| `pkg/cache/disk` | 412 | `pkg/cache/disk` | 先回流 OF 的 5 处类型断言 `ok` 守卫(通用健壮性修复),再删本地副本 |
| `pkg/cap` | 511 | `plugins/domain/cap/pow` | **不能直接复用**(跨插件 import 被禁)。二选一:pow 提取到上游 `pkg/pow`,或留 `server` 内并放弃“复用” |
| `infra/objectstore`、`infra/diskcache` | 1,058 | `plugins/infra/storage/*` | 改走 `contracts.StorageService`/上游 infra(P6) |
| `infra/task`(worker/scheduler/handlers) | 1,125 | `plugins/drivers/driver_asynq_*` | 换内核驱动(P6) |
| `infra/config`(全局 `Config` 单例) | 444 | `core/config.go` + `plugins/infra/config` | 改 `ctx.Config().Bind`(P6,18 处引用) |
| `infra/persistence`(DB/Redis 初始化) | 639 | `plugins/infra/database` | 换 `contracts.DBService`(P6) |
| `infra/persistence/migrator` | 298 | `w_schema_versions` + 每插件迁移 | 见 foundation 计划 P3(含历史 stamp 桥接) |
| `repository/logstore` | 3,286 | `plugins/domain/risk_control/logstore` | 近似分叉:合并并保留 OF 的 `AccessLogStore`/`ObservabilityStore` 接口 |
| `repository/analytics` | 2,848 | 同上(仅用户访问日志半边) | 合并半边;`node_*` 与 CH 维护上游没有,留 `server` |
| `repository`、`model` | 8,527 | 各上游域插件内 | 拆分:`w_*` 部分随 P6 归上游,`of_*` 留 `server` |
| `oauth`、`user`、`admin`、`upload`、`cap`、`config`、`health` | ~7,600 | `plugins/domain/{auth,user,admin,upload,cap,system}` | **受“关键防线”约束**,逐端点判定后合并,禁止整体替换 |
| `admin/push` | 1,776 | `plugins/domain/message_gateway` | 合并并保留 OF 的 `RegisterBuiltInEvent`/`RegisterChannelDefinition`/`SyncEvents` 钩子 |
| `admin/updater` | 832 | 上游仅 `GetUpdateStatus/ApplyUpdate` | OF 的 Prepare/Apply/Finish 三段式为业务能力,保留 |
| `openflare/`(27 子包) | 29,678 | 无 | 保留(产品逻辑);`apiutil` 与 `integration/githubrelease` 若被多插件消费再移 `share` |
| `router`、`router/root`、`router/v1` | 1,242 | `core/extpoints` + `driver_http` | 保留为接线层,直至上游具备引擎中间件与 NoRoute 贡献点 |
## 执行顺序
1. **T1 纯风格差异**(`shared/response`、`pkg/{logger,mail,httppool,trace,cache/ram}`、
`infra/persistence/{idgen,batchwriter}`):改 import → 删本地副本 → 全量门禁。
2. **T2 `listener` 改事件订阅**、`testhelper` 合并。
3. **T3 `pkg/cache/disk` 断言守卫回流上游**,再删本地副本。
4. **T4 基础设施换内核**(`infra/config` → `ctx.Config()`;`infra/persistence` → `contracts.DBService`;
`infra/task` → asynq 驱动):与 P3/P4b 合并推进。
5. **T5 平台域逐个合并**(oauth/user/admin/upload/cap/config/health/push):每个域先列差异
(路由、门禁、响应体、表列),差异以 `server` 内薄封装或回流上游解决,逐域过 256 条对拍。
6. **T6 `repository`/`model` 按表所有权拆分**:`w_*` 归上游,`of_*` 留 `server`。
每步完成判据:`go build ./...` + `go test ./...` 全绿 + `plugin_parity_test` 零差异 +
`make swagger` 232 条零差异 + `golangci-lint` 0 issues + `frontend/` 零改动。
## 执行记录与阻塞点
### T1 已完成(提交 `0adbfc1a`)
删除并改用上游:`shared/response`→`pkg/response`、`pkg/{logger,mail,trace,httppool,cache/ram}`、
`infra/persistence/batchwriter`→`pkg/batchwriter`。共 7 个本地包消失,`go test` 包数 144→138。
证据:256 条路由对拍零差异;232 条 swagger 操作零增减,且**把两个随包路径改名的定义
(`…shared_response.Any`→`response.Any`、`…pkg_logger.LogEntry`→`logger.LogEntry`)归一化后,
新旧 swagger.json 深度相等**——即接口形状完全未变。`$ref` 名由 Go 包路径派生,属生成物命名变化。
### 新发现的阻塞点(勿重复踩)
1. **`pkg/idgen` 暂不能换**:上游 `pkg/idgen` 要求先 `Init(nodeID)` 否则 panic
(`idgen: Init must be called before generating IDs`),而 OpenFlare 副本是懒初始化。
5 个 push/user 用例直接 panic。必须与 T4(`infra/config`、`infra/persistence` 改走
`ctx.Config()`/`contracts.DBService`)一起做,在 bootstrap 阶段显式 `Init`。
2. **`pkg/cap` 不能“复用上游”**:上游 pow 实现在 `plugins/domain/cap/pow` 里,
跨插件 import 被架构门禁禁止。合规出路二选一:把 pow 提取为上游 `pkg/pow`(通用能力,
按规约回流 Wavelet 后两边共用),或承认它是 server 的业务实现留在插件内。
3. **`listener` 需先有内核上下文**:`core.Subscribe[T](bus, topic, fn)` 要 `*core.EventBus` 实例,
而调用方(`oauth/handler_callback.go`、`user/routers.go`、`admin/push/custom_events/*`)都在
内核之外、手上没有 `*core.Context`。现在改只会把全局变量从包级切片挪到包级总线。
待 `server` 的 `Apply`/bootstrap 拿到 ctx 后随 T4 一起做。
4. **`pkg/trace` 覆盖缺口(非本次引入)**:核实后上游与 OpenFlare 副本都是 4 个同名文件、
都**没有**测试文件,因此删除本地副本没有降低覆盖率。要补测试应直接补在上游 `backend/pkg/trace/`。
5. **`pkg/cache/disk` 已完成**:5 处类型断言的 `ok` 守卫按规约回流上游
(Wavelet `f3d85d5`,附 `cache_corruption_test.go`:去掉守卫即 panic、加上守卫 4 用例全过),
上游的 `Default()` 全局实例保留(下游不使用它,不构成行为差异);本地副本已删除。
@@ -0,0 +1,236 @@
# OpenFlare Cordis 架构改造设计
- **文档类型**: 架构设计 / 改造规范(Spec)
- **日期**: 2026-08-29
- **范围**: OpenFlare 后端全量迁移到 Wavelet Cordis 插件化架构
- **上游参照**: `/Users/ryan/Code/Go/Wavelet`(`backend/`,module `Wavelet`)
- **改造基线**: OpenFlare `main` @ `9f79fb99`(v3.5.4),工作分支 `cordis`
---
## 1. 目标与不可协商约束
| # | 约束 | 验证方式 |
| :-- | :-- | :-- |
| G1 | OpenFlare 改造为 Cordis 架构,上游包名为 `Wavelet` | `backend/Wavelet/` 与上游逐字节一致(`share/` 除外);`go build ./...` 通过 |
| G2 | OpenFlare 功能收敛为 **4 个插件**:`server`、`agent`、`relay`、`flared` | `backend/plugins/` 下仅此 4 个目录,均实现 `core.Plugin` |
| G3 | 在上游树内创建 `share` 包承载跨插件共享资源 | `backend/Wavelet/share/` 存在且被 4 个插件至少两处 import |
| G4 | **不修改前端** | `git diff --name-only main...cordis -- frontend/` 为空 |
| G5 | 数据库迁移谨慎:已部署库不重跑历史、不丢数据、零破坏性变更 | 三方 schema 一致性门禁(§5.4) |
| G6 | 既有 HTTP 契约不变(前端与 API 消费者零感知) | 路由清单 diff + swagger 对拍 |
| G7 | 边缘三进制的 CLI 形态不变(`-config` 默认路径、退出码、日志格式) | 启动冒烟 + 现有测试保持绿 |
## 1.5 布局修正(v1.1,取代 §3 与 §4 中的路径描述)
初版设计提出的「`backend/` + `backend/Wavelet/` 双 module、OpenFlare 插件放 `backend/plugins/`」已被否定。
**采用与 Wavelet 完全同构的单 module 布局**(已落地,提交 `9bf0b2de`):
```
backend/ module Wavelet(与上游同名,上游 import 路径逐字一致)
├── main.go cmd/ 控制面装配根(server)
├── core/ 【上游】微内核 + contracts + extpoints
├── pkg/ 【上游】通用库(禁止 import plugins)
├── plugins/{drivers,infra,domain}/ 【上游】平台插件
├── docs/ swaggo 产物(json/yaml 复制回根 docs/ 供站点消费)
├── share/ 【新增 G3】跨插件共享资源层
└── OpenFlare/ 【下游】占据上游 backend/downstream 的位置
├── cmd/{agent,relay,flared}/ 三个 daemon 入口(package main)
└── plugins/{server,agent,relay,flared}/ 【G2】4 个业务插件
```
约束更新:
- 上游同步 = 覆盖 `backend/{core,pkg,plugins}`,零 import 改写;`OpenFlare/` 与 `share/` 永不被覆盖。
- OpenFlare 现有代码(apps/repository/model/infra/router/shared/…)暂整体驻留 `backend/OpenFlare/**`,由 P4/P5 逐个搬入 `backend/OpenFlare/plugins/<插件>`;搬完后 `backend/OpenFlare` 下不再保留平铺的遗留层。
- 装配根在 `backend/cmd`(模块内非 internal 路径),因此下游树不得使用 `internal/`(已在本次落地时展平)。
- 平台表由上游 `plugins/domain/*` 拥有,业务表 `of_*` 由 `OpenFlare/plugins/server` 拥有;§5 迁移方案不变,仅历史链落点改为 `backend/OpenFlare/plugins/server/migrations/{postgres,sqlite}`。
落地验证(实测,非推断):`go build ./...` 通过;`go test ./...` exit 0 且 142 个包 ok(含上游插件测试);`make swagger` exit 0 且 232 条 API 操作与改造前基线逐条一致;`make build-all` 产出 4 个二进制;`git status --short -- frontend/` 为空。
上游 `plugins`、`core` 暂从 swaggo 扫描中排除(其包名 `model`、`disk` 与下游遗留注解的裸名引用冲突),P4 挂载上游路由时随 `OpenFlare/model` 展平一并解除。
## 2. 已核实的事实基线(设计前提,非推测)
1. **OpenFlare 是 Cordis 之前的 Wavelet 分叉**:`go.mod` module 路径即 `github.com/Rain-kl/Wavelet`;`internal/apps/` 同时含继承自 Wavelet 的平台应用(admin/oauth/user/upload/cap/config/health)与 OpenFlare 自有业务(openflare 29.6k 行、agent 7.4k、edge 1.5k、relay 1.0k、flared 0.9k)。Go 代码总量 151,473 行,209 个测试文件。
2. **表前缀双轨**:平台表沿用 `w_*`(OpenFlare 的 76 迁移已在其上分叉),业务表 `of_*`(28 张)。上游 `w_*` 表宇宙 17 张,且上游含 `w_message_channels`/`w_message_bindings`/`w_message_pairing_codes`(OpenFlare 无)→ **双向分叉**。
3. **内核支持非 HTTP 形态**(`Wavelet/backend/core`):
- `normalizeProfile`(app.go:684)对未知 profile 字符串原样透传;`matchesProfile`(app.go:668)`default` 分支做字符串相等比较 → `core.Profile("agent")` 仅启动 `DriverType("agent")` 的驱动。
- `App.Run`(app.go:600-613)**始终**阻塞在 `signal.NotifyContext`;即使零驱动匹配也会阻塞 → 插件自身禁止阻塞。
- `Profile` 枚举仅 api/worker/schedule/all;驱动仅在 `Start` 中按 profile 过滤(app.go:487-494),插件 `Apply`、IoC、事件、配置在各 profile 下行为一致。
- 长生命周期服务标准写法(`plugins/drivers/driver_inproc_worker/plugin.go:118-151`):`Apply` 内保存 coreCtx → `core.Provide` → `ctx.OnDispose(...)` → `ctx.RegisterDriver(p)`;`Stop` 逆序(LIFO)调用并受 `shutdownTimeout` 约束。
- `RunMigrations()`(app.go:414-433)在无 `MigrationEngine` 且容器无该服务时返回 nil → 无 DB 的 daemon 合法。
- DI 真实 API 为包级函数 `core.Provide[T](ctx, svc)` / `core.Inject[T](ctx)` / `core.Using[T](ctx, fn)` / `core.When[T](ctx, fn)`;`Context` **没有** `Provide/DB/Cache/Logger/Storage/DistLock` 方法( cookbook 中的 `ctx.DB()` 写法为文档性简化,且内核无 DistLock 能力)。
4. **迁移子系统现状**:
- `w_schema_versions(plugin_id, version_id, applied_at)` 的 DDL 来自 Go(`Wavelet/backend/cmd/app.go:142-153`),**没有** SQL 文件创建它;插入使用 `ON CONFLICT DO NOTHING`。
- `sharedStore` 未实现 `TableExists` → goose 回退探测失败后会给每个 plugin_id 插入哨兵 `version_id = 0` 行(幂等、良性,但桥接必须知道)。
- 版本号解析:goose `NumericComponent` 取文件名首个 `_` 之前部分按十进制 int64 解析,须 ≥1;`2026MMDDNNNN` 与 `00001` 均合法;同一 plugin_id 内重复版本号致命。
- `findMigrationFS`(app.go:309-358)按目录名 `postgres`/`sqlite` 探测,**注册时传入的 `Dir` 参数被忽略** → 目录基名必须是 `postgres`/`sqlite`。
- 方言选择:`ctx.Config().Bool("database.enabled", false)` → postgres,否则 sqlite3;无 `WithSessionLocker` → **多节点并发迁移无保护**(上游未解)。
- 上游 `risk_control/logstore/migrations-clickhouse/00001_initial.sql` 是**未被 embed、无处应用**的孤儿文件。
- **上游不存在任何历史库基线/stamp 能力**(无 migrate 命令、无 `--baseline`),桥接为 OpenFlare 自有产物。
5. **`backend/` 化的路径耦合点**(Phase 1 必须一并处理):
- `internal/router/root/frontend.go:18` `//go:embed all:dist`(前端导出物被 Go 包内嵌);
- `.github/workflows/build-release.yml` 三处 `-X 'github.com/Rain-kl/Wavelet/internal/apps/{agent,relay,flared}/config.Version'` 与 `./cmd/*/main.go` 构建路径;
- `Makefile` 的 `build-embedded` / `build-backend` / `cross-build` / `swagger` / `code-check` / `dev-*`;
- `docker/` 四个镜像的构建上下文与 COPY 路径;`config.example.yaml`、`config/clickhouse`、`.env.example` 的读取路径。
## 3. 目标仓库布局
```
OpenFlare/
├── backend/ # Go 代码根,module OpenFlare
│ ├── go.mod # require Wavelet + replace Wavelet => ./Wavelet
│ ├── go.work # 本地一体构建(use . ./Wavelet)
│ ├── Wavelet/ # 【上游】从 Wavelet/backend 原样拷入
│ │ ├── go.mod # module Wavelet
│ │ ├── core/ # 微内核(零业务)
│ │ ├── core/contracts/ # 跨插件契约(纯 Interface + DTO)
│ │ ├── core/extpoints/ # 扩展点(Router/Task/Schedule/Migration/Setting/Config/Driver)
│ │ ├── plugins/drivers/ # driver_http / asynq_* / inproc_*
│ │ ├── plugins/infra/ # database / cache / cache_memory / config / logger / storage
│ │ ├── plugins/domain/ # admin / user / auth / message_gateway / risk_control / upload / cap / system
│ │ ├── pkg/ # 上游通用库(禁止 import plugins)
│ │ ├── scripts/check_cordis_architecture.sh
│ │ └── share/ # 【G3 新增】跨插件共享资源层,OpenFlare 所有
│ ├── plugins/ # 【G2】OpenFlare 的 4 个插件
│ │ ├── server/ # 控制面:of_* 业务域 + 边缘接入 API
│ │ ├── agent/ # 边缘 nginx/WAF 代理守护
│ │ ├── relay/ # frps 中继守护
│ │ └── flared/ # frpc 隧道客户端守护
│ ├── cmd/ # 装配根:root/server/agent/relay/flared/bridge
│ ├── main.go # server 入口(swagger 注释保留原位)
│ └── internal/ # 仅保留非业务基础设施(迁移桥接、构建信息)
├── frontend/ # 【不修改】
├── docs/ # VitePress + swagger 产物路径不变
├── docker/ Makefile .github/ # 路径改写至 backend/
└── config.example.yaml # 位置不变(新增 plugins.* 节点)
```
### 3.1 双 module 而非单 module 的理由
上游树内 import 保持 `Wavelet/core`、`Wavelet/plugins/...` 逐字节等于上游仓库,同步 = `rsync --delete` + 一条 `replace` 指令,无需任何 import 改写。单 module 方案(`OpenFlare/Wavelet/core`)每次同步都要全量重写 import,同步成本随上游演进线性上升,且必然产生漂移。以 Go module 依赖引用上游(不拷代码)被否,因为 OpenFlare 已分叉平台代码,需先在 `backend/Wavelet/` 内落地再回流。
### 3.2 `share` 的落点与所有权
`share` 建在上游 module 内(`Wavelet/share/...`),因为:`Wavelet/pkg/` 禁止 import 业务代码,而 `protocol`、`wsclient`、edge 日志/状态机、`geoip` 等资源**同时被 server 与三个 daemon 消费**;Cordis 规则禁止插件互相 import,故必须存在一个双方都可见的中立层。
**所有权声明**(写入 `backend/Wavelet/share/README.md`):`share/` 由 OpenFlare 拥有,同步脚本 `scripts/sync-upstream.sh` 显式排除该目录;上游同步仅覆盖 `core/`、`plugins/`、`pkg/`、`scripts/`。此决定的代价是 `share/` 不能从上游获得更新——这是刻意选择,且是唯一需要否定的子决策(备选:落点 `backend/share/`,import `OpenFlare/share/...`,其余设计不变)。
### 3.3 模块与命名
- OpenFlare module 路径:`OpenFlare`(对齐上游 `module Wavelet` 的简化命名惯例)。
- 插件 ID 与目录名同名:`server` / `agent` / `relay` / `flared`。
- 表所有权:`of_*` 全部归 `server`;`w_*` 归各上游平台插件;`w_schema_versions` 归装配根。
- 插件内部分层:一律采用模式 2(`plugin.go` + `handler/ service/ repository/ model/ errs/ migrations/`),子包内文件按业务实体命名,禁止 `handlers_*` 平铺。
## 4. 插件设计
### 4.1 `server`
装配 profile:`api` / `worker` / `schedule` / `all`(与上游语义一致)。挂载上游 infra(database、cache 或 cache_memory、logger、storage)、8 个上游 domain 插件、`driver_http` + asynq/inproc 驱动对,再挂 `server` 插件。
`server` 插件职责:
- 路由:`ctx.Router().Group("/api/v1/...")` 自包含注册 OpenFlare 业务路由;免鉴权路径(边缘节点接入、健康探针、OAuth 回调)由本插件 `RegisterWhitelist` 主动声明,杜绝误拦截。
- 任务/调度:`ctx.Task().Register` / `ctx.Schedule().RegisterCron`(迁移现有 `internal/platform/bootstrap` 的显式装配)。
- 设置:`ctx.Settings().Register`(迁移现有动态设置声明)。
- 契约:向平台暴露 `contracts.*` 之外不新增跨插件事务;OpenFlare 自有能力经 `server` 单入口暴露。
- 迁移:`of_*` 全量 + 历史链(§5)。
内部按子域分包:`zone / cloudflare / pages / waf / node / proxy_route / dns / acme / tls / edge_control / config_version / health`,每子域内部遵循 handler→service→repository→model 单向依赖。
### 4.2 `agent` / `relay` / `flared`
同一模式,各自实现 `core.Plugin` + `core.Driver`:
```go
func (p *Plugin) Name() string { return "agent" }
func (p *Plugin) Type() core.DriverType { return core.DriverType("agent") }
func (p *Plugin) Apply(ctx *core.Context) error {
if err := ctx.Config().Bind("agent", &p.cfg); err != nil { return err } // JSON 配置经 ConfigSource 适配
core.Provide[contracts.LoggerService](ctx, lg)
ctx.OnDispose(func() error { return p.stop() })
return ctx.RegisterDriver(p)
}
func (p *Plugin) Start(ctx context.Context) error { /* util.Go 启动 heartbeat/sync/updater,select ctx.Done() */ }
func (p *Plugin) Stop(ctx context.Context) error { /* 收敛 frpc/frps 子进程与 ws 连接 */ }
```
- 入口 `cmd/agent/main.go` → `core.NewApp(core.WithProfile(core.Profile("agent")), core.WithConfigSource(jsonSource))` + `app.Use(agent.New())`;**不挂** http/database/cache/asynq 驱动。
- JSON 配置文件(`agent.json`/`relay.json`/`flared.json`)通过实现 `extpoints.ConfigSource`(`Lookup/LookupEnv/Describe`)接入,优先级:env 覆盖 → 文件 → default,与上游 `plugins/infra/config` 的解析优先级一致;`-config` 旗标与默认路径不变。
- 三者共享的 protocol / wsclient / edge logging / state 迁入 `Wavelet/share/`;`internal/apps/edge/` 消失(其内容分别归入 `share` 与 daemon 插件)。
- 裸 `go func()` 一律经 `util.Go`(架构门禁禁止裸 go)。
## 5. 数据库迁移方案(G5)
### 5.1 历史链保真
`goose/postgres`(76)与 `goose/sqlite`(76)文件**逐字节原样**迁入 `backend/plugins/server/migrations/{postgres,sqlite}/`,文件名、序号、内容不改;ClickHouse 链(14,含 `goose_clickhouse_version` 版本表)与其现有 `MigrateClickHouse` 路径**完全保持不动**,不并入 `w_schema_versions`(语义不同、且上游 CH 文件为孤儿,不引入其语义)。
### 5.2 插件初建与对齐
- 上游平台插件的 `00001_initial.sql` 保留:全部 `CREATE TABLE IF NOT EXISTS` / `CREATE INDEX IF NOT EXISTS` / seed `ON CONFLICT DO NOTHING` → 在 OpenFlare 老库上只补建上游新增表(如 `w_message_channels`),已存在表自动跳过。
- 若上游模型需要 OpenFlare 现库缺失的列,**只允许**在对应插件的 `migrations/{postgres,sqlite}/00002_openflare_align.sql` 追加(`ADD COLUMN` / `CREATE INDEX IF NOT EXISTS`)。全链路禁止 `DROP TABLE` / `DROP COLUMN` / `TRUNCATE`。
- 双方言目录必须文件名与版本号一一对应,否则 PG 与 sqlite 的 `version_id` 漂移。
### 5.3 一次性桥接(`cmd/bridge`,启动前置)
1. 以与上游 `cmd/app.go:142-153` **逐字节一致**的 DDL 建 `w_schema_versions`(保证上游 `CreateVersionTable` 的 `IF NOT EXISTS` 成为 no-op)。
2. 取 PG advisory lock(`pg_advisory_lock(key)`)/ sqlite 依赖单写者,补齐上游缺失的并发保护。
3. 若存在老库版本表(`goose_db_version` / OpenFlare 现名)且 `w_schema_versions` 无 `openflare/legacy` 记录:写入 `(openflare/legacy, 0)` 哨兵与 `(openflare/legacy, MAX(老库已应用版本))`,全部 `ON CONFLICT DO NOTHING`。
4. 桥接幂等:重复执行零变更;老库此后**永不重跑**历史链,新库正常跑完整链后收敛到同一 schema。
5. 桥接仅由 server 装配根在 `app.RunMigrations()` 之前调用一次;agent/relay/flared 不涉及(无 DB)。
### 5.4 三方一致性门禁(不可跳过)
| 路径 | 构造方式 | 断言 |
| :-- | :-- | :-- |
| A(基线) | 改造前 `main` 二进制在全新 sqlite/PG 上跑完 76 链 | dump `information_schema` + seed 行数 |
| B(新架构全新安装) | 桥接 + 历史链 + 各插件 initial/align | 与 A **逐项 diff 为空** |
| C(升级路径) | 恢复 A 的库文件后再启动一次新架构 | schema diff 为空,且本次启动**零 SQL 变更**(迁移结果集为空) |
工具:`backend/cmd/migrate-audit`(导 dump 与 diff,只读、不写源码树,临时目录用 `t.TempDir()`)。任一路径失败即视为改造未完成,禁止合并。
## 6. 前端与 API 契约(G4/G6)
`frontend/` 零改动(`git diff --name-only main...cordis -- frontend/` 必须为空)。内嵌物路径由 `Makefile` 的 `build-embedded` 负责:前端导出物仍拷到 `backend/internal/router/root/dist`(或迁移后对应包目录),`//go:embed all:dist` 随包位置同步。
服务端 HTTP 契约保持:路径、`{error_msg,data}` 信封、分页 `{total,results}`、成功 200 / 失败 `Abort*` 语义、swagger 产物路径(`docs/swagger.json|yaml`)。验收:改造前后各跑一次 `swag init`,对拍 `paths` 键集合必须一致(新增路径允许,删改不允许)。
## 7. 工具链、CI 与文档
- `Makefile`:Go 目标切到 `backend/`;新增 `sync-upstream`、`migrate-audit`、`arch-check`。
- `.github/workflows`:4 个 build-image 工作流的构建上下文/`Dockerfile` COPY 路径、`build-release.yml` 的 `./cmd/*/main.go` 与三处 `-X` ldflags 模块路径(改为 `OpenFlare/plugins/{agent,relay,flared}/config.Version`)。
- 门禁移植:`backend/Wavelet/scripts/check_cordis_architecture.sh`(core 不 import gin/gorm/asynq;pkg 不 import plugins;插件间不互相 import;禁 AutoMigrate;禁裸 go)在 OpenFlare `backend/` 下纳入 `make code-check`。
- 文档:中文文档同步(不同步英文);`docs/changelog/index.md` 的 `[Unreleased]` 记录用户可读变更。
## 8. 分期执行计划(每期终点:`go build ./...` 通过 + 209 测试文件绿)
| 期 | 交付 | 验证 |
| :-- | :-- | :-- |
| P0 | 基线测量:构建/测试记录、A 路 schema dump、路由清单快照 | 存档于 `docs/superpowers/specs/baseline/` |
| P1 | Go 树 `git mv` 入 `backend/`,module 改 `OpenFlare`,import 全量改写,Makefile/CI/Docker 路径 | `go build ./...`、四二进制产出、swagger diff 为空 |
| P2 | 上游 vendoring 成 `backend/Wavelet/`(第二 module)+ `share/` 骨架 + 同步脚本 | `go work sync`、`go build ./...`、架构门禁通过 |
| P3 | 迁移子系统:`w_schema_versions` 引擎接入、桥接、历史链归属、align 迁移、`migrate-audit` | **§5.4 三方 diff 全空** |
| P4 | `server` 插件化:`apps/openflare` + health/config/edge 控制面 → 插件子包 + 扩展点注册 | 路由对拍 + 业务测试绿 |
| P5 | `agent`/`relay`/`flared` 插件化 + `share` 落地(protocol/wsclient/edge) | 三 daemon 实跑冒烟 + JSON 配置兼容 |
| P6 | 平台分叉审计与回流:OpenFlare 对 admin/oauth/user/upload/cap/push 的定制逐项移植进 `backend/Wavelet/` | 分叉清单逐项关闭,无静默丢功能 |
| P7 | 工具链/CI/文档/changelog | `make code-check`、`make format` |
| P8 | 加固:`go test -race ./...`、`-shuffle=on`、四 profile 实跑、老库升级演练 | 全绿方可声明完成 |
依赖:P3 依赖 P2;P4/P5 依赖 P2、P3;P6 可与 P4 并行但须在同一期收口。
## 9. 风险与对策
1. **fresh 安装与升级路径 schema 漂移**(桥接最大风险,`CREATE TABLE IF NOT EXISTS` 会静默保留旧表)→ §5.4 三方 diff 门禁 + `migrate-audit` 常驻 CI。
2. **平台分叉被静默丢弃**(OpenFlare 的 admin/oauth/push 定制)→ P6 强制逐项清单,禁止以"上游没有"为由删除行为。
3. **多节点同时迁移竞争**(上游无 session locker)→ 桥接与迁移阶段 advisory lock。
4. **29.6k 行业务塞进单插件导致不可维护**→ `server` 内部按子域物理分包(§4.1),插件数保持 4 不变。
5. **CI/Docker 路径漏改导致发布链断裂**(release 工作流含硬编码 ldflags 模块路径)→ P1 一次性收口并跑 dry-run 构建。
6. **规模**:151k 行、166 迁移文件,上游自身耗时 100+ 提交 → 分期增量、每期可编译可测;不以"部分完成"冒充目标达成。
## 10. 已定决策(可在复审时否定)
- 布局:镜像 `backend/`,上游为第二 module(用户选定)。
- 插件边界:每二进制 = 1 插件,四者全部走内核(用户选定)。
- 迁移:保留历史 + 版本 stamp 桥接(用户选定)。
- `share` 落点:`backend/Wavelet/share/`,OpenFlare 所有、同步排除(本设计采纳,备选见 §3.2)。
- 桥接粒度:stamp 至 `MAX(已应用版本)` 而非逐条 76 行(版本号语义为「≤ max 即已应用」,逐条无额外收益且放大漂移面)。
- ClickHouse:保持既有 `goose_clickhouse_version` 独立链,不并入插件版本表。
@@ -0,0 +1,256 @@
DELETE /api/v1/admin/auth-sources/:id
DELETE /api/v1/admin/push/channels/:id
DELETE /api/v1/admin/push/events/:id
DELETE /api/v1/admin/tasks/schedules/:id
DELETE /api/v1/admin/templates/:key
DELETE /api/v1/admin/uploads/:id
DELETE /api/v1/admin/users/:id
DELETE /api/v1/upload/:id
DELETE /api/v1/user/access-tokens/:id
GET /api/health
GET /api/swagger/*any
GET /api/v1/admin/auth-sources
GET /api/v1/admin/cache/status
GET /api/v1/admin/db-export
GET /api/v1/admin/db-info
GET /api/v1/admin/db-manage/overview
GET /api/v1/admin/db-manage/table-data
GET /api/v1/admin/db-manage/tables
GET /api/v1/admin/logs
GET /api/v1/admin/logs/access
GET /api/v1/admin/logs/analytics
GET /api/v1/admin/logs/ws
GET /api/v1/admin/push/channels
GET /api/v1/admin/push/channels/definitions
GET /api/v1/admin/push/events
GET /api/v1/admin/push/events/builtin
GET /api/v1/admin/push/histories
GET /api/v1/admin/status
GET /api/v1/admin/status/log-database
GET /api/v1/admin/system-configs
GET /api/v1/admin/system-configs/:key
GET /api/v1/admin/tasks/executions
GET /api/v1/admin/tasks/executions/:id
GET /api/v1/admin/tasks/schedules
GET /api/v1/admin/tasks/types
GET /api/v1/admin/templates
GET /api/v1/admin/templates/:key
GET /api/v1/admin/update
GET /api/v1/admin/uploads
GET /api/v1/admin/uploads/download/:id
GET /api/v1/admin/uploads/stats
GET /api/v1/admin/uploads/types
GET /api/v1/admin/users
GET /api/v1/admin/users/:id
GET /api/v1/agent/config-versions/active
GET /api/v1/agent/pages/deployments/:deployment_id/hash
GET /api/v1/agent/pages/deployments/:deployment_id/package
GET /api/v1/agent/pages/projects/:project_id/latest/hash
GET /api/v1/agent/pages/projects/:project_id/latest/package
GET /api/v1/agent/ws
GET /api/v1/config/public
GET /api/v1/d/access-logs
GET /api/v1/d/access-logs/
GET /api/v1/d/access-logs/folds
GET /api/v1/d/access-logs/folds/ip-summary
GET /api/v1/d/access-logs/ip-summary
GET /api/v1/d/access-logs/ip-summary/analysis
GET /api/v1/d/access-logs/ip-summary/trend
GET /api/v1/d/access-logs/overview
GET /api/v1/d/acme-accounts/default
GET /api/v1/d/apply-logs
GET /api/v1/d/apply-logs/
GET /api/v1/d/cloudflare/connection
GET /api/v1/d/cloudflare/domains/available
GET /api/v1/d/cloudflare/groups
GET /api/v1/d/cloudflare/groups/
GET /api/v1/d/cloudflare/groups/:id
GET /api/v1/d/cloudflare/groups/:id/members
GET /api/v1/d/cloudflare/overview
GET /api/v1/d/config-versions
GET /api/v1/d/config-versions/
GET /api/v1/d/config-versions/:id
GET /api/v1/d/config-versions/active
GET /api/v1/d/config-versions/diff
GET /api/v1/d/config-versions/preview
GET /api/v1/d/dashboard/overview
GET /api/v1/d/dns-accounts
GET /api/v1/d/dns-accounts/
GET /api/v1/d/nodes
GET /api/v1/d/nodes/
GET /api/v1/d/nodes/:id/agent-release
GET /api/v1/d/nodes/:id/observability
GET /api/v1/d/nodes/bootstrap-token
GET /api/v1/d/option
GET /api/v1/d/option/
GET /api/v1/d/origins
GET /api/v1/d/origins/
GET /api/v1/d/origins/:id
GET /api/v1/d/pages
GET /api/v1/d/pages/
GET /api/v1/d/pages/:id
GET /api/v1/d/pages/:id/deployments
GET /api/v1/d/pages/:id/source
GET /api/v1/d/pages/deployments/:deployment_id/files
GET /api/v1/d/proxy-routes
GET /api/v1/d/proxy-routes/
GET /api/v1/d/proxy-routes/:id
GET /api/v1/d/status
GET /api/v1/d/tls-certificates
GET /api/v1/d/tls-certificates/
GET /api/v1/d/tls-certificates/:id
GET /api/v1/d/tls-certificates/:id/content
GET /api/v1/d/waf/ip-groups
GET /api/v1/d/waf/ip-groups/:id
GET /api/v1/d/waf/rule-groups
GET /api/v1/d/waf/rule-groups/:id
GET /api/v1/d/waf/sites/:route_id/rule-groups
GET /api/v1/d/zones
GET /api/v1/d/zones/
GET /api/v1/d/zones/:id/overview
GET /api/v1/d/zones/:id/stats
GET /api/v1/oauth/:source/authorize
GET /api/v1/oauth/external-accounts
GET /api/v1/oauth/login
GET /api/v1/oauth/logout
GET /api/v1/oauth/sources
GET /api/v1/oauth/user-info
GET /api/v1/relay/ws
GET /api/v1/tunnel/config/active
GET /api/v1/tunnel/ws
GET /api/v1/upload/download/:id
GET /api/v1/upload/my
GET /api/v1/user-info
GET /api/v1/user/access-tokens
GET /api/v1/user/logout
GET /api/v1/user/self
GET /f/:id
GET /robots.txt
POST /api/cap/challenge
POST /api/cap/redeem
POST /api/v1/admin/auth-sources
POST /api/v1/admin/cache/clear
POST /api/v1/admin/cache/config
POST /api/v1/admin/db-manage/query
POST /api/v1/admin/push/channels
POST /api/v1/admin/push/channels/test
POST /api/v1/admin/push/events
POST /api/v1/admin/push/events/:id/toggle
POST /api/v1/admin/push/test
POST /api/v1/admin/system-configs
POST /api/v1/admin/system-configs/smtp/test
POST /api/v1/admin/tasks/dispatch
POST /api/v1/admin/tasks/executions/:id/retry
POST /api/v1/admin/tasks/schedules
POST /api/v1/admin/templates
POST /api/v1/admin/update/apply
POST /api/v1/admin/uploads/download/batch
POST /api/v1/admin/users
POST /api/v1/agent/apply-logs
POST /api/v1/agent/nodes/heartbeat
POST /api/v1/agent/nodes/register
POST /api/v1/agent/waf/ip-groups/sync
POST /api/v1/d/access-logs/cleanup
POST /api/v1/d/apply-logs/cleanup
POST /api/v1/d/cloudflare/connection/clear
POST /api/v1/d/cloudflare/connection/verify
POST /api/v1/d/cloudflare/groups
POST /api/v1/d/cloudflare/groups/
POST /api/v1/d/cloudflare/groups/:id/delete
POST /api/v1/d/cloudflare/groups/:id/members
POST /api/v1/d/cloudflare/groups/:id/members/:memberId/remove
POST /api/v1/d/cloudflare/groups/:id/members/:memberId/sync
POST /api/v1/d/cloudflare/groups/:id/members/:memberId/update
POST /api/v1/d/cloudflare/groups/:id/sync
POST /api/v1/d/cloudflare/groups/:id/update
POST /api/v1/d/config-versions/:id/activate
POST /api/v1/d/config-versions/cleanup
POST /api/v1/d/config-versions/publish
POST /api/v1/d/dns-accounts
POST /api/v1/d/dns-accounts/
POST /api/v1/d/dns-accounts/:id/delete
POST /api/v1/d/dns-accounts/:id/update
POST /api/v1/d/nodes
POST /api/v1/d/nodes/
POST /api/v1/d/nodes/:id/agent-update
POST /api/v1/d/nodes/:id/delete
POST /api/v1/d/nodes/:id/force-sync
POST /api/v1/d/nodes/:id/observability/cleanup
POST /api/v1/d/nodes/:id/openresty-restart
POST /api/v1/d/nodes/:id/update
POST /api/v1/d/nodes/bootstrap-token/rotate
POST /api/v1/d/option/geoip/lookup
POST /api/v1/d/option/update
POST /api/v1/d/option/update-batch
POST /api/v1/d/origins
POST /api/v1/d/origins/
POST /api/v1/d/origins/:id/delete
POST /api/v1/d/origins/:id/update
POST /api/v1/d/pages
POST /api/v1/d/pages/
POST /api/v1/d/pages/:id/delete
POST /api/v1/d/pages/:id/deployments/:deployment_id/activate
POST /api/v1/d/pages/:id/deployments/:deployment_id/delete
POST /api/v1/d/pages/:id/deployments/upload
POST /api/v1/d/pages/:id/deployments/upload-from-url
POST /api/v1/d/pages/:id/source/check
POST /api/v1/d/pages/:id/source/delete
POST /api/v1/d/pages/:id/source/sync
POST /api/v1/d/pages/:id/source/update
POST /api/v1/d/pages/:id/update
POST /api/v1/d/proxy-routes
POST /api/v1/d/proxy-routes/
POST /api/v1/d/proxy-routes/:id/delete
POST /api/v1/d/proxy-routes/:id/update
POST /api/v1/d/tls-certificates
POST /api/v1/d/tls-certificates/
POST /api/v1/d/tls-certificates/:id/convert-acme
POST /api/v1/d/tls-certificates/:id/delete
POST /api/v1/d/tls-certificates/:id/renew
POST /api/v1/d/tls-certificates/:id/update
POST /api/v1/d/tls-certificates/:id/update-acme
POST /api/v1/d/tls-certificates/apply
POST /api/v1/d/tls-certificates/import-file
POST /api/v1/d/uptimekuma/sync
POST /api/v1/d/waf/ip-groups
POST /api/v1/d/waf/ip-groups/:id/delete
POST /api/v1/d/waf/ip-groups/:id/sync
POST /api/v1/d/waf/ip-groups/:id/update
POST /api/v1/d/waf/ip-groups/test
POST /api/v1/d/waf/rule-groups
POST /api/v1/d/waf/rule-groups/:id/delete
POST /api/v1/d/waf/rule-groups/:id/graph
POST /api/v1/d/waf/rule-groups/:id/meta
POST /api/v1/d/waf/sites/:route_id/rule-groups
POST /api/v1/d/zones
POST /api/v1/d/zones/
POST /api/v1/d/zones/:id/delete
POST /api/v1/d/zones/:id/domains
POST /api/v1/d/zones/:id/domains/:domainId/delete
POST /api/v1/d/zones/:id/domains/:domainId/update
POST /api/v1/d/zones/:id/update
POST /api/v1/oauth/callback
POST /api/v1/oauth/external-accounts/:id/delete
POST /api/v1/relay/heartbeat
POST /api/v1/tunnel/apply-log
POST /api/v1/tunnel/heartbeat
POST /api/v1/upload
POST /api/v1/upload/download/batch
POST /api/v1/user/access-tokens
POST /api/v1/user/access-tokens/:id/rotate
POST /api/v1/user/change-password
POST /api/v1/user/login
POST /api/v1/user/register
POST /api/v1/user/send-email-code
PUT /api/v1/admin/auth-sources/:id
PUT /api/v1/admin/auth-sources/:id/toggle
PUT /api/v1/admin/push/channels/:id
PUT /api/v1/admin/push/events/:id
PUT /api/v1/admin/system-configs/:key
PUT /api/v1/admin/tasks/schedules/:id
PUT /api/v1/admin/templates/:key
PUT /api/v1/admin/users/:id
PUT /api/v1/admin/users/:id/status
PUT /api/v1/d/cloudflare/connection
PUT /api/v1/upload/:id
PUT /api/v1/user/profile
+232
View File
@@ -0,0 +1,232 @@
DELETE /api/v1/admin/auth-sources/{id}
DELETE /api/v1/admin/push/channels/{id}
DELETE /api/v1/admin/push/events/{id}
DELETE /api/v1/admin/tasks/schedules/{id}
DELETE /api/v1/admin/templates/{key}
DELETE /api/v1/admin/uploads/{id}
DELETE /api/v1/admin/users/{id}
DELETE /api/v1/upload/{id}
DELETE /api/v1/user/access-tokens/{id}
GET /api/health
GET /api/v1/admin/auth-sources
GET /api/v1/admin/cache/status
GET /api/v1/admin/db-export
GET /api/v1/admin/db-info
GET /api/v1/admin/db-manage/overview
GET /api/v1/admin/db-manage/tables
GET /api/v1/admin/logs
GET /api/v1/admin/logs/access
GET /api/v1/admin/logs/analytics
GET /api/v1/admin/logs/ws
GET /api/v1/admin/push/channels
GET /api/v1/admin/push/channels/definitions
GET /api/v1/admin/push/events
GET /api/v1/admin/push/events/builtin
GET /api/v1/admin/push/histories
GET /api/v1/admin/status
GET /api/v1/admin/status/log-database
GET /api/v1/admin/system-configs
GET /api/v1/admin/system-configs/{key}
GET /api/v1/admin/tasks/executions
GET /api/v1/admin/tasks/executions/{id}
GET /api/v1/admin/tasks/schedules
GET /api/v1/admin/tasks/types
GET /api/v1/admin/templates
GET /api/v1/admin/templates/{key}
GET /api/v1/admin/update
GET /api/v1/admin/uploads
GET /api/v1/admin/uploads/download/{id}
GET /api/v1/admin/uploads/stats
GET /api/v1/admin/uploads/types
GET /api/v1/admin/users
GET /api/v1/admin/users/{id}
GET /api/v1/agent/config-versions/active
GET /api/v1/agent/pages/deployments/{deployment_id}/hash
GET /api/v1/agent/pages/deployments/{deployment_id}/package
GET /api/v1/agent/pages/projects/{project_id}/latest/hash
GET /api/v1/agent/pages/projects/{project_id}/latest/package
GET /api/v1/agent/ws
GET /api/v1/config/public
GET /api/v1/d/access-logs
GET /api/v1/d/access-logs/folds
GET /api/v1/d/access-logs/folds/ip-summary
GET /api/v1/d/access-logs/ip-summary
GET /api/v1/d/access-logs/ip-summary/analysis
GET /api/v1/d/access-logs/ip-summary/trend
GET /api/v1/d/access-logs/overview
GET /api/v1/d/acme-accounts/default
GET /api/v1/d/apply-logs
GET /api/v1/d/cloudflare/connection
GET /api/v1/d/cloudflare/domains/available
GET /api/v1/d/cloudflare/groups
GET /api/v1/d/cloudflare/groups/{id}
GET /api/v1/d/cloudflare/groups/{id}/members
GET /api/v1/d/cloudflare/overview
GET /api/v1/d/config-versions
GET /api/v1/d/config-versions/active
GET /api/v1/d/config-versions/diff
GET /api/v1/d/config-versions/preview
GET /api/v1/d/config-versions/{id}
GET /api/v1/d/dashboard/overview
GET /api/v1/d/dns-accounts
GET /api/v1/d/nodes
GET /api/v1/d/nodes/bootstrap-token
GET /api/v1/d/nodes/{id}/agent-release
GET /api/v1/d/nodes/{id}/observability
GET /api/v1/d/option
GET /api/v1/d/origins
GET /api/v1/d/origins/{id}
GET /api/v1/d/pages
GET /api/v1/d/pages/deployments/{deployment_id}/files
GET /api/v1/d/pages/{id}
GET /api/v1/d/pages/{id}/deployments
GET /api/v1/d/pages/{id}/source
GET /api/v1/d/proxy-routes
GET /api/v1/d/proxy-routes/{id}
GET /api/v1/d/status
GET /api/v1/d/tls-certificates
GET /api/v1/d/tls-certificates/{id}
GET /api/v1/d/tls-certificates/{id}/content
GET /api/v1/d/waf/ip-groups
GET /api/v1/d/waf/ip-groups/{id}
GET /api/v1/d/waf/rule-groups
GET /api/v1/d/waf/rule-groups/{id}
GET /api/v1/d/waf/sites/{route_id}/rule-groups
GET /api/v1/d/zones
GET /api/v1/d/zones/{id}/overview
GET /api/v1/d/zones/{id}/stats
GET /api/v1/oauth/external-accounts
GET /api/v1/oauth/login
GET /api/v1/oauth/logout
GET /api/v1/oauth/sources
GET /api/v1/oauth/user-info
GET /api/v1/oauth/{source}/authorize
GET /api/v1/relay/ws
GET /api/v1/tunnel/config/active
GET /api/v1/tunnel/ws
GET /api/v1/upload/my
GET /api/v1/user-info
GET /api/v1/user/access-tokens
GET /api/v1/user/logout
GET /api/v1/user/self
GET /f/{id}
GET /robots.txt
POST /api/cap/challenge
POST /api/cap/redeem
POST /api/v1/admin/auth-sources
POST /api/v1/admin/cache/clear
POST /api/v1/admin/cache/config
POST /api/v1/admin/db-manage/query
POST /api/v1/admin/push/channels
POST /api/v1/admin/push/channels/test
POST /api/v1/admin/push/events
POST /api/v1/admin/push/events/{id}/toggle
POST /api/v1/admin/push/test
POST /api/v1/admin/system-configs
POST /api/v1/admin/system-configs/smtp/test
POST /api/v1/admin/tasks/dispatch
POST /api/v1/admin/tasks/executions/{id}/retry
POST /api/v1/admin/tasks/schedules
POST /api/v1/admin/templates
POST /api/v1/admin/update/apply
POST /api/v1/admin/uploads/download/batch
POST /api/v1/admin/users
POST /api/v1/agent/apply-logs
POST /api/v1/agent/nodes/heartbeat
POST /api/v1/agent/nodes/register
POST /api/v1/agent/waf/ip-groups/sync
POST /api/v1/d/access-logs/cleanup
POST /api/v1/d/apply-logs/cleanup
POST /api/v1/d/cloudflare/connection/clear
POST /api/v1/d/cloudflare/connection/verify
POST /api/v1/d/cloudflare/groups
POST /api/v1/d/cloudflare/groups/{id}/delete
POST /api/v1/d/cloudflare/groups/{id}/members
POST /api/v1/d/cloudflare/groups/{id}/members/{memberId}/remove
POST /api/v1/d/cloudflare/groups/{id}/members/{memberId}/sync
POST /api/v1/d/cloudflare/groups/{id}/members/{memberId}/update
POST /api/v1/d/cloudflare/groups/{id}/sync
POST /api/v1/d/cloudflare/groups/{id}/update
POST /api/v1/d/config-versions/cleanup
POST /api/v1/d/config-versions/publish
POST /api/v1/d/config-versions/{id}/activate
POST /api/v1/d/dns-accounts
POST /api/v1/d/dns-accounts/{id}/delete
POST /api/v1/d/dns-accounts/{id}/update
POST /api/v1/d/nodes
POST /api/v1/d/nodes/bootstrap-token/rotate
POST /api/v1/d/nodes/{id}/agent-update
POST /api/v1/d/nodes/{id}/delete
POST /api/v1/d/nodes/{id}/force-sync
POST /api/v1/d/nodes/{id}/observability/cleanup
POST /api/v1/d/nodes/{id}/openresty-restart
POST /api/v1/d/nodes/{id}/update
POST /api/v1/d/option/geoip/lookup
POST /api/v1/d/option/update
POST /api/v1/d/option/update-batch
POST /api/v1/d/origins
POST /api/v1/d/origins/{id}/delete
POST /api/v1/d/origins/{id}/update
POST /api/v1/d/pages
POST /api/v1/d/pages/{id}/delete
POST /api/v1/d/pages/{id}/deployments/upload
POST /api/v1/d/pages/{id}/deployments/upload-from-url
POST /api/v1/d/pages/{id}/deployments/{deployment_id}/activate
POST /api/v1/d/pages/{id}/deployments/{deployment_id}/delete
POST /api/v1/d/pages/{id}/source/check
POST /api/v1/d/pages/{id}/source/delete
POST /api/v1/d/pages/{id}/source/sync
POST /api/v1/d/pages/{id}/source/update
POST /api/v1/d/pages/{id}/update
POST /api/v1/d/proxy-routes
POST /api/v1/d/proxy-routes/{id}/delete
POST /api/v1/d/proxy-routes/{id}/update
POST /api/v1/d/tls-certificates
POST /api/v1/d/tls-certificates/apply
POST /api/v1/d/tls-certificates/import-file
POST /api/v1/d/tls-certificates/{id}/convert-acme
POST /api/v1/d/tls-certificates/{id}/delete
POST /api/v1/d/tls-certificates/{id}/renew
POST /api/v1/d/tls-certificates/{id}/update
POST /api/v1/d/tls-certificates/{id}/update-acme
POST /api/v1/d/uptimekuma/sync
POST /api/v1/d/waf/ip-groups
POST /api/v1/d/waf/ip-groups/test
POST /api/v1/d/waf/ip-groups/{id}/delete
POST /api/v1/d/waf/ip-groups/{id}/sync
POST /api/v1/d/waf/ip-groups/{id}/update
POST /api/v1/d/waf/rule-groups
POST /api/v1/d/waf/rule-groups/{id}/delete
POST /api/v1/d/waf/rule-groups/{id}/graph
POST /api/v1/d/waf/rule-groups/{id}/meta
POST /api/v1/d/waf/sites/{route_id}/rule-groups
POST /api/v1/d/zones
POST /api/v1/d/zones/{id}/delete
POST /api/v1/d/zones/{id}/domains
POST /api/v1/d/zones/{id}/domains/{domainId}/delete
POST /api/v1/d/zones/{id}/domains/{domainId}/update
POST /api/v1/d/zones/{id}/update
POST /api/v1/oauth/callback
POST /api/v1/oauth/external-accounts/{id}/delete
POST /api/v1/relay/heartbeat
POST /api/v1/tunnel/apply-log
POST /api/v1/tunnel/heartbeat
POST /api/v1/upload
POST /api/v1/user/access-tokens
POST /api/v1/user/access-tokens/{id}/rotate
POST /api/v1/user/change-password
POST /api/v1/user/login
POST /api/v1/user/register
POST /api/v1/user/send-email-code
PUT /api/v1/admin/auth-sources/{id}
PUT /api/v1/admin/auth-sources/{id}/toggle
PUT /api/v1/admin/push/channels/{id}
PUT /api/v1/admin/push/events/{id}
PUT /api/v1/admin/system-configs/{key}
PUT /api/v1/admin/tasks/schedules/{id}
PUT /api/v1/admin/templates/{key}
PUT /api/v1/admin/users/{id}
PUT /api/v1/admin/users/{id}/status
PUT /api/v1/d/cloudflare/connection
PUT /api/v1/upload/{id}
PUT /api/v1/user/profile
@@ -0,0 +1,785 @@
-- index idx_of_apply_logs_created_at
CREATE INDEX idx_of_apply_logs_created_at ON of_apply_logs(created_at);
-- index idx_of_apply_logs_node_id
CREATE INDEX idx_of_apply_logs_node_id ON of_apply_logs(node_id);
-- index idx_of_cf_connections_dns_account_id
CREATE INDEX idx_of_cf_connections_dns_account_id ON of_cf_connections (dns_account_id);
-- index idx_of_cf_pointing_groups_active_node_id
CREATE INDEX idx_of_cf_pointing_groups_active_node_id ON of_cf_pointing_groups (active_node_id);
-- index idx_of_cf_pointing_groups_backup_node_id
CREATE INDEX idx_of_cf_pointing_groups_backup_node_id ON of_cf_pointing_groups (backup_node_id);
-- index idx_of_cf_pointing_groups_primary_node_id
CREATE INDEX idx_of_cf_pointing_groups_primary_node_id ON of_cf_pointing_groups (primary_node_id);
-- index idx_of_cf_pointing_members_group_id
CREATE INDEX idx_of_cf_pointing_members_group_id ON of_cf_pointing_members (group_id);
-- index idx_of_cf_pointing_members_zone_domain_id
CREATE UNIQUE INDEX idx_of_cf_pointing_members_zone_domain_id ON of_cf_pointing_members (zone_domain_id);
-- index idx_of_config_versions_is_active
CREATE INDEX idx_of_config_versions_is_active ON of_config_versions (is_active);
-- index idx_of_node_access_logs_host
CREATE INDEX idx_of_node_access_logs_host ON of_node_access_logs (host, logged_at DESC);
-- index idx_of_node_access_logs_host_lower
CREATE INDEX idx_of_node_access_logs_host_lower ON of_node_access_logs (lower(trim(host)));
-- index idx_of_node_access_logs_logged_at
CREATE INDEX idx_of_node_access_logs_logged_at ON of_node_access_logs (logged_at DESC, id DESC);
-- index idx_of_node_access_logs_node_id
CREATE INDEX idx_of_node_access_logs_node_id ON of_node_access_logs (node_id, logged_at DESC);
-- index idx_of_node_access_logs_remote_addr
CREATE INDEX idx_of_node_access_logs_remote_addr ON of_node_access_logs (remote_addr, logged_at DESC);
-- index idx_of_node_access_logs_status_code
CREATE INDEX idx_of_node_access_logs_status_code ON of_node_access_logs (status_code, logged_at DESC);
-- index idx_of_node_edge_health_node
CREATE INDEX idx_of_node_edge_health_node ON of_node_edge_health (node_id, captured_at DESC);
-- index idx_of_node_health_events_event_type
CREATE INDEX idx_of_node_health_events_event_type ON of_node_health_events (event_type);
-- index idx_of_node_health_events_first_triggered_at
CREATE INDEX idx_of_node_health_events_first_triggered_at ON of_node_health_events (first_triggered_at);
-- index idx_of_node_health_events_last_triggered_at
CREATE INDEX idx_of_node_health_events_last_triggered_at ON of_node_health_events (last_triggered_at);
-- index idx_of_node_health_events_node_id
CREATE INDEX idx_of_node_health_events_node_id ON of_node_health_events (node_id);
-- index idx_of_node_health_events_reported_at
CREATE INDEX idx_of_node_health_events_reported_at ON of_node_health_events (reported_at);
-- index idx_of_node_health_events_resolved_at
CREATE INDEX idx_of_node_health_events_resolved_at ON of_node_health_events (resolved_at);
-- index idx_of_node_health_events_status
CREATE INDEX idx_of_node_health_events_status ON of_node_health_events (status);
-- index idx_of_node_metric_snapshots_node
CREATE INDEX idx_of_node_metric_snapshots_node ON of_node_metric_snapshots (node_id, captured_at DESC);
-- index idx_of_node_obs_frpc_node
CREATE INDEX idx_of_node_obs_frpc_node ON of_node_obs_frpc (node_id, captured_at DESC);
-- index idx_of_node_obs_frps_node
CREATE INDEX idx_of_node_obs_frps_node ON of_node_obs_frps (node_id, captured_at DESC);
-- index idx_of_node_system_profiles_node_id
CREATE UNIQUE INDEX idx_of_node_system_profiles_node_id ON of_node_system_profiles (node_id);
-- index idx_of_node_system_profiles_reported_at
CREATE INDEX idx_of_node_system_profiles_reported_at ON of_node_system_profiles (reported_at);
-- index idx_of_nodes_access_token
CREATE INDEX idx_of_nodes_access_token ON of_nodes (access_token);
-- index idx_of_nodes_node_id
CREATE UNIQUE INDEX idx_of_nodes_node_id ON of_nodes (node_id);
-- index idx_of_origins_address
CREATE UNIQUE INDEX idx_of_origins_address ON of_origins (address);
-- index idx_of_pages_deployment_files_deployment_id
CREATE INDEX idx_of_pages_deployment_files_deployment_id ON of_pages_deployment_files (deployment_id);
-- index idx_of_pages_deployments_checksum
CREATE INDEX idx_of_pages_deployments_checksum ON of_pages_deployments (checksum);
-- index idx_of_pages_deployments_project_id
CREATE INDEX idx_of_pages_deployments_project_id ON of_pages_deployments (project_id);
-- index idx_of_pages_deployments_project_number
CREATE UNIQUE INDEX idx_of_pages_deployments_project_number
ON of_pages_deployments (project_id, deployment_number);
-- index idx_of_pages_deployments_source_revision
CREATE UNIQUE INDEX idx_of_pages_deployments_source_revision
ON of_pages_deployments (project_id, source_identity, source_revision)
WHERE source_identity IS NOT NULL AND source_revision IS NOT NULL;
-- index idx_of_pages_deployments_status
CREATE INDEX idx_of_pages_deployments_status ON of_pages_deployments (status);
-- index idx_of_pages_deployments_upload_id
CREATE INDEX idx_of_pages_deployments_upload_id ON of_pages_deployments (upload_id);
-- index idx_of_pages_project_source_runtime_next_check_at
CREATE INDEX idx_of_pages_project_source_runtime_next_check_at
ON of_pages_project_source_runtime (next_check_at);
-- index idx_of_pages_project_sources_project_id
CREATE UNIQUE INDEX idx_of_pages_project_sources_project_id
ON of_pages_project_sources (project_id);
-- index idx_of_pages_projects_active_deployment_id
CREATE INDEX idx_of_pages_projects_active_deployment_id ON of_pages_projects (active_deployment_id);
-- index idx_of_pages_projects_slug
CREATE UNIQUE INDEX idx_of_pages_projects_slug ON of_pages_projects (slug);
-- index idx_of_proxy_routes_origin_id
CREATE INDEX idx_of_proxy_routes_origin_id ON of_proxy_routes (origin_id);
-- index idx_of_proxy_routes_pages_project_id
CREATE INDEX idx_of_proxy_routes_pages_project_id ON of_proxy_routes (pages_project_id);
-- index idx_of_proxy_routes_site_name
CREATE UNIQUE INDEX idx_of_proxy_routes_site_name ON of_proxy_routes (site_name);
-- index idx_of_proxy_routes_tunnel_node_id
CREATE INDEX idx_of_proxy_routes_tunnel_node_id ON of_proxy_routes (tunnel_node_id);
-- index idx_of_tls_certificates_name
CREATE UNIQUE INDEX idx_of_tls_certificates_name ON of_tls_certificates (name);
-- index idx_of_waf_group_route
CREATE UNIQUE INDEX idx_of_waf_group_route ON of_waf_rule_group_bindings (rule_group_id, proxy_route_id);
-- index idx_of_waf_ip_groups_next_sync_at
CREATE INDEX idx_of_waf_ip_groups_next_sync_at ON of_waf_ip_groups (next_sync_at);
-- index idx_of_waf_ip_groups_type
CREATE INDEX idx_of_waf_ip_groups_type ON of_waf_ip_groups (type);
-- index idx_of_waf_rule_group_bindings_proxy_route_id
CREATE INDEX idx_of_waf_rule_group_bindings_proxy_route_id ON of_waf_rule_group_bindings (proxy_route_id);
-- index idx_of_waf_rule_groups_is_global
CREATE INDEX idx_of_waf_rule_groups_is_global ON of_waf_rule_groups (is_global);
-- index idx_of_zone_domains_cert_id
CREATE INDEX idx_of_zone_domains_cert_id ON of_zone_domains (cert_id);
-- index idx_of_zone_domains_domain
CREATE UNIQUE INDEX idx_of_zone_domains_domain ON of_zone_domains (domain);
-- index idx_of_zone_domains_proxy_route_id
CREATE INDEX idx_of_zone_domains_proxy_route_id ON of_zone_domains (proxy_route_id);
-- index idx_of_zone_domains_zone_id
CREATE INDEX idx_of_zone_domains_zone_id ON of_zone_domains (zone_id);
-- index idx_of_zones_domain
CREATE UNIQUE INDEX idx_of_zones_domain ON of_zones (domain);
-- index idx_w_access_tokens_user_id
CREATE INDEX idx_w_access_tokens_user_id ON w_access_tokens (user_id);
-- index idx_w_auth_sources_is_active
CREATE INDEX idx_w_auth_sources_is_active ON w_auth_sources (is_active);
-- index idx_w_external_accounts_auth_source_id
CREATE INDEX idx_w_external_accounts_auth_source_id ON w_external_accounts (auth_source_id);
-- index idx_w_external_accounts_source_external
CREATE UNIQUE INDEX idx_w_external_accounts_source_external ON w_external_accounts (auth_source_id, external_id);
-- index idx_w_external_accounts_user_id
CREATE INDEX idx_w_external_accounts_user_id ON w_external_accounts (user_id);
-- index idx_w_push_channels_enabled
CREATE INDEX idx_w_push_channels_enabled ON w_push_channels(enabled);
-- index idx_w_push_channels_name
CREATE INDEX idx_w_push_channels_name ON w_push_channels(name);
-- index idx_w_push_events_enabled
CREATE INDEX idx_w_push_events_enabled ON w_push_events(enabled);
-- index idx_w_push_events_task_type
CREATE INDEX idx_w_push_events_task_type ON w_push_events(task_type);
-- index idx_w_push_histories_created
CREATE INDEX idx_w_push_histories_created ON w_push_histories(created_at);
-- index idx_w_push_histories_event
CREATE INDEX idx_w_push_histories_event ON w_push_histories(event_key);
-- index idx_w_schedules_is_active
CREATE INDEX idx_w_schedules_is_active ON w_schedules (is_active);
-- index idx_w_task_executions_created_at
CREATE INDEX idx_w_task_executions_created_at ON w_task_executions (created_at);
-- index idx_w_task_executions_started_at
CREATE INDEX idx_w_task_executions_started_at ON w_task_executions (started_at);
-- index idx_w_task_executions_status
CREATE INDEX idx_w_task_executions_status ON w_task_executions (status);
-- index idx_w_task_executions_task_type
CREATE INDEX idx_w_task_executions_task_type ON w_task_executions (task_type);
-- index idx_w_templates_created_at
CREATE INDEX idx_w_templates_created_at ON w_templates (created_at);
-- index idx_w_templates_is_system
CREATE INDEX idx_w_templates_is_system ON w_templates (is_system);
-- index idx_w_templates_updated_at
CREATE INDEX idx_w_templates_updated_at ON w_templates (updated_at);
-- index idx_w_uploads_file_path
CREATE INDEX idx_w_uploads_file_path ON w_uploads (file_path);
-- index idx_w_uploads_hash
CREATE INDEX idx_w_uploads_hash ON w_uploads (hash);
-- index idx_w_uploads_hash_file_size_status
CREATE INDEX idx_w_uploads_hash_file_size_status ON w_uploads (hash, file_size, status);
-- index idx_w_uploads_status_created_at
CREATE INDEX idx_w_uploads_status_created_at ON w_uploads (status, created_at);
-- index idx_w_uploads_type
CREATE INDEX idx_w_uploads_type ON w_uploads (type);
-- index idx_w_uploads_user_id
CREATE INDEX idx_w_uploads_user_id ON w_uploads (user_id);
-- index idx_w_user_access_logs_user_id
CREATE INDEX idx_w_user_access_logs_user_id ON w_user_access_logs (user_id, created_at DESC);
-- index idx_w_users_created_at
CREATE INDEX idx_w_users_created_at ON w_users (created_at);
-- index idx_w_users_email
CREATE INDEX idx_w_users_email ON w_users (email);
-- index idx_w_users_is_active
CREATE INDEX idx_w_users_is_active ON w_users (is_active);
-- index idx_w_users_last_login_at
CREATE INDEX idx_w_users_last_login_at ON w_users (last_login_at);
-- table of_acme_accounts
CREATE TABLE of_acme_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL DEFAULT '',
url TEXT NOT NULL DEFAULT '',
private_key TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_apply_logs
CREATE TABLE of_apply_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL,
version TEXT NOT NULL,
result TEXT NOT NULL,
message TEXT,
checksum TEXT NOT NULL DEFAULT '',
main_config_checksum TEXT NOT NULL DEFAULT '',
route_config_checksum TEXT NOT NULL DEFAULT '',
support_file_count INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL
);
-- table of_cf_connections
CREATE TABLE of_cf_connections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source TEXT NOT NULL DEFAULT '',
dns_account_id INTEGER,
authorization TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT '',
verified_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_cf_pointing_groups
CREATE TABLE of_cf_pointing_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
primary_node_id INTEGER NOT NULL,
backup_node_id INTEGER,
active_node_id INTEGER NOT NULL,
default_proxied BOOLEAN NOT NULL DEFAULT FALSE,
enabled BOOLEAN NOT NULL DEFAULT FALSE,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_cf_pointing_members
CREATE TABLE of_cf_pointing_members (
id INTEGER PRIMARY KEY AUTOINCREMENT,
group_id INTEGER NOT NULL,
zone_domain_id INTEGER NOT NULL,
proxied BOOLEAN NOT NULL DEFAULT FALSE,
cf_zone_id TEXT NOT NULL DEFAULT '',
cf_record_id TEXT NOT NULL DEFAULT '',
desired_ip TEXT NOT NULL DEFAULT '',
sync_status TEXT NOT NULL DEFAULT 'pending',
last_error TEXT NOT NULL DEFAULT '',
synced_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_config_versions
CREATE TABLE "of_config_versions" (
version VARCHAR(32) PRIMARY KEY,
snapshot_json TEXT NOT NULL,
main_config TEXT NOT NULL DEFAULT '',
rendered_config TEXT NOT NULL,
support_files_json TEXT NOT NULL DEFAULT '[]',
checksum VARCHAR(64) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT FALSE,
created_by VARCHAR(64) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_dns_accounts
CREATE TABLE of_dns_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
authorization TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_node_access_logs
CREATE TABLE of_node_access_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL DEFAULT '',
logged_at DATETIME NOT NULL,
remote_addr TEXT NOT NULL DEFAULT '',
region TEXT NOT NULL DEFAULT '',
host TEXT NOT NULL DEFAULT '',
path TEXT NOT NULL DEFAULT '',
user_agent TEXT NOT NULL DEFAULT '',
cache_status TEXT NOT NULL DEFAULT '',
status_code INTEGER NOT NULL DEFAULT 0,
bytes_sent INTEGER NOT NULL DEFAULT 0,
request_length INTEGER NOT NULL DEFAULT 0,
request_time_ms INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_node_edge_health
CREATE TABLE of_node_edge_health (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL DEFAULT '',
captured_at DATETIME NOT NULL,
status TEXT NOT NULL DEFAULT '',
connections INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_node_health_events
CREATE TABLE of_node_health_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL,
event_type TEXT NOT NULL,
severity TEXT NOT NULL,
status TEXT NOT NULL,
message TEXT,
first_triggered_at DATETIME NOT NULL,
last_triggered_at DATETIME NOT NULL,
reported_at DATETIME NOT NULL,
resolved_at DATETIME,
metadata_json TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_node_metric_snapshots
CREATE TABLE of_node_metric_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL DEFAULT '',
captured_at DATETIME NOT NULL,
cpu_usage_percent REAL NOT NULL DEFAULT 0,
memory_used_bytes INTEGER NOT NULL DEFAULT 0,
memory_total_bytes INTEGER NOT NULL DEFAULT 0,
storage_used_bytes INTEGER NOT NULL DEFAULT 0,
storage_total_bytes INTEGER NOT NULL DEFAULT 0,
disk_read_bytes INTEGER NOT NULL DEFAULT 0,
disk_write_bytes INTEGER NOT NULL DEFAULT 0,
network_rx_bytes INTEGER NOT NULL DEFAULT 0,
network_tx_bytes INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_node_obs_frpc
CREATE TABLE of_node_obs_frpc (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL DEFAULT '',
captured_at DATETIME NOT NULL,
tunnel_status TEXT NOT NULL DEFAULT '',
connected_relays_count INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_node_obs_frps
CREATE TABLE of_node_obs_frps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL DEFAULT '',
captured_at DATETIME NOT NULL,
frps_connections INTEGER NOT NULL DEFAULT 0,
frps_proxy_count INTEGER NOT NULL DEFAULT 0,
frps_client_count INTEGER NOT NULL DEFAULT 0,
frps_proxies TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_node_system_profiles
CREATE TABLE of_node_system_profiles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL,
hostname TEXT NOT NULL DEFAULT '',
os_name TEXT NOT NULL DEFAULT '',
os_version TEXT NOT NULL DEFAULT '',
kernel_version TEXT NOT NULL DEFAULT '',
architecture TEXT NOT NULL DEFAULT '',
cpu_model TEXT NOT NULL DEFAULT '',
cpu_cores INTEGER NOT NULL DEFAULT 0,
total_memory_bytes INTEGER NOT NULL DEFAULT 0,
total_disk_bytes INTEGER NOT NULL DEFAULT 0,
uptime_seconds INTEGER NOT NULL DEFAULT 0,
reported_at DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_nodes
CREATE TABLE of_nodes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL,
name TEXT NOT NULL,
ip TEXT NOT NULL DEFAULT '',
ip_manual_override INTEGER NOT NULL DEFAULT 0,
geo_name TEXT NOT NULL DEFAULT '',
geo_latitude REAL,
geo_longitude REAL,
geo_manual_override INTEGER NOT NULL DEFAULT 0,
access_token TEXT NOT NULL DEFAULT '',
auto_update_enabled INTEGER NOT NULL DEFAULT 0,
update_requested INTEGER NOT NULL DEFAULT 0,
update_channel TEXT NOT NULL DEFAULT 'stable',
update_tag TEXT NOT NULL DEFAULT '',
restart_openresty_requested INTEGER NOT NULL DEFAULT 0,
version TEXT NOT NULL DEFAULT '',
ext_version TEXT NOT NULL DEFAULT '',
openresty_status TEXT NOT NULL DEFAULT 'unknown',
openresty_message TEXT,
status TEXT NOT NULL DEFAULT 'offline',
current_version TEXT NOT NULL DEFAULT '',
last_seen_at DATETIME,
last_error TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
node_type TEXT NOT NULL DEFAULT 'edge_node',
relay_bind_port INTEGER NOT NULL DEFAULT 0,
relay_vhost_http_port INTEGER NOT NULL DEFAULT 0,
relay_auth_token TEXT NOT NULL DEFAULT '',
relay_agent_access_addr TEXT NOT NULL DEFAULT '',
relay_client_access_addr TEXT NOT NULL DEFAULT '',
relay_client_proxy_url TEXT NOT NULL DEFAULT '',
capabilities_json TEXT NOT NULL DEFAULT '[]',
relay_status TEXT NOT NULL DEFAULT 'unknown',
relay_web_server_enabled INTEGER NOT NULL DEFAULT 0
);
-- table of_origins
CREATE TABLE of_origins (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
address TEXT NOT NULL,
remark TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_pages_deployment_files
CREATE TABLE of_pages_deployment_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
deployment_id INTEGER NOT NULL,
path TEXT NOT NULL,
size INTEGER NOT NULL DEFAULT 0,
checksum TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_pages_deployments
CREATE TABLE of_pages_deployments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
deployment_number INTEGER NOT NULL,
checksum TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'uploaded',
artifact_path TEXT NOT NULL,
file_count INTEGER NOT NULL DEFAULT 0,
total_size INTEGER NOT NULL DEFAULT 0,
created_by TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
activated_at DATETIME
, upload_id INTEGER NOT NULL DEFAULT 0, source_type TEXT NOT NULL DEFAULT '', source_identity TEXT, source_revision TEXT, source_label TEXT NOT NULL DEFAULT '', source_meta TEXT NOT NULL DEFAULT '', trigger_type TEXT NOT NULL DEFAULT '');
-- table of_pages_project_source_runtime
CREATE TABLE of_pages_project_source_runtime (
source_id INTEGER PRIMARY KEY,
etag TEXT NOT NULL DEFAULT '',
last_seen_revision TEXT NOT NULL DEFAULT '',
last_seen_detail TEXT NOT NULL DEFAULT '',
last_applied_revision TEXT NOT NULL DEFAULT '',
last_applied_detail TEXT NOT NULL DEFAULT '',
sync_status TEXT NOT NULL DEFAULT '',
last_error TEXT NOT NULL DEFAULT '',
last_checked_at DATETIME,
last_synced_at DATETIME,
next_check_at DATETIME,
lease_expires_at DATETIME,
lease_token TEXT NOT NULL DEFAULT '',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_pages_project_sources
CREATE TABLE of_pages_project_sources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
source_type TEXT NOT NULL DEFAULT '',
remote_url TEXT NOT NULL DEFAULT '',
allow_insecure INTEGER NOT NULL DEFAULT 0,
github_repository TEXT NOT NULL DEFAULT '',
release_selector TEXT NOT NULL DEFAULT '',
release_tag TEXT NOT NULL DEFAULT '',
asset_name TEXT NOT NULL DEFAULT '',
auto_update_enabled INTEGER NOT NULL DEFAULT 0,
check_interval_minutes INTEGER NOT NULL DEFAULT 0,
config_version INTEGER NOT NULL DEFAULT 0,
source_identity TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_pages_projects
CREATE TABLE of_pages_projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
slug TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
enabled INTEGER NOT NULL DEFAULT 1,
spa_fallback_enabled INTEGER NOT NULL DEFAULT 0,
spa_fallback_path TEXT NOT NULL DEFAULT '/index.html',
api_proxy_enabled INTEGER NOT NULL DEFAULT 0,
api_proxy_path TEXT NOT NULL DEFAULT '',
api_proxy_pass TEXT NOT NULL DEFAULT '',
api_proxy_rewrite TEXT NOT NULL DEFAULT '',
active_deployment_id INTEGER,
root_dir TEXT NOT NULL DEFAULT '',
entry_file TEXT NOT NULL DEFAULT 'index.html',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
, content_config_version INTEGER NOT NULL DEFAULT 0);
-- table of_proxy_routes
CREATE TABLE "of_proxy_routes" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
site_name TEXT NOT NULL DEFAULT '',
origin_id INTEGER,
origin_url TEXT NOT NULL,
origin_host TEXT NOT NULL DEFAULT '',
upstreams TEXT NOT NULL DEFAULT '[]',
enabled INTEGER NOT NULL DEFAULT 1,
enable_https INTEGER NOT NULL DEFAULT 0,
redirect_http INTEGER NOT NULL DEFAULT 0,
limit_conn_per_server INTEGER NOT NULL DEFAULT 0,
limit_conn_per_ip INTEGER NOT NULL DEFAULT 0,
limit_rate TEXT NOT NULL DEFAULT '',
cache_enabled INTEGER NOT NULL DEFAULT 0,
cache_policy TEXT NOT NULL DEFAULT '',
cache_rules TEXT NOT NULL DEFAULT '[]',
custom_headers TEXT NOT NULL DEFAULT '[]',
basic_auth_enabled INTEGER NOT NULL DEFAULT 0,
basic_auth_username TEXT NOT NULL DEFAULT '',
basic_auth_password TEXT NOT NULL DEFAULT '',
upstream_type TEXT NOT NULL DEFAULT 'direct',
tunnel_node_id INTEGER,
tunnel_target_addr TEXT NOT NULL DEFAULT '',
tunnel_target_protocol TEXT NOT NULL DEFAULT '',
pages_project_id INTEGER,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
, limit_req_per_ip VARCHAR(32) NOT NULL DEFAULT '');
-- table of_tls_certificates
CREATE TABLE of_tls_certificates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
cert_pem TEXT NOT NULL,
key_pem TEXT NOT NULL,
not_before DATETIME,
not_after DATETIME,
remark TEXT NOT NULL DEFAULT '',
provider TEXT NOT NULL DEFAULT 'upload',
acme_account_id INTEGER NOT NULL DEFAULT 0,
dns_account_id INTEGER NOT NULL DEFAULT 0,
key_algorithm TEXT NOT NULL DEFAULT '',
auto_renew INTEGER NOT NULL DEFAULT 0,
primary_domain TEXT NOT NULL DEFAULT '',
other_domains TEXT NOT NULL DEFAULT '',
disable_cname INTEGER NOT NULL DEFAULT 0,
skip_dns INTEGER NOT NULL DEFAULT 0,
dns1 TEXT NOT NULL DEFAULT '',
dns2 TEXT NOT NULL DEFAULT '',
apply_status TEXT NOT NULL DEFAULT 'ready',
apply_message TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_waf_ip_groups
CREATE TABLE of_waf_ip_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
ip_list TEXT NOT NULL DEFAULT '[]',
auto_config TEXT NOT NULL DEFAULT '{}',
ext_ips TEXT NOT NULL DEFAULT '[]',
subscription_url TEXT NOT NULL DEFAULT '',
subscription_format TEXT NOT NULL DEFAULT 'text',
subscription_mapping_rule TEXT NOT NULL DEFAULT '',
sync_interval_minutes INTEGER NOT NULL DEFAULT 1440,
last_synced_at DATETIME,
next_sync_at DATETIME,
last_sync_status TEXT NOT NULL DEFAULT '',
last_sync_message TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_waf_rule_group_bindings
CREATE TABLE of_waf_rule_group_bindings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
rule_group_id INTEGER NOT NULL,
proxy_route_id INTEGER NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
, sequence INTEGER NOT NULL DEFAULT 0);
-- table of_waf_rule_groups
CREATE TABLE of_waf_rule_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
is_global BOOLEAN NOT NULL DEFAULT FALSE,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
, graph TEXT NOT NULL DEFAULT '', revision INTEGER NOT NULL DEFAULT 1);
-- table of_zone_domains
CREATE TABLE of_zone_domains (
id INTEGER PRIMARY KEY AUTOINCREMENT,
zone_id INTEGER NOT NULL,
proxy_route_id INTEGER,
domain TEXT NOT NULL,
cert_id INTEGER,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table of_zones
CREATE TABLE of_zones (
id INTEGER PRIMARY KEY AUTOINCREMENT,
domain TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table w_access_tokens
CREATE TABLE "w_access_tokens" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id BIGINT NOT NULL,
name VARCHAR(128) NOT NULL,
token_hash VARCHAR(64) NOT NULL UNIQUE,
masked_token VARCHAR(64) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
, is_admin BOOLEAN NOT NULL DEFAULT 0);
-- table w_auth_sources
CREATE TABLE "w_auth_sources" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(80) NOT NULL UNIQUE,
type VARCHAR(20) NOT NULL,
display_name VARCHAR(100),
is_active BOOLEAN NOT NULL DEFAULT FALSE,
client_id VARCHAR(255),
client_secret VARCHAR(1024),
openid_discovery_url VARCHAR(1024),
scopes VARCHAR(255),
icon_url VARCHAR(1024),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- table w_external_accounts
CREATE TABLE "w_external_accounts" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
auth_source_id BIGINT,
user_id BIGINT NOT NULL,
external_id VARCHAR(255) NOT NULL,
external_username VARCHAR(255),
email VARCHAR(255),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- table w_push_channels
CREATE TABLE w_push_channels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
type TEXT NOT NULL DEFAULT 'custom',
token TEXT,
url TEXT NOT NULL,
other TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL
);
-- table w_push_events
CREATE TABLE w_push_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
channels TEXT NOT NULL,
targets TEXT NOT NULL,
template TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT FALSE,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL
, task_type VARCHAR(100) NOT NULL DEFAULT '');
-- table w_push_histories
CREATE TABLE w_push_histories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_key TEXT NOT NULL,
channel TEXT NOT NULL,
target TEXT NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
level TEXT NOT NULL,
status TEXT NOT NULL,
error_msg TEXT,
created_at DATETIME NOT NULL
);
-- table w_schedules
CREATE TABLE "w_schedules" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(128) NOT NULL,
task_type VARCHAR(64) NOT NULL,
cron VARCHAR(64) NOT NULL,
payload TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- table w_schedules_backup_of_database_auto_cleanup
CREATE TABLE w_schedules_backup_of_database_auto_cleanup(
id INT,
name TEXT,
task_type TEXT,
cron TEXT,
payload TEXT,
is_active NUM,
created_at NUM,
updated_at NUM
);
-- table w_system_configs
CREATE TABLE "w_system_configs" (
key VARCHAR(64) PRIMARY KEY,
value TEXT NOT NULL,
type VARCHAR(32) NOT NULL DEFAULT 'system',
visibility INTEGER NOT NULL DEFAULT 0,
description VARCHAR(255),
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- table w_task_executions
CREATE TABLE "w_task_executions" (
id BIGINT PRIMARY KEY,
task_id VARCHAR(128) NOT NULL UNIQUE,
task_type VARCHAR(64) NOT NULL,
task_name VARCHAR(128),
status VARCHAR(32) NOT NULL,
retryable BOOLEAN NOT NULL DEFAULT FALSE,
max_retry INTEGER NOT NULL DEFAULT 0,
retry_count INTEGER NOT NULL DEFAULT 0,
log TEXT,
error_message TEXT,
result TEXT,
started_at DATETIME,
finished_at DATETIME,
duration BIGINT,
payload TEXT,
triggered_by VARCHAR(32) NOT NULL DEFAULT 'system',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- table w_templates
CREATE TABLE "w_templates" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key VARCHAR(80) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
type VARCHAR(20) NOT NULL DEFAULT 'email',
subject VARCHAR(255),
content TEXT NOT NULL,
description VARCHAR(255),
is_system BOOLEAN NOT NULL DEFAULT FALSE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- table w_upload_stats
CREATE TABLE w_upload_stats (
dimension VARCHAR(32) NOT NULL,
stat_key VARCHAR(64) NOT NULL DEFAULT '',
file_count BIGINT NOT NULL DEFAULT 0,
file_size BIGINT NOT NULL DEFAULT 0,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (dimension, stat_key)
);
-- table w_uploads
CREATE TABLE "w_uploads" (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size BIGINT NOT NULL,
mime_type VARCHAR(100) NOT NULL,
extension VARCHAR(50) NOT NULL,
hash VARCHAR(64),
type VARCHAR(50) NOT NULL,
status VARCHAR(20) NOT NULL,
metadata JSON,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
, access_mode INTEGER NOT NULL DEFAULT 0);
-- table w_user_access_logs
CREATE TABLE w_user_access_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL DEFAULT 0,
path TEXT NOT NULL DEFAULT '',
method TEXT NOT NULL DEFAULT '',
ip TEXT NOT NULL DEFAULT '',
user_agent TEXT NOT NULL DEFAULT '',
headers TEXT NOT NULL DEFAULT '',
status INTEGER NOT NULL DEFAULT 0,
latency INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- table w_users
CREATE TABLE "w_users" (
id BIGINT PRIMARY KEY,
username VARCHAR(64) UNIQUE,
password VARCHAR(255),
nickname VARCHAR(255),
email VARCHAR(255),
avatar_url VARCHAR(255),
is_active BOOLEAN DEFAULT TRUE,
is_admin BOOLEAN DEFAULT FALSE,
bio VARCHAR(500),
phone VARCHAR(32),
gender VARCHAR(16),
website VARCHAR(255),
location VARCHAR(255),
last_login_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- total objects (excl. bookkeeping): 130
@@ -0,0 +1,77 @@
0
202606050001
202606090001
202606100001
202606100002
202606110001
202606110002
202606110003
202606110004
202606110005
202606120001
202606120002
202606130001
202606130002
202606130003
202606140001
202606140003
202606140004
202606140005
202606150001
202606160001
202606170001
202606170002
202606170003
202606180001
202606190001
202606190002
202606190003
202606190004
202606190005
202606190006
202606190007
202606190008
202606190009
202606190010
202606190011
202606190012
202606190013
202606190014
202606200001
202606200002
202606200003
202606200004
202606200005
202606200006
202606200007
202606220001
202606220002
202606220003
202606220004
202606220005
202606270001
202606300001
202607120001
202607120002
202607130001
202607140001
202607150001
202607150002
202607150003
202607170001
202607180001
202607190001
202607190002
202607190003
202607200001
202608040001
202608060001
202608060002
202608070001
202608070002
202608080001
202608080002
202608080003
202608090001
202608090002
202608090003
+324 -324
View File
@@ -50,7 +50,7 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.ChallengeResponse"
"$ref": "#/definitions/Wavelet_OpenFlare_plugins_server_cap.ChallengeResponse"
}
}
}
@@ -102,7 +102,7 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse"
"$ref": "#/definitions/Wavelet_OpenFlare_plugins_server_cap.RedeemResponse"
}
}
}
@@ -4515,7 +4515,7 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.PagesDeploymentHashResponse"
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.PagesDeploymentHashResponse"
}
}
}
@@ -4619,7 +4619,7 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.PagesProjectLatestHashResponse"
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.PagesProjectLatestHashResponse"
}
}
}
@@ -14253,7 +14253,7 @@
}
},
"400": {
"description": "用户名或密码错误、帐号已禁用等",
"description": "用户名或密码错误",
"schema": {
"$ref": "#/definitions/response.Any"
}
@@ -14590,6 +14590,316 @@
}
},
"definitions": {
"Wavelet_OpenFlare_plugins_server_cap.ChallengeResponse": {
"type": "object",
"properties": {
"challenge": {
"type": "object",
"properties": {
"c": {
"type": "integer"
},
"d": {
"type": "integer"
},
"s": {
"type": "integer"
}
}
},
"expires": {
"description": "ms timestamp",
"type": "integer"
},
"token": {
"type": "string"
}
}
},
"Wavelet_OpenFlare_plugins_server_cap.RedeemResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"expires": {
"type": "integer"
},
"success": {
"type": "boolean"
},
"token": {
"type": "string"
}
}
},
"Wavelet_OpenFlare_plugins_server_model_analytics.ClickHouseOperationalStats": {
"type": "object",
"properties": {
"active_parts": {
"type": "integer"
},
"async_insert_bytes": {
"type": "integer"
},
"async_insert_queue": {
"type": "integer"
},
"batch_writers": {
"description": "BatchWriters reports in-process queue depth/drops/flush errors for CH writers.",
"type": "array",
"items": {
"$ref": "#/definitions/analytics.BatchWriterStats"
}
},
"database": {
"type": "string"
},
"pending_mutations": {
"type": "integer"
},
"total_rows": {
"type": "integer"
}
}
},
"Wavelet_OpenFlare_share_protocol.ActiveConfigMeta": {
"type": "object",
"properties": {
"checksum": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"Wavelet_OpenFlare_share_protocol.BufferedObservabilityRecord": {
"type": "object",
"properties": {
"access_logs": {
"type": "array",
"items": {
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.NodeAccessLog"
}
},
"captured_at_unix": {
"type": "integer"
},
"edge_health": {
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.NodeEdgeHealth"
},
"host_metrics": {
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.NodeMetricSnapshot"
}
}
},
"Wavelet_OpenFlare_share_protocol.NodeAccessLog": {
"type": "object",
"properties": {
"bytes_sent": {
"description": "body bytes = 已提供数据",
"type": "integer"
},
"cache_status": {
"description": "$upstream_cache_status",
"type": "string"
},
"host": {
"type": "string"
},
"logged_at_unix": {
"type": "integer"
},
"path": {
"type": "string"
},
"remote_addr": {
"type": "string"
},
"request_length": {
"description": "接收数据",
"type": "integer"
},
"request_time_ms": {
"description": "optional",
"type": "integer"
},
"status_code": {
"type": "integer"
},
"user_agent": {
"type": "string"
}
}
},
"Wavelet_OpenFlare_share_protocol.NodeEdgeHealth": {
"type": "object",
"properties": {
"captured_at_unix": {
"type": "integer"
},
"connections": {
"type": "integer"
},
"message": {
"type": "string"
},
"status": {
"type": "string"
}
}
},
"Wavelet_OpenFlare_share_protocol.NodeHealthEvent": {
"type": "object",
"properties": {
"event_type": {
"type": "string"
},
"message": {
"type": "string"
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"severity": {
"type": "string"
},
"triggered_at_unix": {
"type": "integer"
}
}
},
"Wavelet_OpenFlare_share_protocol.NodeMetricSnapshot": {
"type": "object",
"properties": {
"captured_at_unix": {
"type": "integer"
},
"cpu_usage_percent": {
"type": "number"
},
"disk_read_bytes": {
"type": "integer"
},
"disk_write_bytes": {
"type": "integer"
},
"memory_total_bytes": {
"type": "integer"
},
"memory_used_bytes": {
"type": "integer"
},
"storage_total_bytes": {
"type": "integer"
},
"storage_used_bytes": {
"type": "integer"
}
}
},
"Wavelet_OpenFlare_share_protocol.NodeSystemProfile": {
"type": "object",
"properties": {
"architecture": {
"type": "string"
},
"cpu_cores": {
"type": "integer"
},
"cpu_model": {
"type": "string"
},
"hostname": {
"type": "string"
},
"kernel_version": {
"type": "string"
},
"os_name": {
"type": "string"
},
"os_version": {
"type": "string"
},
"reported_at_unix": {
"type": "integer"
},
"total_disk_bytes": {
"type": "integer"
},
"total_memory_bytes": {
"type": "integer"
},
"uptime_seconds": {
"type": "integer"
}
}
},
"Wavelet_OpenFlare_share_protocol.PagesDeploymentHashResponse": {
"type": "object",
"properties": {
"deployment_id": {
"type": "integer"
},
"hash": {
"type": "string"
}
}
},
"Wavelet_OpenFlare_share_protocol.PagesProjectLatestHashResponse": {
"type": "object",
"properties": {
"deployment_id": {
"type": "integer"
},
"file_count": {
"type": "integer"
},
"hash": {
"type": "string"
},
"package_size": {
"type": "integer"
},
"project_id": {
"type": "integer"
},
"total_size": {
"type": "integer"
}
}
},
"Wavelet_OpenFlare_share_protocol.WAFIPGroup": {
"type": "object",
"properties": {
"checksum": {
"type": "string"
},
"enabled": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"ip_list": {
"type": "array",
"items": {
"type": "string"
}
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"agent.ActiveConfigMeta": {
"type": "object",
"properties": {
@@ -14679,20 +14989,20 @@
"access_logs": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeAccessLog"
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.NodeAccessLog"
}
},
"buffered": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.BufferedObservabilityRecord"
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.BufferedObservabilityRecord"
}
},
"current_version": {
"type": "string"
},
"edge_health": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeEdgeHealth"
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.NodeEdgeHealth"
},
"ext_version": {
"type": "string"
@@ -14700,11 +15010,11 @@
"health_events": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeHealthEvent"
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.NodeHealthEvent"
}
},
"host_metrics": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeMetricSnapshot"
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.NodeMetricSnapshot"
},
"ip": {
"type": "string"
@@ -14725,7 +15035,7 @@
"type": "string"
},
"profile": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeSystemProfile"
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.NodeSystemProfile"
},
"schema_version": {
"type": "integer"
@@ -14844,7 +15154,7 @@
"groups": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.WAFIPGroup"
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.WAFIPGroup"
}
}
}
@@ -15669,7 +15979,7 @@
"type": "object",
"properties": {
"active_config": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.ActiveConfigMeta"
"$ref": "#/definitions/Wavelet_OpenFlare_share_protocol.ActiveConfigMeta"
},
"tunnel_settings": {
"$ref": "#/definitions/protocol.RelaySettings"
@@ -15699,316 +16009,6 @@
}
}
},
"github_com_Rain-kl_Wavelet_internal_apps_cap.ChallengeResponse": {
"type": "object",
"properties": {
"challenge": {
"type": "object",
"properties": {
"c": {
"type": "integer"
},
"d": {
"type": "integer"
},
"s": {
"type": "integer"
}
}
},
"expires": {
"description": "ms timestamp",
"type": "integer"
},
"token": {
"type": "string"
}
}
},
"github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"expires": {
"type": "integer"
},
"success": {
"type": "boolean"
},
"token": {
"type": "string"
}
}
},
"github_com_Rain-kl_Wavelet_internal_model_analytics.ClickHouseOperationalStats": {
"type": "object",
"properties": {
"active_parts": {
"type": "integer"
},
"async_insert_bytes": {
"type": "integer"
},
"async_insert_queue": {
"type": "integer"
},
"batch_writers": {
"description": "BatchWriters reports in-process queue depth/drops/flush errors for CH writers.",
"type": "array",
"items": {
"$ref": "#/definitions/analytics.BatchWriterStats"
}
},
"database": {
"type": "string"
},
"pending_mutations": {
"type": "integer"
},
"total_rows": {
"type": "integer"
}
}
},
"github_com_Rain-kl_Wavelet_pkg_protocol.ActiveConfigMeta": {
"type": "object",
"properties": {
"checksum": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"github_com_Rain-kl_Wavelet_pkg_protocol.BufferedObservabilityRecord": {
"type": "object",
"properties": {
"access_logs": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeAccessLog"
}
},
"captured_at_unix": {
"type": "integer"
},
"edge_health": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeEdgeHealth"
},
"host_metrics": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeMetricSnapshot"
}
}
},
"github_com_Rain-kl_Wavelet_pkg_protocol.NodeAccessLog": {
"type": "object",
"properties": {
"bytes_sent": {
"description": "body bytes = 已提供数据",
"type": "integer"
},
"cache_status": {
"description": "$upstream_cache_status",
"type": "string"
},
"host": {
"type": "string"
},
"logged_at_unix": {
"type": "integer"
},
"path": {
"type": "string"
},
"remote_addr": {
"type": "string"
},
"request_length": {
"description": "接收数据",
"type": "integer"
},
"request_time_ms": {
"description": "optional",
"type": "integer"
},
"status_code": {
"type": "integer"
},
"user_agent": {
"type": "string"
}
}
},
"github_com_Rain-kl_Wavelet_pkg_protocol.NodeEdgeHealth": {
"type": "object",
"properties": {
"captured_at_unix": {
"type": "integer"
},
"connections": {
"type": "integer"
},
"message": {
"type": "string"
},
"status": {
"type": "string"
}
}
},
"github_com_Rain-kl_Wavelet_pkg_protocol.NodeHealthEvent": {
"type": "object",
"properties": {
"event_type": {
"type": "string"
},
"message": {
"type": "string"
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"severity": {
"type": "string"
},
"triggered_at_unix": {
"type": "integer"
}
}
},
"github_com_Rain-kl_Wavelet_pkg_protocol.NodeMetricSnapshot": {
"type": "object",
"properties": {
"captured_at_unix": {
"type": "integer"
},
"cpu_usage_percent": {
"type": "number"
},
"disk_read_bytes": {
"type": "integer"
},
"disk_write_bytes": {
"type": "integer"
},
"memory_total_bytes": {
"type": "integer"
},
"memory_used_bytes": {
"type": "integer"
},
"storage_total_bytes": {
"type": "integer"
},
"storage_used_bytes": {
"type": "integer"
}
}
},
"github_com_Rain-kl_Wavelet_pkg_protocol.NodeSystemProfile": {
"type": "object",
"properties": {
"architecture": {
"type": "string"
},
"cpu_cores": {
"type": "integer"
},
"cpu_model": {
"type": "string"
},
"hostname": {
"type": "string"
},
"kernel_version": {
"type": "string"
},
"os_name": {
"type": "string"
},
"os_version": {
"type": "string"
},
"reported_at_unix": {
"type": "integer"
},
"total_disk_bytes": {
"type": "integer"
},
"total_memory_bytes": {
"type": "integer"
},
"uptime_seconds": {
"type": "integer"
}
}
},
"github_com_Rain-kl_Wavelet_pkg_protocol.PagesDeploymentHashResponse": {
"type": "object",
"properties": {
"deployment_id": {
"type": "integer"
},
"hash": {
"type": "string"
}
}
},
"github_com_Rain-kl_Wavelet_pkg_protocol.PagesProjectLatestHashResponse": {
"type": "object",
"properties": {
"deployment_id": {
"type": "integer"
},
"file_count": {
"type": "integer"
},
"hash": {
"type": "string"
},
"package_size": {
"type": "integer"
},
"project_id": {
"type": "integer"
},
"total_size": {
"type": "integer"
}
}
},
"github_com_Rain-kl_Wavelet_pkg_protocol.WAFIPGroup": {
"type": "object",
"properties": {
"checksum": {
"type": "string"
},
"enabled": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"ip_list": {
"type": "array",
"items": {
"type": "string"
}
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"handler.batchDownloadRequest": {
"type": "object",
"required": [
@@ -19787,7 +19787,7 @@
}
},
"clickhouse": {
"$ref": "#/definitions/github_com_Rain-kl_Wavelet_internal_model_analytics.ClickHouseOperationalStats"
"$ref": "#/definitions/Wavelet_OpenFlare_plugins_server_model_analytics.ClickHouseOperationalStats"
},
"migration": {
"description": "idle | migrating",
+219 -219
View File
@@ -1,5 +1,210 @@
basePath: /
definitions:
Wavelet_OpenFlare_plugins_server_cap.ChallengeResponse:
properties:
challenge:
properties:
c:
type: integer
d:
type: integer
s:
type: integer
type: object
expires:
description: ms timestamp
type: integer
token:
type: string
type: object
Wavelet_OpenFlare_plugins_server_cap.RedeemResponse:
properties:
error:
type: string
expires:
type: integer
success:
type: boolean
token:
type: string
type: object
Wavelet_OpenFlare_plugins_server_model_analytics.ClickHouseOperationalStats:
properties:
active_parts:
type: integer
async_insert_bytes:
type: integer
async_insert_queue:
type: integer
batch_writers:
description: BatchWriters reports in-process queue depth/drops/flush errors
for CH writers.
items:
$ref: '#/definitions/analytics.BatchWriterStats'
type: array
database:
type: string
pending_mutations:
type: integer
total_rows:
type: integer
type: object
Wavelet_OpenFlare_share_protocol.ActiveConfigMeta:
properties:
checksum:
type: string
version:
type: string
type: object
Wavelet_OpenFlare_share_protocol.BufferedObservabilityRecord:
properties:
access_logs:
items:
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.NodeAccessLog'
type: array
captured_at_unix:
type: integer
edge_health:
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.NodeEdgeHealth'
host_metrics:
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.NodeMetricSnapshot'
type: object
Wavelet_OpenFlare_share_protocol.NodeAccessLog:
properties:
bytes_sent:
description: body bytes = 已提供数据
type: integer
cache_status:
description: $upstream_cache_status
type: string
host:
type: string
logged_at_unix:
type: integer
path:
type: string
remote_addr:
type: string
request_length:
description: 接收数据
type: integer
request_time_ms:
description: optional
type: integer
status_code:
type: integer
user_agent:
type: string
type: object
Wavelet_OpenFlare_share_protocol.NodeEdgeHealth:
properties:
captured_at_unix:
type: integer
connections:
type: integer
message:
type: string
status:
type: string
type: object
Wavelet_OpenFlare_share_protocol.NodeHealthEvent:
properties:
event_type:
type: string
message:
type: string
metadata:
additionalProperties:
type: string
type: object
severity:
type: string
triggered_at_unix:
type: integer
type: object
Wavelet_OpenFlare_share_protocol.NodeMetricSnapshot:
properties:
captured_at_unix:
type: integer
cpu_usage_percent:
type: number
disk_read_bytes:
type: integer
disk_write_bytes:
type: integer
memory_total_bytes:
type: integer
memory_used_bytes:
type: integer
storage_total_bytes:
type: integer
storage_used_bytes:
type: integer
type: object
Wavelet_OpenFlare_share_protocol.NodeSystemProfile:
properties:
architecture:
type: string
cpu_cores:
type: integer
cpu_model:
type: string
hostname:
type: string
kernel_version:
type: string
os_name:
type: string
os_version:
type: string
reported_at_unix:
type: integer
total_disk_bytes:
type: integer
total_memory_bytes:
type: integer
uptime_seconds:
type: integer
type: object
Wavelet_OpenFlare_share_protocol.PagesDeploymentHashResponse:
properties:
deployment_id:
type: integer
hash:
type: string
type: object
Wavelet_OpenFlare_share_protocol.PagesProjectLatestHashResponse:
properties:
deployment_id:
type: integer
file_count:
type: integer
hash:
type: string
package_size:
type: integer
project_id:
type: integer
total_size:
type: integer
type: object
Wavelet_OpenFlare_share_protocol.WAFIPGroup:
properties:
checksum:
type: string
enabled:
type: boolean
id:
type: integer
ip_list:
items:
type: string
type: array
name:
type: string
type:
type: string
type: object
agent.ActiveConfigMeta:
properties:
checksum:
@@ -58,24 +263,24 @@ definitions:
properties:
access_logs:
items:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeAccessLog'
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.NodeAccessLog'
type: array
buffered:
items:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.BufferedObservabilityRecord'
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.BufferedObservabilityRecord'
type: array
current_version:
type: string
edge_health:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeEdgeHealth'
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.NodeEdgeHealth'
ext_version:
type: string
health_events:
items:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeHealthEvent'
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.NodeHealthEvent'
type: array
host_metrics:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeMetricSnapshot'
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.NodeMetricSnapshot'
ip:
type: string
last_error:
@@ -89,7 +294,7 @@ definitions:
openresty_status:
type: string
profile:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeSystemProfile'
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.NodeSystemProfile'
schema_version:
type: integer
version:
@@ -166,7 +371,7 @@ definitions:
properties:
groups:
items:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.WAFIPGroup'
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.WAFIPGroup'
type: array
type: object
analytics.BatchWriterStats:
@@ -708,7 +913,7 @@ definitions:
flared.HeartbeatResponse:
properties:
active_config:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.ActiveConfigMeta'
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.ActiveConfigMeta'
tunnel_settings:
$ref: '#/definitions/protocol.RelaySettings'
type: object
@@ -727,211 +932,6 @@ definitions:
version:
type: string
type: object
github_com_Rain-kl_Wavelet_internal_apps_cap.ChallengeResponse:
properties:
challenge:
properties:
c:
type: integer
d:
type: integer
s:
type: integer
type: object
expires:
description: ms timestamp
type: integer
token:
type: string
type: object
github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse:
properties:
error:
type: string
expires:
type: integer
success:
type: boolean
token:
type: string
type: object
github_com_Rain-kl_Wavelet_internal_model_analytics.ClickHouseOperationalStats:
properties:
active_parts:
type: integer
async_insert_bytes:
type: integer
async_insert_queue:
type: integer
batch_writers:
description: BatchWriters reports in-process queue depth/drops/flush errors
for CH writers.
items:
$ref: '#/definitions/analytics.BatchWriterStats'
type: array
database:
type: string
pending_mutations:
type: integer
total_rows:
type: integer
type: object
github_com_Rain-kl_Wavelet_pkg_protocol.ActiveConfigMeta:
properties:
checksum:
type: string
version:
type: string
type: object
github_com_Rain-kl_Wavelet_pkg_protocol.BufferedObservabilityRecord:
properties:
access_logs:
items:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeAccessLog'
type: array
captured_at_unix:
type: integer
edge_health:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeEdgeHealth'
host_metrics:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.NodeMetricSnapshot'
type: object
github_com_Rain-kl_Wavelet_pkg_protocol.NodeAccessLog:
properties:
bytes_sent:
description: body bytes = 已提供数据
type: integer
cache_status:
description: $upstream_cache_status
type: string
host:
type: string
logged_at_unix:
type: integer
path:
type: string
remote_addr:
type: string
request_length:
description: 接收数据
type: integer
request_time_ms:
description: optional
type: integer
status_code:
type: integer
user_agent:
type: string
type: object
github_com_Rain-kl_Wavelet_pkg_protocol.NodeEdgeHealth:
properties:
captured_at_unix:
type: integer
connections:
type: integer
message:
type: string
status:
type: string
type: object
github_com_Rain-kl_Wavelet_pkg_protocol.NodeHealthEvent:
properties:
event_type:
type: string
message:
type: string
metadata:
additionalProperties:
type: string
type: object
severity:
type: string
triggered_at_unix:
type: integer
type: object
github_com_Rain-kl_Wavelet_pkg_protocol.NodeMetricSnapshot:
properties:
captured_at_unix:
type: integer
cpu_usage_percent:
type: number
disk_read_bytes:
type: integer
disk_write_bytes:
type: integer
memory_total_bytes:
type: integer
memory_used_bytes:
type: integer
storage_total_bytes:
type: integer
storage_used_bytes:
type: integer
type: object
github_com_Rain-kl_Wavelet_pkg_protocol.NodeSystemProfile:
properties:
architecture:
type: string
cpu_cores:
type: integer
cpu_model:
type: string
hostname:
type: string
kernel_version:
type: string
os_name:
type: string
os_version:
type: string
reported_at_unix:
type: integer
total_disk_bytes:
type: integer
total_memory_bytes:
type: integer
uptime_seconds:
type: integer
type: object
github_com_Rain-kl_Wavelet_pkg_protocol.PagesDeploymentHashResponse:
properties:
deployment_id:
type: integer
hash:
type: string
type: object
github_com_Rain-kl_Wavelet_pkg_protocol.PagesProjectLatestHashResponse:
properties:
deployment_id:
type: integer
file_count:
type: integer
hash:
type: string
package_size:
type: integer
project_id:
type: integer
total_size:
type: integer
type: object
github_com_Rain-kl_Wavelet_pkg_protocol.WAFIPGroup:
properties:
checksum:
type: string
enabled:
type: boolean
id:
type: integer
ip_list:
items:
type: string
type: array
name:
type: string
type:
type: string
type: object
handler.batchDownloadRequest:
properties:
ids:
@@ -3433,7 +3433,7 @@ definitions:
type: string
type: array
clickhouse:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_model_analytics.ClickHouseOperationalStats'
$ref: '#/definitions/Wavelet_OpenFlare_plugins_server_model_analytics.ClickHouseOperationalStats'
migration:
description: idle | migrating
type: string
@@ -4357,7 +4357,7 @@ paths:
- $ref: '#/definitions/response.Any'
- properties:
data:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.ChallengeResponse'
$ref: '#/definitions/Wavelet_OpenFlare_plugins_server_cap.ChallengeResponse'
type: object
"500":
description: 内部服务错误
@@ -4388,7 +4388,7 @@ paths:
- $ref: '#/definitions/response.Any'
- properties:
data:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_internal_apps_cap.RedeemResponse'
$ref: '#/definitions/Wavelet_OpenFlare_plugins_server_cap.RedeemResponse'
type: object
"400":
description: 参数错误或核销失败
@@ -7026,7 +7026,7 @@ paths:
- $ref: '#/definitions/response.Any'
- properties:
data:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.PagesDeploymentHashResponse'
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.PagesDeploymentHashResponse'
type: object
"400":
description: 参数错误
@@ -7089,7 +7089,7 @@ paths:
- $ref: '#/definitions/response.Any'
- properties:
data:
$ref: '#/definitions/github_com_Rain-kl_Wavelet_pkg_protocol.PagesProjectLatestHashResponse'
$ref: '#/definitions/Wavelet_OpenFlare_share_protocol.PagesProjectLatestHashResponse'
type: object
"400":
description: 参数错误
@@ -12869,7 +12869,7 @@ paths:
$ref: '#/definitions/oauth.BasicUserInfo'
type: object
"400":
description: 用户名或密码错误、帐号已禁用等
description: 用户名或密码错误
schema:
$ref: '#/definitions/response.Any'
"500":