File size: 8,124 Bytes
5be0a06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#!/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()