"""云库备份: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()