mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 14:06:36 +08:00
docs: update
This commit is contained in:
+47
-15
@@ -55,6 +55,24 @@ Quick links:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
openflare:
|
||||
image: ghcr.io/rain-kl/openflare-server:latest
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- ./uploads:/app/uploads
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
@@ -63,29 +81,43 @@ services:
|
||||
POSTGRES_USER: openflare
|
||||
POSTGRES_PASSWORD: replace-with-strong-password
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
- ./data/postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
openflare:
|
||||
image: ghcr.io/rain-kl/openflare:latest
|
||||
redis:
|
||||
image: valkey/valkey:8.0-alpine
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
SESSION_SECRET: replace-with-random-string
|
||||
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
|
||||
GIN_MODE: release
|
||||
LOG_LEVEL: info
|
||||
command: ["valkey-server", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- ./data/valkey:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "valkey-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:25.3-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
CLICKHOUSE_DB: openflare
|
||||
CLICKHOUSE_USER: default
|
||||
CLICKHOUSE_PASSWORD: 123456
|
||||
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
volumes:
|
||||
- ./data/clickhouse_data:/var/lib/clickhouse
|
||||
healthcheck:
|
||||
test: ["CMD", "clickhouse-client", "--query", "SELECT 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 15s
|
||||
```
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,501 +0,0 @@
|
||||
# Wavelet 系统性能分析与优化建议
|
||||
|
||||
> 分析日期:2026-06-17
|
||||
> 范围:Go 后端 + Next.js 前端
|
||||
> 目标:识别可能在生产环境真实出现的性能问题,并给出高 ROI 优化路线
|
||||
|
||||
**状态图例**:`✅ 已完成` · `🔶 部分完成` · `⬜ 待做`
|
||||
|
||||
| 修复批次 | 范围 | 状态 |
|
||||
|----------|------|------|
|
||||
| P0 后端 #1–#4 | WebP 锁、文件路径缓存、增量统计、复合索引 | ✅ |
|
||||
| P0 前端 #6–#7 | 认证并行化、日志虚拟化 | ✅ |
|
||||
| P1 #9 | 公共配置 Redis 列表缓存 | ✅ |
|
||||
| P1 参数中心 | 系统配置 Otter RAM 缓存 + 统一失效 + 多节点 pub/sub | ✅ |
|
||||
| P1 CAPTCHA | 运行时配置快照 + 批量加载 + pub/sub 失效 | ✅ |
|
||||
| P0 前端 #12–#19 | dynamic 分割、React Query、登录并行、Tooltip、lazy、barrel 收窄 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [架构概览与核心瓶颈](#架构概览与核心瓶颈)
|
||||
- [Critical — 高概率生产问题](#critical--高概率生产问题)
|
||||
- [Medium — 中等风险](#medium--中等风险)
|
||||
- [高价值优化路线图](#高价值优化路线图)
|
||||
- [已做得好的设计](#已做得好的设计)
|
||||
- [场景风险矩阵](#场景风险矩阵)
|
||||
- [优先行动清单](#优先行动清单)
|
||||
|
||||
---
|
||||
|
||||
## 架构概览与核心瓶颈
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph frontend["前端 (Static Export)"]
|
||||
A[HTML 静态壳] --> B[Hydrate]
|
||||
B --> C["UserProvider.getUserInfo()"]
|
||||
C --> D[页面数据请求]
|
||||
D --> E[渲染]
|
||||
end
|
||||
|
||||
subgraph backend["后端热点路径"]
|
||||
F["/f/{id}?quality=..."] --> G[DB 查 upload]
|
||||
G --> H[迁移状态 DB 查询]
|
||||
H --> I[白名单 Redis/DB]
|
||||
I --> J{WebP 缓存命中?}
|
||||
J -->|否| K["全量读文件 + 编码 + 磁盘缓存(全局锁)"]
|
||||
J -->|是| L[返回]
|
||||
end
|
||||
|
||||
C -.->|已解除阻塞| D
|
||||
```
|
||||
|
||||
**参数中心读路径**(`SystemConfig.GetByKey`):
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
R[业务调用 GetByKey] --> A{RAM 命中?}
|
||||
A -->|是| Z[返回]
|
||||
A -->|否| B{Redis HGET 命中?}
|
||||
B -->|是| C[写入 RAM]
|
||||
C --> Z
|
||||
B -->|否| D[查 PostgreSQL]
|
||||
D --> E[回写 Redis + RAM]
|
||||
E --> Z
|
||||
|
||||
W[管理员 Create/Update] --> F[写 DB]
|
||||
F --> G["InvalidateSystemConfigCache(key)"]
|
||||
G --> H[清本机 RAM + Redis field]
|
||||
G --> I[pub/sub 通知其他节点清 RAM]
|
||||
```
|
||||
|
||||
当前最大的结构性问题(2026-06-17 更新):
|
||||
|
||||
1. **前端**:~~全局认证瀑布流~~ ✅ 已改为 layout 即时渲染 + 子页面 `RequireAuth` 自行处理未登录态;~~Admin 重模块无 `dynamic()` 分割~~ ✅ database/logs/settings 已懒加载子模块。其余路由 `page.tsx` 仍为 `"use client"`(静态导出下 RSC 收益有限,待逐步薄壳化)。
|
||||
2. **后端**:文件服务路径(`/f/{id}`)仍是最高频热点;~~磁盘缓存全局互斥锁~~ ✅ 已改为 `RWMutex` + `singleflight`,但 WebP miss 仍在请求线程内同步编码,部署预热与异步回退原图尚未落地。
|
||||
3. **参数中心**:~~`GetByKey` 每次直打 Redis~~ ✅ 已统一使用底层的进程内缓存库(`pkg/cache/store`),读路径直接为 RAM → DB(无 Redis 数据缓存);管理员写配置后通过 Redis pub/sub 进行广播(`system:config_broadcast`),多节点本地触发全量预热/刷新,实现最终一致性。
|
||||
|
||||
---
|
||||
|
||||
## Critical — 高概率生产问题
|
||||
|
||||
### 1. 图片 WebP 服务:请求路径阻塞 + 全局锁串行化 `🔶 部分完成`
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
- `internal/apps/upload/file_server.go`
|
||||
- `pkg/cache/disk/cache.go`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
缓存未命中时,在 HTTP 请求 goroutine 内执行:
|
||||
|
||||
1. `io.ReadAll` 将原始文件全量读入内存
|
||||
2. 进程内 WebP 解码 + 编码
|
||||
3. 写入磁盘缓存
|
||||
|
||||
同时,磁盘缓存 `Get`/`Set` 使用**全局 `sync.Mutex`**,所有并发图片请求在缓存层完全串行。
|
||||
|
||||
```go
|
||||
// file_server.go — 缓存 miss 时的重操作
|
||||
origBytes, err := getOriginalFileBytes(ctx, upload) // io.ReadAll
|
||||
webpBytes, err = CompressImageToWebP(bytes.NewReader(origBytes), quality)
|
||||
cache.Set(cacheKey, webpBytes, diskcache.NoExpiration)
|
||||
|
||||
// pkg/cache/disk/cache.go — 全局互斥锁
|
||||
func (c *Cache) Get(key string) ([]byte, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 首次访问或缓存淘汰后,P99 延迟从几十毫秒飙升到数秒
|
||||
- 并发图片请求形成「隐形队列」
|
||||
- 大文件全量读入带来内存尖峰,可能触发 OOM 或 GC 停顿
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [x] ✅ 磁盘缓存改用 `RWMutex`,读路径不互斥 — `pkg/cache/disk/cache.go`
|
||||
- [x] ✅ 对同一 cache key 使用 `singleflight` 合并并发 miss — `internal/apps/upload/file_server.go`
|
||||
- [ ] 部署后强制执行 `upload:warm_image_cache` 异步预热任务
|
||||
- [ ] 考虑 miss 时先返回原图,后台异步生成 WebP
|
||||
|
||||
---
|
||||
|
||||
### 2. 文件访问路径:每次请求多次 DB/Redis 查询 `✅ 已完成`
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
- `internal/apps/upload/storage_ops.go`
|
||||
- `internal/apps/upload/file_server.go`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
存储迁移状态**无进程内缓存**,每次文件操作都查询 `w_task_executions`:
|
||||
|
||||
```go
|
||||
// storage_ops.go
|
||||
func StorageReadOnly(ctx context.Context) bool {
|
||||
execution, ok, err := latestStorageMigrationExecution(ctx)
|
||||
// ...
|
||||
}
|
||||
|
||||
func backendForStoredDriver(ctx context.Context, driver storage.Driver) (storage.Backend, error) {
|
||||
// 可能再次调用 currentMigrationTargetConfig → 又一次相同 DB 查询
|
||||
}
|
||||
```
|
||||
|
||||
公开文件白名单每次走 Redis/DB:
|
||||
|
||||
```go
|
||||
// file_server.go
|
||||
func isFilePublic(ctx context.Context, uploadType string) bool {
|
||||
sc.GetByKey(ctx, model.ConfigKeyFileAccessWhitelist)
|
||||
// JSON 解析 + 遍历
|
||||
}
|
||||
```
|
||||
|
||||
对比:`storage.Active()` 已有 5 秒内存缓存 + Redis pub/sub 失效机制,迁移状态却未复用该模式。
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 每个 `/f/{id}` 请求额外 2–4 次 DB/Redis 往返
|
||||
- 图片站/CDN 场景下 QPS 放大后 PostgreSQL 连接池压力明显
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [x] ✅ 为 `StorageReadOnly` / `latestStorageMigrationExecution` 增加 5s TTL 进程内缓存 — `internal/apps/upload/access_cache.go`
|
||||
- [x] ✅ 配置变更或迁移状态变化时通过 Redis pub/sub 失效 — `access_cache.go` + `system_config/routers.go`
|
||||
- [x] ✅ `file_access_whitelist` 增加进程内缓存,复用 `GetByKey` 的失效机制 — `access_cache.go`
|
||||
|
||||
---
|
||||
|
||||
### 3. Admin 文件统计:无界全表扫描 `✅ 已完成`
|
||||
|
||||
**涉及文件**:`internal/apps/upload/stats.go`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
```go
|
||||
err = db.DB(ctx).Model(&model.Upload{}).
|
||||
Select("extension, mime_type, file_size").
|
||||
Where("status != ?", model.UploadStatusDeleted).
|
||||
Scan(&fileRaws).Error
|
||||
// 然后在 Go 中遍历全量结果做分类统计
|
||||
```
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 10 万+ 文件时,管理端「文件统计」接口耗时数秒
|
||||
- 占用数百 MB 内存,可能拖垮 admin API
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [ ] 改为 SQL `GROUP BY` + `CASE WHEN` 聚合(未采用)
|
||||
- [x] ✅ 维护增量统计表,上传/删除时更新计数 — `w_upload_stats` + `stats_counter.go` + `GetFileStats` 读统计表
|
||||
|
||||
---
|
||||
|
||||
### 4. `w_uploads` 索引缺口 `✅ 已完成`
|
||||
|
||||
**涉及文件**:`internal/db/migrator/goose/postgres/202606090001_initial_schema.sql`
|
||||
|
||||
**当前索引**:`user_id`, `file_path`, `hash`, `type`
|
||||
|
||||
**缺失的高频查询索引**:
|
||||
|
||||
| 查询场景 | 建议索引 |
|
||||
|----------|----------|
|
||||
| 清理任务 `status + created_at` | `(status, created_at)` |
|
||||
| 存储迁移 `storage_driver + status` | `(storage_driver, status)` |
|
||||
| 秒传去重 `hash + file_size + status` | `(hash, file_size, status)` |
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 数据量增长后,清理 worker、迁移任务、上传去重退化为顺序扫描
|
||||
- 后台任务积压,admin 操作变慢
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [x] ✅ 通过 goose migration 新增上述复合索引(PostgreSQL + SQLite 双方言)— `202606170001_add_upload_composite_indexes.sql`
|
||||
|
||||
---
|
||||
|
||||
### 5. 批量 ZIP 下载:无上限 + 同步阻塞
|
||||
|
||||
**涉及文件**:`internal/apps/upload/routers.go` — `BatchDownloadFiles`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
- `req.IDs` 无数量上限
|
||||
- 在请求 goroutine 内串行打开每个文件并 `io.Copy` 到 ZIP
|
||||
- 远端 S3 场景下单个文件就可能耗时数秒
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 网关超时、连接耗尽
|
||||
- Admin 批量下载操作卡死
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [ ] 限制单次批量数量(如 max 50)
|
||||
- [ ] 或改为 Asynq 后台任务生成 ZIP,前端轮询下载链接
|
||||
|
||||
---
|
||||
|
||||
### 6. 前端全局认证瀑布流 `✅ 已完成`
|
||||
|
||||
**涉及文件**:
|
||||
|
||||
- `frontend/contexts/user-context.tsx`
|
||||
- `frontend/app/(main)/layout.tsx`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
```tsx
|
||||
// user-context.tsx — 挂载时获取用户
|
||||
useEffect(() => {
|
||||
fetchUser()
|
||||
}, [fetchUser])
|
||||
|
||||
// layout.tsx — 阻塞所有子页面渲染
|
||||
if (loading || !user) {
|
||||
return <LoadingPage text="登录状态" badgeText="Auth" />
|
||||
}
|
||||
```
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 每次进入 `/home`、`/files`、`/admin/*` 都先等 `getUserInfo`(约 200–800ms)
|
||||
- 页面级数据请求无法并行启动,TTI 被硬性拉长
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [x] ✅ Layout 不阻塞渲染,子页面自行处理未登录状态 — `layout.tsx` + `RequireAuth` / `RequireAdminAuth`
|
||||
- [ ] 或 Server Component 通过 cookie 预取 session,消除客户端首屏等待
|
||||
- [x] ✅ `/login`、`/register` 跳过 `getUserInfo` — `user-context.tsx`
|
||||
|
||||
---
|
||||
|
||||
### 7. 实时日志面板:2000 行 DOM 无虚拟化 `✅ 已完成`
|
||||
|
||||
**涉及文件**:`frontend/components/common/admin/app-logs.tsx`
|
||||
|
||||
**问题描述**:
|
||||
|
||||
- 日志上限 2000 行(内存有界,但 DOM 无界)
|
||||
- 每行渲染完整 `<div>`,无虚拟滚动
|
||||
- `@tanstack/react-virtual` 已在 `package.json` 但未使用
|
||||
|
||||
**生产表现**:
|
||||
|
||||
- 管理员开着日志 Tab 时 CPU/内存持续升高
|
||||
- 滚动卡顿,长时间运行拖慢整台机器
|
||||
|
||||
**优化价值**:⭐⭐⭐⭐
|
||||
|
||||
**建议**:
|
||||
|
||||
- [x] ✅ 使用 `useVirtualizer` 只渲染可视区域行 — `app-logs.tsx`
|
||||
- [x] ✅ 行组件 `React.memo` 避免无效重渲染 — `LogLine`
|
||||
|
||||
---
|
||||
|
||||
## Medium — 中等风险
|
||||
|
||||
| # | 问题 | 位置 | 影响 |
|
||||
|---|------|------|------|
|
||||
| 1 | ~~公共配置接口无 Redis 缓存~~ ✅ | `internal/model/system_configs.go` — `ListVisibleSystemConfigs` | ~~每次前端启动/登录直查 PostgreSQL~~ → Redis 列表缓存 + Create/Update 时失效 |
|
||||
| 2 | ~~CAPTCHA 每次 5 次独立 `GetByKey`~~ ✅ | `internal/apps/cap/runtime_settings.go` | ~~登录高峰 5× 配置读取~~ → `CurrentSettings` 快照一次加载 6 个 key,`Generate`/`Redeem`/中间件零 `GetByKey` |
|
||||
| 3 | ~~系统配置单 key 无进程内缓存~~ ✅ | `system_config_cache.go`, `pkg/cache/ram` | ~~热路径重复 Redis HGET~~ → Otter RAM + 写后 `InvalidateSystemConfigCache` + pub/sub |
|
||||
| 4 | OIDC 每次 `oidc.NewProvider` 无缓存 | `internal/apps/oauth/sources.go:164` | 登录发起/回调多一次外部 HTTP |
|
||||
| 5 | CORS 每次跨域查 `server_address` 配置 `🔶` | `internal/router/middlewares.go:75` | 预检请求仍每次调用 `GetByKey`,但 `server_address` 已受益于 RAM 缓存 |
|
||||
| 6 | 推送通知无界 goroutine + 逐 target DB 查询 | `internal/apps/admin/push/events.go:102` | 通知风暴时 goroutine/DB 双压 |
|
||||
| 7 | 上传清理:每文件一个事务 | `internal/apps/upload/cleanup.go` | 大量 pending 文件时 commit 风暴 |
|
||||
| 8 | ClickHouse 风控:每请求 `json.Marshal` 全部 headers | `internal/apps/risk_control/middleware.go:58` | 高 QPS 时 CPU 开销(写入本身已异步批处理) |
|
||||
| 9 | 存储迁移日志大量写 Redis | `internal/apps/upload/storage_migration_task.go` | 迁移期间 Redis CPU/内存压力 |
|
||||
| 10 | 存储迁移后二次 SHA 全量读取验证 | `storage_migration_task.go` | 迁移期间对象 I/O 翻倍 |
|
||||
| 11 | Admin 状态页 5s 轮询 | `frontend/components/common/admin/status.tsx` | Tab 常驻时持续打后端 |
|
||||
| 12 | 路由切换 500ms fade 动画 | `frontend/app/(main)/layout.tsx:53-60` | 即使数据已缓存,感知仍慢 |
|
||||
| 13 | ~~无 `next/dynamic` 代码分割~~ ✅ | `database/`, `logs/`, `settings/` page-client | Admin 重模块拆分为独立 chunk |
|
||||
| 14 | 19/24 个 `page.tsx` 为 `"use client"` `🔶` | 各路由 | database/logs/settings 已薄壳化;其余待迁移 |
|
||||
| 15 | ~~Admin 部分页面用 `useEffect` 而非 React Query~~ ✅ | `access-logs.tsx`, `task-executions.tsx` | 列表/详情走 React Query 缓存去重 |
|
||||
| 16 | ~~登录页 OIDC sources 等待 public config~~ ✅ | `login-form.tsx` | public config 与 auth sources 并行请求 |
|
||||
| 17 | ~~Users 表每行嵌套 3 个 `TooltipProvider`~~ ✅ | `admin/users/page.tsx` | 表格外层单一 Provider |
|
||||
| 18 | ~~缩略图用原生 `<img>` 无 lazy loading~~ ✅ | `file-list.tsx`, `file-manager.tsx` | `loading="lazy"` + `decoding="async"` |
|
||||
| 19 | ~~`@/lib/services` barrel 导入~~ ✅ | 全前端消费侧 | 改为 `@/lib/services/<module>` 直接导入 |
|
||||
| 20 | SQLite 模式无连接池调优 | `internal/db/postgres.go` | 默认 SQLite 写锁瓶颈 |
|
||||
| 21 | Session Redis 仅用第一个地址 | `internal/router/router.go` | Sentinel/Cluster 场景不一致 |
|
||||
|
||||
---
|
||||
|
||||
## 高价值优化路线图
|
||||
|
||||
### P0 — 立即做(1–2 周,收益最大)
|
||||
|
||||
| # | 优化项 | 涉及模块 | 预期收益 | 复杂度 | 状态 |
|
||||
|---|--------|----------|----------|--------|------|
|
||||
| 1 | WebP:`singleflight` + `RWMutex` + 强制预热 | `file_server.go`, `pkg/cache/disk/` | 图片 P99 ↓ 80%+,并发吞吐 ↑ 5–10x | 中 | 🔶 锁与去重已完成,预热待做 |
|
||||
| 2 | 缓存 `StorageReadOnly` / 迁移状态 | `access_cache.go` | 每文件请求减少 1–3 次 DB | 低 | ✅ |
|
||||
| 3 | 内存缓存 `file_access_whitelist` | `access_cache.go` | 每公开文件请求减少 1 次 Redis | 低 | ✅ |
|
||||
| 4 | `GetFileStats` 增量统计表 | `stats.go`, `w_upload_stats` | Admin 统计从 O(n) → O(1) | 低 | ✅ |
|
||||
| 5 | 新增 `w_uploads` 复合索引 | goose migration | 清理/迁移/秒传全面加速 | 低 | ✅ |
|
||||
| 6 | 前端日志虚拟化 | `app-logs.tsx` | Admin 日志 Tab 流畅度质变 | 低 | ✅ |
|
||||
| 7 | Admin 重模块 `dynamic()` 懒加载 | `database/page-client.tsx`, `logs/page-client.tsx`, `settings/page-client.tsx` | 首包 JS ↓ 150–300KB | 低 | ✅ |
|
||||
|
||||
### P1 — 短期(2–4 周)
|
||||
|
||||
| # | 优化项 | 预期收益 | 状态 |
|
||||
|---|--------|----------|------|
|
||||
| 8 | 认证并行化:layout 不阻塞 / Server 预取 session | TTI ↓ 200–800ms | 🔶 客户端并行化已完成,RSC 预取待做 |
|
||||
| 9 | `ListVisibleSystemConfigs` 加 Redis 缓存 | 前端冷启动加速 | ✅ |
|
||||
| 10 | 系统配置 Otter RAM 缓存 + 统一失效 | 热路径 `GetByKey` 零 Redis RTT(命中后) | ✅ |
|
||||
| 11 | CAPTCHA 运行时配置快照 | 验证码路径配置读取 → O(1) 快照 | ✅ |
|
||||
| 12 | OIDC Provider/JWKS 进程内缓存(TTL 1h) | 登录延迟 ↓ 100–500ms | ⬜ |
|
||||
| 13 | 批量下载限制(max 50)或异步任务 | 消除网关超时风险 | ⬜ |
|
||||
| 14 | Admin `useEffect` 数据获取迁移到 React Query | 去重、缓存、后台刷新 | 🔶 access-logs / task-executions 已完成 |
|
||||
| 15 | 登录页并行请求 public config + auth sources | 登录页 ↓ 100–300ms | ✅ |
|
||||
| 16 | 状态轮询在 `document.hidden` 时暂停 | 降低后台 + 客户端负载 | ⬜ |
|
||||
|
||||
### P2 — 中期架构演进
|
||||
|
||||
| # | 优化项 | 预期收益 |
|
||||
|---|--------|----------|
|
||||
| 16 | 批量 ZIP 改为 Asynq 后台任务 | 彻底解耦长耗时操作 |
|
||||
| 17 | 存储迁移日志降噪 + 跳过已验证文件二次 SHA | 迁移期间 Redis/I/O ↓ 50% |
|
||||
| 18 | 推送通知 target 批量解析(`WHERE id IN ?`) | 通知风暴 DB 查询 ↓ N 倍 |
|
||||
| 19 | 上传清理改为批量 UPDATE + 异步存储删除 | 减少 DB commit 频率 |
|
||||
| 20 | 路由动画 0.5s → 0.15s 或纯 CSS | 导航感知速度 ↑ |
|
||||
| 21 | ~~服务导入收窄(直接 import 具体 Service)~~ ✅ | 每路由 bundle ↓ 10–30KB |
|
||||
| 22 | Admin 路由级 `loading.tsx` + Suspense | 渐进式渲染体验 |
|
||||
| 23 | ~~缩略图 `loading="lazy"` + 固定尺寸~~ ✅ | 文件管理页初始 paint 加速 |
|
||||
|
||||
---
|
||||
|
||||
## 已做得好的设计
|
||||
|
||||
以下设计说明团队已有性能意识,优化应在此基础上增量改进,**不必重复造轮子**:
|
||||
|
||||
| # | 设计 | 位置 |
|
||||
|---|------|------|
|
||||
| 1 | 系统配置两层缓存 RAM → DB | `pkg/cache/store`, `system_config_cache.go`, `GetByKey` |
|
||||
| 2 | 系统配置统一刷新 + 多节点 pub/sub 预热广播 | `InvalidateSystemConfigCache`, `InvalidateAllSystemConfigCaches` |
|
||||
| 3 | Storage Backend 单例 + 5s TTL + pub/sub 失效 | `internal/storage/storage.go` — `Active()` |
|
||||
| 4 | 推送事件/渠道 24h Redis 缓存 + GORM hook 失效 | `internal/model/push_event.go`, `push_channel.go` |
|
||||
| 5 | 风控日志异步批写 ClickHouse(1 万缓冲 + 1000 条/1s + 429 背压) | `internal/apps/risk_control/` |
|
||||
| 6 | HTTP 连接池统一(`httppool` + OTel) | `pkg/httppool/` |
|
||||
| 7 | DB/Redis 连接池显式配置 | `config.yaml`, `internal/db/` |
|
||||
| 8 | 游标分批处理(`id > ? LIMIT n`) | `cleanup.go`, image warmup |
|
||||
| 9 | 存储迁移并发上限 `errgroup.SetLimit(10)` | `storage_migration_task.go` |
|
||||
| 10 | 邮件/推送走 Asynq,不在 HTTP 路径同步发送 | `user/logics.go`, `push/events.go` |
|
||||
| 11 | 文件服务 ETag/304 + 原图 `DataFromReader` 流式返回 | `file_server.go` |
|
||||
| 12 | 无 GORM `Preload` 滥用 | 全项目 |
|
||||
| 13 | 前端 API 请求去重(`pendingRequests` Map) | `frontend/lib/services/core/api-client.ts` |
|
||||
| 14 | React Query 全局 30s `staleTime` | `frontend/components/providers/query-provider.tsx` |
|
||||
| 15 | React Compiler 已启用 | `frontend/next.config.ts` |
|
||||
| 16 | 读副本支持(`dbresolver`) | `internal/db/postgres.go` |
|
||||
| 17 | 任务执行日志 Redis 缓冲 + 批量回写 | `internal/model/task_execution.go` |
|
||||
| 18 | 公共配置列表 Redis 缓存 + 写后失效 | `ListVisibleSystemConfigs`, `InvalidateVisibleSystemConfigsCache` |
|
||||
| 19 | 上传文件统计增量表 `w_upload_stats` | `stats_counter.go`, 上传/删除 hook |
|
||||
| 20 | 文件访问路径进程内缓存 + pub/sub | `internal/apps/upload/access_cache.go` |
|
||||
| 21 | 磁盘缓存读路径 `RWMutex` + WebP `singleflight` | `pkg/cache/disk/cache.go`, `file_server.go` |
|
||||
| 22 | 前端认证非阻塞 + 页面级鉴权 | `use-auth-redirect.ts`, `require-auth.tsx` |
|
||||
| 23 | Admin 实时日志虚拟滚动 | `frontend/components/common/admin/app-logs.tsx` |
|
||||
| 24 | CAPTCHA 运行时配置快照 + 批量加载 | `runtime_settings.go`, `ListSystemConfigsByKeys` |
|
||||
|
||||
---
|
||||
|
||||
## 场景风险矩阵
|
||||
|
||||
| 场景 | 最可能爆的点 | 对应优先级 |
|
||||
|------|-------------|-----------|
|
||||
| 图片站 / 公开相册 | WebP miss(锁/白名单已优化) | P0 #1 预热待做 |
|
||||
| 文件量 10 万+ | 清理慢(统计/索引已优化) | P2 #19 清理批量化 |
|
||||
| 管理端日常使用 | ~~大 bundle~~(dynamic 分割 + barrel 收窄已落地) | P2 #22 路由 loading.tsx |
|
||||
| 存储迁移进行中 | Redis 日志风暴 | P2 #17 |
|
||||
| 登录高峰 | OIDC discovery 无缓存 | P1 #12 OIDC |
|
||||
| 多租户 / 跨域前端 | CORS 仍每次调 `GetByKey`(`server_address` 已 RAM 缓存) | 可选 CORS 快照 |
|
||||
| 参数热更新 | 多节点 RAM 一致性 | ✅ `system:config_invalidation` pub/sub |
|
||||
| 批量文件操作 | ZIP 同步打包无上限 | P0 #5, P1 #12 |
|
||||
|
||||
---
|
||||
|
||||
## 优先行动清单
|
||||
|
||||
如果只选 **3 件事** 先做(预计用户感知延迟降低 50–70%):
|
||||
|
||||
1. ~~**WebP 路径解耦**~~ ✅ `singleflight` + `RWMutex` 已落地;**下一步**:部署后预热 + miss 异步回退原图
|
||||
2. ~~**文件路径查询缓存**~~ ✅ 迁移状态 + 白名单进程内缓存已落地
|
||||
3. ~~**前端认证与首屏并行化**~~ ✅ 全局 auth gate 已移除;~~Admin `dynamic()` 代码分割~~ ✅ 已落地;**下一步**:其余 Admin 路由薄壳化 + `loading.tsx`
|
||||
|
||||
### 实施检查清单
|
||||
|
||||
```
|
||||
P0 后端
|
||||
[x] disk cache RWMutex + singleflight ✅ 2026-06-17
|
||||
[x] StorageReadOnly 5s 缓存 + pub/sub 失效 ✅ 2026-06-17
|
||||
[x] file_access_whitelist 进程内缓存 ✅ 2026-06-17
|
||||
[x] GetFileStats 增量统计表 (w_upload_stats) ✅ 2026-06-17
|
||||
[x] w_uploads 复合索引 migration ✅ 2026-06-17
|
||||
[ ] 批量下载数量上限
|
||||
[ ] WebP 部署预热 + miss 异步回退原图
|
||||
|
||||
P0 前端
|
||||
[x] app-logs.tsx 虚拟滚动 ✅ 2026-06-17
|
||||
[x] SQLConsole / Settings Tabs / Logs Tabs dynamic import ✅ 2026-06-17
|
||||
[x] 认证 gate 并行化 ✅ 2026-06-17
|
||||
[x] 登录页 public config + auth sources 并行 ✅ 2026-06-17
|
||||
[x] access-logs / task-executions → React Query ✅ 2026-06-17
|
||||
[x] Users TooltipProvider 合并 ✅ 2026-06-17
|
||||
[x] 缩略图 loading="lazy" ✅ 2026-06-17
|
||||
[x] @/lib/services barrel 导入收窄 ✅ 2026-06-17
|
||||
|
||||
P1
|
||||
[x] ListVisibleSystemConfigs Redis 缓存 ✅ 2026-06-17
|
||||
[x] 系统配置 Otter RAM 缓存 + 统一失效 + pub/sub ✅ 2026-06-17
|
||||
[x] CAPTCHA 运行时配置快照 ✅ 2026-06-17
|
||||
[ ] OIDC Provider 缓存
|
||||
[ ] Admin useEffect → React Query 统一(database overview 等待)
|
||||
[ ] 状态轮询 visibility 感知
|
||||
[ ] Server Component session 预取
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录:关键代码路径索引
|
||||
|
||||
| 路径 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 图片服务 | `internal/apps/upload/file_server.go` | `/f/{id}` 热点 |
|
||||
| 磁盘缓存 | `pkg/cache/disk/cache.go` | ✅ RWMutex 读路径 |
|
||||
| 迁移/白名单缓存 | `internal/apps/upload/access_cache.go` | ✅ 5s TTL + pub/sub |
|
||||
| 文件统计 | `internal/apps/upload/stats.go` | ✅ 读 `w_upload_stats` |
|
||||
| 公共配置列表 | `internal/model/system_configs.go` | ✅ Redis 列表缓存 |
|
||||
| RAM 缓存封装 | `pkg/cache/ram/cache.go` | ✅ Otter v2 薄封装 |
|
||||
| 系统配置缓存 | `internal/model/system_config_cache.go` | ✅ RAM + 失效 + pub/sub |
|
||||
| 参数失效 API | `InvalidateSystemConfigCache` | ✅ 清 RAM + Redis field |
|
||||
| CAPTCHA 快照 | `internal/apps/cap/runtime_settings.go` | ✅ `CurrentSettings` + pub/sub |
|
||||
| 批量下载 | `internal/apps/upload/routers.go` | 同步 ZIP |
|
||||
| 上传索引 | `internal/db/migrator/goose/*202606170001*.sql` | ✅ 复合索引已加 |
|
||||
| 认证 gate | `frontend/app/(main)/layout.tsx` | ✅ 即时渲染 + `useAuthRedirect` |
|
||||
| 页面鉴权 | `frontend/components/auth/require-auth.tsx` | ✅ 子页面按需拦截 |
|
||||
| 用户上下文 | `frontend/contexts/user-context.tsx` | ✅ 登录/注册页跳过 fetch |
|
||||
| 实时日志 | `frontend/components/common/admin/app-logs.tsx` | ✅ `useVirtualizer` |
|
||||
| API 去重 | `frontend/lib/services/core/api-client.ts` | 已有,可复用模式 |
|
||||
+15
-102
@@ -11,115 +11,28 @@ sidebar: false
|
||||
## 重大变更
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 2.3.2 开始使用 JWT_SECRET 环境变量替代 SESSION_SECRET 进行管理端 API 的 JWT 签名密钥管理。SESSION_SECRET 将会在之后的版本中逐步废弃,请务必尽快迁移到 JWT_SECRET。
|
||||
> 3.0.0 版本为 Wavelet 平台迁移与架构重构版本,涉及数据库表结构、环境变量以及前后端底层架构的重大变更。请务必在升级前备份数据库,并且更新到 V2.3.4。
|
||||
> 目前已知的兼容性问题:
|
||||
> - Pages 无法迁移, 升级前请先手动下载并备份 Pages 静态站点的 ZIP 包,升级后重新创建。
|
||||
> - 性能调优参数重置, 升级后请重新配置
|
||||
|
||||
## [unreleased]
|
||||
|
||||
## [Unreleased]
|
||||
## [v3.0.0] - 2026-06-27
|
||||
|
||||
### 新增
|
||||
### 升级与迁移注意事项
|
||||
|
||||
- 新增可配置的 FRPS 内置 WebUI 开关和监听端口。在数据库 w_system_configs 中新增 `relay_frps_web_ui_enabled` 与 `relay_frps_web_ui_port`,支持通过后台系统设置页进行图形化管理与动态同步至 Relay 节点。
|
||||
- 将 TLS 证书续签逻辑接入 Asynq 异步任务框架。新增单证书续期任务 `of_ssl_single_renew`(`openflare:ssl_single_renew`),支持在管理后台查看每步的申请状态和详细日志,并提供失败重试能力。
|
||||
> [!WARNING]
|
||||
> 本次重构涉及数据库表结构以及环境变量的重大变更,老版本务必从 v2.3.4 最新版本升级迁移,否则可能导致数据库结构不兼容或管理端 API 无法访问。
|
||||
> 升级前务必备份数据库
|
||||
|
||||
### 修复
|
||||
### 重大重构说明
|
||||
|
||||
- 修复修改 WAF 规则、IP 组或站点信息后,在配置版本预览与发布页面可能误判定“无配置差异”而无法直接发布的问题:在前端 `hasConfigDiff` 差异检测函数中补齐对 WAF 配置变更状态 `waf_config_changed`,以及 `added_sites`、`removed_sites`、`modified_sites` 变化的检查,避免其禁用“确认发布”按钮。
|
||||
- 修复由于重构移除系统配置 Redis L2 缓存层后,遗留的系统配置缓存测试用例仍检查 Redis 物理键值导致测试失败的问题:改写测试为验证 L1 RAM 缓存行为,并在失效操作(Invalidate)后引入适当延迟以消除本地 Redis 广播异步被消费带来的测试竞态问题。
|
||||
- 修复数据库历史迁移代码在重构中丢失了 `tableExistsSQL` 和 `tablesWithPrefixSQL` 辅助函数定义,导致 `internal/db/migrator` 包和 `internal/cmd` 包编译失败的问题:在 `migrator.go` 底部重新实现并补齐了这俩函数的跨数据库方言支持。
|
||||
- 修复 Agent 包 IP 探测测试中,由于包级别缓存变量 `cachedIP` 跨用例污染导致 `TestLoadFallsBackToLocalIPWhenOutboundLookupFails` 最终获取到上个测试的 cached IP 从而报错失败的问题:在 `nodeip` 包中增加并导出 `ResetCacheForTest` 函数以在测试 Setup/Teardown 中清除缓存状态。
|
||||
本项目近期完成了**前后端底层架构的重大迁移与重构**,将原有的独立控制端重构为基于 **Wavelet 统一开发框架** 的全新架构:
|
||||
- **后端重构**:全面接入 Wavelet 服务平台,收敛并复用了标准的用户管理、安全验证(PoW/邮件验证)、RAM L1 缓存以及 Redis 订阅发布同步机制。配置体系从原 `of_options` 物理表完全迁移合并至标准系统配置框架 `w_system_configs`(类型归为 `business` 业务级配置),废弃原进程级 `OptionMap` 热重载。
|
||||
- **前端重构**:管理后台前端使用 Next.js App Router、TypeScript 与 Tailwind CSS(基于 shadcn/ui 组件库与 Wavelet 设计风格)进行了完全重写,提供了更具呼吸感和一致性的用户界面,优化了配置版本预览与发布体验。
|
||||
- **架构解耦**:将原有“站点 (Site)”配置体系拆分为 **「网站管理 -> 域名列表」**(处理域名与证书绑定)与 **「规则管理」**(处理反向代理、静态托管、WAF 和缓存等路由匹配规则)两个维度,极大地提升了复杂拓扑配置的灵活性。内网穿透隧道也统一作为 `tunnel_client` 类型节点整合进了 **「节点管理」** 中。
|
||||
|
||||
- 修复旧版本迁移升级后,发布版本报错“版本号生成冲突,请重试”的问题。根本原因是配置版本表 `of_config_versions` 的自增主键 `id` 序列与导入的旧数据冲突;现重构配置版本表,将自增 `id` 移除,改由版本号字符串(如 `20260626-003`)直接作为主键(通过迁移 `202606270001_make_version_primary_key` 完成),并同步修改 Agent 和 Flared 模块中的排序及查询条件,解决删除 `id` 列后心跳上报报 `column "id" does not exist` 的故障。
|
||||
- 修复代理路由详情页点击“发布配置”时,同时弹出配置差异对话框和确认发布对话框导致重叠的问题:点击发布时不再展示配置差异,直接进行确认发布。
|
||||
- 修复配置版本发布到 Agent 后 `openresty -t` 因 `proxy_cache_path` 使用 `/var/cache/openresty` 导致非 root 用户 `mkdir` 失败的问题:发布快照与渲染将 `/var/` 下路径规范为 `__OPENFLARE_PROXY_CACHE_PATH__`,Agent 应用时落地为 `data_dir/var/cache/openflare_proxy` 并兼容重写已发布配置中的旧路径。
|
||||
- 修复配置版本发布到 Agent 后 `openresty -t` 因证书私钥无法解析而失败的问题。根因是发布快照生成 `certs/{id}.key` 时直接写入库内加密的 `KeyPEM`(`enc:v1:`),未解密为 PEM;现与证书详情接口一致,发布前通过 `OpenKeyPEM` 解密后再下发。
|
||||
- 修复 `/api/v1/d/option` 批量更新 OpenResty 等业务配置不生效的问题。根本原因是 option 模块在读写时做了 PascalCase 与 snake_case 的机械转换(如 `OpenRestyEventsUse` → `open_resty_events_use`),与 `w_system_configs` 中实际 key(`openresty_events_use`)不一致,更新写入了错误的幽灵配置行。现改为 API 直接使用与数据库一致的 snake_case key,并同步更新前端性能调优与运维设置页。
|
||||
- 修复 PostgreSQL 数据库执行迁移时报 `duplicate key value violates unique constraint "goose_db_version_pkey"` 导致迁移中断的问题。根本原因:`goose_db_version.id` 自增序列落后于表内 `MAX(id)`(常见于从 dump 恢复或历史迁移以显式 id 复制版本记录后),goose 记录新版本号时自增 id 与既有行冲突。修复方式:在 `goose.Up` 前对 PostgreSQL 执行 `setval` 重新对齐 `goose_db_version` 的 id 序列。
|
||||
- 修复 openflared(Tunnel Client)WebSocket 连接在 Cloudflare 代理环境下频繁收到 EOF 断连的问题。根本原因:服务端 `read_pump` 仅在收到 WebSocket 协议层 Pong 帧时刷新读超时,而客户端(`golang.org/x/net/websocket`)以 JSON 应用层 `{"type":"pong"}` 响应 ping,服务端 90s 读超时到期后主动关闭连接,客户端收到 EOF 并进入无限重连循环。修复方式:在 `clientPongType` 分支中同步调用 `conn.SetReadDeadline` 刷新超时。
|
||||
- 修复 openflared frpc 子进程异常退出(`exit status 1`)时缺乏详细诊断信息的问题。现捕获 frpc stderr 并在进程退出时将其输出记录到结构化日志 `stderr` 字段,便于排查配置格式错误、Auth Token 鉴权失败、relay 端不可达等具体原因。
|
||||
- 修复 Relay 节点启动时在双栈网络环境可能上报 IPv6 地址,导致 Tunnel frpc 客户端无法连接 frps 的问题。强化 `pkg/geoip.HTTPOutboundIPStrategy` 在回退到双栈客户端后仍优先返回 IPv4 地址,确保 Relay 心跳上报的 IP 与 frpc 连接兼容。
|
||||
- 修复 WAF 黑白名单判定时,白名单作为严格准入控制导致黑名单逻辑失效的问题。现将白名单逻辑调整为信任放行(Bypass/Allow),命中的请求直接放行,未命中的请求继续进入黑名单等防护模块判定。
|
||||
- 修复 WAF IP 组手动编辑和配置版本发布后,未向 Agent 触发 WebSocket 实时广播导致配置变更不能即时生效的问题。
|
||||
|
||||
### 变更
|
||||
|
||||
- 将 OpenFlare 配置体系从独立的 `of_options` 表统一迁移至标准系统配置框架 `w_system_configs`(SystemConfig),全部归类为业务类型(`type=business`)。涵盖 Agent(心跳间隔、发现令牌、更新仓库等)、UptimeKuma 集成、GeoIP、数据库自动清理及全部 OpenResty 主配置项共 48 项。业务代码统一改为通过 `repository.GetSystemConfigByKey`/`GetBoolByKey`/`GetIntByKey` 读取,移除进程级内存快照 `OptionMap` 与启动时热重载机制,配置变更经 Redis 缓存失效实现动态生效。已存在的同义配置(如 `password_login_enabled`、`smtp_host`)不重复迁移,旧系统遗留的 `SystemName`、`Footer`、`HomePageLink`、`About` 等无用项一并清理;公开状态接口 `/api/v1/d/status` 相应移除 `system_name`、`home_page_link`、`footer_html` 字段。数据迁移完成后通过 goose 迁移 `202606220005` 删除遗留的 `of_options` 表。
|
||||
- 优化并统一 `agent`、`relay`、`flared` 的 IP 探测与上报逻辑,均复用公用 `nodeip` 包;在未指定 `node_ip` 时实现心跳 Tick 动态探测上报。
|
||||
- 优化 `pkg/geoip.GetOutboundIP` 出口 IP 探测策略,优先通过 `tcp4` 建立 HTTP 连接以获得 IPv4 公网地址,并在纯 IPv6/无 IPv4 路由环境下自动降级为双栈 `tcp` 握手。
|
||||
- 优化 `agent` 系统指纹缓存算法,计算指纹时排除 `UptimeSeconds` 和 `ReportedAtUnix` 动态字段,防止周期心跳时不断触发冗余完整的系统 Profile 数据上报。
|
||||
|
||||
- 彻底移除废弃的 GitHub OAuth 和微信登录相关遗留设置项(包括 `GitHubOAuthEnabled`、`GitHubClientId`、`GitHubClientSecret`、`WeChatAuthEnabled` 等),从公开状态接口 `/api/v1/d/status` 移除这些字段的返回。
|
||||
|
||||
- 前端路由调整:将 TLS 证书和 DNS 账号的路由地址移出 `/websites`(分别变更为顶级路由 `/certificates` 和 `/dns-accounts`),将 WAF IP 组的路由地址移出 `/waf`(变更为顶级路由 `/ip-groups`)。
|
||||
|
||||
- 前端页面鉴权改为默认私域:除 `/login`、`/register`、`/callback` 外,未登录访问任意页面(含数据看板 `/`)均重定向至登录页。
|
||||
|
||||
- 重构优化:收敛 `flared` 和 `relay` 客户端模块中重复声明的 `APIResponse` 结构体,统一通过类型别名复用 `pkg/protocol.APIResponse`。
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复由于生成的 Docker 安装/部署命令硬编码拉取 `:latest` 镜像,导致使用 v3 新版协议的控制端与 v2 旧版协议的 Relay 节点无法通信的问题。前端改用动态获取当前控制端版本(serverVersion)并自动拉取与当前控制端匹配的 `:beta` 或具体版本镜像。
|
||||
|
||||
- 修复点击「打开 FRPS WebUI」跳转到 `about:blank#blocked`:在 Relay 节点详情页的 WebUI 磁贴卡片中增加展示具体 URL 地址,并提供一键复制按钮。解决因 Chrome 浏览器限制从公网安全源(HTTPS)跨域直接访问本地/私有网络 HTTP 端口(Private Network Access 限制)而导致新页面打开被拦截的问题。
|
||||
|
||||
- 修复 Docker 部署模式下 Agent 无法自更新:修改 Docker 启动脚本 `agent-entrypoint.sh`,在降权前将二进制文件所在目录 `/usr/local/bin` 及二进制文件自身的属主赋予 `openflare`,解决容器内自更新写入时报 `permission denied` 的问题。
|
||||
|
||||
- 修复 Agent 升级版本比对逻辑:使用统一的 `pkg/utils.CompareVersions` 对比版本,正确处理预览/预发布版本(如 `v3.0.0-beta` 升级到 `v3.0.0-beta.1`),避免升级按钮非预期禁用的问题。
|
||||
|
||||
- 修复 Agent 以 `openflare` 非 root 运行时 OpenResty `-t`/reload 失败:nginx `pid` 与 `client_body_temp`/`proxy_temp` 等临时目录改写入 `data_dir/var/run` 与 `data_dir/var/cache/nginx`(`__OPENFLARE_PID_PATH__` / `__OPENFLARE_NGINX_CACHE_DIR__` 占位符),不再使用 OpenResty 安装目录下不可写路径。
|
||||
|
||||
- 修复 OpenResty 响应泄露版本号:默认主配置模板与 safe fallback 模板补充 `server_tokens off;`,隐藏 `Server` 头与错误页中的 nginx/OpenResty 版本信息。
|
||||
|
||||
- 修复 Agent 与 OpenResty worker 权限不一致导致 Pages/WAF 等静态资源 Permission denied:引入共享运行时用户 `openflare`(Agent 进程、OpenResty worker、文件属主统一);Docker 入口脚本在启动前修正 volume 属主并降权;本地 systemd 服务以 `openflare` 运行并授予 `CAP_NET_BIND_SERVICE`;`data_dir` 与 `pages_dir` 等路径在同步/Apply 时统一 `chown` 与 `0755/0644` 规范化。
|
||||
|
||||
- 修复 Pages 站点根路径 `/` 访问异常:OpenResty 渲染增加 `location = /` 精确匹配;未启用 SPA Fallback 时直接提供入口文件(`index` 指令在 `try_files ... =404` 场景下不生效);启用 SPA Fallback 时避免 `try_files $uri $uri/ /index.html` 因 `$uri/` 命中站点根目录触发内部重定向循环而返回 500。
|
||||
|
||||
- 修复代理路由详情认证配置 Tab:移除 PoW 配置(PoW 仅在 WAF 规则组中设置);保留 Basic Auth 保存能力;移除页头重复的「保存当前分区」按钮。
|
||||
|
||||
- 修复 Pages 路由发布失败并报 `pages module is not available`:配置快照发布流程补齐 Pages 项目激活部署解析与 `pages_deployment` 写入。
|
||||
|
||||
- 修复仪表盘与节点详情「24 小时网络趋势」误按速率展示:改为 OpenResty 入/出站小时流量与近 24 小时总量摘要,Y 轴与 tooltip 自动换算 B/KB/MB/GB。
|
||||
|
||||
- 修复 Pages 上传或节点同步时报 `pages file size out of bounds`:允许 ZIP 包内的 0 字节文件,并兼容未声明解压大小的 ZIP 条目。
|
||||
|
||||
- 修复 Agent 在 OpenResty 配置 checksum 已一致时跳过 Pages 部署包下载,导致 `deployments/{id}/releases` 为空、站点文件未落地:在 state 中缓存 Pages 部署引用;周期同步通过 `GET /api/v1/agent/pages/deployments/:id/hash` 对比 upload SHA-256,仅在哈希变化或本地 release 未就绪时下载 ZIP,避免重复拉取完整配置与部署包。
|
||||
|
||||
- 收敛 Pages 部署包读取路径:`upload` 域新增 `GetActiveUpload` / `OpenStoredUpload` / `ActiveUploadHash` / `ResolveLocalFile` / `IngestFromLocalPath` 门面;遗留 `artifact_path` 回填与本地路径解析迁入 upload 域;`OpenDeploymentPackage` 改为返回 `DeploymentPackage`(`io.ReadCloser` + 元数据),不再向 Agent Handler 泄漏 `storage.Object`。
|
||||
|
||||
- 修复节点详情 OpenResty 连接数与吞吐显示为「—」:节点可观测 API 将 OpenResty 观测数据合并进 `metric_snapshots`;指标文案改为「请求/分钟」(近 60 秒窗口),连接数为 0 时正常显示 0。
|
||||
|
||||
- 修复仪表盘「24 小时请求趋势」摘要误显示当前小时请求量/错误量:改为汇总近 24 小时总量。
|
||||
|
||||
- 修复 Pages 部署包上传报「请求超时,请稍后重试」:上传请求使用独立 10 分钟超时(覆盖默认 15 秒),并在大文件上传完成后提示服务端处理中。
|
||||
|
||||
- 修复应用日志异常膨胀:Agent 配置同步加锁避免并发重复上报,成功且版本/checksum 未变时跳过重复 apply 日志;Server 入库前对相同成功记录去重;Flared 配置未变更时不再上报 apply 日志。
|
||||
|
||||
- 修复应用日志页「清空」无效果:原按钮仅重置筛选;新增「清空日志」入口并对接 `/api/v1/d/apply-logs/cleanup`,支持确认后删除全部记录。
|
||||
|
||||
- 配置版本列表按 `created_at` 倒序展示,最新发布版本固定显示在列表顶部。
|
||||
|
||||
- 修复 WAF 规则组保存/绑定网站时报 `of_waf_rule_group_bindings_pkey` 冲突:PostgreSQL 在迁移导入显式 ID 后同步绑定表序列,并在写入前自动校正序列。
|
||||
|
||||
- 修复 PostgreSQL 启动迁移失败:`of_waf_rule_group_bindings` 序列表为空时 `setval(0)` 越界,改为空表重置为 1、有数据时对齐 `MAX(id)`。
|
||||
|
||||
- 修复 WAF 规则组 PoW 策略发布后边缘不生效:统一 WAF 绑定站点名与 OpenResty 路由 `site_name` 解析逻辑,并为所有已启用网站生成 `site_rule_groups` 条目(含仅依赖全局规则组的站点)。
|
||||
|
||||
- 修复 WAF PoW 已启用但挑战页不弹出:`pow_enabled=true` 且 `pow_config` 为空时补齐默认配置写入 `waf_config.json`,OpenResty 按全局+已绑定规则组解析 PoW 注入,Lua 对空配置使用运行时默认值。
|
||||
|
||||
- 修复 Agent 使用 volume 映射时 PoW/WAF 运行时配置无法加载:OpenResty worker(`nobody`)对 `0700` 父目录无法遍历导致 `waf_config.json` 虽为 `0644` 仍不可读;Apply 后强制修正 `data_dir` 至运行时目录链为 `0755`,PoW Lua 在不可读时输出 WARN。
|
||||
|
||||
- 收敛子代理站点标识双轨逻辑:新增 `routeidentity` 统一包,`proxy_route`、`config_version`、`uptimekuma`、`flared` 与 OpenResty 渲染共用 `ResolveSiteName` / `DecodeDomains`;移除废弃 `RenderPoWConfig`;PoW Lua 与 WAF 一致仅依赖 `$openflare_waf_site`。
|
||||
|
||||
- 修复全球态势板在仅有 `geo_name`(如 mmdb 的 Germany)而无经纬度时误用美国 fallback 坐标的问题;按国家名/ISO 匹配地图质心。
|
||||
|
||||
- 修复 Agent 心跳上报公网 IP 后节点地理位置未自动更新:进程启动时按 `GeoIPProvider` 初始化 `pkg/geoip`,`mmdb` 模式从内置 GeoLite2 种子到 `data/`,并在 Relay 心跳同步地理位置。
|
||||
|
||||
- 修复 Agent 启动时 Pages 部署包下载失败:Pages 部署包统一下载走 upload 文件存储框架,部署记录持久化 `upload_id`,legacy `artifact_path` 仅用于一次性回填 upload。
|
||||
|
||||
- 修复登录 Cap 人机验证:前端 `cap-solver` 与 Cap 路由测试对齐 `b3a55d4` 之后的统一 API 信封 `{ error_msg, data }`,避免 `challenge` 解构失败。
|
||||
|
||||
- 修复配置版本快照/发布预览侧栏无法滚动:内容区改为 `flex-1 min-h-0 overflow-y-auto`,与项目内其他可滚动 Sheet 布局一致。
|
||||
|
||||
- 修复 Agent CI/Docker 构建:将 `GeoLite2-Country.mmdb` 提交至仓库作为兜底,构建前优先尝试 `scripts/fetch-agent-geoip-mmdb.sh` 拉取最新库,远程失败时回退使用已提交文件。
|
||||
|
||||
## [v2.3.4] - 2026-06-17
|
||||
|
||||
|
||||
+2
-2
@@ -72,6 +72,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] {
|
||||
items: [
|
||||
{ text: '概览', link: '' },
|
||||
{ text: '快速开始', link: 'quick-start' },
|
||||
{ text: 'TLS 证书与自动续期', link: 'certificates' },
|
||||
{ text: '新建反代配置', link: 'proxy-config' },
|
||||
{ text: 'Pages 静态托管使用', link: 'pages-usage' },
|
||||
{ text: '内网穿透与隧道使用', link: 'tunnel-usage' },
|
||||
@@ -94,8 +95,7 @@ function sidebarReference(): DefaultTheme.SidebarItem[] {
|
||||
items: [
|
||||
{ text: '概览', link: '' },
|
||||
{ text: '配置项', link: 'configuration' },
|
||||
{ text: '命令与脚本', link: 'cli' },
|
||||
{ text: 'API 约定', link: 'api' }
|
||||
{ text: '命令与脚本', link: 'cli' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -135,7 +135,10 @@ docker run -d --name openflare-agent --restart unless-stopped \
|
||||
> **非 Root 安全加固运行**
|
||||
> Agent 容器内部已完成安全加固,在启动后会统一以低权限非 root 用户 `openflare` 运行。
|
||||
> 容器已内置了 `cap_net_bind_service` 内核能力,使得低权限进程依然能够正常监听宿主机的 `80` 和 `443` 特权端口。
|
||||
> 同时,OpenResty 运行时所需的各种临时路径(包括 PID 路径、各类临时缓存目录如 `client_body_temp_path`、`proxy_temp_path` 等)都由 Agent 控制器动态渲染并自动重定向至挂载的 `/data` 数据目录,彻底避免在非 root 权限运行时写入默认系统路径而导致的权限拒绝错误(Permission Denied)。
|
||||
> 同时,OpenResty 运行时所需的各种临时路径(包括 PID 路径、各类临时缓存目录如 `client_body_temp_path`、`proxy_temp_path` 等)都由 Agent 控制器动态渲染并自动重定向至容器内的 `/data` 目录,彻底避免在非 root 权限运行时写入默认系统路径而导致的权限拒绝错误(Permission Denied)。
|
||||
> 具体物理缓存写入路径为:
|
||||
> * 临时缓存目录:`/data/var/cache/nginx`
|
||||
> * 代理缓存目录:`/data/var/cache/openflare_proxy`
|
||||
|
||||
## 启动与验证
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ go run main.go all
|
||||
Docker 部署是 Agent 推荐的部署方式。Docker 部署时直接运行 Agent 镜像,该镜像基于 OpenResty 镜像制作,内置 Agent 控制器与 OpenResty 二进制。未显式配置 `node_ip` 时,Agent 会优先通过第三方 API 获取真实出口 IP,避免把 Docker 网桥地址登记为节点 IP。
|
||||
|
||||
> [!NOTE]
|
||||
> Agent 镜像已完成非 Root 安全加固,统一以普通用户 `openflare` 权限运行,通过内核 capabilities 授权(`cap_net_bind_service`)监听 80/443 特权端口,并自动重定向临时文件和 PID 路径至挂载数据卷以防止写入冲突。
|
||||
> Agent 镜像已完成非 Root 安全加固,统一以普通用户 `openflare` 权限运行,通过内核 capabilities 授权(`cap_net_bind_service`)监听 80/443 特权端口,并自动重定向临时文件和 PID 路径至容器内 `/data` 目录以防止写入冲突。
|
||||
|
||||
挂载配置文件:
|
||||
|
||||
@@ -127,7 +127,6 @@ docker pull ghcr.io/rain-kl/openflare-agent:latest
|
||||
docker rm -f openflare-agent 2>/dev/null || true
|
||||
docker run -d --name openflare-agent --restart unless-stopped \
|
||||
-p 80:80 -p 443:443/tcp -p 443:443/udp \
|
||||
-v openflare-agent-data:/data \
|
||||
-v ./agent.json:/etc/openflare/agent.json:ro \
|
||||
ghcr.io/rain-kl/openflare-agent:latest
|
||||
```
|
||||
|
||||
@@ -50,15 +50,21 @@ docker rm -f openflare-relay 2>/dev/null || true
|
||||
|
||||
docker run -d --name openflare-relay --restart unless-stopped \
|
||||
-p 7000:7000 \
|
||||
-p 17500:17500 \
|
||||
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
|
||||
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
|
||||
-v openflare-relay-data:/var/lib/openflare-relay \
|
||||
-v openflare-relay-data:/app/data \
|
||||
ghcr.io/rain-kl/openflare-relay:latest
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> 这里的 `-p 7000:7000` 映射的是 `frpc` 客户端连接中继的端口。如果管理端配置了自定义的 `relay_bind_port`,请对应修改宿主机端口映射。
|
||||
|
||||
> [!NOTE]
|
||||
> **开启内嵌 frps Web UI**:
|
||||
> 如果在 Server 控制端开启了中继流量监控面板(即数据库/系统设置中的 `relay_frps_web_ui_enabled` 设为 `true`),你需要将 Web 端口(默认是 `17500`,由系统设置中的 `relay_frps_web_ui_port` 控制)也通过 `-p 17500:17500` 映射到宿主机。
|
||||
> 登录 Web UI 时的用户名固定为 `admin`,密码为当前中继节点的 `agent_token`。
|
||||
|
||||
---
|
||||
|
||||
## 宿主机手动运行
|
||||
|
||||
@@ -41,9 +41,9 @@ services:
|
||||
DB_ENABLED: "false" # 禁用 PostgreSQL,自动启用内置 SQLite 后备
|
||||
SQLITE_PATH: "/data/openflare.db"
|
||||
REDIS_ENABLED: "true"
|
||||
REDIS_ADDRS: "redis:6379"
|
||||
REDIS_ADDR: "redis:6379"
|
||||
CLICKHOUSE_ENABLED: "true"
|
||||
CLICKHOUSE_HOSTS: "clickhouse:9000"
|
||||
CLICKHOUSE_HOST: "clickhouse:9000"
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -17,15 +17,4 @@ docker compose up
|
||||
|
||||
## Agent 升级
|
||||
|
||||
Agent 可以随意升级,升级后会在下次心跳时自动拉取最新配置。升级方式:
|
||||
|
||||
```
|
||||
docker pull ghcr.io/rain-kl/openflare-agent:beta
|
||||
docker rm -f openflare-agent 2>/dev/null || true
|
||||
docker run -d --name openflare-agent --restart unless-stopped \
|
||||
-p 80:80 -p 443:443/tcp -p 443:443/udp \
|
||||
-e OPENFLARE_SERVER_URL=<OPENFLARE_SERVER_URL> \
|
||||
-e OPENFLARE_AGENT_TOKEN=<OPENFLARE_AGENT_TOKEN> \
|
||||
ghcr.io/rain-kl/openflare-agent:beta
|
||||
|
||||
```
|
||||
Agent 是完全无状态的,升级时直接拉取最新镜像重建容器即可。具体部署命令与安装方式请参考 **[接入 Agent](./agent.md)**。
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
# 边缘运行时重构设计
|
||||
|
||||
你会学到:Agent、Relay、OpenFlared 三组件的重复代码如何收敛到 `internal/apps/edge/`,以及后续演进路线。
|
||||
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
三类边缘守护进程共享同一运行时骨架:
|
||||
|
||||
```text
|
||||
配置加载 → HTTP/WS 客户端 → 定时心跳 →(可选)配置同步 → 自更新 → 信号优雅退出
|
||||
```
|
||||
|
||||
重构前,以下模块在三个组件间近乎复制粘贴:
|
||||
|
||||
| 模块 | 重复度 |
|
||||
| --- | --- |
|
||||
| `updater/` + `restart_{unix,windows}.go` | ~98% |
|
||||
| `httpclient` 传输层 (`do/postJSON/getJSON`) | ~90% |
|
||||
| `tryAutoUpdate` | ~98% |
|
||||
| `detectNodeIP` | ~95% |
|
||||
| `relay/flared runner` WS 重连环 | ~85% |
|
||||
| `parseLevel`(main 内联) | 100% |
|
||||
|
||||
Agent 额外包含 nginx 栈、geoip、观测缓冲等**领域特有**逻辑,不宜强行合并。
|
||||
|
||||
---
|
||||
|
||||
## 共享包结构
|
||||
|
||||
```
|
||||
internal/apps/edge/
|
||||
├── logging/ # Setup、ParseLevel
|
||||
├── nodeip/ # Detect、DetectLocal(可注入 LookupOutboundIP)
|
||||
├── httpclient/ # 基础 HTTP 客户端(鉴权头可配置)
|
||||
├── wsclient/ # 基础 WebSocket 客户端(组件 Preset 驱动 HeaderKey + WSPath)
|
||||
├── updater/ # GitHub Release 自更新 + 二进制替换重启
|
||||
├── heartbeat/ # TryAutoUpdate 统一入口
|
||||
└── runner/ # WS 重连循环、SleepContext
|
||||
```
|
||||
|
||||
### 组件层保留
|
||||
|
||||
各组件仅保留**薄包装**与**领域逻辑**:
|
||||
|
||||
| 组件 | 保留模块 |
|
||||
| --- | --- |
|
||||
| Agent | `nginx/`、`sync/`(OpenResty)、`geoipupdate/`、`agent/runner`(discovery/WS 混合) |
|
||||
| Relay | `frps/`、`observability/` |
|
||||
| Flared | `frpc/`、`sync/`(tunnel) |
|
||||
|
||||
各组件 `updater/`、`httpclient/`、`wsclient/` 变为类型别名 + `New()` 工厂函数。
|
||||
|
||||
---
|
||||
|
||||
## API 约定
|
||||
|
||||
### 自更新
|
||||
|
||||
```go
|
||||
edgeupdater.New(edgeupdater.Config{
|
||||
LocalVersion: config.Version,
|
||||
AssetPrefix: "openflare-agent", // relay: openflare-relay, flared: openflared
|
||||
LogLabel: "agent",
|
||||
})
|
||||
```
|
||||
|
||||
### HTTP 客户端
|
||||
|
||||
```go
|
||||
edgehttp.New(baseURL, token, timeout, "X-Agent-Token") // Agent/Relay
|
||||
edgehttp.New(baseURL, token, timeout, "X-Tunnel-Token") // Flared
|
||||
```
|
||||
|
||||
### WebSocket 客户端
|
||||
|
||||
```go
|
||||
edgews.New(edgews.PresetAgent, baseURL, token, timeout) // HeaderKey=X-Agent-Token, /api/v1/agent/ws
|
||||
edgews.New(edgews.PresetRelay, baseURL, token, timeout) // HeaderKey=X-Agent-Token, /api/v1/relay/ws
|
||||
edgews.New(edgews.PresetFlared, baseURL, token, timeout) // HeaderKey=X-Tunnel-Token, /api/v1/tunnel/ws
|
||||
```
|
||||
|
||||
### 节点 IP 探测
|
||||
|
||||
```go
|
||||
nodeip.Detect() // outbound → local 回退
|
||||
```
|
||||
|
||||
测试可通过替换 `nodeip.LookupOutboundIP` / `nodeip.LookupLocalIP` 注入桩。
|
||||
|
||||
---
|
||||
|
||||
## 已完成(Phase 0–2)
|
||||
|
||||
- [x] `edge/updater` — 三组件 updater 收敛(删除 ~1100 行重复)
|
||||
- [x] `edge/logging` — relay/flared main 统一日志初始化
|
||||
- [x] `edge/nodeip` — 删除三处 detectNodeIP 重复
|
||||
- [x] `edge/httpclient` — 三组件 HTTP 传输层收敛
|
||||
- [x] `edge/heartbeat/autoupdate` — tryAutoUpdate 统一
|
||||
- [x] `edge/runner` — relay/flared WS 重连环收敛
|
||||
|
||||
---
|
||||
|
||||
## 已完成(Phase 3 Batch 1)
|
||||
|
||||
- [x] `edge/config/duration.go` — MillisecondDuration 三处合并(含 MarshalJSON)
|
||||
- [x] `edge/observability/linux.go` — agent/relay collector 底层 Linux 指标采集收敛
|
||||
- [x] `edge/heartbeat/loop.go` — relay/flared 心跳 ticker 循环统一
|
||||
|
||||
## 已完成(Phase 3 Batch 2)
|
||||
|
||||
- [x] `heartbeat/cycle.go` — Agent HTTP 心跳周期从 runner 下沉(payload 构建、同步、自动更新)
|
||||
- [x] `pkg/protocol/agent.go` — Agent 客户端协议类型迁入公共包,`internal/apps/agent/protocol` 保留别名 re-export
|
||||
|
||||
## 已完成(Phase 3 Batch 3)
|
||||
|
||||
- [x] `edge/wsclient` — Agent/Relay/Flared WebSocket 传输层收敛(Preset 配置表 + `AgentConnection` 适配 `protocol.WebSocketConnection`)
|
||||
- [x] Server 侧协议统一 — `internal/apps/openflare/{agent,relay,flared}` 心跳/观测类型改为 `pkg/protocol` 别名
|
||||
|
||||
## 可选后续
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
|
||||
---
|
||||
|
||||
## 迁移原则
|
||||
|
||||
1. **领域逻辑不下沉**:nginx/frps/frpc/sync 核心业务保留在各自组件。
|
||||
2. **鉴权头显式传入**:禁止 httpclient 默认 Token Header,避免 Agent/Tunnel 混用。
|
||||
3. **小步 PR**:每阶段独立可测,自更新路径需集成验证。
|
||||
4. **测试随包迁移**:updater 测试已迁至 `edge/updater/`。
|
||||
@@ -190,4 +190,4 @@ OpenFlare 已收敛为**单 monorepo**(Go 模块 `github.com/Rain-kl/Wavelet`
|
||||
* 发布、同步、回滚与 Agent 模型变化:更新 [Agent 与发布模型](./agent-design.md)。
|
||||
* 部署方式变化:更新 [部署说明](../deployment/deployment.md) 与 README。
|
||||
* 配置项变化:更新 [配置项参考](../reference/configuration.md)。
|
||||
* 边缘组件共享运行时重构:更新 [边缘运行时重构](./edge-runtime-refactor.md)。
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ sequenceDiagram
|
||||
Server->>Server: Middleware (CapAuth) 校验并消费 X-Cap-Token
|
||||
alt token 合法且未过期且未被消费
|
||||
Server->>Server: c.Next() -> 执行常规登录逻辑 (密码 Bcrypt 校验)
|
||||
Server->>Browser: 返回登录成功 (JWT session)
|
||||
Server->>Browser: 返回登录成功 (Session Cookie)
|
||||
else token 无效或已被消费
|
||||
Server->>Browser: 拦截并返回验证码错误 (401 Unauthorized)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# TLS 证书与自动续期
|
||||
|
||||
本指南介绍如何在 OpenFlare 中管理 TLS 证书。为了使用 HTTPS 安全加密流量,你需要配置对应的证书。OpenFlare 支持**手动导入已有证书**以及**通过 ACME 自动申请与托管续期**。
|
||||
|
||||
---
|
||||
|
||||
## 方式一:手动导入已有证书
|
||||
|
||||
如果你已经从第三方服务商(如腾讯云、阿里云等)申请了免费或收费的证书,或者在本地生成了自签名证书:
|
||||
|
||||
1. 登录管理端控制面板,进入左侧导航 **「网站管理」->「TLS证书」** 页面。
|
||||
2. 点击右上角的 **「导入证书」**。
|
||||
3. 填写配置信息:
|
||||
* **证书名称**:输入一个易于识别的别名(如 `my-domain-cert`)。
|
||||
* **证书内容 (PEM)**:复制并粘贴 PEM 格式 of 证书公钥内容(通常以 `-----BEGIN CERTIFICATE-----` 开头)。
|
||||
* **证书私钥 (KEY)**:复制并粘贴证书的私钥内容(通常以 `-----BEGIN PRIVATE KEY-----` 或 `-----BEGIN RSA PRIVATE KEY-----` 开头)。
|
||||
4. 点击 **「保存」**。导入成功后,该证书即可在配置域名时直接绑定使用。
|
||||
|
||||
---
|
||||
|
||||
## 方式二:自动申请与到期自动续签 (ACME)
|
||||
|
||||
OpenFlare 内置了 ACME 客户端并对接了 **Asynq 异步任务队列**。通过配合云解析服务商的 DNS API,系统能自动完成 DNS-01 挑战(Challenge)校验,并向 CA(默认 Let's Encrypt)申请通配符/单域名证书,并在**到期前 30 天自动触发后台秒级续签**。
|
||||
|
||||
### 第一步:在 Cloudflare 申请 DNS API Token
|
||||
|
||||
为了使 OpenFlare 能够自动在你的域名下添加 TXT 记录以完成 DNS 校验,你需要准备一个具有特定权限的 Cloudflare API Token。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 安全起见,**强烈建议使用限定权限的 API Token**,而非全局 API Key (Global API Key)。
|
||||
|
||||
1. 登录 [Cloudflare 控制台](https://dash.cloudflare.com/)。
|
||||
2. 点击右上角的用户头像,选择 **「我的个人资料 (My Profile)」**。
|
||||
3. 在左侧菜单中选择 **「API 令牌 (API Tokens)」**,然后点击 **「创建令牌 (Create Token)」**。
|
||||
4. 找到 **「编辑区域 DNS (Edit Zone DNS)」** 模板,点击 **「使用模板 (Use template)」**。
|
||||
5. 配置令牌权限与范围(保持默认或根据实际情况限定):
|
||||
* **权限 (Permissions)**:
|
||||
* `区域 (Zone)` - `DNS` - `编辑 (Edit)` (必须,ACME 写入 TXT 记录用)
|
||||
* `区域 (Zone)` - `区域 (Zone)` - `读取 (Read)` (必须,用于列出和检索区域 ID)
|
||||
* **区域资源 (Zone Resources)**:
|
||||
* 选择 **「包括 (Include)」** -> **「所有区域 (All zones)」**,或者选择 **「特定区域 (Specific zone)」** 并指向你托管的特定域名。
|
||||
6. 点击 **「继续以转到摘要 (Continue to summary)」**,确认无误后点击 **「创建令牌 (Create Token)」**。
|
||||
7. 复制生成的 **API 令牌 (Token)** 字符串。*注意:该令牌仅展示一次,请妥善保存*。
|
||||
|
||||
### 第二步:在控制端添加 DNS 账号
|
||||
|
||||
1. 登录 OpenFlare 管理端,进入左侧导航 **「网站管理」->「DNS账号」**。
|
||||
2. 点击 **「添加账号」**。
|
||||
3. 填写配置信息:
|
||||
* **账号名称**:如 `cloudflare-main`。
|
||||
* **DNS 服务商**:选择 `Cloudflare`。
|
||||
* **API Token**:填入刚刚在 Cloudflare 复制的 API 令牌(该值在入库时会自动加密存储,保障安全)。
|
||||
4. 点击 **「保存」**。
|
||||
|
||||
### 第三步:提交证书申请任务
|
||||
|
||||
1. 进入左侧导航 **「网站管理」->「TLS证书」**,点击右上角 **「申请证书」**。
|
||||
2. 在申请表单中填写:
|
||||
* **证书名称**:自定义名称(如 `wildcard-example-cert`)。
|
||||
* **主域名**:你申请的主域名(支持通配符,如 `example.com` 或 `*.example.com`)。
|
||||
* **关联域名**:如有多个,在此处追加(支持通配符,多个域名间用英文逗号分隔)。
|
||||
* **DNS 账号**:在下拉列表中选择刚才添加的 DNS 账号(如 `cloudflare-main`)。
|
||||
3. 点击 **「保存并申请」**。
|
||||
|
||||
### 第四步:查看申请进度与续期状态
|
||||
|
||||
- **查看实时进度**:保存后,系统会向 Asynq 队列投递单证书续期/申请任务(`of_ssl_single_renew`)。你可以进入管理后台的任务或节点日志页面,实时查看每一步(添加 TXT 记录、DNS 记录全球生效探测、ACME 验证、证书颁发落地等)的详细日志。
|
||||
- **自动续期**:所有通过 ACME 申请的证书都会被系统自动托管。后台的 Scheduler 每日会自动扫描证书有效期,在到期前 30 天自动通过异步任务触发续签,无需任何手动维护。
|
||||
@@ -23,14 +23,17 @@ OpenFlare 的发布链路以“不可变配置版本”为核心。你在管理
|
||||
|
||||
为了快速验证,我们首先部署一个最基础的 HTTP 反代站点:
|
||||
|
||||
1. 登录控制面板,进入 **「网站配置」**,点击 **「创建网站」**。
|
||||
2. 填写最基础的站点配置:
|
||||
* **网站名称**:输入简易标识(如 `first-app`)。
|
||||
* **域名 (Domains)**:输入用于测试的域名(如 `first.example.com`)。**第一项默认作为主域名**。
|
||||
3. 配置上游源站(Upstream):
|
||||
* **源站类型**:选择「标准反代」。
|
||||
* **源站地址**:勾选手动输入并填入后端服务地址(如 `http://10.0.0.10:8080` 或测试专用的 `http://httpbin.org`)。
|
||||
4. 点击保存,完成网站创建。
|
||||
1. 登录控制面板,进入左侧导航 **「网站管理」->「域名列表」**,点击 **「新增网站」**。
|
||||
2. 填写域名配置:
|
||||
* **域名**:输入用于测试的域名(如 `first.example.com`)。
|
||||
* **绑定证书**:选择不绑定证书(作为 HTTP 快速验证)。
|
||||
* 点击保存,完成网站登记。
|
||||
3. 进入左侧导航 **「规则管理」**,点击 **「新增规则」**:
|
||||
* **规则名称**:输入简易标识(如 `first-app-route`)。
|
||||
* **域名匹配**:填入你的测试域名(如 `first.example.com`)。
|
||||
* 在下方 **「反向代理」** 选项卡中,配置 **源站类型** 为「标准反代」 (Direct)。
|
||||
* **上游地址**:填写后端服务地址(如测试专用的 `http://httpbin.org`)。
|
||||
* 点击保存创建规则。
|
||||
|
||||
> [!TIP]
|
||||
> **关于 HTTPS 与证书准备**
|
||||
|
||||
@@ -26,7 +26,7 @@ OpenFlare Pages 提供受 Cloudflare Pages 启发的 **Direct Upload (直接上
|
||||
|
||||
## 第一步:上传部署包与创建 Pages 项目
|
||||
|
||||
1. 登录管理端控制面板,进入左侧导航 **「静态托管 (Pages)」**,点击 **「创建项目」**。
|
||||
1. 登录管理端控制面板,进入左侧导航 **「Pages」** 菜单,点击 **「创建项目」**。
|
||||
2. 填写项目基本信息:
|
||||
* **项目名称**:业务名称(如 `我的前端应用`)。
|
||||
* **项目标识 (Slug)**:URL 友好的唯一英文标识(如 `my-react-app`),将作为存储目录的文件夹名。
|
||||
@@ -71,10 +71,10 @@ OpenFlare Pages 提供受 Cloudflare Pages 启发的 **Direct Upload (直接上
|
||||
|
||||
Pages 项目配置并上传好部署包后,需要绑定到对外公开的域名上才能被访客访问。
|
||||
|
||||
1. 导航至左侧菜单 **「网站配置」**,创建或编辑一个代理站点。
|
||||
2. 在「路由规则」中修改或添加一条路由:
|
||||
* **源站类型 (Upstream Type)**:选择 **「Pages 静态托管」**。
|
||||
* **绑定 Pages 项目**:选择你刚才创建的项目,并指定要激活的部署版本(默认会自动关联最新上传成功的部署)。
|
||||
1. 导航至左侧菜单 **「规则管理」**,创建或编辑一条代理规则。
|
||||
2. 切换到 **「反向代理」** 选项卡:
|
||||
* **源站类型**:选择 **「Pages」**。
|
||||
* **选择 Pages 项目**:选择你刚才创建的项目,并关联要激活的部署版本(默认会自动关联最新上传成功的部署)。
|
||||
3. 点击右上角 **「配置预览」** -> 确认无误后点击 **「发布并激活」**。
|
||||
|
||||
## 运维与回滚
|
||||
|
||||
+13
-29
@@ -16,24 +16,11 @@
|
||||
|
||||
---
|
||||
|
||||
## 第一步:证书准备(导入与申请)
|
||||
## 第一步:证书准备
|
||||
|
||||
在使用 HTTPS 安全加密流量前,你需要先配置对应的 TLS 证书。OpenFlare 支持以下两种证书获取方式:
|
||||
在使用 HTTPS 安全加密流量前,你需要先准备好对应的 TLS 证书(支持手动导入已有证书,或通过 DNS 验证自动向 CA 申请并托管续期)。
|
||||
|
||||
### 1. 手动导入已有证书
|
||||
如果你已经有第三方的证书(如腾讯云、阿里云申请的免费/收费证书,或者自签证书):
|
||||
1. 导航至左侧菜单 **「证书管理」**,点击 **「导入证书」**。
|
||||
2. 填入证书名称(如 `my-domain-cert`)。
|
||||
3. 复制并粘贴你的 **证书内容 (PEM 格式公钥)** 以及 **证书私钥 (KEY 格式)**,点击保存。
|
||||
|
||||
### 2. 通过 ACME 协议自动申请
|
||||
OpenFlare 集成了 ACME 客户端,支持自动向 Let's Encrypt 申请并到期续签证书:
|
||||
1. **添加 ACME 账户**:进入「证书管理」->「ACME 账户」->「创建账户」,填入你的联系邮箱。
|
||||
2. **添加 DNS 账户 (用于 DNS-01 验证)**:进入「证书管理」->「DNS 账户」->「创建账户」,选择你的 DNS 托管商(当前仅 Cloudflare)并填入 API Token 凭证。
|
||||
3. **申请证书**:在「证书管理」中点击「申请证书」:
|
||||
* 选择配置好的 ACME 账户和 DNS 账户。
|
||||
* 输入需要托管证书的域名(支持通配符,如 `*.example.com`)。
|
||||
* 点击申请,系统将自动配置 DNS 挑战码并向 CA 申请证书,且会在到期前 30 天自动触发续期。
|
||||
为了保持反代配置指南的简洁,证书相关的详细操作(包括如何在 Cloudflare 申请专用 DNS API Token)已独立拆分为专属指南。请先前往 **[TLS 证书与自动续期](./certificates.md)** 完成证书准备,然后回到这里继续下一步。
|
||||
|
||||
---
|
||||
|
||||
@@ -41,7 +28,7 @@ OpenFlare 集成了 ACME 客户端,支持自动向 Let's Encrypt 申请并到
|
||||
|
||||
源站(Origin)代表被代理的后端真实服务地址。虽然在新建网站时可以直接填写 IP,但推荐先在源站库中进行注册,以便后续复用与维护:
|
||||
|
||||
1. 进入左侧导航 **「源站管理」**,点击 **「创建源站」**。
|
||||
1. 进入左侧导航 **「网站管理」->「源站地址」**,点击 **「创建源站」**。
|
||||
2. 填写源站名称(如 `production-api`)。
|
||||
3. 填入合法的上游地址(如 `http://10.0.0.10:8080`),点击保存。
|
||||
|
||||
@@ -51,18 +38,15 @@ OpenFlare 集成了 ACME 客户端,支持自动向 Let's Encrypt 申请并到
|
||||
|
||||
证书和源站就绪后,即可创建核心网站代理路由:
|
||||
|
||||
1. 进入左侧导航 **「网站配置」**,点击 **「创建网站」**。
|
||||
2. 填写网站基本配置:
|
||||
* **网站名称**:业务唯一标识(如 `app-portal`)。
|
||||
* **域名 (Domains)**:输入该站点绑定的域名列表。**第一项将自动视为主域名**。
|
||||
3. 配置上游源站(Upstream):
|
||||
* **源站类型**:选择「标准反代」。
|
||||
* **源站地址**:从下拉框中选择第二步创建的源站;或者勾选手动输入并填入 `http://10.0.0.20:9000`。
|
||||
4. **绑定证书启用 HTTPS**:
|
||||
* 在域名列表中,点击域名旁边的配置按钮或 HTTPS 切换开关。
|
||||
* 勾选「启用 HTTPS」,并从证书下拉列表中选择第一步准备好的证书。
|
||||
* *注意:未绑定证书的域名只会保留 80 端口 HTTP 服务,不会被写入 443 端口代理中。*
|
||||
5. 点击保存创建配置。
|
||||
1. 进入左侧导航 **「网站管理」->「域名列表」**,点击 **「新增网站」**:
|
||||
* **域名**:输入该站点绑定的域名。
|
||||
* **绑定证书**:选择第一步准备或申请好的证书。
|
||||
2. 配置请求路由规则:进入 **「规则管理」** 页面,点击 **「新增规则」** 或编辑已有规则:
|
||||
* **规则名称**:输入规则的唯一简易标识(如 `app-portal-route`)。
|
||||
* **域名匹配**:填入对应的域名(支持通配符或精确域名,需与上面登记的域名一致)。
|
||||
* 在下方 **「反向代理」** 选项卡下,选择源站类型为 **「标准反代」**。
|
||||
* **源站选择**:从下拉框中选择第二步创建的源站;或者选择手动输入并填入 `http://10.0.0.20:9000`。
|
||||
3. 点击保存创建配置。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -152,8 +152,8 @@ Agent 可以用两类凭证接入:
|
||||
|
||||
在管理端准备其中一种凭证后,进入下一步。
|
||||
|
||||
- **`discovery_token`** 获取菜单路径:「系统设置」->「自动注册」
|
||||
- **`agent_token`** 获取菜单路径:「节点管理」->「新增节点」
|
||||
- **`discovery_token`** 获取菜单路径:「系统设置」 (Settings) -> 「OpenFlare」选项卡 -> 「自动注册」凭证
|
||||
- **`agent_token`** 获取菜单路径:在「节点管理」中创建节点后,点击进入节点详情页即可查看到对应的专属 Token。
|
||||
|
||||
---
|
||||
|
||||
@@ -170,7 +170,6 @@ docker pull ghcr.io/rain-kl/openflare-agent:latest
|
||||
docker rm -f openflare-agent 2>/dev/null || true
|
||||
docker run -d --name openflare-agent --restart unless-stopped \
|
||||
-p 80:80 -p 443:443/tcp -p 443:443/udp \
|
||||
-v openflare-agent-data:/data \
|
||||
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
|
||||
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
|
||||
ghcr.io/rain-kl/openflare-agent:latest
|
||||
|
||||
+2
-2
@@ -45,7 +45,7 @@ https://openflare.example.com/oauth/company-oidc
|
||||
2. `Homepage URL` 填写 OpenFlare 访问地址。
|
||||
3. `Authorization callback URL` 填写 OpenFlare 显示的回调地址,例如 `https://openflare.example.com/oauth/github`。
|
||||
4. 复制 GitHub 提供的 Client ID 和 Client Secret。
|
||||
5. 登录 OpenFlare 管理端,进入“设置 -> 系统设置 -> 配置认证源”。
|
||||
5. 登录 OpenFlare 管理端,进入左侧导航 **「系统设置」** (Settings),选择 **「安全设置」** 选项卡,在 **「认证源管理」** 栏目中进行配置。
|
||||
6. 新增认证源,类型选择 `GitHub`。
|
||||
7. 填写认证源名称、展示名称、Client ID、Client Secret。
|
||||
8. Scope 默认使用 `user:email`,通常无需修改。
|
||||
@@ -60,7 +60,7 @@ https://openflare.example.com/oauth/company-oidc
|
||||
3. Redirect URI / Callback URL 填写 OpenFlare 显示的回调地址,例如 `https://openflare.example.com/oauth/company-oidc`。
|
||||
4. 复制 Client ID 和 Client Secret。
|
||||
5. 获取 Provider 的 Discovery URL,通常以 `/.well-known/openid-configuration` 结尾。
|
||||
6. 登录 OpenFlare 管理端,进入“设置 -> 系统设置 -> 配置认证源”。
|
||||
6. 登录 OpenFlare 管理端,进入左侧导航 **「系统设置」** (Settings),选择 **「安全设置」** 选项卡,在 **「认证源管理」** 栏目中进行配置。
|
||||
7. 新增认证源,类型选择 `OIDC`。
|
||||
8. 填写认证源名称、展示名称、Client ID、Client Secret、OIDC Discovery URL。
|
||||
9. Scope 默认使用 `openid profile email`。如果 Provider 限制了 scope,请按 Provider 允许的值调整。
|
||||
|
||||
@@ -64,7 +64,7 @@ curl -I http://127.0.0.1:3000
|
||||
2. 如果是源码运行,确认已经构建前端静态产物:
|
||||
|
||||
```bash
|
||||
cd openflare-server/web
|
||||
cd frontend
|
||||
pnpm build
|
||||
```
|
||||
|
||||
@@ -73,7 +73,7 @@ pnpm build
|
||||
4. 如果通过前端开发服务器访问,确认后端代理地址:
|
||||
|
||||
```bash
|
||||
cd openflare-server/web
|
||||
cd frontend
|
||||
NEXT_DEV_BACKEND_URL=http://127.0.0.1:3000 pnpm dev
|
||||
```
|
||||
|
||||
@@ -85,8 +85,8 @@ NEXT_DEV_BACKEND_URL=http://127.0.0.1:3000 pnpm dev
|
||||
|
||||
1. 确认连接的是预期数据库,避免 `SQLITE_PATH` 或 `DSN` 指向了另一个环境。
|
||||
2. 查看 Server 日志中使用的是 `sqlite` 还是 `postgres`。
|
||||
3. 在浏览器开发者工具中确认管理端 API 请求携带 `OPENFLARE_TOKEN` 请求头。
|
||||
4. 清理浏览器本地存储中的旧 `openflare_token` 后重新登录。
|
||||
3. 在浏览器开发者工具中确认管理端 API 请求已正确携带 Session Cookie。
|
||||
4. 清理浏览器缓存及 Cookie 后重新登录。
|
||||
|
||||
### 应急重置管理员密码
|
||||
|
||||
@@ -220,7 +220,7 @@ curl -Iv https://your-domain
|
||||
执行:
|
||||
|
||||
```bash
|
||||
cd openflare-server/web
|
||||
cd frontend
|
||||
corepack enable
|
||||
pnpm install
|
||||
pnpm lint
|
||||
|
||||
+18
-22
@@ -12,12 +12,10 @@ OpenFlare 提供了**基于反向中继穿透隧道**的整体解决方案。你
|
||||
|
||||
在使用内网穿透功能前,你需要熟悉以下组件与核心概念:
|
||||
|
||||
| 概念 | 说明 | 对应组件/操作 |
|
||||
| --- | --- | --- |
|
||||
| **中继节点 (Relay)** | 部署在公网边缘的流量中继服务,负责监听内网客户端的长连接,并作为网关 Agent (OpenResty) 与内网流量的中转桥梁。 | 运行 `openflare-relay` 守护的 `tunnel_relay` 节点 |
|
||||
| **穿透隧道 (Tunnel)** | 逻辑上的穿透客户端实例,拥有全局唯一 ID 与安全认证令牌,用以标识一个具体的内网环境。 | 由 Server 随机生成 `tunnel_id` (tun-<32hex>) |
|
||||
| **穿透隧道 (Tunnel)** | 逻辑上的穿透客户端实例,拥有全局唯一 ID 与安全认证令牌,用以标识一个具体的内网环境。 | 在「节点管理」中创建的 `tunnel_client` 节点,分配专属 Tunnel Token |
|
||||
| **隧道客户端 (Client)** | 运行在内网环境下的轻量控制器,根据 Server 下发的配置自动管理底层的 frpc 隧道子进程。 | 内网部署的 `openflared` 容器或独立二进制进程 |
|
||||
| **隧道上游 (Tunnel Upstream)** | 网站配置中的特殊上游类型。选择此类型后,网关会将公网流量转发至本地中继端的 Vhost 端口,最终送达内网源站。 | 网站详情中配置的 `tunnel` 类型上游 |
|
||||
| **隧道上游 (Tunnel Upstream)** | 路由规则中的特殊反代类型。选择此类型后,网关会将公网流量转发至本地中继端的 Vhost 端口,最终送达内网源站。 | 在「规则管理」详情页中配置的反向代理类型,选择源站类型为「内网穿透」并绑定对应 Tunnel 节点 |
|
||||
|
||||
---
|
||||
|
||||
@@ -26,10 +24,10 @@ OpenFlare 提供了**基于反向中继穿透隧道**的整体解决方案。你
|
||||
将一个内网服务发布到公网,推荐按这个顺序进行:
|
||||
|
||||
1. 注册并部署至少一个公网 **中继节点 (Relay)** 并保持在线。
|
||||
2. 在管理端创建 **穿透隧道 (Tunnel)** 并复制对应的专属 Token。
|
||||
2. 进入 **「节点管理」**,新建一个类型为 **Tunnel 节点 (tunnel_client)** 的节点,获取专属 Token。
|
||||
3. 在内网服务器中部署并启动 **隧道客户端 (OpenFlared)**。
|
||||
4. 确认管理端中该隧道的在线状态显示为「在线」。
|
||||
5. 新增网站配置,上游类型选择 **内网穿透**,绑定对应隧道并填写内网端口(如 `127.0.0.1:8080`)。
|
||||
4. 确认管理端中该 Tunnel 节点的状态显示为「在线」。
|
||||
5. 在 **「规则管理」** 页面新增或编辑规则,在「反向代理」选项卡中选择源站类型为 **「内网穿透」**,绑定对应 Tunnel 节点并填写内网服务端口(如 `127.0.0.1:8080`)。
|
||||
6. 发布并激活新版本。
|
||||
7. 通过公网域名访问,验证内网穿透链路是否打通。
|
||||
|
||||
@@ -58,14 +56,12 @@ OpenFlare 提供了**基于反向中继穿透隧道**的整体解决方案。你
|
||||
> [!IMPORTANT]
|
||||
> 请务必在云服务器安全组中放行 `7000` 端口(frpc 客户端连接控制端口)。如果你的 Server 与中继节点部署在同一台机器,这里的 `OPENFLARE_SERVER_URL` 应指向 Server 的公网或内网通信 IP。
|
||||
|
||||
### 第二步:在管理端创建穿透隧道
|
||||
### 第二步:在管理端创建 Tunnel 节点
|
||||
|
||||
1. 导航至管理侧边栏的 **「内网穿透」** 页面。
|
||||
2. 点击 **「创建隧道」** 按钮,在弹窗中填写:
|
||||
* **隧道名称**:描述此内网环境,例如 `home-lab` 或 `office-dev`。
|
||||
* **描述**:可选填,描述此隧道的具体用途。
|
||||
3. 点击保存后,系统将自动生成该隧道的全局唯一 ID 与一串专属的 `tunnel_token`(形如 `tun-xxxx...`)。
|
||||
4. 复制弹窗中为你生成的 **客户端部署命令**,用于下一步内网环境的部署。
|
||||
1. 导航至管理侧边栏的 **「节点管理」** 页面。
|
||||
2. 点击 **「新增节点」** 按钮,在弹窗中选择节点类型为 **「Tunnel 节点 (tunnel_client)」**。
|
||||
3. 填入节点名称与描述,点击保存。
|
||||
4. 在节点列表中点击进入刚才创建的 Tunnel 节点详情页,你可以找到专属的 **Tunnel Token** 及相应的客户端一键部署命令。
|
||||
|
||||
### 第三步:部署内网客户端 (OpenFlared)
|
||||
|
||||
@@ -104,19 +100,19 @@ docker run -d --name openflared --restart unless-stopped \
|
||||
#### 状态确认
|
||||
|
||||
启动成功后,内网客户端会通过出向网络向控制面发送心跳同步配置。此时:
|
||||
1. 刷新管理端的 **「内网穿透」** 列表,刚才创建的隧道状态指示灯应当变为绿色的 **「在线」**。
|
||||
2. 点击隧道详情,你可以直观地查看到当前内网客户端连接了公网的哪些中继 Relay 节点。
|
||||
1. 刷新管理端的 **「节点管理」** 列表,刚才创建的 Tunnel 节点状态指示灯应当变为绿色的 **「在线」**。
|
||||
2. 点击节点进入详情页,你可以直观地查看到当前内网客户端连接了公网的哪些中继 Relay 节点。
|
||||
|
||||
### 第四步:创建网站并绑定隧道上游
|
||||
### 第四步:配置请求路由并绑定隧道上游
|
||||
|
||||
现在你可以为你的内网服务配置公网反向代理和域名访问了。
|
||||
|
||||
1. 进入 **「网站配置」** 页面,点击 **「新建网站」**。
|
||||
2. 填写公网访问该网站所需的 **域名**,例如 `nas.example.com`。
|
||||
3. 关键配置:在 **「上游配置」** 区域,将 **上游类型** 从默认的「直连」切换为 **「内网穿透」**。
|
||||
4. 在下拉列表中选择你刚刚部署上线的 **内网隧道**(如 `home-lab`)。
|
||||
1. 首先进入 **「网站管理」->「域名列表」** 录入你想要公开访问的域名。
|
||||
2. 进入 **「规则管理」** 页面,点击 **「新增规则」** 或编辑已有规则。
|
||||
3. 在下方 **「反向代理」** 选项卡下,将 **源站类型** 切换为 **「内网穿透」**。
|
||||
4. 从下拉列表中选择刚才部署在线的 **Tunnel 节点**。
|
||||
5. 填写 **内网目标地址**(对于内网客户端来说可访问的本地地址与端口,例如 `127.0.0.1:8080`)与 **内网协议**(通常为 `http`)。
|
||||
6. 配置其他站点常规项(如 TLS 证书等),并点击保存。
|
||||
6. 配置其他站点常规项,并点击保存。
|
||||
|
||||
### 第五步:发布与生效
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
## 第一步:在系统设置中配置集成
|
||||
|
||||
1. 登录管理端控制面板,进入左侧导航 **「系统设置」** -> **「Uptime Kuma 集成」**(或通过控制台中的集成配置入口)。
|
||||
1. 登录管理端控制面板,进入左侧导航 **「系统设置」** (Settings),选择 **「OpenFlare」** 选项卡,在 **「Uptime Kuma 集成」** 区域进行配置。
|
||||
2. 配置以下核心连接参数:
|
||||
* **启用状态 (Enabled)**:开启集成开关。
|
||||
* **实例地址 (Instance URL)**:你的 Uptime Kuma 服务地址。例如 `http://192.168.1.100:3001` 或 `https://kuma.example.com`(必须包含协议前缀 `http://` 或 `https://`)。
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
|
||||
配置网站的安全防护时,推荐按这个顺序进行:
|
||||
|
||||
1. 进入 IP 组管理,创建所需的 **手动 IP 组** (如开发者白名单) 或 **自动 IP 组** (如根据 404 扫描自动封禁的 IP)。
|
||||
2. 创建或编辑 **WAF 规则组**:
|
||||
1. 进入左侧菜单 **「安全性」->「IP 组」**,创建所需的 **手动 IP 组** (如开发者白名单) 或 **自动 IP 组** (如根据 404 扫描自动封禁的 IP)。
|
||||
2. 创建或编辑 **WAF 规则组**(菜单路径 **「安全性」->「WAF」**):
|
||||
* 绑定需要引用或阻断的 IP 组。
|
||||
* 配置国家或省份的地域黑白名单限制。
|
||||
* (可选) 在 `PoW` 标签页配置人机挑战参数。
|
||||
* 在 `拦截返回` 标签页设定自定义状态码(如 403, 418)和 HTML 拦截页。
|
||||
3. 将规则组关联到对应的 **网站配置**。
|
||||
3. 将规则组关联到对应的 **路由规则**(在 **「规则管理」** 页面编辑对应规则,并在「WAF」选项卡中勾选关联规则组)。
|
||||
4. 发布并激活配置版本,使边缘节点 (Agent) 开始应用 WAF 规则过滤流量。
|
||||
|
||||
---
|
||||
@@ -73,7 +73,7 @@ IP 组是进行大批量 IP 过滤的基石。OpenFlare 提供了极富弹性的
|
||||
|
||||
### 第二步:创建与配置 WAF 规则组
|
||||
|
||||
1. 导航至左侧菜单 **「安全防护 (WAF)」**,点击 **「创建规则组」**。
|
||||
1. 导航至左侧菜单 **「安全性」->「WAF」**,点击 **「创建规则组」**。
|
||||
2. 填写规则组名称(如 `production-api-shield`),选择是否为「全局规则组」。
|
||||
3. 进入规则组详情,在下方几个配置 Tab 中依次设置:
|
||||
|
||||
@@ -103,12 +103,11 @@ IP 组是进行大批量 IP 过滤的基石。OpenFlare 提供了极富弹性的
|
||||
|
||||
---
|
||||
|
||||
### 第三步:将规则组关联到网站
|
||||
### 第三步:将规则组关联到路由规则
|
||||
|
||||
规则组配置完成后,并不会自动生效,你需要将其与具体的网站配置绑定。
|
||||
规则组配置完成后,并不会自动生效,你需要将其与具体的路由规则绑定。
|
||||
|
||||
* **方案 A (推荐)**:在规则组详情页面的 **「绑定网站」** 选项卡中,一键勾选你希望启用此防护的网站并保存。
|
||||
* **方案 B**:回到 **「网站配置」** 中编辑某个具体网站,在其「安全防护」配置区,勾选并绑定刚才创建的规则组。
|
||||
* **关联配置步骤**:进入 **「规则管理」** 页面,点击进入对应反代或静态托管规则的详情,切换到 **「WAF」** 选项卡,勾选并绑定刚才创建的 WAF 规则组。
|
||||
|
||||
> [!NOTE]
|
||||
> 如果规则组被标记为 **「全局规则组 (is_global)」**,它将自动应用到网关上托管的**所有网站**,无需手动执行绑定。
|
||||
|
||||
+1
-9
@@ -11,15 +11,7 @@
|
||||
|
||||
## 正在进行的计划
|
||||
|
||||
| 计划 | 说明 |
|
||||
| --- | --- |
|
||||
| [OpenFlare → Wavelet 后端迁移计划](./20260618-openflare-wavelet-backend-migration.md) | 将 `openflare-server` 后端迁移至 Wavelet 框架,保留 `/api/*` 路径,复用用户/认证等平台能力 |
|
||||
| [OpenFlare 后端迁移 — AI 接手](./handover-openflare-backend-migration.md) | 后端迁移当前进度、任务队列、定时任务、goose 版本与下一步行动(阶段 5 收尾) |
|
||||
| [OpenFlare → Wavelet 前端迁移计划](./20260618-openflare-wavelet-frontend-migration.md) | 将 `openflare-server/web` 业务 UI 按 Wavelet 设计风格重写,复用框架组件与 Admin 基建 |
|
||||
| [OpenFlare 前端迁移 — AI 委派](./handover-openflare-frontend-migration.md) | 前端迁移任务队列与验收状态 |
|
||||
| [前端路由验证](./verify-frontend-routes.md) · [Service 验证](./verify-frontend-services.md) · [UI 验证](./verify-frontend-ui.md) · [构建验证](./verify-frontend-build.md) | 多角度迁移验收报告 |
|
||||
| [文档结构更新 — AI 接手](./handover-docs-restructure-update.md) | 重构后多智能体分析结论与文档批量更新记录 |
|
||||
| [边缘运行时 Phase 3 任务拆解](./20260619-edge-phase3-tasks.md) | Batch 1 并行任务(duration / observability / heartbeat loop) |
|
||||
当前暂无正在进行的开发计划或 AI 接手计划。所有历史迁移与重构项目(如后端/前端向 Wavelet 平台的迁移、边缘运行时重构等)均已完成开发并上线,对应的临时计划文档已归档清理。
|
||||
|
||||
## 使用建议
|
||||
|
||||
|
||||
@@ -145,24 +145,27 @@ Server 的所有核心基础配置定义在 `config.yaml` 中,且均支持环
|
||||
| `password_register_enabled` | `bool` | 是否允许通过邮箱/密码方式在前端直接注册 | `false` |
|
||||
| `oidc_login_enabled` | `bool` | 是否启用 OIDC (SSO) 第三方免密登录方案 | `false` |
|
||||
| `max_api_keys_per_user` | `int` | 每个后台用户可生成的最大 API 密钥(API Token)数量 | `5` |
|
||||
| `login_session_ttl_hours` | `int` | 用户会话在浏览器 Cookie 中的有效期(小时)。0 为随浏览器关闭清除 | `24` |
|
||||
| `login_session_ttl_hours` | `int` | 用户会话在浏览器 Cookie 中的有效期(小时)。0 为随浏览器关闭清除 | `0` |
|
||||
| `upload_allowed_extensions` | `string` | 允许用户上传的静态静态托管包文件扩展名(逗号分隔) | `zip,tar.gz,gz,tar,ssl,key,pem,txt,json` |
|
||||
| `file_access_whitelist` | `json` | 允许免登录直接公开下载或访问的文件业务类型列表 (JSON 数组) | `["ssl_cert", "pages_release"]` |
|
||||
| `disk_cache_max_size_mb` | `int` | 平台本地磁盘缓存的最大存储阈值(MB) | `1024` |
|
||||
| `disk_cache_ttl_minutes` | `int` | 本地磁盘缓存对象的默认生存周期(分钟) | `1440` (24h) |
|
||||
| `file_access_whitelist` | `json` | 允许免登录直接公开下载或访问的文件业务类型列表 (JSON 数组) | `["avatar"]` |
|
||||
| `disk_cache_max_size_mb` | `int` | 平台本地磁盘缓存的最大存储阈值(MB) | `100` |
|
||||
| `disk_cache_ttl_minutes` | `int` | 本地磁盘缓存对象的默认生存周期(分钟) | `60` |
|
||||
| `disk_cache_lru_enabled` | `bool` | 当本地磁盘缓存空间不足时是否启用 LRU 算法剔除最旧缓存 | `true` |
|
||||
| `update_upstream_repository` | `string` | 系统检测自更新的 GitHub 仓库地址 | `Rain-kl/OpenFlare` |
|
||||
| `storage_config` | `json` | 对象存储的结构化配置 (JSON),支持本地磁盘与 AWS S3 兼容存储配置 | 本地存储模式 |
|
||||
| `relay_frps_web_ui_enabled` | `bool` | 是否允许在中继节点上默认开启内嵌的 frps 流量监视面板 Web UI | `true` |
|
||||
| `relay_frps_web_ui_port` | `int` | 中继节点 frps 监视面板所监听绑定的宿主机端口 | `7500` |
|
||||
| `search_engine_indexing_enabled` | `bool` | 是否允许搜索引擎爬取/检索该站点 | `false` |
|
||||
| `menu_display_config` | `string` | 目录显示的结构化配置 (JSON 字符串,格式为 `{url: enabled}`) | `{}` |
|
||||
|
||||
### 2. 人机安全校验 (PoW Captcha)
|
||||
| 配置键 (Key) | 数据类型 | 作用说明 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| `cap_login_enabled` | `bool` | 是否在登录界面强制要求进行本地 PoW 算力防爆破人机验证 | `false` |
|
||||
| `cap_login_enabled` | `bool` | 是否在登录界面强制要求进行本地 PoW 算力防爆破人机验证 | `true` |
|
||||
| `cap_auto_solve` | `bool` | 打开页面后是否由浏览器自动开始后台背景计算算力(无需用户手动点击)| `true` |
|
||||
| `cap_challenge_count` | `int` | 人机验证所需的计算难题数。数量越大,计算要求时间越长(推荐 1~5) | `1` |
|
||||
| `cap_challenge_difficulty`| `int`| 每次计算所需的 PoW 哈希前缀匹配难度。推荐数值在 3-5 之间 | `4` |
|
||||
| `cap_challenge_size` | `int` | 人机验证盐值长度 | `32` |
|
||||
| `cap_challenge_ttl_seconds`| `int`| 难题下发后等待计算提交的最长有效时间(秒),超时自动作废 | `300` |
|
||||
| `cap_token_ttl_seconds` | `int` | 完成计算并置换到登录凭证后的有效期(秒),限制需在规定时间内登录 | `600` |
|
||||
|
||||
@@ -184,7 +187,7 @@ Server 的所有核心基础配置定义在 `config.yaml` 中,且均支持环
|
||||
| `agent_websocket_upgrade_enabled` | `bool` | 是否授权 Agent 在 HTTP 心跳握手成功后升级建立持久 WebSocket 实时连接 | `true` |
|
||||
| `node_offline_threshold` | `int` | 在管理后台中判定节点失去心跳并标注为离线状态的无响应阈值(毫秒) | `120000` (120s) |
|
||||
| `agent_update_repo` | `string` | Agent 节点更新下载自身二进制的 Release 仓库源 | `Rain-kl/OpenFlare` |
|
||||
| `geoip_provider` | `string` | GeoIP 提供商,支持 `maxmind` 等,用于 WAF 防护时地域分析 | `maxmind` |
|
||||
| `geoip_provider` | `string` | GeoIP 提供商,支持 `maxmind` 等,用于 WAF 防护时地域分析 | `ipinfo` |
|
||||
| `database_auto_cleanup_enabled` | `bool` | 是否在每天凌晨 3:00 自动清理过期观测历史日志(降低数据库空间) | `true` |
|
||||
| `database_auto_cleanup_retention_days` | `int` | 自动清理观测数据(访问日志、度量曲线、审计等)的默认保留天数 | `30` |
|
||||
|
||||
@@ -206,42 +209,42 @@ Server 的所有核心基础配置定义在 `config.yaml` 中,且均支持环
|
||||
### 6. OpenResty 核心主配置与渲染选项 (OpenResty Config)
|
||||
| 配置键 (Key) | 数据类型 | 作用说明 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| `openresty_default_server_return_status` | `int` | 默认未命中匹配路由的 HTTP 请求返回的响应状态码 | `444` (直接丢弃) |
|
||||
| `openresty_default_server_return_status` | `int` | 默认未命中匹配路由的 HTTP 请求返回的响应状态码 | `421` |
|
||||
| `openresty_worker_processes` | `string` | nginx `worker_processes` 参数设置。支持固定整数值或自动分配 `auto` | `auto` |
|
||||
| `openresty_worker_connections` | `int` | nginx `worker_connections` 单进程最大连接承载限制 | `1024` |
|
||||
| `openresty_worker_connections` | `int` | nginx `worker_connections` 单进程最大连接承载限制 | `4096` |
|
||||
| `openresty_worker_rlimit_nofile` | `int` | nginx `worker_rlimit_nofile` 能够打开的最大物理文件描述符限制 | `65535` |
|
||||
| `openresty_events_use` | `string` | 绑定的事件轮询引擎(例如 Linux 下首选 `epoll`) | `epoll` |
|
||||
| `openresty_events_multi_accept_enabled` | `bool` | 允许 worker 进程单次批量接受所有挂起的网络握手请求 | `true` |
|
||||
| `openresty_keepalive_timeout` | `int` | nginx 连接保持连接复用的 `keepalive_timeout` 限制时长(秒) | `65` |
|
||||
| `openresty_keepalive_requests` | `int` | 单一 TCP 连接复用过程中被允许的最大累计请求处理次数 | `100` |
|
||||
| `openresty_client_header_timeout` | `int` | 接收客户端整个 Request Header 头信息的读取超时上限时长(秒) | `60` |
|
||||
| `openresty_client_body_timeout` | `int` | 接收客户端 Request Body 载荷体的数据读取超时上限时长(秒) | `60` |
|
||||
| `openresty_client_max_body_size` | `string` | 允许客户端请求上传的最大 Body 大小限制,通常需要单位如 `10m`/`50m` | `100m` |
|
||||
| `openresty_large_client_header_buffers` | `string` | 复杂请求超大请求头的专属缓冲区数目与大小大小(如 `4 8k`) | `4 8k` |
|
||||
| `openresty_send_timeout` | `int` | 向客户端推送 Response 数据回执单次传输最大的间隔超时时长(秒)| `60` |
|
||||
| `openresty_keepalive_timeout` | `int` | nginx 连接保持连接复用的 `keepalive_timeout` 限制时长(秒) | `20` |
|
||||
| `openresty_keepalive_requests` | `int` | 单一 TCP 连接复用过程中被允许的最大累计请求处理次数 | `1000` |
|
||||
| `openresty_client_header_timeout` | `int` | 接收客户端整个 Request Header 头信息的读取超时上限时长(秒) | `15` |
|
||||
| `openresty_client_body_timeout` | `int` | 接收客户端 Request Body 载荷体的数据读取超时上限时长(秒) | `15` |
|
||||
| `openresty_client_max_body_size` | `string` | 允许客户端请求上传的最大 Body 大小限制,通常需要单位如 `10m`/`50m` | `64m` |
|
||||
| `openresty_large_client_header_buffers` | `string` | 复杂请求超大请求头的专属缓冲区数目与大小大小(如 `4 16k`) | `4 16k` |
|
||||
| `openresty_send_timeout` | `int` | 向客户端推送 Response 数据回执单次传输最大的间隔超时时长(秒)| `30` |
|
||||
| `openresty_resolvers` | `string` | 节点进行 DNS 域名动态解析所关联绑定的域名解析器地址与配置参数 | 空 |
|
||||
| `openresty_proxy_connect_timeout` | `int` | 向后台代理源站发起 TCP 三次握手建连的最长超时上限时长(秒) | `30` |
|
||||
| `openresty_proxy_connect_timeout` | `int` | 向后台代理源站发起 TCP 三次握手建连的最长超时上限时长(秒) | `3` |
|
||||
| `openresty_proxy_send_timeout` | `int` | 向源站单次写入并发送请求流数据的最大写入操作间隔时长(秒) | `60` |
|
||||
| `openresty_proxy_read_timeout` | `int` | 源站收到请求后返回数据,Agent 最大的等待数据返回间隔时长(秒) | `60` |
|
||||
| `openresty_websocket_enabled` | `bool` | 是否在 HTTP 段中自动载入和渲染支持 WebSocket 协议的全局变量及头信息 | `true` |
|
||||
| `openresty_http3_enabled` | `bool` | 是否在生成 nginx 监听描述中渲染支持 HTTP/3 QUIC 双栈监听能力 | `false` |
|
||||
| `openresty_websocket_enabled` | `bool` | 是否在 HTTP 段中自动载入渲染支持 WebSocket 协议的全局变量及头信息 | `true` |
|
||||
| `openresty_http3_enabled` | `bool` | 是否在生成 nginx 监听描述中渲染支持 HTTP/3 QUIC 双栈监听能力 | `true` |
|
||||
| `openresty_proxy_request_buffering_enabled`| `bool` | 是否将客户端 Request Body 先在网关做完全部读取缓存再向源站递交 | `false` |
|
||||
| `openresty_proxy_buffering_enabled` | `bool` | 是否允许网关暂存源站的大量 Response 数据待全部解析后再转发给用户 | `true` |
|
||||
| `openresty_proxy_buffers` | `string` | nginx 反代响应缓冲区的分配数量与单缓存大大小(如 `8 4k`) | `8 4k` |
|
||||
| `openresty_proxy_buffer_size` | `string` | 存放源站返回 Response Header 头部信息的专属缓冲区限制 | `4k` |
|
||||
| `openresty_proxy_busy_buffers_size` | `string` | 响应数据流返回过大时限制网关处于 Busy 状态的缓冲上限 | `8k` |
|
||||
| `openresty_proxy_buffers` | `string` | nginx 反代响应缓冲区的分配数量与单缓存大大小(如 `16 16k`) | `16 16k` |
|
||||
| `openresty_proxy_buffer_size` | `string` | 存放源站返回 Response Header 头部信息的专属缓冲区限制 | `8k` |
|
||||
| `openresty_proxy_busy_buffers_size` | `string` | 响应数据流返回过大时限制网关处于 Busy 状态的缓冲上限 | `64k` |
|
||||
| `openresty_gzip_enabled` | `bool` | 是否在网关对符合条件的内容启用 gzip 编码实时压缩返回 | `true` |
|
||||
| `openresty_gzip_min_length` | `int` | 触发 gzip 实时压缩的文件大小门槛。低于此大小无需压缩浪费 CPU | `1024` (1KB) |
|
||||
| `openresty_gzip_comp_level` | `int` | gzip 压缩强度等级。支持 1-9,数字越大压缩率越高,越消耗算力 | `5` |
|
||||
| `openresty_cache_enabled` | `bool` | 是否在全局配置中初始化代理缓存区域(Proxy Cache Path) | `false` |
|
||||
| `openresty_cache_path` | `string` | 节点上代理缓存存放的临时物理目录路径 | `/var/cache/openresty` |
|
||||
| `openresty_cache_path` | `string` | 节点上代理缓存存放的临时物理目录路径 | `__OPENFLARE_PROXY_CACHE_PATH__` |
|
||||
| `openresty_cache_levels` | `string` | 代理缓存的存储目录树层级分配设置 | `1:2` |
|
||||
| `openresty_cache_inactive` | `string` | 缓存文件多长时间无人访问后将自动从磁盘上失效抹除的时间时长 | `7d` (7天) |
|
||||
| `openresty_cache_max_size` | `string` | 代理缓存区域在节点上占用的最大可用物理磁盘额度 | `10g` (10GB) |
|
||||
| `openresty_cache_key_template` | `string` | 默认生成代理缓存键的识别模板 | `$scheme$host$request_uri` |
|
||||
| `openresty_cache_inactive` | `string` | 缓存文件多长时间无人访问后将自动从磁盘上失效抹除的时间时长 | `30m` (30分钟) |
|
||||
| `openresty_cache_max_size` | `string` | 代理缓存区域在节点上占用的最大可用物理磁盘额度 | `1g` (1GB) |
|
||||
| `openresty_cache_key_template` | `string` | 默认生成代理缓存键 the 识别模板 | `$scheme$host$request_uri` |
|
||||
| `openresty_cache_lock_enabled` | `bool` | 遭遇高并发请求击穿同一失效资源时是否对向源站发起建连排队加锁 | `true` |
|
||||
| `openresty_cache_lock_timeout` | `string` | 抢夺代理缓存锁排队建连时排队等待的最长等待耗时限制 | `5s` |
|
||||
| `openresty_cache_use_stale` | `string` | 当源站遇到特定报错(如502/504等)时是否直接向用户投递过期缓存 | `error timeout updating http_502 http_503 http_504` |
|
||||
| `openresty_cache_use_stale` | `string` | 当源站遇到特定报错(如500/502/504等)时是否直接向用户投递过期缓存 | `error timeout updating http_500 http_502 http_503 http_504` |
|
||||
| `openresty_main_config_template` | `string` | 允许用户完全重写整个 OpenResty nginx.conf 的底层结构大骨架模板 | 空 (内置缺省骨架) |
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user