mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-02 14:56:38 +08:00
merge: 合并 PR #22 Pages 部署源 V2 到 feat/pages-source-sync-v2
基于最新 main 合并 deqiying/feat/pages-source-sync-v2, 解决 docs/changelog/index.md 与限流相关条目的冲突。
This commit is contained in:
@@ -25,6 +25,7 @@ sidebar: false
|
||||
### 新增
|
||||
|
||||
- 安全性新增「限流」设置:可为边缘站点配置默认并发与带宽;站点未设置时继承,填 `-1` 可显式关闭。
|
||||
- Pages 项目新增持久部署源,可配置 Remote URL 或公开 GitHub Release,并支持手动检查、同步发布、来源状态查看与同一 Release 资源替换确认;GitHub latest 来源可按设定间隔自动检查并发布更新,部署历史会保留安全的来源快照。
|
||||
|
||||
### 改进
|
||||
|
||||
@@ -32,6 +33,10 @@ sidebar: false
|
||||
- Agent Docker 部署命令默认挂载命名卷 `openflare-agent-pages` 持久化 Pages 目录,重建容器时无需重新拉取静态站点包。
|
||||
- 限流页新增「分析」视图:默认展示近 24 小时请求压力(RPS)与独立访客双轴趋势(1 分钟桶),支持域名过滤与 24 小时/3 天预设,并按窗口平均 RPS 排行域名与 IP;原全局默认配置迁入「配置」页签。
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复 Pages 部署包路径校验、归档展开限额、历史版本裁剪、代理路由绑定与 Agent 下载过程中的安全和一致性问题;大包改为流式处理,部署入口、旧版目录切换、保留版本及上传记录在并发场景下更加可靠,异常中断遗留的部署包也会被安全补偿清理。
|
||||
|
||||
## [v3.4.1] - 2026-07-19
|
||||
|
||||
### 新增
|
||||
|
||||
@@ -98,7 +98,7 @@ Agent 对数据面 OpenResty 的管控实现了端到端的闭环,包含配置
|
||||
* `certs/`:证书存放目录(文件命名为 `{cert_id}.crt` 和 `{cert_id}.key`)。
|
||||
* `waf/` 与 `pow/`:WAF 及防 CC 挑战所需的专用 Lua 运行时脚本。
|
||||
* `waf_config.json` 与 `waf_ip_groups.json`:WAF 过滤引擎所需的结构化规则配置文件。
|
||||
* `pages_dir`:Pages 静态站点部署目录,默认位于 `data_dir/var/lib/openflare/pages`。当激活配置引用 Pages **项目**时,Agent 按 `project_id` 请求控制面「最新激活包」(hash + package,下载后再校验 hash 防竞态),解压到 `projects/{project_id}/releases/{hash}`,切换 `current` 后**立即删除同项目其它历史 release**(仅保留最新)。项目内切换激活无需重发主配置;多项目对账时单项目失败不阻塞其它项目。
|
||||
* `pages_dir`:Pages 静态站点部署目录,默认位于 `data_dir/var/lib/openflare/pages`。当激活配置引用 Pages **项目**时,Agent 按 `project_id` 请求控制面「最新激活包」(hash + package),以流式方式写入临时文件并执行实际响应上限与 SHA-256 校验,再安全解压到 `projects/{project_id}/releases/{hash}`。解压后会复核文件数与总字节,绝对防御上限为 2 GiB 包、1,000 个文件、单文件及总量 8 GiB;随后原子切换 `current` 并**立即删除同项目其它历史 release**(仅保留最新)。项目内切换激活无需重发主配置;多项目对账时单项目失败不阻塞其它项目。
|
||||
|
||||
### 2. 精细化的重载动作
|
||||
1. **备份当前配置**:在写入新文件之前,Agent 会将现有的配置文件复制到 `.backup` 临时目录下,保留完整的现场快照。
|
||||
@@ -174,3 +174,4 @@ graph TD
|
||||
2. **严格的 Token 过滤与前缀验证**:Agent 侧向 Server 请求资源时,接口端点固定以 `/api/v1/agent/` 为前缀,并强制携带 `X-Agent-Token` 进行签名或令牌核验。
|
||||
3. **节点自治原则**:Agent 须具备完备的离线工作能力。在与 Server 失去连接期间,本地 OpenResty 必须依靠本地已落地的配置保持反向代理服务的绝对正常运行。
|
||||
4. **观测只上报事实**:访问日志以明细形式上送;主机指标上报计数器/瞬时读数。禁止在 Agent 内计算业务 UV、Top 域名、24h 已提供数据等结论性指标(由 Server 聚合)。详见 [边缘可观测与业务流量统计](./observability-design.md)。
|
||||
5. **Pages 只消费控制面产物**:Remote URL、GitHub Release、自动 scanner,以及未来仓库 checkout/build executor 均属于 Server 职责。Agent 不接收外部 URL、访问令牌、仓库凭据或任意 clone/install/build 命令,只拉取已经激活且带完整性元数据的部署包。
|
||||
|
||||
@@ -80,7 +80,7 @@ OpenResty (Agent, TLS/WAF)
|
||||
* 提供管理端 REST API(`/api/v1/d/*`),通过 **Session Cookie** 鉴权,可选 `X-Access-Token` 访问令牌。
|
||||
* 边缘节点协议走 `/api/v1/agent|relay|tunnel/*`,分别使用 `X-Agent-Token` / `X-Tunnel-Token` 鉴权。
|
||||
* 包含配置编译器(Compiler),将数据库中的规则、证书与全局参数统一编译为不可变的配置快照及 OpenResty 物理配置文件文本。
|
||||
* 存储 Pages 部署 ZIP 包于本地 Artifacts 目录,并向 Agent 提供受控的下载接口。
|
||||
* 统一接收 Pages 本地上传、Remote URL 与公开 GitHub Release 预构建产物,完成来源检查、受限下载、归档校验和不可变 deployment;manual 上传生成待显式激活的 candidate,持久来源 sync 才 create-or-load 并原子激活。Server 向 Agent 提供受控的 latest 下载接口;内部 scanner 负责 GitHub latest 的限量检查、租约恢复、可选自动发布与孤儿上传记录补偿,通用任务管理入口不能修改该排程。未来仓库源码构建由独立 Server build executor 扩展,Agent 不执行第三方拉取或构建命令。
|
||||
* 后台集成 Uptime Kuma 监控同步服务,自动为可用站点维护 HTTP 探测任务。
|
||||
* 启动入口为根目录 `main.go` + `internal/cmd/`(`api` / `worker` / `scheduler` / `all`);OpenFlare 业务在 `internal/apps/openflare/`,边缘协议处理在 `internal/apps/openflare/{agent,relay,flared}/`。
|
||||
* *详细设计请参阅:[Agent 与发布模型设计](./agent-design.md) 以及 [Uptime Kuma 监控同步设计](./kuma-design.md)*
|
||||
@@ -126,6 +126,7 @@ OpenResty (Agent, TLS/WAF)
|
||||
### 2. 静态托管与 API 代理流
|
||||
* 静态资源解压落地于 Agent 节点的 `projects/{project_id}/current` 下(按项目 latest 拉取,仅保留最新包),OpenResty 通过 `root`/`index`/`try_files` 在边缘直接提供静态资源服务。
|
||||
* 当启用 API 代理时,OpenResty 自动根据站点配置的 `api_proxy_path`(如 `/api`)将 API 请求重写并转发(`proxy_pass`)给后端动态接口。
|
||||
* 管理员操作和内部 scanner 都只生成受约束的 artifact candidate,并复用统一 inspect、`upload.Ingest` 与 deployment pipeline。manual 上传创建新的未激活 candidate;持久来源 sync/scanner 才 create-or-load 并原子激活。未来 repository build executor 也只能向同一 artifact pipeline 输出产物;Agent 始终只是 active deployment 消费者。
|
||||
* *部署包校验、解压逃逸防御及 Nginx 规则渲染详见:[Pages 静态托管设计文档](./pages-design.md)*
|
||||
|
||||
### 3. WAF 安全过滤流
|
||||
@@ -160,7 +161,7 @@ OpenResty 健康与连接数 --> 边缘健康(瞬时,不作 24h 业务总量
|
||||
当前系统核心实体包括:
|
||||
|
||||
* **反代与配置**:`zones` (根域管理边界), `zone_domains` (明确域名与证书/路由关联), `proxy_routes` (路由策略), `origins` (源站), `config_versions` (配置版本), `tls_certificates` (证书). 详见 [Zone 与域名资源设计](./zone-design.md)。
|
||||
* **Pages 静态托管**:`pages_projects` (Pages项目), `pages_deployments` (不可变部署), `pages_deployment_files` (部署文件清单).
|
||||
* **Pages 静态托管**:`of_pages_projects` (Pages项目), `of_pages_project_sources` / `of_pages_project_source_runtime` (可变来源配置与运行态), `of_pages_deployments` (不可变部署), `of_pages_deployment_files` (部署文件清单).
|
||||
* **节点与穿透**:`nodes` (节点), `tunnels` (隧道客户端), `node_system_profiles` (系统概况), `apply_logs` (应用日志).
|
||||
* **WAF 与安全**:`waf_rule_groups` (WAF规则组), `waf_ip_groups` (WAF IP组), `waf_rule_group_bindings` (网站WAF绑定).
|
||||
* **系统与账号**:`acme_accounts` (ACME账户), `dns_accounts` (DNS账户), `geoip_update_configs` (GeoIP更新配置).
|
||||
@@ -179,6 +180,7 @@ OpenResty 健康与连接数 --> 边缘健康(瞬时,不作 24h 业务总量
|
||||
| 运行时配置与控制库解耦 | WAF 规则发布时编译并随 OpenResty reload 加载;动态 IP 组通过 checksum 驱动的内存快照独立刷新 |
|
||||
| 业务流量以访问日志为唯一真相 | Agent 禁止业务预聚合;看板与 Zone 共用 Server 侧聚合,避免 openresty_tx 与 bytes_sent 双轨 |
|
||||
| 业务交付 / 边缘健康 / 主机资源分层 | 已提供数据≠宿主机网卡出站≠OpenResty 连接数,UI 与 API 分名分区 |
|
||||
| Pages artifact 与仓库构建分离 | 现有来源只导入预构建产物;未来 checkout/build 由 Server 隔离 executor 完成并复用 artifact pipeline,Agent 不执行第三方构建 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ OpenFlare 适合需要统一管理多台 OpenResty 代理节点的团队,具
|
||||
| **配置版本控制** | 支持全局单一激活版本的预览、发布、不可变快照历史与秒级一键回滚 | [Agent 与发布模型](./agent-design.md) |
|
||||
| **WAF 安全防护** | 支持可视化 DAG 编排规则、手动/自动/订阅型 IP 组、GeoIP 匹配与 PoW CC 防护 | [WAF 设计](./waf-design.md) / [WAF 可编排规则设计](./waf-orchestration-design.md) / [WAF 使用指南](../guide/waf-usage.md) |
|
||||
| **内网穿透** | 通过中继节点(Relay)与内网客户端(OpenFlared),反向穿透暴露内网 Web 服务 | [内网穿透设计](./tunnel-design.md) / [穿透使用指南](../guide/tunnel-usage.md) |
|
||||
| **Pages 静态托管** | 直接上传前端压缩包(zip / tar.gz / tar.xz / 7z 等),由边缘节点拉取并由 OpenResty 本地服务,支持 API 反代与 SPA Fallback | [Pages 静态托管设计](./pages-design.md) |
|
||||
| **Pages 静态托管** | 支持上传或从 Remote URL、公开 GitHub Release 同步预构建产物;GitHub latest 可定时检查并可选自动发布。不可变部署由边缘节点拉取并由 OpenResty 本地服务,支持回滚、API 反代与 SPA Fallback | [Pages 静态托管设计](./pages-design.md) / [Pages 使用指南](../guide/pages-usage.md) |
|
||||
| **TLS 证书自动续期** | 将证书显式绑定到 Zone 域名,并通过 ACME 协议向 Let's Encrypt 申请/续期证书 | [Zone 与域名资源设计](./zone-design.md) |
|
||||
| **多节点监控与观测** | 访问日志为业务流量唯一真相;Agent 只上报明细与主机读数,Server 统一聚合;与 Zone/看板对账 | [观测数据传输模型](./observability-transport-model.md) / [边缘可观测与业务流量统计](./observability-design.md) / [上报协议与表结构](./observability-data-model.md) / [系统架构](./architecture.md) |
|
||||
|
||||
@@ -53,8 +53,9 @@ OpenFlare 适合需要统一管理多台 OpenResty 代理节点的团队,具
|
||||
* **Tunnel 与 Node 体系隔离**:Tunnel 客户端在内网发起出向建连,与控制面托管的边缘 Node(公网节点)是独立的实体,使用专属的 `tunnel_token` 进行鉴权。
|
||||
|
||||
### 4. Pages 静态托管边界
|
||||
* **Direct Upload 托管模式**:仅支持直接上传预构建的 ZIP 静态资源包。不支持外部 Git 仓库自动构建、边缘 Serverless 函数、动态 SSR 服务或生成的二级预览域名。
|
||||
* **包体硬上限限制**:为了保障边缘节点安全,ZIP 压缩包体最大 25 MiB,解压文件树不超过 1,000 个且总体积不超过 100 MiB。禁止上传含有任何软链接或目录跨越(Zip-Slip)的安全高危压缩包。
|
||||
* **预构建产物来源**:项目可保持手动上传,或配置一个 Remote URL / 公开 GitHub Release asset 来源。Remote 与固定 tag 只支持手动操作;只有 GitHub latest 进入定时检查并可选择自动更新。来源可切换,但不可变 deployment 与当前生产版本不会随 source 编辑或删除而丢失。
|
||||
* **归档与资源上限**:支持 `zip`、`tar.gz` / `tgz`、`tar.xz` / `txz`、`tar.bz2` / `tbz2`、`tar`、`7z`。压缩包上限由 `pages_max_package_size_mb` 控制(默认 100 MiB,范围 1~2048);展开后的单文件和总量上限为包上限的 4 倍且最低 100 MiB,最多 1,000 个常规文件。Server 与 Agent 都校验实际字节,并拒绝路径逃逸、软/硬链接与特殊文件。
|
||||
* **构建与运行时边界**:当前不从外部 Git 仓库拉取源码或执行构建,也不提供边缘 Serverless、动态 SSR 或二级预览域名。未来仓库集成必须使用独立 `git_repository` Provider 与 Server 侧隔离 build executor,只向统一 artifact 管线输出受限产物;Agent 不接收仓库凭据、外部 URL 或 clone/install/build 命令。
|
||||
|
||||
### 5. 系统与版本边界
|
||||
* **全局单一激活版本**:所有节点拉取并消费同一份全局激活配置。不进行按节点分组的差异化配置发布。
|
||||
|
||||
+74
-51
@@ -12,20 +12,31 @@
|
||||
2. **多节点分发困难**:当控制面管理多台边缘节点时,将静态文件同步分发到所有节点,并确保文件一致性,需要维护复杂的同步脚本(如 rsync 等)。
|
||||
3. **回滚缺乏一致性**:一旦新前端包发布失败或存在严重缺陷,不仅要恢复静态文件,还要恢复对应的反代规则,很难做到原子回滚。
|
||||
|
||||
为了解决这些问题,OpenFlare 引入了受 Cloudflare Pages 启发的 **Pages 静态托管** 功能。该功能将“前端部署包上传”与“网站代理规则配置”合二为一,依托 OpenFlare 的 pull-based(拉取式)协同架构,实现静态文件分发与反代配置发布的强一致性、不可变性与一键秒级回滚。
|
||||
为了解决这些问题,OpenFlare 引入了受 Cloudflare Pages 启发的 **Pages 静态托管** 功能。该功能将“预构建产物导入”与“网站代理规则配置”纳入同一控制面,依托 OpenFlare 的 pull-based(拉取式)协同架构,以不可变 deployment、单节点原子切换和周期对账实现多 Agent 最终收敛,并支持快速回滚。
|
||||
|
||||
---
|
||||
|
||||
## 核心功能
|
||||
|
||||
Pages 静态托管子系统包含以下核心能力:
|
||||
* **Direct Upload 部署模式**:支持直接上传预构建的静态资源压缩包(`zip`、`tar.gz`、`tar.xz`、`tar.bz2`、`tar`、`7z`),或填写 URL 由控制面代为下载导入(允许内网地址与自签证书),省去复杂的 Git 集成和构建环境依赖。
|
||||
* **不可变部署快照**:每次上传产生一个带唯一 ID 和 SHA-256 Checksum 的不可变部署记录。支持按系统配置保留最近 N 个历史部署,并可随时激活和回滚。
|
||||
* **预构建产物部署**:支持直接上传静态资源压缩包,也可为项目保存一个 Remote URL 或公开 GitHub Release asset 来源。外部来源只由 Server 访问,成功同步后统一创建或复用不可变 deployment 并原子激活。
|
||||
* **不可变部署快照**:本地上传每次创建新的候选 deployment;持久来源同步按 source identity/revision 创建或复用 deployment 并激活。所有部署都有唯一 ID 和整包 SHA-256,支持按系统配置保留最近 N 个历史版本并随时回滚。
|
||||
* **检查与自动更新**:GitHub latest 可按项目间隔定时检查;默认只提示可用更新,管理员显式开启后才按检查到的精确 revision 自动同步并发布。
|
||||
* **SPA Fallback 支持**:支持对单页应用(SPA)进行 Fallback 路由配置,请求找不到静态文件时自动重定向到入口文件。
|
||||
* **内置 API 反代服务**:支持在 Pages 规则内一键启用 API 代理,消除跨域问题,将请求转发给指定的后端服务。
|
||||
* **安全包校验与解压缩**:内置路径逃逸防御、防软链接劫持、文件大小/数量上限与可配置上传包体积控制,保障节点物理安全。
|
||||
* **可配置限额**:管理员可在运维设置中调整「部署包大小上限」与「历史部署保留数」。
|
||||
|
||||
### 部署源与未来构建边界
|
||||
|
||||
项目当前支持 manual、Remote URL、GitHub Release 三种来源视图。无 source 记录即 manual;切换或删除 source 不删除历史 deployment,也不改变当前 active deployment。Remote URL 只允许手动“同步并发布”;GitHub Release 支持 latest/tag 手动检查与同步,只有 latest 可选择定时检查和自动更新。
|
||||
|
||||
source 是可变配置,deployment 是不可变事实。source 配置与运行态游标、状态、租约分别存储;deployment 只保存创建时的安全 provenance 快照。所有产物都复用“下载或接收产物 → 真实字节与入口校验 → `upload.Ingest` → deployment”的 artifact pipeline:manual 上传停在 candidate,等待管理员显式激活;持久来源 sync 才在同一业务事务中 create-or-load 并原子激活。Agent 只消费 active deployment,不感知来源类型。
|
||||
|
||||
后续从 Git 仓库拉取源码并自动构建时,将新增独立 `git_repository` provider 与隔离的 build executor。它输出受限的预构建产物后继续复用上述导入管线;不得把 clone、依赖安装或任意构建命令下发给 Agent,也不得把 branch/build/env 字段塞入现有 `github_release` source。当前 V2 不增加这些未来字段或空任务,只稳定 provider 输出、source discriminated view 与 deployment provenance 三个扩展边界。
|
||||
|
||||
管理端信息架构参考 Cloudflare Pages 当前把 [Git integration](https://developers.cloudflare.com/pages/configuration/git-integration/) 与 [Direct Upload](https://developers.cloudflare.com/pages/get-started/direct-upload/) 分离、并统一展示生产状态与历史部署的方式:OpenFlare 项目详情按“当前生产部署 → 部署源 → 部署历史”组织。OpenFlare 仍允许切换来源并保留历史部署,不采用 Cloudflare 项目创建后来源不可切换的限制。
|
||||
|
||||
---
|
||||
|
||||
## Pages 静态托管架构
|
||||
@@ -40,35 +51,41 @@ graph TD
|
||||
OpenResty -->|2. 转发 API 代理| BackEnd[4. 后端 API 服务]
|
||||
|
||||
%% 控制流与心跳
|
||||
Server[OpenFlare Server 控制面] <-->|Agent API / Heartbeat| Agent[openflare-agent 进程]
|
||||
Server -.->|5. 存储部署包| LocalStore[(Server 本地存储)]
|
||||
Admin[管理员 / CI] -->|上传或配置来源| Server[OpenFlare Server 控制面]
|
||||
Providers[Remote / GitHub Provider] -->|受限 artifact candidate| Server
|
||||
Scanner[内部 scanner / action task] -->|检查与自动同步| Server
|
||||
Server <-->|Agent API / Heartbeat| Agent[openflare-agent 进程]
|
||||
Server -.->|统一 upload.Ingest| UploadStore[(平台 upload backend)]
|
||||
|
||||
Agent -->|1. 发现新版本| Server
|
||||
Agent -->|2. 下载部署包| Server
|
||||
Agent -->|3. 校验并解压缩| StaticFiles
|
||||
Agent -->|4. 应用并 Reload| OpenResty
|
||||
Agent -->|3. 校验、解压并原子切换| StaticFiles
|
||||
|
||||
style Browser fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style StaticFiles fill:#9f9,stroke:#333,stroke-width:2px
|
||||
style Server fill:#f96,stroke:#333,stroke-width:2px
|
||||
```
|
||||
|
||||
* **控制面(Control Plane)**:Server 接收前端上传的部署包,并将包存储于本地磁盘,元数据写入数据库。配置发布时,编译出带有 `pages_deployment` 详情的不可变全局版本快照。
|
||||
* **数据面(Data Plane)**:Agent 在心跳同步中发现版本更新并引用了 Pages 部署,通过专属 API 下载对应的部署包并执行校验解压缩。OpenResty 拦截域名请求,在本地提供静态文件服务。
|
||||
* **控制面(Control Plane)**:Server 接收本地上传,或通过受限 Provider 获取 Remote/GitHub 预构建产物;action task 与内部 scanner 负责检查、同步和自动更新。所有产物经统一 inspect 与 `upload.Ingest` 写入平台存储后端;manual 上传创建新的 candidate,持久来源 sync 则 create-or-load deployment 并原子激活。配置发布时只编译稳定的项目锚点与静态服务元数据。
|
||||
* **数据面(Data Plane)**:Agent 在心跳/WS 对账中发现配置引用的 Pages 项目,通过专属 API 拉取该项目当前激活包并执行校验解压缩。OpenResty 在本地提供静态文件服务;Agent 不感知产物来自上传、Remote、GitHub 或未来 build executor。
|
||||
|
||||
---
|
||||
|
||||
## 数据模型与元数据设计
|
||||
|
||||
### 1. 核心数据库实体
|
||||
* **Pages 项目 (`pages_projects`)**:
|
||||
* **Pages 项目 (`of_pages_projects`)**:
|
||||
* 记录项目的业务名称、Slug 标识(URL 友好型)、启用状态、静态服务根目录(RootDir,可为空)、入口文件名(EntryFile,默认 `index.html`)、SPA Fallback 设置,以及 API 反向代理配置(APIProxyPath, APIProxyPass, APIProxyRewrite)。
|
||||
* **Pages 部署 (`pages_deployments`)**:
|
||||
* 记录单次上传生成的不可变快照。包含:部署号 (DeploymentNumber, 递增序列)、SHA-256 Checksum 校验和、部署状态 (uploaded/active)、部署包的本地存储路径、解压后的文件数与总字节数。
|
||||
* **部署文件清单 (`pages_deployment_files`)**:
|
||||
* 存储每次部署的完整静态文件树路径与文件大小(来自压缩包声明的未压缩大小),供控制台展示与统计。
|
||||
* 不再为包内每个文件计算内容哈希;完整性由**整包** SHA-256(`pages_deployments.checksum`)保证,Agent 拉取时只校验整包 hash。
|
||||
* 控制面 inspect 通过文件句柄 / 随机访问读取归档索引(zip/7z 中央目录;tar 流式读 header 并丢弃 body),避免将整包 `ReadFile` 进内存,也避免逐文件解压算 hash。
|
||||
* **部署源配置 (`of_pages_project_sources`)**:
|
||||
* 每个项目最多一条可变来源配置,使用 `source_type` 区分 Remote URL 与 GitHub Release。`config_version` 用于 fence 旧任务;Remote 完整 URL 只保存在配置表中,不会进入响应、日志、任务 payload 或 deployment provenance。V2 不承诺数据库列加密。
|
||||
* **部署源运行态 (`of_pages_project_source_runtime`)**:
|
||||
* 与 source 1:1 保存 ETag、seen/applied revision、最近检查/同步、下次检查、错误和 lease。状态固定为 `idle | checking | update_available | syncing | failed | attention`,排队/完成状态由 `TaskExecution` 承担。
|
||||
* **Pages 部署 (`of_pages_deployments`)**:
|
||||
* 记录不可变部署事实:项目内递增部署号、整包 SHA-256、`upload_id`、文件数/总字节、创建者,以及可空的 source identity/revision、来源安全快照与 trigger。`artifact_path` 仅为旧数据兼容字段,不再是新部署的存储真相。
|
||||
* **部署文件清单 (`of_pages_deployment_files`)**:
|
||||
* 存储每次部署的完整常规文件路径与实际字节数,供控制台展示与统计。
|
||||
* 不再为包内每个文件计算内容哈希;完整性由**整包** SHA-256(`of_pages_deployments.checksum`)保证,Agent 拉取时校验整包 hash。
|
||||
* 控制面 inspect 通过文件句柄读取归档,流式消费每个常规文件体并核对声明大小与实际字节,避免将整包 `ReadFile` 进内存,也避免逐文件落盘计算 hash。
|
||||
|
||||
### 2. 路由关联与快照
|
||||
`proxy_routes` 路由规则通过 `upstream_type = "pages"` 及 `pages_project_id` 关联 Pages 项目。当路由类型为 `pages` 且该项目存在已激活的部署时,才允许将该路由加入发布流程。
|
||||
@@ -87,7 +104,7 @@ graph TD
|
||||
"api_proxy_path": "/api",
|
||||
"api_proxy_pass": "http://api.internal:8000",
|
||||
"api_proxy_rewrite": "/api/(.*) /$1",
|
||||
"local_root": "__OPENFLARE_PAGES_DIR__/deployments/12/current"
|
||||
"local_root": "__OPENFLARE_PAGES_DIR__/projects/1/current"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -98,7 +115,7 @@ graph TD
|
||||
* Agent 按项目请求「最新激活包」(类似 `github/release/latest`):
|
||||
* `GET /api/v1/agent/pages/projects/:project_id/latest/hash`
|
||||
* `GET /api/v1/agent/pages/projects/:project_id/latest/package`
|
||||
* 控制面根据该项目**当前激活部署**返回哈希与压缩包;Agent 不关心具体 deployment_id。
|
||||
* 控制面根据该项目**当前激活部署**返回 deployment ID、哈希、包大小与展开清单元数据。Agent 用 deployment ID 与其它 latest 元数据识别下载期间的指针竞态,但主配置和本地目录的稳定锚点仍是 project ID。
|
||||
* 因此:在项目内切换激活部署后,**不必发布主配置**;Agent 在周期性对账时轮询 latest hash,发现变化即下载并切换 `current`。
|
||||
* 快照中的 `pages_deployment` 字段仍可记录发布时元数据(入口文件、SPA/API 代理等),但不作为 Agent 拉包的版本锁定。
|
||||
|
||||
@@ -107,40 +124,49 @@ graph TD
|
||||
## Server 端 (控制面) 职责与生命周期
|
||||
|
||||
### 1. 部署包安全校验与分析
|
||||
为了避免不可信的用户上传恶意压缩包攻击服务器,控制面在 `UploadDeployment` 时执行严格校验:
|
||||
为了避免不可信产物攻击服务器,控制面对本地上传和所有外部来源执行同一套严格校验:
|
||||
* **格式支持**:`zip`、`tar.gz` / `tgz`、`tar.xz` / `txz`、`tar.bz2` / `tbz2`、`tar`、`7z`。
|
||||
* **大小限制**:压缩包体积由系统配置 `pages_max_package_size_mb` 控制(默认 100 MiB,范围 1~2048);展开后总体积上限为「包大小 × 4」且不低于 100 MiB(按归档**声明**的未压缩大小累计,默认不流式重读每个文件内容)。
|
||||
* **大小限制**:压缩包体积由系统配置 `pages_max_package_size_mb` 控制(默认 100 MiB,范围 1~2048);展开后的单文件与总体积上限为「包大小 × 4」且不低于 100 MiB。inspect 始终流式读取常规文件体,核对声明大小与实际字节并按实际值执行上限。
|
||||
* **数量限制**:压缩包中包含的静态文件总数不得超过 1,000 个。
|
||||
* **软链接阻断**:遍历归档文件,一旦检测到任何软链接,立即抛出错误并拒绝上传,防御软链接劫持攻击。
|
||||
* **路径逃逸防御**:对每个压缩文件路径进行 `Clean` 并检查是否包含 `..` 或以 `/` 开头,防御目录跨越漏洞,防止写入系统敏感路径。
|
||||
* **入口文件校验**:项目指定的入口文件(例如 `index.html`,可在 `project.RootDir` 下)必须在部署包中存在,否则拒绝上传。
|
||||
* **公共根目录去噪**:许多打包工具会包含一个多余的主文件夹作为公共根前缀。控制面自动探测公共根前缀并将其安全剥离。
|
||||
* **整包完整性**:上传/导入时对压缩包字节计算一次 SHA-256,写入部署记录;Agent 拉包后按整包 hash 对账。包内单文件不做内容哈希。
|
||||
* **可选体积实测**:`InspectOptions.VerifySizes` 可对流式统计实际字节并与声明大小比对(仍不算 hash);默认关闭以降低上传 CPU/IO。
|
||||
* **历史保留**:系统配置 `pages_max_history_count`(默认 20,0 表示不限制)在每次上传成功后执行裁剪。语义为:**每个项目最多保留 N 条部署**;当前激活部署始终保留;其余名额按部署 ID 从新到旧填充;超出的非激活部署连同文件清单与存储对象一并删除。上传已成功时裁剪失败只记日志、不回滚上传;并发上传下可能短暂超过 N,后续上传的裁剪会收敛回 N。主配置版本回滚不依赖旧 Pages 包(见上节双轨关系)。
|
||||
* **实际体积复核**:`InspectOptions.VerifySizes` 只保留兼容意义;当前 inspect 无论该值为何都会读取常规文件体、核对声明值并累计实际大小,但仍不为单文件计算内容 hash。
|
||||
* **历史保留**:系统配置 `pages_max_history_count`(默认 20,0 表示不限制)在部署成功后执行裁剪。通常语义为:**每个项目最多保留 N 条部署**;当前激活部署始终保留,其余名额按部署 ID 从新到旧填充。`history_count=1` 时,manual 上传会临时保留 active 与最新 candidate 两条,下一次上传替换旧 candidate;candidate 激活后恢复严格上限。超出的非激活 deployment 与文件清单会删除,对应 upload record 通过平台原语幂等软删除;Pages 不直接物理删除可能被 dedup 共享的 blob。部署已成功时裁剪失败只记日志、不回滚激活;并发操作下可能短暂超过 N,后续裁剪会收敛回 N。主配置版本回滚不依赖旧 Pages 包(见上节双轨关系)。
|
||||
|
||||
### 2. 部署包存储规划
|
||||
控制面通过统一上传框架(`upload.Ingest`)存储原始部署包,并在数据库中记录 `upload_id` 与文件清单。**大体积静态包不写入 config_versions 记录和任何配置推送通道**,以保障控制面数据同步的轻量与高效。
|
||||
控制面通过统一上传框架(`upload.Ingest`)把本地、Remote 和 GitHub 产物存入配置的本地/S3 后端,并在数据库中记录 `upload_id` 与文件清单。**大体积静态包不写入 config_versions 记录和任何配置推送通道**,以保障控制面数据同步的轻量与高效。
|
||||
|
||||
### 3. 来源检查、自动更新与上传补偿
|
||||
|
||||
* `openflare:pages_source_action` 执行管理员 check/sync 或 scanner 派发的精确 revision sync;payload 不携带 URL、Token、ETag 或 lease token。手动 sync 只接受真实用户 actor,自动 sync 只接受系统 actor 与 `scheduled_auto_update` trigger。
|
||||
* `openflare:pages_source_scan` 是固定 `*/5 * * * *` 的 internal-only TaskHandler,只接受 `{}`,不会出现在通用任务类型与排程管理界面。每轮按“恢复过期 lease → 补偿 orphan upload → 扫描到期来源”执行。
|
||||
* scanner 按 `next_check_at, source_id` 稳定排序,每批最多串行检查 20 个 GitHub latest source;ETag/304 仍推进检查时间,403/429 记录状态码和实际退避截止时间,单来源失败不阻塞后续来源。
|
||||
* 发现更新总会先保存 seen cursor。只有 `auto_update_enabled=true` 且状态为普通 `update_available` 时,才携带本次检查得到的精确 revision 派发同步;`attention`、Remote 和固定 tag 不会自动发布。人工激活其它 deployment 会 fence 在途任务并关闭 auto。
|
||||
* orphan 补偿每轮最多检查 100 条至少隔离 2 小时的 upload record,并要求 system owner、Pages 保留 type、V2 marker、无 deployment 引用。候选在 `project → source → runtime → upload` 锁序内复查,只通过上传框架软删除 record 和更新统计,不直接物理删除可能被 dedup 共享的 blob。
|
||||
|
||||
---
|
||||
|
||||
## Agent 端 (数据落地) 职责与自愈
|
||||
|
||||
Agent 运行在各边缘代理节点上,在应用配置版本前,必须先将 Pages 静态资源“原子”地拉取到节点本地。
|
||||
Agent 运行在各边缘代理节点上:首次应用引用 Pages 项目的配置时,以及后续周期性 latest 对账时,都会把当前激活的静态资源“原子”地拉取到节点本地。
|
||||
|
||||
### 1. 按项目拉取 latest
|
||||
1. Agent 从激活主配置中解析 `UpstreamType == "pages"` 的路由,收集稳定锚点 **`pages_project_id`**。
|
||||
2. 对每个项目调用 `GET /api/v1/agent/pages/projects/:project_id/latest/hash` 获取控制面当前激活包哈希(类似 latest 指针)。
|
||||
3. 若本地 `projects/{project_id}/releases/{hash}` 尚未就绪,再下载 `.../latest/package`。下载后 **再次请求 hash** 与包内容 SHA-256 对齐,避免激活切换造成的竞态;不一致则有限次重试。
|
||||
3. 若本地 `projects/{project_id}/releases/{hash}` 尚未就绪,再把 `.../latest/package` 流式下载到临时文件,执行真实响应上限与 SHA-256;下载后 **再次请求 hash**,避免激活切换造成的竞态,不一致则有限次重试。
|
||||
4. 请求头携带节点 `X-Agent-Token`。
|
||||
|
||||
### 2. 安全解压缩、原子切换与只保留最新
|
||||
1. 下载字节计算 SHA-256,须与「下载后再次查询」的 latest hash 一致。
|
||||
2. 解压至 `projects/{project_id}/releases/{hash}.tmp`(支持 zip / tar.* / 7z)。Agent 信任控制面业务校验,仅做路径逃逸/软链防护。
|
||||
3. 写入 `.openflare-pages.json` 后 rename 为 `releases/{hash}`。
|
||||
4. **原子切换** `projects/{project_id}/current` 指向新 release(优先 symlink,失败则拷贝)。
|
||||
5. **仅当新包已就绪且 current 切换成功后**,删除该项目下其它 `releases/*`(含 `.tmp`),**不保留历史部署包**。边缘节点每个项目永远只保留一份最新内容。
|
||||
6. 多项目对账时 **隔离失败**:单个项目失败记日志并继续其它项目,最后汇总返回错误。
|
||||
1. 包体绝对上限为 2 GiB;下载内容的 SHA-256 须与「下载后再次查询」的 latest hash 一致,整个包不会进入 `[]byte`。
|
||||
2. 解压至 `projects/{project_id}/releases/.{hash}-<random>.tmp` 随机 staging 目录(支持 zip / tar.* / 7z),拒绝路径逃逸、链接和特殊文件。Agent 同时服从 Server metadata 上限与本地绝对上限:最多 1,000 个文件,单文件及总量最多 8 GiB。
|
||||
3. 解压完成后遍历实际文件树,精确复核文件数与总字节是否等于 Server metadata;不一致时拒绝切换。
|
||||
4. 写入 `.openflare-pages.json` 后 rename 为 `releases/{hash}`。
|
||||
5. **原子切换** `projects/{project_id}/current` 指向新 release(优先 symlink,失败则拷贝)。
|
||||
6. **仅当新包已就绪且 current 切换成功后**,删除该项目下其它 `releases/*`(含 `.tmp`),**不保留历史部署包**。边缘节点每个项目永远只保留一份最新内容。
|
||||
7. 多项目对账时 **隔离失败**:单个项目失败记日志并继续其它项目,最后汇总返回错误。
|
||||
|
||||
---
|
||||
|
||||
@@ -211,26 +237,23 @@ server {
|
||||
|
||||
## 交互逻辑与同步流程
|
||||
|
||||
一次完整的 Pages 上传与全局生效的生命周期如下:
|
||||
一次完整的预构建产物导入与生效生命周期如下。首次绑定项目需要发布主配置;后续 active deployment 变化通过项目 latest 独立收敛:
|
||||
|
||||
```text
|
||||
[ 前端管理员 ] [ Server (控制面) ] [ Agent (数据落地) ] [ OpenResty ]
|
||||
| | | |
|
||||
|--- 1. 上传 ZIP 包 ----->| | |
|
||||
| |--- 2. 安全校验与解压分析 ----| |
|
||||
| |--- 3. 归档包与持久化清单 ---| |
|
||||
| | | |
|
||||
|--- 4. 绑定路由并发布 -->| | |
|
||||
| |--- 5. 生成新配置版本并广播 ->| |
|
||||
| | | |
|
||||
| | |--- 6. 下载 ZIP 部署包 -->|
|
||||
| | |<-- 7. 返回文件数据 -------|
|
||||
| | | |
|
||||
| | |--- 8. 强一致性 Checksum -|
|
||||
| | |--- 9. 安全解压缩 -------|
|
||||
| | |--- 10. 原子切换 current -|
|
||||
| | |--- 11. 测试与重载配置 ---->|
|
||||
| | |<-- 12. 重载成功 ---------|
|
||||
| |<-- 13. 上报 Apply Success | |
|
||||
| | | |
|
||||
[管理员 / scanner] [Server 控制面] [Agent] [OpenResty]
|
||||
| | | |
|
||||
|-- manual 上传 ------>|-- inspect / Ingest ---->| |
|
||||
| |-- 创建 candidate | |
|
||||
|-- 显式激活 candidate ->|-- 切换 active | |
|
||||
| | | |
|
||||
|-- source sync ------>|-- inspect / Ingest | |
|
||||
| |-- create/load + 原子激活 | |
|
||||
| | | |
|
||||
|-- 首次绑定项目并发布 ->|-- 广播项目锚点 -------->|-- 写入/重载路由 ---------->|
|
||||
| | | |
|
||||
|-- 后续激活/同步/回滚 ->|-- active latest 改变 ---| |
|
||||
| |<-- latest 元数据对账 ----| |
|
||||
| |--- 流式返回 package ---->| |
|
||||
| | |-- 校验、解压、复核 --------|
|
||||
| | |-- 原子切换 current -------->|
|
||||
```
|
||||
|
||||
+590
-4
@@ -2595,7 +2595,7 @@ const docTemplate = `{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "返回系统所有的定时任务配置列表,包括名称、关联的异步任务类型、Cron 表达式和启用状态,需要管理员权限",
|
||||
"description": "返回管理员可管理的定时任务配置列表,包括名称、关联的异步任务类型、Cron 表达式和启用状态;系统内部排程不会暴露,需要管理员权限",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -2860,6 +2860,12 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "定时任务不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "删除定时任务失败",
|
||||
"schema": {
|
||||
@@ -3738,6 +3744,12 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "系统保留类型或存储只读",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8578,7 +8590,7 @@ const docTemplate = `{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "从用户提供的 HTTP(S) 链接下载部署包并创建部署记录;服务端使用浏览器伪装请求头拉取,允许内网地址与不安全 TLS 证书,需要管理员权限",
|
||||
"description": "已弃用的一次性 URL 导入;使用 trusted_internal 策略兼容内网与自签名证书,不创建持久部署源",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -8589,6 +8601,7 @@ const docTemplate = `{
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "从 URL 导入 Pages 部署包",
|
||||
"deprecated": true,
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
@@ -8795,6 +8808,391 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/source": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "返回脱敏后的项目部署源配置与运行状态,需要管理员权限",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "获取 Pages 部署源",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "项目 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "部署源",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/pages.SourceView"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "参数错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未登录",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "项目或部署源不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/source/check": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "异步检查 GitHub Release 来源;Remote URL 来源不支持检查更新",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "检查 Pages 部署源",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "项目 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "任务回执",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/pages.SourceActionReceipt"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "当前来源不支持检查",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未登录",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "部署源不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "来源任务正在执行",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/source/delete": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "幂等删除持久部署源;已有部署历史与当前生产部署保持不变",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "删除 Pages 部署源",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "项目 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "手动来源视图",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/pages.SourceView"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "参数错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未登录",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "项目不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/source/sync": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "异步下载、校验并原子激活来源部署包;空请求体与空 JSON 对象均有效",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "同步并发布 Pages 部署源",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "项目 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "同步参数",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/pages.SourceSyncInput"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "任务回执",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/pages.SourceActionReceipt"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "参数或来源类型无效",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未登录",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "部署源不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "来源任务正在执行",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/source/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "支持 Remote URL 与公开 GitHub Release 来源;敏感地址仅写入,不会在响应中返回",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "更新 Pages 部署源",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "项目 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "部署源配置",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/pages.SourceUpdateInput"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "更新结果",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/pages.SourceUpdateResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "配置无效",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未登录",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "项目不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -12535,6 +12933,12 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "系统保留类型或存储只读",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
@@ -12729,6 +13133,12 @@ const docTemplate = `{
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "系统保留类型或存储只读",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14574,11 +14984,20 @@ const docTemplate = `{
|
||||
"deployment_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"file_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"hash": {
|
||||
"type": "string"
|
||||
},
|
||||
"package_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"project_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_size": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -16568,9 +16987,15 @@ const docTemplate = `{
|
||||
"observability.AccessLogView": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bytes_sent": {
|
||||
"type": "integer"
|
||||
},
|
||||
"cache_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16595,6 +17020,12 @@ const docTemplate = `{
|
||||
"remote_addr": {
|
||||
"type": "string"
|
||||
},
|
||||
"request_length": {
|
||||
"type": "integer"
|
||||
},
|
||||
"request_time_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status_code": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -17290,12 +17721,21 @@ const docTemplate = `{
|
||||
"project_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"source_label": {
|
||||
"type": "string"
|
||||
},
|
||||
"source_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"total_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"trigger_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"upload_id": {
|
||||
"type": "string",
|
||||
"example": "0"
|
||||
@@ -17343,6 +17783,150 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceActionReceipt": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string"
|
||||
},
|
||||
"execution_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"task_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceRevisionView": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"asset_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"revision": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceSyncInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"confirmed_revision": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceUpdateInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"asset_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"auto_update_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"check_interval_minutes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"release_selector": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_tag": {
|
||||
"type": "string"
|
||||
},
|
||||
"remote_network_policy": {
|
||||
"type": "string"
|
||||
},
|
||||
"remote_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"remote_url_set": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"repository_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"source_type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceUpdateResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"check_task": {
|
||||
"$ref": "#/definitions/pages.SourceActionReceipt"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/definitions/pages.SourceView"
|
||||
},
|
||||
"warning": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceView": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"asset_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"auto_update_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"check_interval_minutes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"display_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"github_repository": {
|
||||
"type": "string"
|
||||
},
|
||||
"has_remote_url": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"last_applied": {
|
||||
"$ref": "#/definitions/pages.SourceRevisionView"
|
||||
},
|
||||
"last_checked_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_error": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_seen": {
|
||||
"$ref": "#/definitions/pages.SourceRevisionView"
|
||||
},
|
||||
"last_synced_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"next_check_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_selector": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_tag": {
|
||||
"type": "string"
|
||||
},
|
||||
"remote_network_policy": {
|
||||
"type": "string"
|
||||
},
|
||||
"source_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"sync_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"update_available": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.UploadFromURLInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -19310,7 +19894,8 @@ const docTemplate = `{
|
||||
"block",
|
||||
"ip_match",
|
||||
"geo_match",
|
||||
"pow"
|
||||
"pow",
|
||||
"ua_check"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"RuleNodeStart",
|
||||
@@ -19318,7 +19903,8 @@ const docTemplate = `{
|
||||
"RuleNodeBlock",
|
||||
"RuleNodeIPMatch",
|
||||
"RuleNodeGeoMatch",
|
||||
"RuleNodePoW"
|
||||
"RuleNodePoW",
|
||||
"RuleNodeUACheck"
|
||||
]
|
||||
},
|
||||
"waf.RulePosition": {
|
||||
|
||||
+89
-59
@@ -1,86 +1,116 @@
|
||||
# Pages 静态托管使用
|
||||
|
||||
你会学到:如何在 OpenFlare 中使用 Pages 静态托管功能部署前端项目(如 React、Vue 等 SPA 或 VitePress、Hugo 等静态站点),配置单页应用 (SPA) Fallback 路由以及接口反向代理 (API Proxy),并理解不可变部署与 Agent 侧原子切换的底层逻辑。
|
||||
你会学到:如何通过本地上传、Remote URL 或公开 GitHub Release asset 部署预构建静态站点,配置 SPA Fallback 与 API 反向代理,并安全地检查更新、自动发布和回滚。
|
||||
|
||||
---
|
||||
|
||||
## 核心机制与工作流
|
||||
## 核心机制与页面结构
|
||||
|
||||
OpenFlare Pages 提供受 Cloudflare Pages 启发的 **Direct Upload (直接上传)** 静态网站托管服务。它与常规代理站点的不同之处在于,数据面的边缘节点 (Agent) 会将静态文件拉取并解压到节点本地,直接通过本地的 OpenResty 提供高性能的静态文件服务,无需维护额外的 Nginx 宿主机静态目录同步。
|
||||
OpenFlare Pages 受 Cloudflare Pages 的 Direct Upload 与部署历史交互启发,但当前处理的是**预构建产物**,不是仓库源码构建。项目详情按“当前生产部署 → 部署源 → 部署历史”组织:来源配置可以变化,已经创建的 deployment 保持不可变。
|
||||
|
||||
```text
|
||||
[ 管理员 / CI ] ────── 1. 上传 ZIP 压缩包 ──────► [ OpenFlare Server ]
|
||||
│
|
||||
[ 访客浏览器 ] ◄────── 4. 访问页面 / 静态资源 ────────── [ Agent 节点 / OpenResty ]
|
||||
▲
|
||||
│
|
||||
2. 检查 Checksum 并拉取 ZIP
|
||||
3. 解压并原子切换 current 链接
|
||||
本地上传 ─> 统一校验 / upload.Ingest ─> 新 candidate ─> 管理员显式激活 ─┐
|
||||
Remote URL ── Server 受限下载 ────────┐ │
|
||||
GitHub Release asset ─ Server 解析 ───┴─> create/load deployment ─────┤
|
||||
└─> source sync 原子激活 ────────┘
|
||||
|
|
||||
v
|
||||
Agent 按项目 latest 拉取
|
||||
|
|
||||
v
|
||||
OpenResty 本地静态服务
|
||||
```
|
||||
|
||||
1. **直接上传部署包**:在控制面上传预构建好的网站 `.zip` 压缩包,Server 会生成一条带有唯一 SHA-256 校验和 (Checksum) 的不可变部署记录。
|
||||
2. **发布与推送**:在路由配置中将源站类型 (Upstream Type) 设为 `Pages 静态托管` 并绑定项目。发布配置版本后,Server 会广播给所有 Agent 节点。
|
||||
3. **安全拉取与部署**:Agent 节点识别到新配置引用了新的 Pages 部署,增量下载 ZIP 包,校验 Checksum 保证一致性,并在本地解压、完成原子目录切换,重载 OpenResty 使服务生效。
|
||||
外部 URL、GitHub 元数据和自动检查都只由 Server 处理。Agent 只从控制面拉取当前激活的部署包,不接收外部来源凭据,也不执行 `git clone`、依赖安装或构建命令。
|
||||
|
||||
---
|
||||
## 第一步:创建项目
|
||||
|
||||
## 第一步:上传部署包与创建 Pages 项目
|
||||
1. 登录管理端,进入 **「Pages」**,点击 **「创建项目」**。
|
||||
2. 填写项目名称与唯一 Slug。
|
||||
3. 配置内容入口:
|
||||
* **入口文件名**:默认 `index.html`。
|
||||
* **静态资源根路径(RootDir)**:产物位于 `dist/` 等子目录时填写该相对路径;产物就在归档根目录时留空。
|
||||
4. 按需设置 SPA Fallback 与 API 代理。RootDir 和入口文件是项目级配置,会统一应用于所有来源。
|
||||
|
||||
1. 登录管理端控制面板,进入左侧导航 **「Pages」** 菜单,点击 **「创建项目」**。
|
||||
2. 填写项目基本信息:
|
||||
* **项目名称**:业务名称(如 `我的前端应用`)。
|
||||
* **项目标识 (Slug)**:URL 友好的唯一英文标识(如 `my-react-app`),将作为存储目录的文件夹名。
|
||||
3. 设定站点目录结构与入口:
|
||||
* **入口文件名**:默认为 `index.html`。
|
||||
* **静态资源根路径 (RootDir)**:如果你的打包产物在压缩包的子目录下(例如打包出来的 zip 里包含一个 `dist/` 目录),则需要在这里填入子路径(如 `dist`)。若打包产物直接在 zip 根目录,留空即可。
|
||||
4. **上传 ZIP 压缩包**:
|
||||
* 上传你的项目静态资源打包生成的 `.zip` 文件。
|
||||
## 第二步:选择部署源
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **部署包安全限制规范**
|
||||
> 为了保障控制面和边缘节点的系统安全与性能,上传的部署包必须满足以下硬性指标,否则会被系统拒绝:
|
||||
> * **大小限制**:ZIP 压缩包体积不得超过 **25 MiB**,解压后的总文件大小不得超过 **100 MiB**。
|
||||
> * **数量限制**:解压后的文件总数不得超过 **1,000 个**。
|
||||
> * **软链接拦截**:ZIP 包内禁止包含任何软链接 (Symbolic Link),防御软链接劫持攻击。
|
||||
> * **Zip-Slip 防御**:压缩包中所有文件路径会被强制规范化,禁止使用 `..` 或以 `/` 开头,防止解压路径穿越攻击。
|
||||
> * **入口文件检查**:你指定的入口文件(在静态资源根路径下,如 `dist/index.html`)**必须在压缩包中存在**。
|
||||
### 1. 手动上传
|
||||
|
||||
---
|
||||
不配置持久来源时,项目保持手动模式。点击 **「上传部署包」** 选择预构建归档;上传成功会创建一条候选 deployment,再从部署历史中显式激活。重复上传不会修改已有 deployment。
|
||||
|
||||
## 第二步:配置高级路由规则
|
||||
支持 `zip`、`tar.gz` / `tgz`、`tar.xz` / `txz`、`tar.bz2` / `tbz2`、`tar` 与 `7z`。
|
||||
|
||||
在项目详情的配置页面中,你可以根据前端项目类型开启以下高级特性:
|
||||
### 2. Remote URL
|
||||
|
||||
### 1. 单页应用 (SPA) Fallback 路由
|
||||
对于使用 React Router、Vue Router 等进行前端路由的单页应用 (SPA),当用户直接刷新类似 `/profile/settings` 的子路径时,边缘节点本地并不存在该物理文件,会导致 404 错误。
|
||||
* **配置方式**:在项目设置中开启 **「SPA Fallback」**,并将路径设为入口文件(如 `/index.html`)。
|
||||
* **生效逻辑**:开启后,如果访客请求的静态资源在物理上不存在,OpenResty 会自动降级重定向渲染入口文件,将路由交由前端 JavaScript 接管,避免 404 报错。
|
||||
在部署源卡片中选择 **Remote URL**,填写 HTTP(S) 地址并选择网络策略:
|
||||
|
||||
### 2. 内置 API 反向代理
|
||||
为了避免前端请求后端 API 时遭遇跨域 (CORS) 限制,Pages 托管支持在同一个域名下直通后端 API。
|
||||
* **配置方式**:
|
||||
* **API 代理路径 (APIProxyPath)**:匹配的 URL 前缀(如 `/api`)。
|
||||
* **后端服务地址 (APIProxyPass)**:后端 API 的源站地址(如 `http://10.0.0.5:8080`)。
|
||||
* **重写规则 (APIProxyRewrite)**:可选。如果需要剥离前缀或重写路径,可使用正则匹配。例如:
|
||||
* 剥离前缀:将请求 `/api/users` 重写为 `/users` 发送给后端,配置为 `^/api/(.*)$ /$1`。
|
||||
* **生效逻辑**:所有以 `/api` 开头的请求会被直接转发至后端服务,而其他请求则继续由静态托管服务处理。
|
||||
* **public**:默认策略,拒绝 loopback、私网、链路本地地址、DNS rebinding、自签 TLS,以及重定向到非公网目标。
|
||||
* **trusted_internal**:仅用于明确受信的内网或自签服务;保存前需要再次确认风险。
|
||||
|
||||
---
|
||||
保存后地址只以脱敏形式展示。编辑其它配置时无需重新填写;只有选择更换地址时才提交新 URL。Remote 来源只提供 **「同步并发布」**:每次由 Server 下载、校验并原子激活,不支持“检查更新”、定时检查或自动更新。
|
||||
|
||||
## 第三步:绑定代理路由并发布
|
||||
### 3. GitHub Release
|
||||
|
||||
Pages 项目配置并上传好部署包后,需要绑定到对外公开的域名上才能被访客访问。
|
||||
GitHub 来源仅支持公开 `github.com` 仓库。填写:
|
||||
|
||||
1. 导航至左侧菜单 **「规则管理」**,创建或编辑一条代理规则。
|
||||
2. 切换到 **「反向代理」** 选项卡:
|
||||
* **源站类型**:选择 **「Pages」**。
|
||||
* **选择 Pages 项目**:选择你刚才创建的项目,并关联要激活的部署版本(默认会自动关联最新上传成功的部署)。
|
||||
3. 点击右上角 **「配置预览」** -> 确认无误后点击 **「发布并激活」**。
|
||||
* `https://github.com/{owner}/{repo}` 格式的仓库地址;
|
||||
* **最新 Release** 或 **固定 Tag**;
|
||||
* 精确、区分大小写的 Release Asset 文件名,默认 `dist.zip`。
|
||||
|
||||
## 运维与回滚
|
||||
两种选择都可手动 **「检查更新」** 和 **「同步并发布」**。区别如下:
|
||||
|
||||
* **不可变部署与回滚**:每次在 Pages 项目下上传 `.zip` 文件,系统都会产生一个全新且唯一的部署版本。如果在历史部署列表中将上一版本设为激活并重新发布,可实现边缘节点的秒级回滚。
|
||||
* **原子切换与自愈**:边缘节点(Agent)在拉取静态资源包时,会执行校验与流式解压,并通过原子切换物理目录来保障服务的无缝过渡。同时,Agent 会定时清理不再引用的历史部署包。
|
||||
* **latest**:可设置 5~1440 分钟检查间隔,默认 60 分钟;自动更新默认关闭。开启后,scanner 发现新 revision 才会异步同步并发布。
|
||||
* **tag**:只支持管理员手动检查和同步,不参与定时 scanner。
|
||||
|
||||
“检查更新”只解析 Release/asset 并更新版本游标,不下载部署包;“同步并发布”才会下载、校验、创建或复用 deployment 并激活。如果同一个 Release 下的 asset 被替换,来源会进入 **「需要确认」**,必须确认页面显示的精确 revision 后才能发布,避免静默覆盖。
|
||||
|
||||
GitHub Release 在这里是预构建产物源,不等同于连接代码仓库自动构建。未来仓库集成会使用独立的 `git_repository` 来源和 Server build executor,再把构建产物送入同一部署管线。
|
||||
|
||||
### 4. 切换或删除来源
|
||||
|
||||
可以在手动、Remote 和 GitHub Release 之间切换。修改或删除来源不会删除当前生产部署和历史 deployment;切回手动模式后可继续上传并显式激活。
|
||||
|
||||
## 部署包安全限制
|
||||
|
||||
部署包必须满足以下约束:
|
||||
|
||||
* 压缩包大小由系统配置 `pages_max_package_size_mb` 控制,默认 100 MiB,可配置 1~2048 MiB。
|
||||
* 展开后的单文件和总量上限为“包大小上限 × 4”,且最低为 100 MiB;最多 1,000 个常规文件。
|
||||
* 控制面会流式读取常规文件体,核对声明大小与实际字节,并校验项目入口文件。
|
||||
* 归档中的绝对路径、`..` 路径逃逸、软链接、硬链接和特殊文件都会被拒绝。
|
||||
|
||||
Agent 下载时还会执行 SHA-256、真实响应字节上限、解压后文件数与总大小复核;失败不会切换现有 `current`。
|
||||
|
||||
## 第三步:配置高级路由规则
|
||||
|
||||
### 1. SPA Fallback
|
||||
|
||||
使用 React Router、Vue Router 等前端路由时,开启 **「SPA Fallback」** 并设置入口路径(通常为 `/index.html`)。访客直接访问不存在的物理路径时,OpenResty 会回退到入口文件交由前端路由处理。
|
||||
|
||||
### 2. API 反向代理
|
||||
|
||||
Pages 可在同一域名下把指定前缀转发到后端 API:
|
||||
|
||||
* **APIProxyPath**:匹配前缀,例如 `/api`。
|
||||
* **APIProxyPass**:后端地址,例如 `http://10.0.0.5:8080`。
|
||||
* **APIProxyRewrite**:可选的路径重写规则。
|
||||
|
||||
匹配 API 前缀的请求走反向代理,其余请求继续由静态站点处理。
|
||||
|
||||
## 第四步:绑定路由并首次发布
|
||||
|
||||
1. 创建或编辑一条代理规则。
|
||||
2. 将源站类型设为 **Pages**,并选择 Pages **项目**。
|
||||
3. 预览配置后发布并激活。
|
||||
|
||||
路由绑定的是稳定的项目 ID,不是某个 deployment。首次发布让 Agent 获得项目锚点;此后本地上传、来源同步、自动更新或人工回滚只会改变项目的 active deployment,Agent 会通过 latest hash 对账收敛,无需重新发布主配置。
|
||||
|
||||
## 运维、状态与回滚
|
||||
|
||||
* 来源卡片展示最近检查/同步、已发现与已应用 revision、下次检查和安全错误。检查或同步任务运行时,页面会轮询任务状态;latest 空闲时只在接近检查时间时低频刷新。
|
||||
* 自动更新失败不会替换旧 active deployment;单个来源失败也不会阻塞 scanner 处理其它项目。
|
||||
* 在部署历史中激活其它 deployment 即完成人工回滚。系统会 fence 在途来源任务,并关闭该来源的自动更新,避免下一轮 latest 又覆盖人工选择;重复激活当前版本是 no-op。
|
||||
* Agent 下载到临时文件并校验 SHA-256,安全解压后原子切换 `current`。任一步失败都保留旧内容,多项目对账时单项目失败不影响其它项目。
|
||||
|
||||
> [!TIP]
|
||||
> 关于不可变部署、目录结构设计、增量拉取和安全防逃逸校验等底层架构与自愈细节,请参阅 [Pages 静态托管设计](../design/pages-design.md)。
|
||||
> 关于来源状态机、自动 scanner、上传补偿、不可变部署和 Agent 原子切换,请参阅 [Pages 静态托管设计](../design/pages-design.md)。
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,10 @@
|
||||
* [访问日志 IP 明细 Tab](./20260719-access-log-ip-tab.md):第三 Tab 按 IP 聚合列表(时间窗/流量/2xx 比例);IP 情报迁入独立详情;日志详情仅请求字段。
|
||||
* [边缘限流全局默认](./20260719-http-default-rate-limit.md):全局默认并发/带宽;站点 0 继承、-1 关闭;RenderRouteConfig 合并。
|
||||
|
||||
## 已完成的计划
|
||||
|
||||
* [Pages 项目部署源与 GitHub Releases 自动更新 V2](./20260719-pages-source-sync-v2.md):已完成 Remote URL / GitHub Release 来源、不可变部署、自动检查更新与安全回滚,并预留独立仓库构建 Provider 边界;生产环境验收边界见计划内验证记录。
|
||||
|
||||
## 使用建议
|
||||
|
||||
* **命名规范**:正在进行的开发计划建议命名为 `docs/plan/YYYYMMDD-[feature-name].md`,接手计划建议命名为 `docs/plan/handover-[task-name].md`。
|
||||
|
||||
+590
-4
@@ -2588,7 +2588,7 @@
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "返回系统所有的定时任务配置列表,包括名称、关联的异步任务类型、Cron 表达式和启用状态,需要管理员权限",
|
||||
"description": "返回管理员可管理的定时任务配置列表,包括名称、关联的异步任务类型、Cron 表达式和启用状态;系统内部排程不会暴露,需要管理员权限",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -2853,6 +2853,12 @@
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "定时任务不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "删除定时任务失败",
|
||||
"schema": {
|
||||
@@ -3731,6 +3737,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "系统保留类型或存储只读",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8571,7 +8583,7 @@
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "从用户提供的 HTTP(S) 链接下载部署包并创建部署记录;服务端使用浏览器伪装请求头拉取,允许内网地址与不安全 TLS 证书,需要管理员权限",
|
||||
"description": "已弃用的一次性 URL 导入;使用 trusted_internal 策略兼容内网与自签名证书,不创建持久部署源",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -8582,6 +8594,7 @@
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "从 URL 导入 Pages 部署包",
|
||||
"deprecated": true,
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
@@ -8788,6 +8801,391 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/source": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "返回脱敏后的项目部署源配置与运行状态,需要管理员权限",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "获取 Pages 部署源",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "项目 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "部署源",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/pages.SourceView"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "参数错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未登录",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "项目或部署源不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/source/check": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "异步检查 GitHub Release 来源;Remote URL 来源不支持检查更新",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "检查 Pages 部署源",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "项目 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "任务回执",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/pages.SourceActionReceipt"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "当前来源不支持检查",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未登录",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "部署源不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "来源任务正在执行",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/source/delete": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "幂等删除持久部署源;已有部署历史与当前生产部署保持不变",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "删除 Pages 部署源",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "项目 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "手动来源视图",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/pages.SourceView"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "参数错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未登录",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "项目不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/source/sync": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "异步下载、校验并原子激活来源部署包;空请求体与空 JSON 对象均有效",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "同步并发布 Pages 部署源",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "项目 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "同步参数",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/pages.SourceSyncInput"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "任务回执",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/pages.SourceActionReceipt"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "参数或来源类型无效",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未登录",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "部署源不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "来源任务正在执行",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/source/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"SessionCookie": []
|
||||
}
|
||||
],
|
||||
"description": "支持 Remote URL 与公开 GitHub Release 来源;敏感地址仅写入,不会在响应中返回",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"openflare-pages"
|
||||
],
|
||||
"summary": "更新 Pages 部署源",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "项目 ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "部署源配置",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/pages.SourceUpdateInput"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "更新结果",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/response.Any"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/pages.SourceUpdateResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "配置无效",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未登录",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "项目不存在",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/d/pages/{id}/update": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -12528,6 +12926,12 @@
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "系统保留类型或存储只读",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "内部错误",
|
||||
"schema": {
|
||||
@@ -12722,6 +13126,12 @@
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "系统保留类型或存储只读",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14567,11 +14977,20 @@
|
||||
"deployment_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"file_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"hash": {
|
||||
"type": "string"
|
||||
},
|
||||
"package_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"project_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_size": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -16561,9 +16980,15 @@
|
||||
"observability.AccessLogView": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bytes_sent": {
|
||||
"type": "integer"
|
||||
},
|
||||
"cache_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -16588,6 +17013,12 @@
|
||||
"remote_addr": {
|
||||
"type": "string"
|
||||
},
|
||||
"request_length": {
|
||||
"type": "integer"
|
||||
},
|
||||
"request_time_ms": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status_code": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -17283,12 +17714,21 @@
|
||||
"project_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"source_label": {
|
||||
"type": "string"
|
||||
},
|
||||
"source_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"total_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"trigger_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"upload_id": {
|
||||
"type": "string",
|
||||
"example": "0"
|
||||
@@ -17336,6 +17776,150 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceActionReceipt": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string"
|
||||
},
|
||||
"execution_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"task_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceRevisionView": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"asset_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"revision": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceSyncInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"confirmed_revision": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceUpdateInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"asset_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"auto_update_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"check_interval_minutes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"release_selector": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_tag": {
|
||||
"type": "string"
|
||||
},
|
||||
"remote_network_policy": {
|
||||
"type": "string"
|
||||
},
|
||||
"remote_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"remote_url_set": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"repository_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"source_type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceUpdateResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"check_task": {
|
||||
"$ref": "#/definitions/pages.SourceActionReceipt"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/definitions/pages.SourceView"
|
||||
},
|
||||
"warning": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.SourceView": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"asset_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"auto_update_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"check_interval_minutes": {
|
||||
"type": "integer"
|
||||
},
|
||||
"display_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"github_repository": {
|
||||
"type": "string"
|
||||
},
|
||||
"has_remote_url": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"last_applied": {
|
||||
"$ref": "#/definitions/pages.SourceRevisionView"
|
||||
},
|
||||
"last_checked_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_error": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_seen": {
|
||||
"$ref": "#/definitions/pages.SourceRevisionView"
|
||||
},
|
||||
"last_synced_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"next_check_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_selector": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_tag": {
|
||||
"type": "string"
|
||||
},
|
||||
"remote_network_policy": {
|
||||
"type": "string"
|
||||
},
|
||||
"source_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"sync_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"update_available": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pages.UploadFromURLInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -19303,7 +19887,8 @@
|
||||
"block",
|
||||
"ip_match",
|
||||
"geo_match",
|
||||
"pow"
|
||||
"pow",
|
||||
"ua_check"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"RuleNodeStart",
|
||||
@@ -19311,7 +19896,8 @@
|
||||
"RuleNodeBlock",
|
||||
"RuleNodeIPMatch",
|
||||
"RuleNodeGeoMatch",
|
||||
"RuleNodePoW"
|
||||
"RuleNodePoW",
|
||||
"RuleNodeUACheck"
|
||||
]
|
||||
},
|
||||
"waf.RulePosition": {
|
||||
|
||||
+368
-2
@@ -760,10 +760,16 @@ definitions:
|
||||
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:
|
||||
@@ -2085,8 +2091,12 @@ definitions:
|
||||
type: object
|
||||
observability.AccessLogView:
|
||||
properties:
|
||||
bytes_sent:
|
||||
type: integer
|
||||
cache_status:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
host:
|
||||
type: string
|
||||
id:
|
||||
@@ -2103,6 +2113,10 @@ definitions:
|
||||
type: string
|
||||
remote_addr:
|
||||
type: string
|
||||
request_length:
|
||||
type: integer
|
||||
request_time_ms:
|
||||
type: integer
|
||||
status_code:
|
||||
type: integer
|
||||
user_agent:
|
||||
@@ -2557,10 +2571,16 @@ definitions:
|
||||
type: integer
|
||||
project_id:
|
||||
type: integer
|
||||
source_label:
|
||||
type: string
|
||||
source_type:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
total_size:
|
||||
type: integer
|
||||
trigger_type:
|
||||
type: string
|
||||
upload_id:
|
||||
example: "0"
|
||||
type: string
|
||||
@@ -2592,6 +2612,100 @@ definitions:
|
||||
spa_fallback_path:
|
||||
type: string
|
||||
type: object
|
||||
pages.SourceActionReceipt:
|
||||
properties:
|
||||
action:
|
||||
type: string
|
||||
execution_id:
|
||||
type: string
|
||||
task_id:
|
||||
type: string
|
||||
type: object
|
||||
pages.SourceRevisionView:
|
||||
properties:
|
||||
asset_name:
|
||||
type: string
|
||||
label:
|
||||
type: string
|
||||
revision:
|
||||
type: string
|
||||
type: object
|
||||
pages.SourceSyncInput:
|
||||
properties:
|
||||
confirmed_revision:
|
||||
type: string
|
||||
type: object
|
||||
pages.SourceUpdateInput:
|
||||
properties:
|
||||
asset_name:
|
||||
type: string
|
||||
auto_update_enabled:
|
||||
type: boolean
|
||||
check_interval_minutes:
|
||||
type: integer
|
||||
release_selector:
|
||||
type: string
|
||||
release_tag:
|
||||
type: string
|
||||
remote_network_policy:
|
||||
type: string
|
||||
remote_url:
|
||||
type: string
|
||||
remote_url_set:
|
||||
type: boolean
|
||||
repository_url:
|
||||
type: string
|
||||
source_type:
|
||||
type: string
|
||||
type: object
|
||||
pages.SourceUpdateResult:
|
||||
properties:
|
||||
check_task:
|
||||
$ref: '#/definitions/pages.SourceActionReceipt'
|
||||
source:
|
||||
$ref: '#/definitions/pages.SourceView'
|
||||
warning:
|
||||
type: string
|
||||
type: object
|
||||
pages.SourceView:
|
||||
properties:
|
||||
asset_name:
|
||||
type: string
|
||||
auto_update_enabled:
|
||||
type: boolean
|
||||
check_interval_minutes:
|
||||
type: integer
|
||||
display_url:
|
||||
type: string
|
||||
github_repository:
|
||||
type: string
|
||||
has_remote_url:
|
||||
type: boolean
|
||||
last_applied:
|
||||
$ref: '#/definitions/pages.SourceRevisionView'
|
||||
last_checked_at:
|
||||
type: string
|
||||
last_error:
|
||||
type: string
|
||||
last_seen:
|
||||
$ref: '#/definitions/pages.SourceRevisionView'
|
||||
last_synced_at:
|
||||
type: string
|
||||
next_check_at:
|
||||
type: string
|
||||
release_selector:
|
||||
type: string
|
||||
release_tag:
|
||||
type: string
|
||||
remote_network_policy:
|
||||
type: string
|
||||
source_type:
|
||||
type: string
|
||||
sync_status:
|
||||
type: string
|
||||
update_available:
|
||||
type: boolean
|
||||
type: object
|
||||
pages.UploadFromURLInput:
|
||||
properties:
|
||||
url:
|
||||
@@ -3908,6 +4022,7 @@ definitions:
|
||||
- ip_match
|
||||
- geo_match
|
||||
- pow
|
||||
- ua_check
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- RuleNodeStart
|
||||
@@ -3916,6 +4031,7 @@ definitions:
|
||||
- RuleNodeIPMatch
|
||||
- RuleNodeGeoMatch
|
||||
- RuleNodePoW
|
||||
- RuleNodeUACheck
|
||||
waf.RulePosition:
|
||||
properties:
|
||||
x:
|
||||
@@ -5601,7 +5717,7 @@ paths:
|
||||
- admin
|
||||
/api/v1/admin/tasks/schedules:
|
||||
get:
|
||||
description: 返回系统所有的定时任务配置列表,包括名称、关联的异步任务类型、Cron 表达式和启用状态,需要管理员权限
|
||||
description: 返回管理员可管理的定时任务配置列表,包括名称、关联的异步任务类型、Cron 表达式和启用状态;系统内部排程不会暴露,需要管理员权限
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -5706,6 +5822,10 @@ paths:
|
||||
description: 无管理员权限
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"404":
|
||||
description: 定时任务不存在
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"500":
|
||||
description: 删除定时任务失败
|
||||
schema:
|
||||
@@ -6157,6 +6277,10 @@ paths:
|
||||
description: 文件不存在
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"409":
|
||||
description: 系统保留类型或存储只读
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
security:
|
||||
- SessionCookie: []
|
||||
summary: 删除文件
|
||||
@@ -9257,7 +9381,8 @@ paths:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 从用户提供的 HTTP(S) 链接下载部署包并创建部署记录;服务端使用浏览器伪装请求头拉取,允许内网地址与不安全 TLS 证书,需要管理员权限
|
||||
deprecated: true
|
||||
description: 已弃用的一次性 URL 导入;使用 trusted_internal 策略兼容内网与自签名证书,不创建持久部署源
|
||||
parameters:
|
||||
- description: 项目 ID
|
||||
in: path
|
||||
@@ -9303,6 +9428,239 @@ paths:
|
||||
summary: 从 URL 导入 Pages 部署包
|
||||
tags:
|
||||
- openflare-pages
|
||||
/api/v1/d/pages/{id}/source:
|
||||
get:
|
||||
description: 返回脱敏后的项目部署源配置与运行状态,需要管理员权限
|
||||
parameters:
|
||||
- description: 项目 ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 部署源
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.Any'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/pages.SourceView'
|
||||
type: object
|
||||
"400":
|
||||
description: 参数错误
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"401":
|
||||
description: 未登录
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"404":
|
||||
description: 项目或部署源不存在
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"500":
|
||||
description: 内部错误
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
security:
|
||||
- SessionCookie: []
|
||||
summary: 获取 Pages 部署源
|
||||
tags:
|
||||
- openflare-pages
|
||||
/api/v1/d/pages/{id}/source/check:
|
||||
post:
|
||||
description: 异步检查 GitHub Release 来源;Remote URL 来源不支持检查更新
|
||||
parameters:
|
||||
- description: 项目 ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 任务回执
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.Any'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/pages.SourceActionReceipt'
|
||||
type: object
|
||||
"400":
|
||||
description: 当前来源不支持检查
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"401":
|
||||
description: 未登录
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"404":
|
||||
description: 部署源不存在
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"409":
|
||||
description: 来源任务正在执行
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"500":
|
||||
description: 内部错误
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
security:
|
||||
- SessionCookie: []
|
||||
summary: 检查 Pages 部署源
|
||||
tags:
|
||||
- openflare-pages
|
||||
/api/v1/d/pages/{id}/source/delete:
|
||||
post:
|
||||
description: 幂等删除持久部署源;已有部署历史与当前生产部署保持不变
|
||||
parameters:
|
||||
- description: 项目 ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 手动来源视图
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.Any'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/pages.SourceView'
|
||||
type: object
|
||||
"400":
|
||||
description: 参数错误
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"401":
|
||||
description: 未登录
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"404":
|
||||
description: 项目不存在
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"500":
|
||||
description: 内部错误
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
security:
|
||||
- SessionCookie: []
|
||||
summary: 删除 Pages 部署源
|
||||
tags:
|
||||
- openflare-pages
|
||||
/api/v1/d/pages/{id}/source/sync:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 异步下载、校验并原子激活来源部署包;空请求体与空 JSON 对象均有效
|
||||
parameters:
|
||||
- description: 项目 ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- description: 同步参数
|
||||
in: body
|
||||
name: request
|
||||
schema:
|
||||
$ref: '#/definitions/pages.SourceSyncInput'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 任务回执
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.Any'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/pages.SourceActionReceipt'
|
||||
type: object
|
||||
"400":
|
||||
description: 参数或来源类型无效
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"401":
|
||||
description: 未登录
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"404":
|
||||
description: 部署源不存在
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"409":
|
||||
description: 来源任务正在执行
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"500":
|
||||
description: 内部错误
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
security:
|
||||
- SessionCookie: []
|
||||
summary: 同步并发布 Pages 部署源
|
||||
tags:
|
||||
- openflare-pages
|
||||
/api/v1/d/pages/{id}/source/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 支持 Remote URL 与公开 GitHub Release 来源;敏感地址仅写入,不会在响应中返回
|
||||
parameters:
|
||||
- description: 项目 ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- description: 部署源配置
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/pages.SourceUpdateInput'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 更新结果
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/response.Any'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/pages.SourceUpdateResult'
|
||||
type: object
|
||||
"400":
|
||||
description: 配置无效
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"401":
|
||||
description: 未登录
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"404":
|
||||
description: 项目不存在
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"500":
|
||||
description: 内部错误
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
security:
|
||||
- SessionCookie: []
|
||||
summary: 更新 Pages 部署源
|
||||
tags:
|
||||
- openflare-pages
|
||||
/api/v1/d/pages/{id}/update:
|
||||
post:
|
||||
consumes:
|
||||
@@ -11589,6 +11947,10 @@ paths:
|
||||
description: 未登录
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"409":
|
||||
description: 系统保留类型或存储只读
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"500":
|
||||
description: 内部错误
|
||||
schema:
|
||||
@@ -11622,6 +11984,10 @@ paths:
|
||||
description: 文件不存在
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
"409":
|
||||
description: 系统保留类型或存储只读
|
||||
schema:
|
||||
$ref: '#/definitions/response.Any'
|
||||
security:
|
||||
- SessionCookie: []
|
||||
summary: 删除我的文件
|
||||
|
||||
Reference in New Issue
Block a user