From 6865bb7914084b5d4a793596dc811eab5af4eead Mon Sep 17 00:00:00 2001 From: gouki Date: Sat, 5 Sep 2026 23:10:46 +0000 Subject: [PATCH] =?UTF-8?q?fix(backup):=20=E5=A4=87=E4=BB=BD=E9=93=BE?= =?UTF-8?q?=E8=B7=AF=E7=BA=B3=E5=85=A5=20master=5Fkey.escrow=EF=BC=88?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E5=BC=8F=E5=8D=95=E4=BB=BD+S3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit escrow 是 MASTER_KEY 遗失恢复链的一环:整机丢失时仅有库备份+离线 RESTORE_KEY 仍无法恢复,缺的正是托管档。随 assets 周期备份,固定文件名覆盖(多版本无意义),未建立托管时 skip。 --- deploy/backup.sh | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/deploy/backup.sh b/deploy/backup.sh index 075b195..0d1e197 100755 --- a/deploy/backup.sh +++ b/deploy/backup.sh @@ -1,9 +1,13 @@ #!/usr/bin/env bash # 数据库分库备份脚本(本地 + S3 兼容对象存储) # 用法:backup.sh [assets|metrics|all] -# assets -> 备份 assets.db,保留 14 份(建议每日) +# assets -> 备份 assets.db + master_key.escrow,保留 14 份(建议每日) # metrics -> 备份 metrics.db,保留 4 份(建议每周) -# all -> 两者都备份 +# all -> 上述都备份 +# +# master_key.escrow 随 assets 周期备份(覆盖式单份):它是被离线 RESTORE_KEY +# 加密的 MASTER_KEY 托管档,进备份/S3 安全;缺了它,整机丢失时即使有库备份 +# 和离线钥匙也无法恢复。 # # S3 上传(可选):配置 .env 中 S3_* 项后,备份文件自动上传到 # 任意 S3 兼容对象存储(Cloudflare R2 / MinIO / 阿里云 OSS / 腾讯云 COS 等), @@ -188,8 +192,34 @@ def run(db_name: str, keep: int) -> None: sys.exit(1) +def backup_escrow() -> None: + """备份 MASTER_KEY 托管档(覆盖式单份 + S3) + + escrow 多版本无意义(解出的都是当前 MASTER_KEY),故固定文件名覆盖; + 重建托管(--force)后下次备份自动带上新档。 + """ + import shutil + src = data_dir / "master_key.escrow" + if not src.exists(): + print("[skip] master_key.escrow 不存在(尚未建立托管)") + return + out_dir = backup_root / "escrow" + out_dir.mkdir(parents=True, exist_ok=True) + dst = out_dir / "master_key.escrow" + shutil.copy2(src, dst) + dst.chmod(0o600) + print(f"[ok] master_key.escrow -> {dst}(覆盖式单份)") + try: + env = load_env(Path(".env")) + s3_upload(env, dst, "escrow", 14) + except Exception as e: + print(f"[s3][error] escrow 上传失败: {e}", file=sys.stderr) + sys.exit(1) + + if target in ("assets", "all"): run("assets.db", 14) + backup_escrow() if target in ("metrics", "all"): run("metrics.db", 4) PYEOF