Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| 智能备份控制器 - 方案B | |
| - 纯变化检测:检测到变化后5分钟内备份 | |
| - 2小时最大间隔保险:超过2小时强制备份 | |
| - 180天备份保留 | |
| - 避免重复备份(5分钟内不重复触发) | |
| """ | |
| import os | |
| import sys | |
| import time | |
| from datetime import datetime, timedelta | |
| from pathlib import Path | |
| import subprocess | |
| import threading | |
| BASE_DIR = "/home/node/.openclaw" | |
| BACKUP_LOCK_FILE = "/tmp/.backup_in_progress" | |
| LAST_BACKUP_TIME_FILE = "/tmp/.last_backup_timestamp" | |
| MIN_BACKUP_INTERVAL = 300 # 5分钟内不重复备份(秒) | |
| MAX_BACKUP_INTERVAL = 7200 # 2小时最大间隔(秒) | |
| def get_last_backup_time(): | |
| """获取上次备份时间""" | |
| try: | |
| if os.path.exists(LAST_BACKUP_TIME_FILE): | |
| with open(LAST_BACKUP_TIME_FILE, 'r') as f: | |
| return float(f.read().strip()) | |
| except: | |
| pass | |
| return 0 | |
| def set_last_backup_time(): | |
| """设置上次备份时间""" | |
| with open(LAST_BACKUP_TIME_FILE, 'w') as f: | |
| f.write(str(time.time())) | |
| def get_time_since_last_backup(): | |
| """获取距离上次备份的时间(秒)""" | |
| last_backup = get_last_backup_time() | |
| if last_backup == 0: | |
| return float('inf') # 从未备份过 | |
| return time.time() - last_backup | |
| def should_backup(): | |
| """检查是否应该备份(避免过于频繁)""" | |
| return get_time_since_last_backup() >= MIN_BACKUP_INTERVAL | |
| def need_forced_backup(): | |
| """检查是否需要强制备份(超过2小时)""" | |
| return get_time_since_last_backup() >= MAX_BACKUP_INTERVAL | |
| def has_changes(): | |
| """检查是否有重要变化(5分钟内)""" | |
| try: | |
| cutoff = time.time() - 300 # 5分钟 | |
| # 检查sessions目录 | |
| sessions_dir = Path(BASE_DIR) / "sessions" | |
| if sessions_dir.exists(): | |
| for root, dirs, files in os.walk(sessions_dir): | |
| for file in files: | |
| try: | |
| if os.path.getmtime(os.path.join(root, file)) > cutoff: | |
| return True | |
| except: | |
| continue | |
| # 检查agents目录(skill安装) | |
| agents_dir = Path(BASE_DIR) / "agents" | |
| if agents_dir.exists(): | |
| for agent_dir in agents_dir.iterdir(): | |
| if agent_dir.is_dir(): | |
| skills_dir = agent_dir / "skills" | |
| if skills_dir.exists(): | |
| for root, dirs, files in os.walk(skills_dir): | |
| for file in files: | |
| try: | |
| if os.path.getmtime(os.path.join(root, file)) > cutoff: | |
| return True | |
| except: | |
| continue | |
| # 检查workspace目录 | |
| workspace_dir = Path(BASE_DIR) / "workspace" | |
| if workspace_dir.exists(): | |
| for root, dirs, files in os.walk(workspace_dir): | |
| for file in files: | |
| try: | |
| if os.path.getmtime(os.path.join(root, file)) > cutoff: | |
| return True | |
| except: | |
| continue | |
| return False | |
| except Exception as e: | |
| print(f"[BACKUP-CONTROL] Error checking changes: {e}") | |
| return False | |
| def do_backup(reason="change-detected"): | |
| """执行备份""" | |
| if not should_backup(): | |
| print(f"[BACKUP-CONTROL] Skipping backup, too soon (reason: {reason})") | |
| return False | |
| # 创建锁文件防止重复执行 | |
| if os.path.exists(BACKUP_LOCK_FILE): | |
| print(f"[BACKUP-CONTROL] Backup already in progress") | |
| return False | |
| try: | |
| with open(BACKUP_LOCK_FILE, 'w') as f: | |
| f.write(str(time.time())) | |
| print(f"[BACKUP-CONTROL] Starting backup (reason: {reason})...") | |
| result = subprocess.run( | |
| ["python3", "/home/node/app/sync.py", "backup"], | |
| capture_output=True, | |
| text=True, | |
| timeout=120 | |
| ) | |
| if result.returncode == 0: | |
| set_last_backup_time() | |
| print(f"[BACKUP-CONTROL] Backup completed successfully") | |
| return True | |
| else: | |
| print(f"[BACKUP-CONTROL] Backup failed: {result.stderr}") | |
| return False | |
| except Exception as e: | |
| print(f"[BACKUP-CONTROL] Backup error: {e}") | |
| return False | |
| finally: | |
| try: | |
| if os.path.exists(BACKUP_LOCK_FILE): | |
| os.remove(BACKUP_LOCK_FILE) | |
| except: | |
| pass | |
| def backup_control_loop(): | |
| """ | |
| 主控制循环 | |
| - 每5分钟检查一次 | |
| - 有变化则备份(5分钟内不重复) | |
| - 超过2小时无备份则强制备份 | |
| """ | |
| print("[BACKUP-CONTROL] Starting backup control loop...") | |
| print("[BACKUP-CONTROL] Strategy: Change detection + 2-hour max interval") | |
| print("[BACKUP-CONTROL] Retention: 180 days") | |
| time_since_last = get_time_since_last_backup() | |
| if time_since_last == float('inf'): | |
| print("[BACKUP-CONTROL] No previous backup found, will backup on first change or 2-hour mark") | |
| else: | |
| print(f"[BACKUP-CONTROL] Time since last backup: {time_since_last/60:.1f} minutes") | |
| # 启动后立即检查一次(如果是新启动) | |
| if time_since_last == float('inf'): | |
| print("[BACKUP-CONTROL] First run, checking for immediate backup need...") | |
| if has_changes(): | |
| do_backup(reason="startup-changes") | |
| while True: | |
| time.sleep(300) # 每5分钟检查一次 | |
| time_since_last = get_time_since_last_backup() | |
| # 策略1: 检查是否需要强制备份(超过2小时) | |
| if need_forced_backup(): | |
| print(f"[BACKUP-CONTROL] No backup for {time_since_last/3600:.1f} hours, forcing backup...") | |
| do_backup(reason="forced-2hour") | |
| continue | |
| # 策略2: 检查是否有变化 | |
| if has_changes(): | |
| if should_backup(): | |
| print("[BACKUP-CONTROL] Changes detected, triggering backup...") | |
| do_backup(reason="change-detected") | |
| else: | |
| print(f"[BACKUP-CONTROL] Changes detected, but backup too soon ({time_since_last/60:.1f} min ago)") | |
| else: | |
| # 只有在快接近2小时时才打印日志 | |
| time_remaining = MAX_BACKUP_INTERVAL - time_since_last | |
| if time_remaining < 600: # 如果剩余不到10分钟 | |
| print(f"[BACKUP-CONTROL] No changes. Forced backup in {time_remaining/60:.1f} minutes") | |
| else: | |
| print(f"[BACKUP-CONTROL] No changes in last 5 minutes") | |
| def manual_backup(): | |
| """手动触发备份""" | |
| return do_backup(reason="manual") | |
| def main(): | |
| """主函数""" | |
| if len(sys.argv) > 1: | |
| if sys.argv[1] == "manual": | |
| if manual_backup(): | |
| print("Manual backup completed") | |
| sys.exit(0) | |
| else: | |
| print("Manual backup failed or skipped") | |
| sys.exit(1) | |
| elif sys.argv[1] == "check": | |
| if has_changes(): | |
| print("Changes detected") | |
| sys.exit(0) | |
| else: | |
| print("No changes") | |
| sys.exit(1) | |
| elif sys.argv[1] == "status": | |
| time_since_last = get_time_since_last_backup() | |
| if time_since_last == float('inf'): | |
| print("No previous backup") | |
| else: | |
| print(f"Last backup: {time_since_last/60:.1f} minutes ago") | |
| print(f"Next forced backup: in {(MAX_BACKUP_INTERVAL - time_since_last)/60:.1f} minutes") | |
| if has_changes(): | |
| print("Changes detected: Yes") | |
| else: | |
| print("Changes detected: No") | |
| sys.exit(0) | |
| else: | |
| # 启动主控制循环 | |
| backup_control_loop() | |
| if __name__ == "__main__": | |
| main() |