diff --git a/.hermes/plans/2026-05-11_205645-genarrative-disaster-recovery.md b/.hermes/plans/2026-05-11_205645-genarrative-disaster-recovery.md new file mode 100644 index 00000000..99985241 --- /dev/null +++ b/.hermes/plans/2026-05-11_205645-genarrative-disaster-recovery.md @@ -0,0 +1,447 @@ +# Genarrative 容灾方案设计计划 + +> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task. + +**Goal:** 基于当前 Genarrative 单机生产部署、Jenkins 流水线、SpacetimeDB 与 Rust `api-server` 架构,补齐一套可落地、可演练、可审计的容灾方案。 + +**Architecture:** 首版容灾不引入复杂多活系统,优先围绕现有 `systemd + Nginx + SpacetimeDB + api-server + Jenkins` 单机生产推荐方案做“备份可恢复、版本可回滚、故障可切换、演练可复盘”。方案采用分层容灾:入口层、静态资源层、API 服务层、SpacetimeDB 数据层、外部服务与密钥层、Jenkins/发布链路层。 + +**Tech Stack:** Nginx、systemd、SpacetimeDB self-hosting、Rust `api-server` / Axum、Jenkins Pipeline、Shell/Node.js 运维脚本、仓库 `deploy/` 与 `docs/technical/` 文档体系。 + +--- + +## 1. 当前上下文与已确认事实 + +### 1.1 当前生产部署口径 + +来自 `docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md` 的现状: + +- 生产为单机推荐方案,不使用 Docker。 +- 公网入口为 Nginx,负责 HTTPS、静态站点、后台静态页面、维护页、`/admin/api/` 与临时 `/api/*` 反向代理。 +- SpacetimeDB 作为 systemd 服务运行: + - `spacetimedb.service` + - 监听:`127.0.0.1:3101` + - 数据根目录:`/stdb` +- Rust `api-server` 作为 systemd 服务运行: + - `genarrative-api.service` + - 监听:`127.0.0.1:8082` + - 环境文件:`/etc/genarrative/api-server.env` +- 静态站点发布到 release/current 目录: + - `/opt/genarrative/releases//` + - `/opt/genarrative/current` + - `/srv/genarrative/web` +- 已有维护模式: + - 开关文件:`/var/lib/genarrative/maintenance/enabled` + - API 发布、SpacetimeDB 模块发布、数据库导入、服务器配置变更必须进入维护模式。 +- 已有数据库导入导出 Jenkins Job: + - `Genarrative-Database-Export` + - `Genarrative-Database-Import` + - 对应文件:`jenkins/Jenkinsfile.production-database-export`、`jenkins/Jenkinsfile.production-database-import` +- 已有回滚基本口径: + - Web 回滚:切 `/srv/genarrative/web` 或 `/opt/genarrative/current` 到上一版本并 reload Nginx。 + - API 回滚:切 `/opt/genarrative/current` 到上一版本并重启 `genarrative-api.service`。 + - SpacetimeDB 模块回滚:发布上一版本 `spacetime_module.wasm`。 + - 数据回滚:使用导入流水线恢复指定备份,必须进入维护模式。 + +### 1.2 关键风险 + +- 当前是单机生产拓扑,单机磁盘、系统盘、`/stdb`、Nginx 或公网 IP 故障会造成整体不可用。 +- SpacetimeDB 是核心业务真相,容灾重点必须围绕 `/stdb`、数据库导出产物、schema 迁移与导入验证。 +- `/etc/genarrative/api-server.env` 持有生产密钥,不能进入 Git,也不能写进普通备份明文归档。 +- Jenkins controller/agent 同时承担构建、发布、备份、导入导出编排;Jenkins 不可用时仍需要有最小人工恢复路径。 +- 外部 LLM、图片、语音、3D 网关不是本仓库可控系统,容灾只能做到配置降级、超时隔离、能力熔断与可观测告警。 + +--- + +## 2. 容灾目标 + +### 2.1 恢复目标建议 + +| 灾难类型 | 目标 RTO | 目标 RPO | 首版策略 | +| --- | ---: | ---: | --- | +| Web 静态资源发布失败 | 5 分钟 | 0 | release/current 原子切换回滚 | +| API 发布失败 | 10 分钟 | 0 | 维护模式 + 上一版二进制回滚 | +| SpacetimeDB wasm 发布失败 | 15 分钟 | 0 或按迁移前备份 | 发布前导出 + 上一版 wasm 回滚 | +| 数据误写 / 迁移失败 | 30-60 分钟 | 最近一次导出点 | 导入流水线从备份恢复 | +| 生产机磁盘损坏 | 2-4 小时 | 最近一次异地备份 | 新机器 provision + 拉取 release 包 + 恢复数据库 | +| Jenkins controller 不可用 | 1-2 小时 | 不影响线上数据 | 手工脚本恢复 + Jenkins 备份恢复 | +| 第三方模型网关不可用 | 5-15 分钟内降级 | 不丢核心数据 | 配置切换 / 功能熔断 / 队列失败可重试 | + +### 2.2 首版不做 + +- 不做跨地域双活写入。 +- 不做 SpacetimeDB 在线主从复制,除非后续官方能力与项目压测验证支持。 +- 不让前端绕过 `api-server` 直接承担正式业务真相。 +- 不把生产密钥、Token、数据库 dump、Jenkins secret 写入 Git。 +- 不恢复旧 `server-node`、Express、PostgreSQL 或 Docker 一体化部署方案。 + +--- + +## 3. 总体容灾设计 + +### 3.1 分层策略 + +1. **入口层:Nginx / DNS / HTTPS** + - 保留 Nginx 配置模板在 Git:`deploy/nginx/genarrative.conf`、`deploy/nginx/genarrative-dev-http.conf`。 + - 为 release 环境建立 Nginx 配置备份与证书恢复流程。 + - 明确 DNS 切换预案:生产机不可恢复时,将域名指向灾备机公网 IP。 + +2. **静态资源层:Web / Admin Web** + - 依赖 `web.tar.gz`、`web.tar.gz.sha256`、`release-manifest.json`。 + - 保留最近 N 个 release 目录与构建产物指针。 + - 回滚只切软链,不重新构建。 + +3. **API 服务层:Rust `api-server`** + - 依赖归档的 `api-server` 二进制、checksum、`release-manifest.json`。 + - `/etc/genarrative/api-server.env` 通过加密备份或密钥管理恢复,不进入 release 包。 + - systemd unit 由 `deploy/systemd/genarrative-api.service` 重新安装。 + +4. **数据层:SpacetimeDB** + - 每次高风险发布前强制导出数据库。 + - 定时导出:建议每天至少 1 次;高活跃期可每 4 小时 1 次。 + - 导出产物同时保存在:Jenkins 归档 + 生产机 `SERVER_BACKUP_DIRECTORY` + 异地对象存储/备份机。 + - 导入前自动生成安全备份,保留当前实现口径。 + +5. **发布编排层:Jenkins** + - Jenkins Job、Jenkinsfile 在 Git 中可恢复。 + - Jenkins controller 配置、凭据、插件清单需要额外备份。 + - 发布 agent 使用 inbound + systemd 自恢复,agent secret 仅存在目标机或 Jenkins 凭据。 + +6. **密钥与外部服务层** + - `/etc/genarrative/api-server.env`、Jenkins Secret Text、SSH PEM、agent secret 不进 Git。 + - 制定密钥清单和恢复责任人,但不在仓库记录明文。 + - 外部服务配置按 `docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md` 维护必配项。 + +--- + +## 4. 建议新增/更新的文档 + +### Task 1: 新增生产容灾技术方案文档 + +**Objective:** 形成团队可共享、可执行的容灾总纲。 + +**Files:** +- Create: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md` +- Modify: `docs/technical/README.md`(若已有技术索引,应加入该文档入口) +- Optional Modify: `.hermes/shared-memory/project-overview.md`(只加稳定索引,不写敏感信息) + +**文档必须覆盖:** + +1. 容灾目标:RTO/RPO 表。 +2. 生产资产清单:Nginx、systemd、release/current、`/stdb`、`/etc/genarrative/api-server.env`、Jenkins、构建产物。 +3. 备份策略: + - 数据库导出。 + - release 产物保留。 + - Nginx/systemd/env 配置备份。 + - Jenkins 配置备份。 +4. 恢复流程: + - Web 回滚。 + - API 回滚。 + - Stdb module 回滚。 + - 数据恢复。 + - 整机重建。 +5. 演练计划:每月一次数据库恢复演练,每季度一次整机重建演练。 +6. 安全边界:密钥不进 Git,备份加密,最小权限。 +7. 验收命令与人工检查清单。 + +**Verification:** + +```bash +npm run check:encoding +``` + +Expected: PASS,无中文乱码、无 BOM/CRLF 问题。 + +--- + +## 5. 建议新增/更新的脚本与流水线 + +### Task 2: 增强数据库定时备份流水线 + +**Objective:** 把现有人工导出扩展为可定时执行、可异地保存、可审计的备份流程。 + +**Files:** +- Modify: `jenkins/Jenkinsfile.production-database-export` +- Modify: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md` +- Optional Create: `scripts/deploy/production-backup-sync.sh` + +**Implementation notes:** + +- 在 Jenkins Job 中保留人工触发能力,同时建议配置 cron: + - development:每天凌晨。 + - release:每天凌晨或业务低峰。 +- 增加备份命名规范: + - `spacetime-migration---.json` +- 增加 `SERVER_BACKUP_DIRECTORY` 默认建议: + - `/var/backups/genarrative/spacetimedb//` +- 增加备份保留策略: + - 本机保留 7-14 天。 + - 异地保留 30-90 天。 +- 如实现 `production-backup-sync.sh`,只做同步框架,不硬编码真实 bucket、账号、endpoint 或密钥。 + +**Verification:** + +```bash +bash -n scripts/deploy/production-backup-sync.sh +npm run check:encoding +``` + +Expected: shell 语法通过;文档编码检查通过。 + +--- + +### Task 3: 增加灾备恢复 Runbook + +**Objective:** 在真正故障时不依赖临场推理,按清单执行恢复。 + +**Files:** +- Create: `docs/operations/PRODUCTION_DR_RUNBOOK_2026-05-11.md` +- Modify: `docs/operations/README.md`(如果存在) + +**Runbook sections:** + +1. 故障分级:P0/P1/P2。 +2. 第一响应: + - 判断 Nginx 是否在线。 + - 判断 `genarrative-api.service` 是否在线。 + - 判断 `spacetimedb.service` 是否在线。 + - 判断磁盘是否满。 + - 判断 Jenkins agent 是否在线。 +3. 快速止血: + - 开维护模式。 + - 禁止继续发布。 + - 保留现场日志。 +4. 回滚流程: + - Web 回滚命令。 + - API 回滚命令。 + - Stdb wasm 回滚命令。 +5. 数据恢复流程: + - 选择备份。 + - dry-run 导入。 + - 确认导入。 + - smoke test。 +6. 整机重建流程: + - 新机器 provision。 + - 恢复 `/etc/genarrative/api-server.env`。 + - 恢复 SpacetimeDB 数据。 + - 发布最近稳定 release。 + - DNS 切换。 +7. 复盘模板。 + +**Verification:** + +```bash +npm run check:encoding +``` + +Expected: PASS。 + +--- + +### Task 4: 增加备份健康检查与恢复演练记录模板 + +**Objective:** 防止“有备份但不可恢复”。 + +**Files:** +- Create: `docs/operations/DR_DRILL_REPORT_TEMPLATE.md` +- Optional Create: `scripts/deploy/verify-database-backup.sh` +- Modify: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md` + +**建议检查项:** + +- 备份文件存在且大小非 0。 +- 备份文件 checksum 可验证。 +- 备份文件可被 `Genarrative-Database-Import` dry-run 解析。 +- 最近一次备份时间未超过 RPO 阈值。 +- 导入后 `/healthz` 可用。 +- 首页、后台登录页、关键 API smoke 可用。 + +**Verification:** + +```bash +bash -n scripts/deploy/verify-database-backup.sh +npm run check:encoding +``` + +Expected: PASS。 + +--- + +## 6. 具体恢复流程草案 + +### 6.1 Web 静态资源回滚 + +1. 进入目标机。 +2. 查看 release 目录:`/opt/genarrative/releases/`。 +3. 选择上一个稳定版本。 +4. 切换 `/srv/genarrative/web` 或 `/opt/genarrative/current` 软链。 +5. 执行 Nginx 配置检查与 reload。 +6. 访问首页与后台静态入口。 + +验收: + +- `/` 返回最新稳定页面。 +- `/admin/` 返回后台页面。 +- 静态资源无 404。 + +### 6.2 API 回滚 + +1. 开维护模式。 +2. 切 `/opt/genarrative/current` 到上一版包含稳定 `api-server` 的 release。 +3. 重启 `genarrative-api.service`。 +4. 本机检查 `http://127.0.0.1:8082/healthz`。 +5. 检查 Nginx 反代路径。 +6. 解除维护模式。 + +验收: + +- `systemctl status genarrative-api.service` 正常。 +- `/healthz` 正常。 +- 后台 `/admin/api/*` 基础接口正常。 + +### 6.3 SpacetimeDB 模块回滚 + +1. 开维护模式。 +2. 确认目标数据库名与当前 API 环境一致:`GENARRATIVE_SPACETIME_DATABASE`。 +3. 选择上一版 `spacetime_module.wasm`。 +4. 使用 `spacetimedb` 服务用户发布上一版 wasm。 +5. 重启或检查 `spacetimedb.service`。 +6. 检查 `api-server` 对目标数据库访问。 +7. 解除维护模式。 + +注意:如果 schema 已迁移且旧 wasm 不兼容当前数据,需要走数据恢复,不应直接盲目发布旧 wasm。 + +### 6.4 数据恢复 + +1. 开维护模式。 +2. 从 Jenkins 归档或 `SERVER_BACKUP_DIRECTORY` 选择备份。 +3. 先执行导入 dry-run。 +4. 真正导入前生成当前数据库安全备份。 +5. 执行导入。 +6. 执行 smoke test。 +7. 解除维护模式。 + +必须记录: + +- 备份文件名。 +- 来源 Job/build number。 +- 恢复目标 database。 +- 恢复开始/结束时间。 +- 恢复后验证结果。 + +### 6.5 整机重建 + +1. 准备新 Linux 机器。 +2. 接入 Jenkins release deploy agent,或准备人工 SSH 运维路径。 +3. 运行 `Genarrative-Server-Provision`: + - 创建用户和目录。 + - 安装 SpacetimeDB。 + - 安装 systemd unit。 + - 安装 Nginx 配置。 +4. 恢复 `/etc/genarrative/api-server.env`。 +5. 发布最近稳定 Web/API/Stdb 产物。 +6. 导入最近一次有效数据库备份。 +7. smoke test。 +8. 切 DNS。 +9. 观察 30-60 分钟。 + +--- + +## 7. 文件可能变更清单 + +首版落地建议按以下文件收口: + +- Create: `docs/technical/PRODUCTION_DISASTER_RECOVERY_PLAN_2026-05-11.md` +- Create: `docs/operations/PRODUCTION_DR_RUNBOOK_2026-05-11.md` +- Create: `docs/operations/DR_DRILL_REPORT_TEMPLATE.md` +- Modify: `docs/technical/README.md` +- Modify: `docs/operations/README.md`(若存在) +- Modify: `.hermes/shared-memory/project-overview.md`(仅增加文档索引) +- Optional Modify: `jenkins/Jenkinsfile.production-database-export` +- Optional Modify: `jenkins/Jenkinsfile.production-database-import` +- Optional Create: `scripts/deploy/production-backup-sync.sh` +- Optional Create: `scripts/deploy/verify-database-backup.sh` + +--- + +## 8. 测试与验收 + +### 8.1 文档与编码 + +```bash +npm run check:encoding +``` + +Expected: PASS。 + +### 8.2 Shell 脚本语法 + +如新增 shell 脚本: + +```bash +bash -n scripts/deploy/production-backup-sync.sh +bash -n scripts/deploy/verify-database-backup.sh +``` + +Expected: PASS。 + +### 8.3 Jenkinsfile 静态检查 + +建议在 Jenkins UI 或本地 Jenkins Pipeline Linter 中检查: + +- `jenkins/Jenkinsfile.production-database-export` +- `jenkins/Jenkinsfile.production-database-import` + +Expected: Pipeline syntax valid。 + +### 8.4 演练验收 + +至少完成一次 development 目标演练: + +1. 触发 `Genarrative-Database-Export`。 +2. 确认备份产物存在并归档。 +3. 使用 `Genarrative-Database-Import` dry-run 验证备份可解析。 +4. 不覆盖生产数据的前提下,记录演练报告。 + +release 目标演练应在业务低峰进行,并先确认通知渠道可用。 + +--- + +## 9. 风险、取舍与开放问题 + +### 9.1 风险 + +- 单机生产仍存在物理机级单点故障,首版只能通过“快速重建 + 异地备份”降低恢复时间。 +- SpacetimeDB schema 回滚不一定可逆,必须把发布前备份作为强约束。 +- Jenkins controller 若在本地 Windows,controller 自身备份和恢复需要单独制定,不应只依赖 agent 自恢复。 +- 外部模型网关失败可能影响创作能力,但不应影响已发布作品浏览和后台基础能力。 + +### 9.2 取舍 + +- 选择先做可执行 runbook 和备份恢复演练,而不是直接引入复杂多活。 +- 选择继续复用现有 Jenkins 导入导出流水线,降低工程改造风险。 +- 选择不把密钥恢复细节写死到 Git 文档,避免泄露。 + +### 9.3 开放问题 + +1. release 环境是否已经有独立备份机或对象存储?如果有,需要补充备份同步目标,但不能提交密钥。 +2. Jenkins controller 的 `JENKINS_HOME` 当前实际部署在哪里?是否已有周期备份? +3. 生产域名 DNS TTL 当前是多少?是否可降低到适合故障切换的值? +4. `/stdb` 所在磁盘是否独立于系统盘?是否已有磁盘水位告警? +5. release 环境的通知渠道除邮件外是否需要接入企业微信/飞书/Telegram? + +--- + +## 10. 推荐实施顺序 + +1. 先只落文档:技术方案 + runbook + 演练模板。 +2. 在 development 目标做一次数据库导出 + dry-run 导入演练。 +3. 根据演练结果补脚本:备份同步、备份健康检查。 +4. 再把 release 备份设置为定时任务。 +5. 最后规划整机重建演练与 DNS 切换演练。 + +首版完成标准: + +- 团队任一成员打开 runbook,即可在 30 分钟内完成 Web/API 回滚或数据库备份 dry-run 恢复。 +- 最近一次数据库备份时间、备份位置、checksum、恢复演练结果可追溯。 +- 生产密钥仍只存在于服务器/Jenkins 凭据/加密备份中,不进入 Git。