Files
codex-app-server/cloud/backup.py
T

65 lines
2.3 KiB
Python

"""云库备份:SQLite 在线备份 + 轮转。
用法:python3 -m cloud.backup --db <库路径> --out <目录> [--keep 3]
- 用 sqlite3 的 backup API 做在线备份(源库正在被 server 读写时
也能安全拷,不用先停服务、不用直接 cp 正在写的文件)。
- 轮转:备份文件名含时间戳,写完后按时间倒序只保留最近 keep
份(默认 3),多余的删掉;keep < 1 时报错,不删光。
- 备份失败(源库不存在、目标目录建不出来、backup API 报错)
明确抛错、不静默,CLI 退出码非零。
- 备份文件本身就是 SQLite 库,可直接用 sqlite3 打开验证含
tasks 表;备份里不含任何密钥(库里本来就只存 API key 哈希)。
"""
import argparse
import glob
import os
import sqlite3
import time
from cloud.db import connect as _db_connect
def backup_db(db_path, out_dir, keep=3, timestamp=None):
"""执行一次备份并轮转,返回备份文件路径。
timestamp 可注入(单测用固定时间戳造多份);缺省用当前时间。
"""
if keep < 1:
raise ValueError("keep 必须 >= 1,否则会删光所有备份")
if not os.path.exists(db_path):
raise FileNotFoundError("源库不存在:%s" % db_path)
os.makedirs(out_dir, exist_ok=True)
stamp = timestamp or time.strftime("%Y%m%d%H%M%S", time.localtime())
dest = os.path.join(out_dir, "cloud-backup-%s.db" % stamp)
src = _db_connect(str(db_path))
try:
dst = sqlite3.connect(dest)
try:
src.backup(dst)
finally:
dst.close()
finally:
src.close()
# 轮转:按文件名时间戳倒序(文件名即时间戳,排序即时间序),
# 只留最近 keep 份。
backups = sorted(glob.glob(os.path.join(out_dir, "cloud-backup-*.db")))
for old in backups[:-keep] if len(backups) > keep else []:
os.remove(old)
return dest
def main(argv=None):
parser = argparse.ArgumentParser(description="cloud control-plane backup")
parser.add_argument("--db", required=True)
parser.add_argument("--out", required=True)
parser.add_argument("--keep", type=int, default=3)
args = parser.parse_args(argv)
dest = backup_db(args.db, args.out, keep=args.keep)
print(dest)
if __name__ == "__main__":
main()