www_site/docs/OPERATIONS.md
flym df6a0e46a6 docs: rework offsite backup into a real, restorable archive
Move backup to /volume2/NetBackup (DSM shared folder, kts_bak read/write) and
change the design from a --delete mirror to an additive, date-stamped archive:
per-run pg_dump snapshots for the DBs and per-run full tar.gz snapshots for
audio, each kept for 7 generations, transferred over encrypted SSH with
post-transfer SHA256 verification (success only when byte-identical). This
removes the --delete risk (source-side deletion no longer wipes the backup),
enables restore-to-a-date, and is verified by an actual restore from the NAS
into isolated DBs (0 errors, quota/ledger and audio associations consistent).
2026-09-10 17:48:51 +08:00

8.0 KiB
Raw Permalink Blame History

运维方案OPERATIONS

日期2026-09-10 · 版本 8657f44 范围:音频容量/保留/清理/备份/恢复、数据库备份/恢复、Worker 与额度对账、告警。本轮不擅自删除任何既有音频。

1. 音频存储现状

  • 位置:/home/flym/kaotings-audio,布局 {user_id}/{task_id}.wav(私有本地目录,非对象存储)。
  • 元数据:audio_filesstorage_keymime_typesize_byteschecksumstatusexpires_at)。
  • 写入:成功结算时先写文件、再登记 audio_files(含 SHA256
  • 读取:回放/下载按 storage_key 取文件;文件缺失返回 404优雅降级

2. 备份(已配置异地备份,真实恢复已验证)

  • 本机暂存:/home/flym/backups/.dump + audio_*.tar.gz;仅暂存,真正归档在 NAS
  • 异地备份目标:Synology NAS 103.40.14.100DSM账号 kts_bak,落盘 /volume2/NetBackup/kaotings/db/ + audio/不在 home 下)。/volume2/NetBackup 为 DSM 共享文件夹,kts_bak 已授读写。
  • 传输SSH 52200加密,密钥 /root/.ssh/id_ed25519_ktsbak不使用 公网明文 rsync 守护端口 50873
  • 设计原则(这是“备份”,不是“镜像”):
    • 追加、绝不用 --delete:每次只向 NAS 追加本次的时间戳快照;源端删除/清理/损坏不会删除或清空 NAS 副本(避免“单一镜像被源端误删连带清空”)。
    • 按日期可恢复DB 每运行一份时间戳 pg_dump 快照;音频每运行一份整树 tar.gz 快照。任一历史日期可单独恢复。
    • 有界保留:每序列保留最近 7 份(本地与 NAS 各自按时间戳裁剪,互不依赖源端状态)。
    • 传输后校验:对本次 3 个产物2 个 dump + 1 个音频快照)逐一比对本地与 NAS 的 SHA256;任一缺失/不一致即判失败并 exit 1只有完整且校验通过才记为成功(半成品/损坏不会标记成功)。
  • 脚本:/usr/local/sbin/kaotings-backup.shroot 运行,原因见 §6
    1. pg_dump --format=custom 业务库(api.envDATABASE_URL)与 CMS 库(cms.envDATABASE_URI)。
    2. tar -czf audio_<ts>.tar.gz 整树音频快照。
    3. rsync 追加 3 个产物到 NAS → SHA256 校验 → 按序列保留 7 份。
  • 定时systemd kaotings-backup.timer,每日 03:00Persistent=trueRandomizedDelaySec=30min)。
  • 已验证(从 NAS 实际恢复,非仅 pg_restore --list):拉取最新 dump + 音频快照 → pg_restore 到独立库 0 错误行数与源一致users 35 / quota 34 / tasks 28 / audio 26 / usage 61额度↔流水 0 不符34 账户:used=Σconsumereserved=ΣreserveΣconsumeΣrelease);音频 22/26 与源一致4 条为既有“有行无文件”,快照如实捕获)。
对象 本机暂存 异地(/volume2/NetBackup/kaotings
业务库 kaotings kaotings_<ts>.dump db/kaotings_<ts>.dump(保留 7
CMS 库 kaotings_cms kaotings_cms_<ts>.dump db/kaotings_cms_<ts>.dump(保留 7
音频 audio_<ts>.tar.gz(整树) audio/audio_<ts>.tar.gz(保留 7

待确认:备份保留份数(当前 7可调脚本 KEEP)、失败通知渠道(见 §6。音频保留期 / 磁盘阈值见 §5。

3. 容量 / 保留 / 清理(需用户决定的参数集中列出)

参数 当前值 需用户决定
音频保留期 AUDIO_RETENTION_SECONDS 7 天(默认) 是(产品/隐私/存储预算)
磁盘容量上限 / 告警阈值 根盘约 48 GiB 可用,无上限告警 是(阈值 + 通知方式)
保留期清理策略 未启用定时清理(仅启动时清孤儿) 是(是否按 expires_at 定时删除)
原始文本保留期 存于 tts_tasks.text 是(产品/隐私)
备份保留份数 / 周期 已配置:每库 7 份 · 每日 03:00 · 异地 NAS 否(可调脚本 KEEP 与 timer
  • 清理安全边界:
    • 启动时 cleanup_orphan_audio 仅删除“ available 记录且 mtime>1h”的孤儿文件。
    • 保留期清理在参数确认并实现后启用;在此之前不删除任何既有音频
  • 告警:磁盘使用率超阈值、audio_files 与磁盘文件数量长期不一致、Worker 长期无 queued 消化。

4. 恢复(已从 NAS 实际恢复验证)

4.1 流程

  1. 停止写入(可选,视 RPOsystemctl stop kaotings-api
  2. 建空库(装 pgcrypto)→ pg_restore --clean --if-exists --no-owner 恢复目标日期的业务库 / CMS 库 dump。
  3. 音频:从 NAS 拉取对应日期audio_<ts>.tar.gz,解压到 /home/flym/kaotings-audio(源:/volume2/NetBackup/kaotings/audio/)。
  4. 校验:行数、音频文件↔audio_files 关联、额度↔流水一致(见 §4.2)。
  5. systemctl start kaotings-api/healthz 复核。

4.2 从 NAS 实际恢复校验(已执行,非仅 pg_restore --list

  • 拉取 NAS 最新 kaotings_<ts>.dump / kaotings_cms_<ts>.dump / audio_<ts>.tar.gzpg_restore 到独立库,业务库 0 错误、CMS 0 错误
  • 行数源=恢复users 35 / quota 34 / tasks 28 / audio 26 / usage 61。
  • 额度↔流水:0 不符34 账户;used=Σconsumereserved=ΣreserveΣconsumeΣrelease)。
  • 音频↔文件:available 行 26快照内命中 22、缺 4与源一致的既有“有行无文件”非备份缺失

4.3 Worker 重启 / 额度对账

  • 单 Workersystemd 单进程),原子领取(FOR UPDATE SKIP LOCKED+ 5 分钟租约 + 过期 running 回收(WORKER_LEASE_EXPIRED,释放冻结)。
  • 对账:GET /admin/usage/summary 汇总 quota_accountsusage_records 口径;GET /admin/tts/tasks 查结算状态consumed/released/pending
  • 不变式(每账户恒成立):limit_snapshot + adjustment - used - reserved >= 0(由 Phase 5 异常矩阵 S3/S4 保证)。

5. 待用户确认清单(集中)

  1. 音频保留期(AUDIO_RETENTION_SECONDS)。
  2. 磁盘容量上限与告警阈值、通知方式。
  3. 是否启用按 expires_at 的定时保留期清理。
  4. 原始文本保留期。
  5. 备份:异地 NAS 已配置(/volume2/NetBackup,每日 03:00追加 + 校验 + 每序列 7 份);剩余待确认:保留份数(脚本 KEEP)、失败通知渠道、是否改为非 root 运行(见 §6
  6. 上游 HTTPS/加密端点、允许来源网段、凭据轮换(见 DEPLOYMENT.md §4

6. 备份运行权限与失败通知

  • 当前以 root 运行kaotings-backup.service User=root)。原因:需读取 root-only 的 /etc/kaotings/api.envcms.env(数据库连接串)并使用 /root/.ssh 密钥。并非“无需管理员权限”
    • 若需降权:可建专用 kts_backup 用户 + 仅含所需 GRANT 的 DB 角色 + 0600 的独立 env 文件 + 专用 SSH 密钥,使备份以非 root 运行。待用户确认是否需要(当前测试环境 root 运行可接受)。
  • 失败通知
    • 已具备:备份脚本校验失败即 exit 1 → systemd 将服务标记为 failed,日志进 journaldjournalctl -u kaotings-backup 可查)。
    • 待接入:主动告警(邮件/webhook/IM需要一个渠道。SMTP 为 V1 已声明限制(未接入)。待用户指定通知渠道后,在 service 增加 ExecStopPost/OnFailure 钩子推送。

7. 待调查 / 未闭环项(不标为完成)

  • 4 条 available 音频行无对应文件:既有 DB/文件系统不一致,待定位根因(文件被外部删除 or 写入路径异常);备份/恢复流程本身通过(快照如实捕获 22 个实际文件)。
  • 上游公网 HTTP:上线阻断,待加密 + 来源限制 + 外部复测(DEPLOYMENT.md §4
  • 音频容量/保留/清理参数待用户确认§5
  • 备份失败通知渠道 / 是否降权待用户确认§6