# Production Release Checklist

小虾 SaaS 生产发布前必须逐项确认。本清单用于首次生产 Alembic 接入和后续 `v*` tag 发布。

## 1. 发布前冻结

- 确认目标 commit 已在 `develop` 部署到 staging，并完成 smoke。
- 确认 staging `/health` healthy。
- 确认 staging `alembic_version` 等于当前 head。
- 确认 CI validate job 通过：format、lint、Bandit、Alembic offline SQL、schema metadata drift、pytest。
- 确认没有未提交的 schema 变更：models、Alembic revision、schema snapshot 必须一起提交。

## 2. 生产环境检查

- `/var/lib/xiaoxia-saas-production/.env` 存在且权限正确。首次初始化必须使用非部署脚本生成独立 production env：

```bash
scripts/init_production_env.sh
```

脚本只创建目录和 `.env`，不会启动应用容器；生成后必须人工替换 `CHANGE_ME_PRODUCTION_DB_PASSWORD`，并确认生产 DB/Redis 使用 `xiaoxia-postgres-production` / `xiaoxia-redis-production`，不得复用 staging 的 `xiaoxia-postgres` / `xiaoxia-redis`。

生产基础设施使用独立 compose project 和端口：

- Postgres：`xiaoxia-postgres-production`，宿主端口 `5433`。
- Redis：`xiaoxia-redis-production`，宿主端口 `6380`。
- API 预留端口：`8001`。
- Web 预留端口：`3002`（`3000` 已被 Gitea 占用）。

- `APP_ENV=production`。
- `DEBUG=false`。
- `AUTO_CREATE_SCHEMA=false`。
- `JWT_SECRET_KEY` 已替换为生产强密钥。
- `DATABASE_URL` 指向生产数据库。
- `REDIS_URL` 指向生产 Redis。
- OSS 配置已确认或明确保持本地 fallback。
- `GENERATED_FILES_HOST_DIR` 生产环境不得指向 staging 目录。
- 本地校验文件：`python scripts/validate_release_env.py /var/lib/xiaoxia-saas-production/.env --strict-external`（SMTP 仅在 `ENABLE_EMAIL_DELIVERY=true` 时强制）。
- 容器内校验已注入环境：`docker exec xiaoxia-api-production python /app/scripts/validate_release_env.py --from-environ --strict-external`（SMTP 仅在启用邮件时强制）。
- 外部服务 smoke：`docker exec xiaoxia-api-production python /app/scripts/smoke_external_services.py --strict --skip-smtp`（SMTP 未启用时）。
- SMTP 真发信 smoke（启用邮件前必须执行）：`docker exec xiaoxia-api-production python /app/scripts/smoke_external_services.py --strict --send-email-to <测试邮箱>`。
- 如开启邮件/session：
  - `ENABLE_EMAIL_DELIVERY=true` 前先验证 SMTP 凭证。
  - `ENABLE_REDIS_SESSIONS=true` 前先验证 Redis 连通性。

## 3. 数据库备份

生产 Alembic 升级前必须创建数据库备份。优先使用脚本：

```bash
BACKUP_ROOT=/root/xiaoxia-backups \
POSTGRES_CONTAINER=xiaoxia-postgres \
POSTGRES_USER=xiaoxia \
POSTGRES_DB=xiaoxia_saas \
scripts/backup_postgres.sh
```

脚本会输出备份目录，并写入 `manifest.txt` 与 `alembic_version.txt`。

手动等价命令：

```bash
BACKUP_DIR=/root/xiaoxia-backups/$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"
docker exec xiaoxia-postgres pg_dump -U xiaoxia -d xiaoxia_saas -Fc > "$BACKUP_DIR/xiaoxia_saas.dump"
docker exec xiaoxia-postgres psql -U xiaoxia -d xiaoxia_saas -Atc 'select version_num from alembic_version;' > "$BACKUP_DIR/alembic_version.txt" 2>/dev/null || true
```

确认备份非空：

```bash
test -s "$BACKUP_DIR/xiaoxia_saas.dump"
ls -lh "$BACKUP_DIR"
```

## 4. 首次 Alembic 接入判断

发布前只读检查生产库状态：

```bash
docker exec xiaoxia-api-production python /app/scripts/alembic_preflight.py
```

也可手动检查：

```bash
docker exec xiaoxia-postgres psql -U xiaoxia -d xiaoxia_saas -Atc "select to_regclass('public.alembic_version');"
docker exec xiaoxia-postgres psql -U xiaoxia -d xiaoxia_saas -Atc "select count(*) from pg_tables where schemaname='public' and tablename != 'alembic_version';"
```

脚本 `recommended_action` 判断规则：

- 已存在 `alembic_version`：执行 `alembic upgrade head`。
- 有业务表但没有 `alembic_version`：先执行 `alembic stamp head`，再执行 `alembic upgrade head`。
- 空库：执行 `alembic upgrade head`。

生产首次接入不允许使用 `Base.metadata.create_all()` 或历史 SQL 快照。

## 5. 生产外部服务验证

上线前必须完成真实凭证 smoke，不能用 no-op 结果替代：

```bash
docker exec xiaoxia-api-production python /app/scripts/validate_release_env.py --from-environ --strict-external
docker exec xiaoxia-api-production python /app/scripts/smoke_external_services.py --strict --skip-smtp
docker exec xiaoxia-api-production python /app/scripts/smoke_external_services.py --strict --send-email-to <测试邮箱>
```

Staging 当前验证状态（2026-06-21）：

- Redis session smoke：通过。
- SMTP strict smoke：MVP 阶段允许 `--skip-smtp`，但启用 `ENABLE_EMAIL_DELIVERY=true` 前必须补真实 SMTP 凭证和真发信 smoke。
- OSS strict smoke：阻塞，缺少 OSS 环境变量/真实凭证。

缺少真实 OSS 凭证时不得把生产上传作为已验收项。SMTP 若暂不启用，必须保持 `ENABLE_EMAIL_DELIVERY=false` 并在 strict smoke 中显式使用 `--skip-smtp`。

## 6. OSS 生命周期与生成文件保留

Production 推荐使用 OSS lifecycle rule 管理生成文件，不建议长期依赖本地 fallback。

建议策略：

- 临时/失败任务产物：7 天后删除。
- 已完成但未购买/未收藏产物：按产品套餐定义保留期。
- 已购买/订单关联产物：按订单合规要求保留，不由通用 cleanup 删除。
- 本地 fallback：每日 dry-run，每周人工复核后 `--apply`，并配置磁盘告警。

本地 fallback dry-run：

```bash
python scripts/cleanup_generated_files.py --dir /var/lib/xiaoxia-saas-production/generated --days 7
```

确认后执行：

```bash
python scripts/cleanup_generated_files.py --dir /var/lib/xiaoxia-saas-production/generated --days 7 --apply
```

OSS 生命周期规则必须在云控制台或 IaC 中绑定到生产 bucket，并在发布记录里写明 rule id。

## 7. 发布执行

- 只能通过 `v*` tag 触发 production deploy。
- 生产 API/Worker 必须使用预构建 runtime image tar，不允许生产机临时 build。
- 生产 Web 必须使用预构建 `apps/web/dist`，不允许生产机运行 `npm`/Node build。
- 当前标准链路：
  1. 推送 `v*` tag。
  2. Gitea `build-production-runtime-images` job 在 `runtime-builder` 上运行。
  3. `runtime-builder` 执行 `scripts/build_release_images.sh <tag>` 生成 `runtime-images-<tag>.tar`。
  4. `runtime-builder` 用 Node Docker 容器构建 `apps/web/dist`。
  5. `runtime-builder` 打包 `release-<tag>.tar.gz`，必须保留 `apps/web/dist/index.html`，同时排除根目录 `./dist`，避免把 runtime tar 再包进 release tar。
  6. `runtime-builder` 上传两个产物到生产：
     - `/var/lib/xiaoxia-saas-production/runtime-images-<tag>.tar`
     - `/var/lib/xiaoxia-saas-production/release-<tag>.tar.gz`
  7. Gitea `deploy-production` job 也在 `runtime-builder` 上运行，通过 SSH 驱动生产机部署。
  8. SSH 部署必须传 `HOST_PREFIX=`，因为 `infra/docker/deploy-production.sh` 默认 `/host` 是给 Docker-in-Docker CI 场景用的。
  9. 生产机只解包 artifact、复制 `.env`、`docker load` runtime images、运行 Alembic、重启容器和 health check。

- 发布期间持续观察 Gitea Actions deploy log。
- 部署后确认：

```bash
curl -fsS http://127.0.0.1:8001/health
docker ps --format '{{.Names}} {{.Image}} {{.Status}}' | grep xiaoxia
docker exec xiaoxia-postgres-production psql -U xiaoxia -d xiaoxia_saas -Atc 'select version_num from alembic_version;'
ls -lh /var/lib/xiaoxia-saas-production/runtime-images-<tag>.tar /var/lib/xiaoxia-saas-production/release-<tag>.tar.gz
```

- 缺少 runtime image tar、release tar 或 `apps/web/dist/index.html` 时，生产部署必须失败，禁止静默复用旧镜像或在生产机补 build。

- `v0.1.6`、`v0.1.7`、`v0.1.8` 是 release-chain 修复过程中的失败/中间态 tag，不作为回滚目标；当前可用回滚基准是已验证的 `v0.1.5` 和 `v0.1.9`。

## 8. Smoke 测试

生产发布后至少验证：

- `/health` 返回 healthy。
- 注册/登录/`/auth/me` 正常。
- 创建 workspace 正常。
- 核心视频生成链路可创建任务。
- 生成文件下载 URL 返回真实文件，不返回前端 HTML fallback。

## 9. 回滚策略

生成恢复计划（不会自动恢复数据库）：

```bash
scripts/restore_postgres_plan.sh /root/xiaoxia-backups/<backup-dir>
```

应用回滚：

- 若数据库未发生不可逆迁移，优先回滚到上一稳定 tag 并重新部署。
- 保留当前失败容器日志后再回滚。

数据库回滚：

- Alembic downgrade 只有在 revision 明确支持且已验证时才能执行。
- 首次生产接入阶段默认使用备份恢复作为最终兜底。
- 恢复前必须二次确认备份路径、目标数据库、停机窗口。
- 恢复步骤必须先由 `scripts/restore_postgres_plan.sh` 生成并复核，不允许紧急状态下即兴拼命令。

## 10. 发布记录

每次生产发布后，在 `docs/PHASE7-PROGRESS.md` 或对应 release note 记录：

- tag
- commit
- Alembic before/after version
- backup path
- smoke 结果
- OSS lifecycle rule id / 本地 cleanup 结果
