xiaowei-system/scripts/backup-cleanup.py

120 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
backup-cleanup.py — 自动清理过期备份2026-09-05 牧尘要求:过期/无用的备份自动删除)
背景9/4 磁盘 86% 的根因 = 备份/快照/损坏留档只增不删:
① memories.lance.broken.* 83G损坏重建后旧目录从不删
② ~/.hermes/backups/ 74 个 pre-watchdog 快照(旧看门狗无轮转)
③ ~/.hermes/ 根目录散落 14 个 state.db.* 手工备份(修完不清理)
④ hermes-backup-20260717 16G手动备份无保留期
策略(统一轮转,防再堆积):
- ~/.hermes/state.db.* 散落备份 → 只留最近 3 份(其他超期删)
- ~/.hermes/backups/state.db.pre-watchdog-* → 只留最近 3 份
- ~/.hermes/backups/state-db-snap*/state-*.db → 只留最近 8 份snapshot-state-db.sh 已做,兜底)
- ~/.hermes/backups/dual-backup/*.bundle → 只留最近 2 份(每日 bundle
- ~/.hermes/backups/dual-backup/zhiyi-*.tar.gz → 只留最近 2 份
- /var/lib/memoryweave/memories.lance.broken* → 删除broken 标记 = 已废弃,重建后无保留价值)
- /var/lib/memoryweave/*.bak-* → 只留最近 2 份
磁盘 >85% 时额外:~/.hermes/cache 清缓存、/tmp 清 1 天前
安全铁律:
- 绝不删活动文件state.db / graph.db / *.lance 活动库(仅 *.broken.* / *.bak-* 模式)
- 绝不跟随软链(-type f -maxdepth 限制)
- 只删明确命名的备份模式,不 glob 宽泛删
"""
import os, re, shutil, sys
from pathlib import Path
HOME = Path.home()
HERMES = HOME / ".hermes"
BACKUPS = HERMES / "backups"
def keep_latest(paths: list, keep: int, dry: bool = True):
"""按 mtime 保留最近 keep 份,其余删除。返回 (deleted, freed_hint)"""
paths = sorted(paths, key=lambda p: p.stat().st_mtime, reverse=True)
removed = 0
for p in paths[keep:]:
try:
if p.is_dir():
if dry:
print(f" [dry] rm -rf {p}")
else:
shutil.rmtree(p, ignore_errors=True)
else:
if dry:
print(f" [dry] rm {p} ({p.stat().st_size//1024//1024}M)")
else:
p.unlink(missing_ok=True)
removed += 1
except OSError as e:
print(f" ⚠️ 删除失败 {p}: {e}")
return removed
def cleanup(dry: bool = True):
print(f"{'[DRY-RUN] ' if dry else ''}backup-cleanup 开始")
total = 0
# 1. ~/.hermes/ 根目录散落 state.db.* 备份(保留最近 3
stray = [p for p in HERMES.glob("state.db.*") if p.is_file()]
total += keep_latest(stray, 3, dry)
# 2. backups/state.db.pre-watchdog-*(保留 3
pre = list(BACKUPS.glob("state.db.pre-watchdog-*"))
total += keep_latest(pre, 3, dry)
# 3. backups/state-db-snap*/(保留 8
for snapdir in BACKUPS.glob("state-db-snap*"):
if snapdir.is_dir():
total += keep_latest(list(snapdir.glob("state-*.db")), 8, dry)
# 4. backups/dual-backup/bundle 留 2tar.gz 留 2
ddir = BACKUPS / "dual-backup"
if ddir.exists():
total += keep_latest(list(ddir.glob("*.bundle")), 2, dry)
total += keep_latest(list(ddir.glob("*.tar.gz")), 2, dry)
# 4b. backups/kanban/(看板导出备份,保留 5v0.21.0 kanban boards export 产物)
kdir = BACKUPS / "kanban"
if kdir.exists():
total += keep_latest(list(kdir.glob("*.tar.gz")), 5, dry)
# 注backups/corruption-hold/ 为人工观察期留存,绝不自动清理(手动确认后删)
# 5. /var/lib/memoryweave/ broken 残留(直接删,废弃标记)
mw = Path("/var/lib/memoryweave")
if mw.exists():
for p in mw.glob("memories.lance.broken*"):
if dry:
print(f" [dry] rm -rf {p}")
total += 1
else:
shutil.rmtree(p, ignore_errors=True)
total += 1
# *.bak-* 保留 2
total += keep_latest([p for p in mw.glob("*.bak-*") if p.is_dir()], 2, dry)
# 6. 磁盘 >85% 额外清理
st = shutil.disk_usage(str(HOME))
pct = st.used / st.total * 100
if pct > 85:
print(f" ⚠️ 磁盘 {pct:.0f}%>85%,额外清理:")
# 清 cache 子目录1 天前)
for c in (HERMES / "cache").glob("*"):
if c.is_dir():
for f in c.glob("*"):
if f.is_file() and (time_now - f.stat().st_mtime) > 86400:
if dry: print(f" [dry] rm cache {f}")
else: f.unlink(missing_ok=True)
print(f"完成,处理 {total}")
return total
if __name__ == "__main__":
import time
global time_now
time_now = time.time()
dry = "--apply" not in sys.argv
cleanup(dry=dry)
if dry:
print("dry-run加 --apply 实际执行)")