11 KiB
11 KiB
运维方案(OPERATIONS)
日期:2026-09-10 · 版本 8657f44
范围:音频容量/保留/清理/备份/恢复、数据库备份/恢复、Worker 与额度对账、告警。本轮不擅自删除任何既有音频。
1. 音频存储现状
- 位置:
/home/flym/kaotings-audio,布局{user_id}/{task_id}.wav(私有本地目录,非对象存储)。 - 元数据:
audio_files(storage_key、mime_type、size_bytes、checksum、status、expires_at)。 - 写入:成功结算时先写文件、再登记
audio_files(含 SHA256)。 - 读取:回放/下载按
storage_key取文件;文件缺失返回 404(优雅降级)。
2. 备份(已配置异地备份,真实恢复已验证)
- 本机暂存:
/home/flym/backups/(.dump+audio_*.tar.gz;仅暂存,真正归档在 NAS)。 - 异地备份目标:Synology NAS
103.40.14.100(DSM),账号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.sh(root 运行,原因见 §6):pg_dump --format=custom业务库(api.env→DATABASE_URL)与 CMS 库(cms.env→DATABASE_URI)。tar -czf audio_<ts>.tar.gz整树音频快照。- rsync 追加 3 个产物到 NAS → SHA256 校验 → 按序列保留 7 份。
- 定时:systemd
kaotings-backup.timer,每日 03:00(Persistent=true,RandomizedDelaySec=30min)。 - 已验证(从 NAS 实际恢复,非仅
pg_restore --list):拉取最新 dump + 音频快照 →pg_restore到独立库 0 错误;行数与源一致(users 35 / quota 34 / tasks 28 / audio 26 / usage 61);额度↔流水 0 不符(34 账户:used=Σconsume、reserved=Σ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 流程
- 停止写入(可选,视 RPO):
systemctl stop kaotings-api。 - 建空库(装
pgcrypto)→pg_restore --clean --if-exists --no-owner恢复目标日期的业务库 / CMS 库 dump。 - 音频:从 NAS 拉取对应日期的
audio_<ts>.tar.gz,解压到/home/flym/kaotings-audio(源:/volume2/NetBackup/kaotings/audio/)。 - 校验:行数、音频文件↔
audio_files关联、额度↔流水一致(见 §4.2)。 systemctl start kaotings-api,/healthz复核。
4.2 从 NAS 实际恢复校验(已执行,非仅 pg_restore --list)
- 拉取 NAS 最新
kaotings_<ts>.dump/kaotings_cms_<ts>.dump/audio_<ts>.tar.gz→pg_restore到独立库,业务库 0 错误、CMS 0 错误。 - 行数源=恢复:users 35 / quota 34 / tasks 28 / audio 26 / usage 61。
- 额度↔流水:0 不符(34 账户;
used=Σconsume、reserved=Σreserve−Σconsume−Σrelease)。 - 音频↔文件:
available行 26,快照内命中 22、缺 4(与源一致的既有“有行无文件”,非备份缺失)。
4.3 Worker 重启 / 额度对账
- 单 Worker(systemd 单进程),原子领取(
FOR UPDATE SKIP LOCKED)+ 5 分钟租约 + 过期running回收(WORKER_LEASE_EXPIRED,释放冻结)。 - 对账:
GET /admin/usage/summary汇总quota_accounts与usage_records口径;GET /admin/tts/tasks查结算状态(consumed/released/pending)。 - 不变式(每账户恒成立):
limit_snapshot + adjustment - used - reserved >= 0(由 Phase 5 异常矩阵 S3/S4 保证)。
5. 待用户确认清单(集中)
- 音频保留期(
AUDIO_RETENTION_SECONDS)。 - 磁盘容量上限与告警阈值、通知方式。
- 是否启用按
expires_at的定时保留期清理。 - 原始文本保留期。
- 备份:异地 NAS 已配置(
/volume2/NetBackup,每日 03:00,追加 + 校验 + 每序列 7 份);剩余待确认:保留份数(脚本KEEP)、失败通知渠道、是否改为非 root 运行(见 §6)。 - 上游 HTTPS/加密端点、允许来源网段、凭据轮换(见
DEPLOYMENT.md§4)。
6. 备份运行权限与失败通知
- 当前以 root 运行(
kaotings-backup.serviceUser=root)。原因:需读取 root-only 的/etc/kaotings/api.env、cms.env(数据库连接串)并使用/root/.ssh密钥。并非“无需管理员权限”。- 若需降权:可建专用
kts_backup用户 + 仅含所需GRANT的 DB 角色 + 0600 的独立 env 文件 + 专用 SSH 密钥,使备份以非 root 运行。待用户确认是否需要(当前测试环境 root 运行可接受)。
- 若需降权:可建专用
- 失败通知:
- 已具备:备份脚本校验失败即
exit 1→ systemd 将服务标记为failed,日志进 journald(journalctl -u kaotings-backup可查)。 - 待接入:主动告警(邮件/webhook/IM)需要一个渠道。SMTP 为 V1 已声明限制(未接入)。待用户指定通知渠道后,在 service 增加
ExecStopPost/OnFailure钩子推送。
- 已具备:备份脚本校验失败即
7. 待调查 / 未闭环项(不标为完成)
- 4 条
available音频行无对应文件:既有 DB/文件系统不一致,待定位根因(文件被外部删除 or 写入路径异常);备份/恢复流程本身通过(快照如实捕获 22 个实际文件)。 - 上游公网 HTTP:上线阻断,待加密 + 来源限制 + 外部复测(
DEPLOYMENT.md§4)。 - 音频容量/保留/清理参数:待用户确认(§5)。
- 备份失败通知渠道 / 是否降权:待用户确认(§6)。
8. Git 推送凭据:Gitea OAuth token 1 小时过期导致「首次推送必失败」(已修复)
8.1 现象
推送时第一次总是失败,重试第二次才成功:
remote: Verify
fatal: Authentication failed for 'http://rand.team:44000/Kaotings/www_site.git/'
8.2 根因(上游 Gitea token 有效期 + GCM 缓存)
- 上游 Gitea(
rand.team:44000)签发的 OAuth token 是一对 JWT,解码 payload 可见:- access token:
{"gnt":2,"tt":0,"exp":iat+3600}→ 仅 1 小时有效 - refresh token:
{"gnt":2,"tt":1,"exp":iat+2628000}→ 30 天有效(每次刷新会轮换)
- access token:
- Git Credential Manager 把 access token 缓存进 Windows 凭据管理器,而它对 generic OAuth 主机无法感知 token 过期时间,于是闲置超过 1 小时后:
- 第一次推送 → GCM 返回已过期的 access token → 服务器拒绝(
remote: Verify); - GCM 删除该缓存凭据;
- 第二次推送 → 缓存已空 → GCM 用 refresh token 静默换新 token → 成功。
- 第一次推送 → GCM 返回已过期的 access token → 服务器拒绝(
即:不是服务器故障,也不是密码错误,而是「1 小时 token + 无过期感知的缓存」。
8.3 解决方式(已在本机实施)
新增凭据助手 C:/Users/kts/.git-credential-kaotings.mjs,改为每次都用 refresh token 换取新 access token,并在本地记录过期时间提前 5 分钟刷新,从而第一次推送即成功。
关键配置(顺序很重要):git 的 helper 按 系统 → 全局 → 仓库 顺序追加,空值 helper = 会清空此前累积的列表。系统级 C:/Program Files/Git/etc/gitconfig 中存在 helper = manager(修改它需管理员权限),因此必须在全局配置 C:/Cadence/SPB_Data/.gitconfig 里先重置再排序:
[credential]
helper =
helper = !node C:/Users/kts/.git-credential-kaotings.mjs
helper = manager
- 助手只响应
host=rand.team:44000,其它主机静默退出并继续交给 GCM(GitHub / Azure DevOps 等不受影响)。 - 长期 refresh token 只存于 Windows 凭据管理器(DPAPI 保护);本地缓存
~/.kaotings-git-token.json只存 1 小时的 access token;刷新时轮换得到的新 refresh token 自动回写凭据管理器。 - 手动兜底(不依赖上述配置,例如换机器时):
git -c credential.helper= -c credential.helper="!node C:/Users/kts/.git-credential-kaotings.mjs" push origin <branch>
- 换新机器/新环境:仍需先走一次浏览器 OAuth(GCM),拿到 refresh token 后本助手才有凭据可用。
8.4 验证记录
- 手动把缓存 access token 置为已过期后,单次
git push直接成功(日志显示助手先执行并刷 token,无 401)。 - 非目标主机(如
example.com)调用助手无输出、正常放行给 GCM。 - 轮换后 refresh token
iat更新且已写入凭据管理器。