给内测补上每日加密备份、宕机告警和日志上限。

主机安装和一次恢复演练留给产品。页面没有变化。

Co-Authored-By: Grok (agent loop)
This commit is contained in:
jesse-ux
2026-10-15 12:58:34 +08:00
co-authored by Grok (agent loop)
parent 71f7fac3a1
commit 53d8df257d
19 changed files with 1761 additions and 2 deletions
@@ -0,0 +1,63 @@
# 生产备份恢复演练(2026-10-14)
给产品负责人照着做。这一次演练还原到一次性库,不碰正在服务的主库,网站不用停。真正整库还原会停写,放在文末,只有确认要覆盖现网数据时才做。
不要把备份文件、密钥、数据库口令、出生资料、姓名、邮箱或会话内容写进仓库、聊天或这份记录。
## 备份在哪里
- 每天北京时间 04:00,从 `main` 自动跑「Backup Production Daily」。也可以在 Gitea 里手动点一次。它只在 `main` 上跑。
- 加密文件在生产主机 `/opt/jyotisha-production/backups/`,文件名是 `production-daily-日期T时间Z.dump.enc`。旁边有同名的 `-restore-verify.json`,里面只有行数和权限是否一致,没有用户正文。
- 手动恢复点仍叫 `production-pre-migration-...`。每日清理只删过期的 `production-daily-` 文件,不会删手动恢复点。
- Gitea 上的加密产物默认留 14 天。主机上的每日文件默认留 7 天。
- 加密方式与现有恢复点相同:AES-256-CBC、PBKDF2。密钥在主机的数据库环境文件里,不要打印。
## 一次性库演练(不停机)
在生产主机上用 `deploy` 用户做。下面的库名换成当天的日期,不要用正在服务的那个库名。
1. 选定一个 `production-daily-*.dump.enc`。确认旁边的校验文件写着 `"ok": true` 和 `"restore_database_removed": true`。
2. 进入 `/opt/jyotisha-production`。数据库口令和加密密钥已经在 `.env.production.database` 里。只在这台机器上读取,不要回显。
3. 确认生产 Postgres 容器只有一个。容器名用 `docker ps` 看,下面记成 `<postgres>`。
4. 建一次性库,名字必须不是主库名,例如 `restore_drill_20261015`:
```bash
sudo -n docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" <postgres> \
psql -U "$POSTGRES_USER" -d postgres -v ON_ERROR_STOP=1 \
-c 'CREATE DATABASE "restore_drill_20261015";'
```
5. 解密并还原进这个一次性库。不要把输出重定向到主库。
```bash
openssl enc -d -aes-256-cbc -pbkdf2 \
-pass env:PRODUCTION_BACKUP_ENCRYPTION_KEY \
-in /opt/jyotisha-production/backups/production-daily-日期T时间Z.dump.enc |
sudo -n docker exec -i -e PGPASSWORD="$POSTGRES_PASSWORD" <postgres> \
pg_restore -U "$POSTGRES_USER" -d restore_drill_20261015 --exit-on-error
```
6. 只记四个数字:`identity.users`、`public.profiles`、`public.credit_transactions`、`public` 里的表张数。不要把表里的内容抄出来。
7. 删掉一次性库,并确认它已经不在:
```bash
sudo -n docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" <postgres> \
psql -U "$POSTGRES_USER" -d postgres -v ON_ERROR_STOP=1 \
-c 'DROP DATABASE "restore_drill_20261015" WITH (FORCE);'
```
8. 把四个数字、文件名(不要附文件)、一次性库是否已删除,填进 `docs/testing/production-restore-drill-20261014.md`。
通过:还原命令退出码是 0,四个数字都是非负整数,一次性库已删除,主库没有被这条命令点名。
失败:停在失败的那一步。不要为了演练去改主库。把退出码交给验收,不要附带环境文件。
## 整库还原(会停机,平时不要做)
只有在确认现网库要被这份备份覆盖时才做。预留 30 分钟停写。这不是实测时间,数据变大就再加。
1. 停掉 `web` 和 `api`,让没有新的写入。Postgres 先留着。
2. 用上面同一条解密和 `pg_restore`,目标改成主库名。这一步会覆盖主库里的现有数据。
3. 核对四个行数,并对照校验文件里的权限三项是否为真。格式版本低于 2 的旧备份不要拿来做这件事。
4. 再启动 `web` 和 `api`。打开 `https://jyotisha.chat/api/health`,`status` 应为 `ok`。
5. 把实际停了多久填进演练模板。不要写用户内容。