Files
codex-app-server/deploy/postgres-rebuild.md

110 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Postgres 快速重建手册(HA 阶段 3,选项 1:单 Postgres + 快速重建)
前提(2026-10-26 实测值,改配置后同步改这里):
- 容器 `codex-postgres`:镜像 `postgres:16`,`--restart unless-stopped`,
只绑 `127.0.0.1:5432`,卷 `/var/lib/codex-postgres` 挂到
`/var/lib/postgresql/data`,库 `codex_control`,用户 `codexsvc`。
- 备份:每天 03:00 CST 由 `codex-pg-backup.timer` 跑 `pg_dump`,
在 `/var/lib/codex-app-server/backups/`,轮转 7 份,目录 700、
文件 600(含生产全量,含 GitHub App 配置,绝不要放宽权限、
绝不要进仓库)。
- 连接参数只在 `/etc/codex-app-server/env`(0600 root)里,密码
只经环境变量传,不进命令行、不进日志。
- RTO:场景 A 约 5 分钟,场景 B 约 30 分钟;RPO = 24 小时
(备份间隔,备份后到故障前的数据会丢,写进事故通报里)。
通用顺序(三个场景都一样):先停两台 server(避免恢复中途有
写请求进来),再动数据库,恢复完先 `pg_isready`、再起 server、
再看 health 与 monitor。不要在 server 还跑着的时候往生产库里
恢复备份。
```sh
systemctl stop codex-cloud-server codex-cloud-server-2
# ... 按场景恢复 ...
systemctl start codex-cloud-server codex-cloud-server-2
curl -s http://127.0.0.1:8765/health # 要求 db_ok=true
curl -s http://127.0.0.1:8766/health # 同上
sh /opt/codex-app-server/deploy/monitor.sh # 要求 overall pass
```
恢复后 running 任务的处理:恢复出来的 running 行心跳一定过期,
会被 worker 的陈旧回收(300 秒)标 `interrupted`;不要手动改
状态,让 sweep 走,之后新建任务重跑(goal 任务本来就不支持
同任务 `/retry`)。
## 场景 A:容器丢了,卷还在
```sh
docker ps -a | grep codex-postgres # 确认容器没了、卷还在
ls /var/lib/codex-postgres # 卷里应有 PGDATA 内容
docker run -d --name codex-postgres --restart unless-stopped \
-p 127.0.0.1:5432:5432 \
-v /var/lib/codex-postgres:/var/lib/postgresql/data \
-e POSTGRES_DB=codex_control -e POSTGRES_USER=codexsvc \
postgres:16
# 注意:卷已存在时 POSTGRES_PASSWORD 不再生效(只在初始化空卷时
# 用),所以这里不用传密码;旧卷的口令就是 env 里的那个。
pg_isready -h 127.0.0.1 -p 5432
```
## 场景 B:卷丢了(磁盘坏、误删),用备份重建
```sh
# 1. 先确认旧卷确实不可恢复(不要在还能抢救的卷上直接重建)。
# 旧目录还在但已损坏时先 mv 走(保留取证),不要直接 rm。
ls /var/lib/codex-postgres || echo "卷已丢"
# 2. 新卷 + 新容器。密码必须等于 env 里 CLOUD_DB_PASSWORD 的值,
# 否则 server 连不上(新卷初始化时口令就按这个值建)。
set -a; . /etc/codex-app-server/env; set +a
mkdir -p /var/lib/codex-postgres
docker run -d --name codex-postgres --restart unless-stopped \
-p 127.0.0.1:5432:5432 \
-v /var/lib/codex-postgres:/var/lib/postgresql/data \
-e POSTGRES_DB=codex_control -e POSTGRES_USER=codexsvc \
-e POSTGRES_PASSWORD="$CLOUD_DB_PASSWORD" \
postgres:16
# 3. 恢复最新备份(备份是 pg_dump 明文 SQL,不含 CREATE DATABASE;
# 库已由容器的 POSTGRES_DB 建好,导入只建表与数据)。
# 新卷是空库,正常应干净导入;有报错先看日志尾部再定。
LATEST=$(ls -t /var/lib/codex-app-server/backups/cloud-backup-*.sql | head -1)
echo "restoring $LATEST"
PGPASSWORD="$CLOUD_DB_PASSWORD" psql -h 127.0.0.1 -U codexsvc \
-d codex_control -f "$LATEST" > /tmp/pg-restore.log 2>&1 \
|| tail -20 /tmp/pg-restore.log
# 4. 校验:表在、行数非零(与事故前最后一次 monitor/演练记录对照)。
PGPASSWORD="$CLOUD_DB_PASSWORD" psql -h 127.0.0.1 -U codexsvc \
-d codex_control -At -c \
"SELECT (SELECT count(*) FROM tasks), \
(SELECT count(*) FROM projects), \
(SELECT count(*) FROM users), \
(SELECT count(*) FROM audit_log);"
```
## 场景 C:整机丢了
按 `deploy/new-host-checklist.md`(HA 阶段 4)先把新机入网
(env、代码、units、镜像、代理白名单),再走场景 B。注意新机
的 `/etc/codex-app-server/env` 必须与旧机一致(尤其是
CLOUD_DB_PASSWORD,否则场景 B 的新卷口令对不上)。
## 安全红线
- 演练永远恢复到测试库(`deploy/restore-drill.sh` 已把“目标库
名绝不等于生产库名”写进脚本逻辑);只有真实重建(场景 B/C)
才以 `codex_control` 为目标,且必须先确认旧卷已丢。
- 备份文件含 GitHub App 的 webhook_secret 与 pem:恢复、拷贝、
传输时一律按密钥处理,不进仓库、不进聊天、不打印内容。
- 重建完先不要急着接流量:health 的 `db_ok` 与 monitor 的
`overall pass` 都通过,再用自己的 API key 调一次
`GET /projects` 能返回,才算通。
## 演练节奏
- 每季度:`sh /opt/codex-app-server/deploy/restore-drill.sh`
(新鲜备份 -> 测试库 -> 四表行数比对 -> 删测试库),PASS 才算
备份有效;不要只看“备份文件存在”。
- 每半年:场景 A 演练一次(`docker restart codex-postgres` 后
health 与 monitor 恢复),验证容器重建路径没过期。
- 演练结果记进运维记录;手册里的实测值(镜像、端口、卷路径、
备份间隔)变了就同步改手册,不要让手册过期。