diff --git a/README.md b/README.md index f168e95e..c67f3c7f 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ OpenFlare 是开源 CDN 编排与边缘安全平台。它支持反向代理、 * **安全内网穿透(Tunnels)**:开源版的 Cloudflare Tunnels。无须公网 IP 或暴露入向端口,通过 Relay 中继节点与 OpenFlared 客户端安全反向穿透内网 Web 服务至公网。 * **边缘 WAF 安全防护**:提供全局与自定义规则组,支持手动/自动/订阅型 IP 组、MaxMind GeoIP 国家级地域准入、IP 组成员 Checksum 差分同步(无需 Nginx 重载)以及自定义拦截响应。 * **防 CC 与人机挑战(PoW)**:内置高性能客户端密码学 Proof of Work 挑战(类似 Turnstile),在网关边缘秒级拦截并阻断僵尸网络与爬虫。 -* **Pages 静态托管**:直接上传预构建 ZIP 包,由边缘 Agent 拉取并通过 OpenResty 本地提供服务,支持 SPA Fallback 与内置 API 反向代理配置。 +* **Pages 静态托管**:支持上传或从受限 Remote URL、公开 GitHub Release asset 同步预构建产物;GitHub latest 可定时检查并可选自动发布。所有来源统一生成不可变部署,由边缘 Agent 拉取并通过 OpenResty 本地提供服务,支持回滚、SPA Fallback 与 API 反向代理。 * **TLS 证书自动化**:支持证书动态上传、多域名证书自动匹配绑定,以及通过 ACME 协议向 Let's Encrypt 自动申请与续期证书。 * **Uptime Kuma 监控同步**:与 Uptime Kuma 集成,自动差分同步监控站点列表,实时感知节点存活与服务可用状态。 * **SSO 单点登录**:支持 GitHub OAuth 与标准 OIDC 协议,无缝接入企业身份提供商实现统一登录。 diff --git a/docs/design/agent-design.md b/docs/design/agent-design.md index 4bb2f665..d42bdd4d 100644 --- a/docs/design/agent-design.md +++ b/docs/design/agent-design.md @@ -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 命令,只拉取已经激活且带完整性元数据的部署包。 diff --git a/docs/design/architecture.md b/docs/design/architecture.md index f58a915c..8cea6e51 100644 --- a/docs/design/architecture.md +++ b/docs/design/architecture.md @@ -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 本地上传、Remote URL 与公开 GitHub Release 预构建产物,完成来源检查、受限下载、归档校验、不可变 deployment 与原子激活,并向 Agent 提供受控的 latest 下载接口。未来仓库源码构建由独立 Server build executor 扩展,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_project_sources` / `pages_project_source_runtime` (可变来源配置与运行态), `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 不执行第三方构建 | --- diff --git a/docs/design/index.md b/docs/design/index.md index b2fb386d..821a053a 100644 --- a/docs/design/index.md +++ b/docs/design/index.md @@ -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. 系统与版本边界 * **全局单一激活版本**:所有节点拉取并消费同一份全局激活配置。不进行按节点分组的差异化配置发布。 diff --git a/docs/design/pages-design.md b/docs/design/pages-design.md index 07343276..6adf2a64 100644 --- a/docs/design/pages-design.md +++ b/docs/design/pages-design.md @@ -12,7 +12,7 @@ 2. **多节点分发困难**:当控制面管理多台边缘节点时,将静态文件同步分发到所有节点,并确保文件一致性,需要维护复杂的同步脚本(如 rsync 等)。 3. **回滚缺乏一致性**:一旦新前端包发布失败或存在严重缺陷,不仅要恢复静态文件,还要恢复对应的反代规则,很难做到原子回滚。 -为了解决这些问题,OpenFlare 引入了受 Cloudflare Pages 启发的 **Pages 静态托管** 功能。该功能将“前端部署包上传”与“网站代理规则配置”合二为一,依托 OpenFlare 的 pull-based(拉取式)协同架构,实现静态文件分发与反代配置发布的强一致性、不可变性与一键秒级回滚。 +为了解决这些问题,OpenFlare 引入了受 Cloudflare Pages 启发的 **Pages 静态托管** 功能。该功能将“预构建产物导入”与“网站代理规则配置”纳入同一控制面,依托 OpenFlare 的 pull-based(拉取式)协同架构,以不可变 deployment、单节点原子切换和周期对账实现多 Agent 最终收敛,并支持快速回滚。 --- @@ -20,7 +20,8 @@ Pages 静态托管子系统包含以下核心能力: * **预构建产物部署**:支持直接上传静态资源压缩包,也可为项目保存一个 Remote URL 或公开 GitHub Release asset 来源。外部来源只由 Server 访问,成功同步后统一创建或复用不可变 deployment 并原子激活。 -* **不可变部署快照**:每次上传产生一个带唯一 ID 和 SHA-256 Checksum 的不可变部署记录。支持按系统配置保留最近 N 个历史部署,并可随时激活和回滚。 +* **不可变部署快照**:本地上传每次创建新的候选 deployment;持久来源同步按 source identity/revision 创建或复用 deployment 并激活。所有部署都有唯一 ID 和整包 SHA-256,支持按系统配置保留最近 N 个历史版本并随时回滚。 +* **检查与自动更新**:GitHub latest 可按项目间隔定时检查;默认只提示可用更新,管理员显式开启后才按检查到的精确 revision 自动同步并发布。 * **SPA Fallback 支持**:支持对单页应用(SPA)进行 Fallback 路由配置,请求找不到静态文件时自动重定向到入口文件。 * **内置 API 反代服务**:支持在 Pages 规则内一键启用 API 代理,消除跨域问题,将请求转发给指定的后端服务。 * **安全包校验与解压缩**:内置路径逃逸防御、防软链接劫持、文件大小/数量上限与可配置上传包体积控制,保障节点物理安全。 @@ -30,7 +31,7 @@ Pages 静态托管子系统包含以下核心能力: 项目当前支持 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 → 原子激活”管线,Agent 不感知来源类型。 +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 三个扩展边界。 @@ -50,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` 且该项目存在已激活的部署时,才允许将该路由加入发布流程。 @@ -97,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" } ``` @@ -108,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 拉包的版本锁定。 @@ -117,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}-.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. 多项目对账时 **隔离失败**:单个项目失败记日志并继续其它项目,最后汇总返回错误。 --- @@ -221,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 -------->| ``` diff --git a/docs/guide/pages-usage.md b/docs/guide/pages-usage.md index 1a8936f7..752593ec 100644 --- a/docs/guide/pages-usage.md +++ b/docs/guide/pages-usage.md @@ -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)。 diff --git a/docs/plan/20260719-pages-source-sync-v2.md b/docs/plan/20260719-pages-source-sync-v2.md index 03978901..e37fe907 100644 --- a/docs/plan/20260719-pages-source-sync-v2.md +++ b/docs/plan/20260719-pages-source-sync-v2.md @@ -1,7 +1,7 @@ # Pages 项目部署源与 GitHub Releases 自动更新 V2 实现方案 日期:2026-07-19 -状态:实施中 +状态:代码实施完成(范围内自动化验证完成;生产环境验收见 §7) 方案版本:V2(设计修订版,不代表新增 `/api/v2`) 关联材料: @@ -32,9 +32,9 @@ V2 保留原方案正确的主链路:外部来源只由 Server 控制面访问 ## 1. 目标与背景 (Goal & Context) -### 1.1 当前实现与问题 +### 1.1 方案制定时的实现与问题 -当前 Pages 已支持: +方案制定时,Pages 已支持: * 管理员本地上传压缩包; * 同步调用 `POST /api/v1/d/pages/:id/deployments/upload-from-url` 完成一次性 URL 导入; @@ -149,7 +149,7 @@ flowchart LR Current --> OpenResty["OpenResty 静态服务"] ``` -scanner 本身是一个正式 TaskHandler,并非绕过任务框架。它在单次执行中先限量恢复 lease/orphan record,再串行检查到期的 GitHub latest source,避免一次 cron 批量投递 20 个并行 GitHub 请求。手动操作和自动下载使用统一 action task;scanner 不执行长时间 package 下载。 +scanner 本身是一个正式 TaskHandler,并非绕过任务框架。它在单次执行中先扫描并精确 CAS 恢复全部过期 lease,再限量补偿最多 100 条 orphan record,最后串行检查最多 20 个到期的 GitHub latest source,避免一次 cron 批量投递并行 GitHub 请求。手动操作和自动下载使用统一 action task;scanner 不执行长时间 package 下载。 ### 2.3 领域对象与不变量 @@ -1057,11 +1057,27 @@ scanner TaskResult 和结构化日志至少记录: #### [NEW] `internal/apps/openflare/pages/source_sync.go` -* runtime 状态、lease、check/sync service、原子 create-or-load/activate、补偿。 +* Remote/GitHub 统一 ingest、deployment create-or-load、原子激活与失败补偿。 + +#### [NEW] `internal/apps/openflare/pages/source_runtime.go` + +* source execution snapshot、短/长 lease、heartbeat、失败终态、过期 lease 精确 CAS 恢复。 + +#### [NEW] `internal/apps/openflare/pages/github_source.go`、`github_source_action.go` + +* GitHub 配置归一化,以及 latest/tag check、ETag/304、精确 revision sync、attention 与 provider 退避。 #### [NEW] `internal/apps/openflare/pages/source_tasks.go` -* scanner/action TaskMeta、payload validation、Handler 与限量 orphan record reconciliation。 +* action TaskMeta、旧/新 payload normalization、actor/trigger 边界与 Handler。 + +#### [NEW] `internal/apps/openflare/pages/source_scanner.go` + +* internal-only scanner、过期 lease 恢复、20 条稳定批次、403/429 退避、backlog 与精确 revision 自动派发。 + +#### [NEW] `internal/apps/openflare/pages/source_orphan_cleanup.go`、`internal/model/openflare_pages_cleanup.go` + +* 100 条/2 小时隔离的 orphan upload 候选查询、统一锁序复检、幂等软删除与缓存修复。 #### [MODIFY] `internal/apps/openflare/pages/logics.go` @@ -1207,6 +1223,7 @@ SQLite `0001` 的 Down 必须通过重建受影响表完整移除新增列、约 * `frontend/tests/openflare/pages-service.test.ts` * `frontend/tests/openflare/pages-source-ui.test.tsx` +* `frontend/tests/openflare/pages-source-auto-update.test.tsx` ### 3.5 文档与生成物(代码实施时) @@ -1370,7 +1387,7 @@ make code-check ## 5. 分阶段实施 -### 阶段 0:安全与一致性前置(独立合并/发布) +### 阶段 0:安全与一致性前置(已完成:`4e8ec232`) * RootDir/EntryFile 严格路径与 LocalRoot 端到端一致; * Server 真实归档限制和 tar 流式实现; @@ -1381,7 +1398,7 @@ make code-check 验收:不引入 source 表/API 的情况下,现有本地上传与旧 URL 路径全部回归;大包内存与路径安全测试通过。 -### 阶段 1:数据模型与 Remote 手动同步 +### 阶段 1:数据模型与 Remote 手动同步(已完成:`38b05169`) * `0001` DDL migration、model、source CRUD/view; * runtime 六态、lease、config/content fence; @@ -1393,7 +1410,7 @@ make code-check 本阶段 API/DTO 变化完成后立即运行 `make swagger` 并将生成物纳入阶段验证,不把 Swagger 漂移累积到阶段 4。 -### 阶段 2:GitHub 手动检查与同步 +### 阶段 2:GitHub 手动检查与同步(已完成:`c39a3edc`) * GitHub client、latest/tag、ETag、asset/digest、rate limit; * action check、首次异步 check、update_available; @@ -1405,7 +1422,7 @@ make code-check 本阶段 API/DTO 变化后再次运行 `make swagger`,保证阶段 2 可独立合并发布。 -### 阶段 3:latest scanner 与自动更新 +### 阶段 3:latest scanner 与自动更新(已完成:`848884d8`、`999428cf`、`67b051c2`) * scanner task、`0002` schedule seed、过期 lease 恢复; * serial batch、jitter、退避、自动 sync dispatch; @@ -1418,20 +1435,20 @@ make code-check 本阶段 API/DTO 变化后再次运行 `make swagger`,阶段 4 只做最终一致性复检。 -### 阶段 4:文档、生成物与全门禁 +### 阶段 4:文档、生成物与验证记录(代码收口已完成) * 同步 Pages design/architecture/guide/README 与中文 changelog; * 生成 Swagger; -* 运行前后端测试、`make prettier`、`make code-check`; -* 按 §4.7 完成手工矩阵并记录未覆盖的真实外部场景。 +* 运行前后端测试、`make prettier`、`make code-check`,并如实记录全仓失败与未执行边界; +* 整理 §4.7 手工矩阵,并记录本地环境未覆盖的真实外部场景。 每个阶段只提交本阶段明确路径并独立验证;阶段 0 不与后续 source 功能捆绑成一个大提交。 --- -## 6. 完成定义 +## 6. 生产验收完成定义 -只有同时满足以下条件,V2 才视为实现完成: +只有同时满足以下条件,V2 才视为生产验收完成: * 三类来源能力边界与 UI/API 完全一致; * 数据模型为 config/runtime 分离的瘦表,状态不超过六态; @@ -1442,3 +1459,47 @@ make code-check * history=1、本地 candidate、source sync 和清理补偿均有自动化覆盖; * PostgreSQL、SQLite、后端、Agent、前端及项目门禁全部通过; * 实际代码、Swagger、中文设计/使用文档和 changelog 同步。 + +本轮状态中的“代码实施完成”表示功能、迁移、前后端交互、生成物和文档均已落地,范围内自动化门禁已经通过。真实 PostgreSQL、真实外部来源和多 Agent 故障矩阵仍是生产验收条件;未执行项及全仓存量失败不会在本文中伪报为通过,统一记录如下。 + +--- + +## 7. 实施结果与验证记录 + +### 7.1 已交付 + +* 完成部署包安全与一致性前置:项目 RootDir 生效、归档实际字节限制、Agent 流式下载与复核、历史裁剪、上传记录补偿及共享对象安全边界。 +* 完成 Remote URL 与公开 GitHub Release source/runtime 模型、CRUD、手动 check/sync、revision 幂等、不可变 deployment 与原子激活。 +* 完成 GitHub latest scanner、稳定批次、lease 恢复、ETag/304、403/429 退避、精确 revision 自动派发和 orphan upload 延迟补偿。 +* 完成 Cloudflare Pages 风格的“当前生产部署 → 部署源 → 部署历史”前端交互,并保留独立 `git_repository` Provider、Server build executor 和统一 artifact pipeline 的后续边界。 +* 完成 Swagger、中文 changelog、Pages 设计、总体架构、Agent 设计、使用指南与 README 同步。 + +### 7.2 已通过的自动化验证 + +* `make swagger`:通过,生成物已随实现提交。 +* `make prettier`:通过;格式化产生的三个无关存量文件变化已恢复,未混入提交。 +* `make code-check`:通过;`golangci-lint`、前端 TypeScript 与全量 ESLint 均无错误。 +* Pages、Agent、GitHub Release integration、归档、上传和迁移相关 Go 定向测试通过;关键并发路径的 race 测试通过。 +* SQLite Pages source 与 scanner schedule migration 的 Up/Down/Up 通过。 +* 前端 Pages 四个测试文件共 34 条用例全部通过,相关 TypeScript 与 ESLint 检查通过。 +* 全仓 Go 测试中 `internal/apps/openflare/pages`、`internal/apps/openflare/agent`、`internal/integration/githubrelease`、`internal/db/migrator`、`internal/apps/upload/*` 与 `pkg/pagesarchive` 均通过。 + +### 7.3 全仓测试中仍存在的范围外失败 + +* `go test ./... -count=1` 未全绿:`internal/apps/admin/system_config` 仍按旧快照断言 32 条默认配置和 3 条 business 配置,实际为 34 和 5;本分支未修改该模块。 +* `internal/apps/flared/frpc` 的 `TestUnexpectedExit0CPUProtection`、`TestBackoffReset` 及 `internal/apps/relay/frps` 的 `TestUnexpectedExitAndAutorestart` 存在进程状态时序失败。 +* `internal/apps/openflare/tasks` 的 `TestRunSSLRenewJobTriggersDueCertificates` 连接本机 Redis 时收到 `NOAUTH Authentication required`,证书状态因此未进入 `applying`。 +* 前端全仓 Vitest 共 101 条通过、1 条失败:`frontend/tests/zone/zone-page.test.tsx:116` 仍查找旧文案“唯一访问者”;Pages 的 34 条测试不受影响。 + +这些失败点不属于本次 Pages V2 功能路径,因此未通过扩大范围修改存量模块来掩盖;它们仍应在各自模块后续收敛。 + +### 7.4 本地环境未执行的生产验收 + +* `OPENFLARE_TEST_POSTGRES_DSN` 未设置,因此 PostgreSQL migration Up/Down/Up、JSONB orphan 候选矩阵和真实行锁竞争未执行;SQLite 对应路径已通过。 +* 未访问真实公开 GitHub Release,也未以真实服务验证 GitHub rate limit、asset redirect、Remote public DNS rebinding 或 `trusted_internal` 自签 TLS;普通测试均使用可注入 client/`httptest.Server` 覆盖协议分支。 +* 未执行多 Agent 实机收敛,以及 Server/Worker 在下载、Ingest、最终提交阶段的进程级中断矩阵。 +* 前端交互由 React Testing Library 覆盖,未执行真实浏览器 E2E。 + +### 7.5 剩余验证债务 + +当前 orphan/deployment 竞态测试能验证锁序与事务结果,但 SQLite 串行执行不能替代 PostgreSQL 下两个独立事务的真实行锁竞争。生产发布前应在可用 PostgreSQL 测试实例上补跑 migration、JSONB 候选和双事务交错测试,并按 §4.7 完成真实网络与多 Agent 最小矩阵。 diff --git a/docs/plan/index.md b/docs/plan/index.md index 07ee80d4..0ef1905f 100644 --- a/docs/plan/index.md +++ b/docs/plan/index.md @@ -19,7 +19,10 @@ * [访问日志 cache_status 明细可见](./20260718-access-log-cache-status.md):上报 `$upstream_cache_status`,明细展示命中/回源/未缓存三态。 * [边缘缓存默认 static 策略](./20260718-edge-cache-static-default.md):开启缓存默认仅静态扩展名;存量 url→all。 * [访问日志 IP 明细 Tab](./20260719-access-log-ip-tab.md):第三 Tab 按 IP 聚合列表(时间窗/流量/2xx 比例);IP 情报迁入独立详情;日志详情仅请求字段。 -* [Pages 项目部署源与 GitHub Releases 自动更新 V2](./20260719-pages-source-sync-v2.md):统一 Remote URL / GitHub Release 来源、不可变部署、自动检查更新与安全回滚,并预留独立仓库构建 Provider 边界。 + +## 已完成的计划 + +* [Pages 项目部署源与 GitHub Releases 自动更新 V2](./20260719-pages-source-sync-v2.md):已完成 Remote URL / GitHub Release 来源、不可变部署、自动检查更新与安全回滚,并预留独立仓库构建 Provider 边界;生产环境验收边界见计划内验证记录。 ## 使用建议