diff --git a/docs/api.md b/docs/api.md index 5471a9fae..0c4aa753d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -230,7 +230,81 @@ auditRetentionDays `rotationStrategy` 可取 `smart`、`quota_reset_priority`、`round_robin`、`sticky`。 -## 8. Dashboard、用量与错误 +## 8. 备份 + +全部备份端点位于 `/api/admin/settings/backups/*`,内部由独立 BackupService 承担,不并入设置用例。响应继续使用 `AdminEnvelope`,wire 字段 camelCase,`Cache-Control: no-store`。 + +| 方法 | 路由 | 请求 | 说明 | +| --- | --- | --- | --- | +| `GET` | `/api/admin/settings/backups` | 无 | 读取存储配置(含明文 Secret)、验证状态与调度配置 | +| `POST` | `/api/admin/settings/backups/storage/update` | S3 配置 | 更新存储配置;`secretAccessKey` 为空字符串会校验失败 | +| `POST` | `/api/admin/settings/backups/storage/test` | 无 | 测试已保存的存储配置(Put/Head/Get/Delete 探针) | +| `POST` | `/api/admin/settings/backups/schedule/update` | 调度配置 | 更新 Cron、时区与保留策略 | +| `GET` | `/api/admin/settings/backups/records` | 查询参数 | 分页查询备份记录 | +| `POST` | `/api/admin/settings/backups/create` | `{ expiresInDays? }` | 创建手动备份,返回 `202 Accepted`;`expiresInDays` 为过期天数(0 或缺省表示不过期) | +| `POST` | `/api/admin/settings/backups/download-url` | `{ backupId }` | 创建 5 分钟有效预签名下载地址(仅 completed) | +| `POST` | `/api/admin/settings/backups/delete` | `{ backupId }` | 请求删除(进入 `deleting`,由 Worker 收敛硬删除) | + +读取设置响应(Secret 以明文返回,由前端掩码显示): + +```text +storageRevision, endpoint, region, bucket, accessKeyId, secretAccessKey, prefix, +forcePathStyle, verified, scheduleEnabled, cronExpression, scheduleTimezone, +retentionDays, retentionCount, nextRunAt, lastVerifiedAt, updatedAt +``` + +更新存储请求字段: + +```text +endpoint, region, bucket, accessKeyId, secretAccessKey, prefix, forcePathStyle +``` + +`secretAccessKey` 为空字符串会校验失败;由于 GET 会回传已保存的明文 Secret,保存时始终整体提交当前值。已有备份记录时,endpoint/region/bucket/forcePathStyle 不允许变化(存储身份锁定,`409`);只允许轮换凭据与修改 prefix。 + +更新调度请求字段: + +```text +scheduleEnabled, cronExpression, scheduleTimezone, retentionDays, retentionCount +``` + +`cronExpression` 为 5 段格式;`retentionDays`/`retentionCount` 为 0 表示禁用对应清理。启用计划前必须已保存完整存储配置且通过连接测试。 + +记录列表查询参数: + +```text +page, pageSize, status, trigger +``` + +`status` 可取 `queued/dumping/uploading/completed/failed/deleting`;`trigger` 可取 `manual/scheduled`。记录响应字段: + +```text +id, triggerKind, status, scheduledAt, objectKey, sizeBytes, sha256, attemptCount, +errorCode, errorMessage, startedAt, completedAt, expiresAt, createdAt, updatedAt +``` + +`expiresAt` 为创建时确定的手动过期时间;到期后由 Worker 自动清理(手动与计划备份均可设置)。 + +连接测试响应: + +```text +{ ok, stage, code, message } +``` + +`stage` 为 `putObject/headObject/getObject/deleteObject`。探测成功后以 `storageRevision` CAS 写入 `lastVerifiedAt`;测试期间配置变化则丢弃结果。 + +备份错误映射(`AdminErrorCode` 既有体系): + +| HTTP | 场景 | +| --- | --- | +| `400` | 配置、Cron、时区或状态参数无效 | +| `404` | 备份记录不存在 | +| `409` | 已有活跃任务、状态冲突或存储身份锁定 | +| `502` | S3 兼容服务返回无效或失败响应 | +| `503` | PostgreSQL、`pg_dump` 或对象存储暂不可用 | + +审计动作:`backup.s3_config_updated`、`backup.s3_connection_tested`、`backup.schedule_updated`、`backup.created`、`backup.download_url_created`、`backup.delete_requested`。审计详情与记录表均不保存 Secret、数据库连接串或预签名 URL query。 + +## 9. Dashboard、用量与错误 | 方法 | 路由 | 说明 | | --- | --- | --- | @@ -255,7 +329,7 @@ OpenAI 的 `serviceTier` 只接受上游响应生命周期事件确认的实际 `flex` 映射为 `Flex`,缺失或 `default` 映射为 `Default`;未知非空值原样展示。Fast 优先使用模型的 priority 价格,缺少专用价格时回退到标准价格的 `2.00x`;Flex 为 `0.50x`,Default 为 `1.00x`。 -## 9. 版本、更新与重启 +## 10. 版本、更新与重启 | 方法 | 路由 | 主要 query/body | 说明 | | --- | --- | --- | --- | diff --git a/docs/architecture.md b/docs/architecture.md index cdece0ae9..b6dc92186 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -26,8 +26,8 @@ backend/ ├── apps/gateway/ composition root(Bundle 装配) ├── crates/gateway-core/ operation、routing、engine、policy、accounting ├── crates/gateway-protocol/ 可共享 wire contract 与 canonical event -├── crates/gateway-admin/ 管理领域、用例与抽象端口 -├── crates/gateway-store/ PostgreSQL、Redis adapter +├── crates/gateway-admin/ 管理领域、用例、抽象端口与备份 Worker 策略 +├── crates/gateway-store/ PostgreSQL、Redis、S3/R2 与 pg_dump adapter ├── crates/gateway-api/ OpenAI Responses 与 Admin HTTP adapter ├── crates/gateway-host/ 配置与日志、HTTP serve/drain、worker 运行时、自更新 ├── crates/providers/openai/ OpenAI credential、catalog、transport @@ -41,7 +41,7 @@ backend/ - `gateway-protocol` 不依赖其他 workspace crate。 - Provider crate 之间禁止互相依赖。 - `gateway-api` 只面向 Admin/Core/Protocol 抽象,不导入具体 Provider。 -- `gateway-store` 实现 Admin/Core 端口,但不拥有业务策略。 +- `gateway-store` 实现 Admin/Core 端口与备份基础设施(S3/R2、`pg_dump`、受控暂存),但不拥有业务策略。 - 只有 `apps/gateway` 组合具体实现。 依赖 DAG 主要由上述约定与评审维护,机器校验只覆盖两处:`gateway-api` 的 architecture 测试冻结自身源码树与测试树,并禁止 manifest 出现 store/Provider/基础设施依赖(禁止清单不含 `gateway-host`);`apps/gateway` 的 architecture 测试冻结应用源码树、禁止生产测试挂载并约束 bootstrap 只做装配。其余 crate 没有 architecture 测试。 @@ -146,7 +146,7 @@ Admin API 不要求客户端提交配置 revision,也不向客户端暴露配 ## 8. PostgreSQL 终态 -`backend/migrations/0001_initial.sql` 创建且只创建七张业务表: +`backend/migrations/0001_initial.sql` 创建且只创建七张业务表;`0003_s3_backup.sql` 增加备份配置与备份记录两张表: | 表 | 权威事实 | | --- | --- | @@ -157,6 +157,16 @@ Admin API 不要求客户端提交配置 revision,也不向客户端暴露配 | `provider_accounts` | 账号资料、Provider-owned 明文 credential JSON、revision、quota、cooldown | | `model_requests` | 请求、attempt、计费、交付与恢复事实 | | `ops_events` | 脱敏运行事件 | +| `backup_settings` | S3/R2 存储、Cron 计划与保留策略单例配置 | +| `backup_records` | 备份任务状态与归档事实(`queued/dumping/uploading/completed/failed/deleting`) | + +备份设计边界见 `docs/s3-backup-design-audit.md`:单副本部署下备份由单个可取消 +`DaemonTask` 承担调度、执行、删除收敛与保留清理,不需要 leader lease、fencing token 或 +heartbeat;计划时间点冲突只记录日志/指标并推进游标,不产生 skipped 记录;删除成功后 +记录硬删除,操作历史进入 `admin_audit_events`。管理员身份事实不重复写入 +`backup_records`。存储身份在存在记录时锁定:endpoint/region/bucket/path-style 不允许 +变化,只允许轮换凭据与修改 prefix。每份备份可设置创建时确定的手动过期时间 +`expires_at`(手动与计划备份均可,`expiresInDays` 天数生成),到期由 Worker 自动清理。 设计规则: @@ -202,12 +212,16 @@ claim owner 可以释放,账号事务提交后才原子删除整个 pending ke ## 10. 后台任务与恢复 -HTTP serve/drain 与 worker 运行时都在 `gateway-host`:Host 负责注册校验、leader lease、失败退避、健康暴露与关闭;`apps/gateway` 的 bootstrap 只装配。WorkerContribution 由 Store、Core 与两个 Provider bundle 显式贡献,host bundle 自身不贡献任何 worker: +HTTP serve/drain 与 worker 运行时都在 `gateway-host`:Host 负责注册校验、leader lease、失败退避、健康暴露与关闭;`apps/gateway` 的 bootstrap 只装配。WorkerContribution 由 Store、Core、Admin 与两个 Provider bundle 显式贡献,host bundle 自身不贡献任何 worker: - Store:stale execution recovery、retention,以及 PostgreSQL execution observation、Redis client admission release 和 Provider circuit feedback 三个 OpsFlush daemon。native continuation 直接写 Redis, 单次 Lua 记录只清理有界数量的过期/超限索引项,避免无界阻塞。 - Core:RuntimeSnapshot revision 周期对账(无 lease,逐副本运行)与 Redis change 长驻订阅(daemon,逐副本运行)。 +- Admin:备份 daemon(`WorkerKind::Backup`)。单副本边界下它是一个可取消 `DaemonTask`,内部循环推进 + Cron 游标、恢复中间状态、完成删除收敛、领取并执行一个 queued 任务、执行一小批保留清理;长时间 + `pg_dump` 与 multipart 上传由 daemon 自身持有,Host 只负责 panic 后重启、健康与关闭。它不使用 + Redis leader lease(单副本无竞争),只依赖 PostgreSQL 部分唯一约束防御并发 API/调度创建。 - Provider:credential refresh、quota/catalog 健康,以及模型 etag 与官方版本 release 检查(OpenAI CLI、OpenAI Desktop、xAI CLI)。 带 lease 的周期任务在每个周期开始向 Redis 申请一次 leader lease,周期内续租,周期结束即释放。这只是单周期互斥,不是跨周期 leader 选举:各副本独立计时,N 副本部署下同一任务的实际执行频率最坏可达单副本的 N 倍。