openclawplus / backup_controller.py
xuyiyi123's picture
Upload backup_controller.py
5be0a06 verified
#!/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()