R1000 commited on
Commit
78c4ab1
·
verified ·
1 Parent(s): b75e78c

Update sync_manager.py

Browse files
Files changed (1) hide show
  1. sync_manager.py +156 -101
sync_manager.py CHANGED
@@ -16,11 +16,8 @@ TOKEN = os.getenv("HF_TOKEN")
16
  BASE_DIR = Path("/root/.openclaw")
17
  PREFIX = "openclawai_backup_"
18
  CONFIG_FILE_NAME = "openclaw.json"
19
- KEEP_LAST = 5
20
 
21
- # กรองเฉพาะไฟล์ที่ต้องการ
22
- ALLOWED_EXTENSIONS = {'.md', '.json', '.db', '.sqlite', '.py'}
23
- IGNORE_PATTERNS = {'.lock', '.tmp', '.wal', '.shm', '__pycache__'}
24
 
25
  # =========================
26
  # UTILS
@@ -28,132 +25,190 @@ IGNORE_PATTERNS = {'.lock', '.tmp', '.wal', '.shm', '__pycache__'}
28
  def log(msg):
29
  print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)
30
 
31
- def is_valid_file(path: Path):
32
- # รวม openclaw.json เข้าไปใน ZIP ด้วยเพื่อเก็บเป็น Snapshot
33
- if path.name == CONFIG_FILE_NAME:
34
- return True
35
- if path.suffix.lower() not in ALLOWED_EXTENSIONS:
36
- return False
37
- if any(pattern in path.name for pattern in IGNORE_PATTERNS):
38
- return False
39
- return True
40
 
41
  # =========================
42
- # CORE FUNCTIONS
43
  # =========================
44
  def create_zip(zip_path):
45
- files = [p for p in BASE_DIR.rglob("*") if p.is_file() and is_valid_file(p)]
46
-
47
- if not files:
48
- log("⚠️ No important files found to backup")
49
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- log(f"📦 Zipping {len(files)} files (including current config snapshot)...")
52
  with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z:
53
- for p in files:
54
- z.write(p, p.relative_to(BASE_DIR))
55
- return True
 
 
 
 
56
 
 
 
 
 
 
57
  def backup():
58
- if not REPO_ID or not TOKEN: return log("❌ Config missing (HF_DATASET or HF_TOKEN)")
59
-
 
 
 
 
 
 
60
  timestamp = time.strftime("%Y%m%d_%H%M%S")
61
  zip_name = f"{PREFIX}{timestamp}.zip"
62
  zip_path = Path(f"/tmp/{zip_name}")
63
 
64
  try:
65
- if BASE_DIR.exists() and create_zip(zip_path):
66
- log(f"📤 Uploading backup snapshot: {zip_name}")
67
- api.upload_file(
68
- path_or_fileobj=str(zip_path),
69
- path_in_repo=zip_name,
70
- repo_id=REPO_ID,
71
- repo_type="dataset",
72
- token=TOKEN
73
- )
74
-
75
- # หมายเหตุ: จะไม่มีการ upload CONFIG_FILE_NAME แยกต่างหาก
76
- # เพื่อรักษาไฟล์ "Master" บน Cloud ไว้จากการแก้ไขด้วยมือของคุณ
77
-
78
- cleanup_old_backups()
79
- log("✅ Backup successful. Master config on Cloud remains untouched.")
 
 
 
80
  finally:
81
- if zip_path.exists(): zip_path.unlink()
 
82
 
 
 
 
83
  def cleanup_old_backups():
84
- try:
85
- contents = list_repo_files(REPO_ID, repo_type="dataset", token=TOKEN)
86
- old_backups = sorted([f for f in contents if f.startswith(PREFIX) and f.endswith(".zip")])
87
- for old in old_backups[:-KEEP_LAST]:
88
- api.delete_file(path_in_repo=old, repo_id=REPO_ID, repo_type="dataset", token=TOKEN)
89
- log(f"🗑️ Deleted old backup: {old}")
90
- except Exception as e:
91
- log(f"⚠️ Cleanup failed: {e}")
 
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  def restore():
94
  if not REPO_ID or not TOKEN:
95
- log("❌ Missing HF config")
96
  return False
97
 
98
- try:
99
- contents = list_repo_files(REPO_ID, repo_type="dataset", token=TOKEN)
100
-
101
- # --- Step 1: Restore จาก ZIP (Snapshot) ---
102
- backups = sorted([f for f in contents if f.startswith(PREFIX) and f.endswith(".zip")])
103
- if backups:
104
- latest = backups[-1]
105
- log(f"📥 Step 1: Extracting snapshot from {latest}")
106
- zip_path = hf_hub_download(
107
- repo_id=REPO_ID, filename=latest, repo_type="dataset", token=TOKEN
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  )
109
- if BASE_DIR.exists():
110
- shutil.rmtree(BASE_DIR)
111
- BASE_DIR.mkdir(parents=True)
112
-
113
- with zipfile.ZipFile(zip_path, "r") as z:
114
- z.extractall(BASE_DIR)
115
- log("📦 Files extracted (includes snapshot config).")
116
- else:
117
- log("⚠️ No backup ZIP found.")
118
- BASE_DIR.mkdir(parents=True, exist_ok=True)
119
-
120
- # --- Step 2: Restore Master Config (ทับไฟล์จาก ZIP) ---
121
- if CONFIG_FILE_NAME in contents:
122
- log(f"📥 Step 2: Overwriting with Master '{CONFIG_FILE_NAME}' from Cloud")
123
- try:
124
- master_config_path = hf_hub_download(
125
- repo_id=REPO_ID, filename=CONFIG_FILE_NAME, repo_type="dataset", token=TOKEN
126
- )
127
- shutil.copy(master_config_path, BASE_DIR / CONFIG_FILE_NAME)
128
- log("✅ Master config restored and active.")
129
- except Exception as e:
130
- log(f"⚠️ Failed to restore Master config: {e}")
131
- else:
132
- log(f"⚠️ Master '{CONFIG_FILE_NAME}' not found on Cloud. Using config from ZIP.")
133
 
134
- return True
135
- except Exception as e:
136
- log(f"❌ Restore failed: {e}")
137
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
  # =========================
140
  # MAIN
141
  # =========================
142
  if __name__ == "__main__":
143
- # รับ action จาก command line: 'python script.py backup' หรือ 'python script.py restore'
144
  action = sys.argv[1].lower() if len(sys.argv) > 1 else "restore"
145
 
146
  if action == "backup":
147
- log("🔄 Backup mode started")
148
- backup()
149
- elif action == "backup_loop":
150
- log("🔄 Backup loop active (Hourly)")
151
- while True:
152
- now = time.localtime()
153
- # รอจนถึงต้นชั่วโมงถัดไป
154
- wait_sec = (60 - now.tm_min) * 60 - now.tm_sec
155
- time.sleep(max(0, wait_sec))
156
- backup()
157
  else:
158
- log("🔄 Restore mode started")
159
- restore()
 
16
  BASE_DIR = Path("/root/.openclaw")
17
  PREFIX = "openclawai_backup_"
18
  CONFIG_FILE_NAME = "openclaw.json"
 
19
 
20
+ KEEP_LAST = 5
 
 
21
 
22
  # =========================
23
  # UTILS
 
25
  def log(msg):
26
  print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)
27
 
28
+ def should_ignore(path):
29
+ ignore = ['.lock', '.tmp', '.wal', '.shm']
30
+ s = str(path)
31
+ return any(x in s or s.endswith(x) for x in ignore)
 
 
 
 
 
32
 
33
  # =========================
34
+ # CREATE ZIP
35
  # =========================
36
  def create_zip(zip_path):
37
+ # เก็บ list ไฟล์ทั้งหมดก่อน
38
+ all_files = []
39
+ for root, _, files in os.walk(BASE_DIR):
40
+ for f in files:
41
+ p = Path(root) / f
42
+ rel = p.relative_to(BASE_DIR)
43
+
44
+ if should_ignore(rel):
45
+ continue
46
+
47
+ all_files.append((p, rel))
48
+
49
+ total = len(all_files)
50
+ if total == 0:
51
+ log("⚠️ No files to zip")
52
+ return
53
+
54
+ log(f"📦 Zipping {total} files...")
55
 
 
56
  with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z:
57
+ for i, (p, rel) in enumerate(all_files, 1):
58
+ z.write(p, rel)
59
+
60
+ # progress ทุก 2%
61
+ if i % max(1, total // 50) == 0 or i == total:
62
+ percent = (i / total) * 100
63
+ print(f"\r📊 Progress: {i}/{total} ({percent:.1f}%)", end="", flush=True)
64
 
65
+ print() # newline หลังจบ
66
+
67
+ # =========================
68
+ # BACKUP
69
+ # =========================
70
  def backup():
71
+ if not REPO_ID or not TOKEN:
72
+ log("❌ Missing config")
73
+ return False
74
+
75
+ if not BASE_DIR.exists():
76
+ log("⚠️ Nothing to backup")
77
+ return False
78
+
79
  timestamp = time.strftime("%Y%m%d_%H%M%S")
80
  zip_name = f"{PREFIX}{timestamp}.zip"
81
  zip_path = Path(f"/tmp/{zip_name}")
82
 
83
  try:
84
+ log(f"📦 Creating {zip_name}")
85
+ create_zip(zip_path)
86
+
87
+ log("📤 Uploading...")
88
+ api.upload_file(
89
+ path_or_fileobj=str(zip_path),
90
+ path_in_repo=zip_name,
91
+ repo_id=REPO_ID,
92
+ repo_type="dataset",
93
+ token=TOKEN,
94
+ commit_message=f"Full backup {timestamp}"
95
+ )
96
+
97
+ log(f"✅ Uploaded: {zip_name}")
98
+
99
+ cleanup_old_backups()
100
+ return True
101
+
102
  finally:
103
+ if zip_path.exists():
104
+ zip_path.unlink()
105
 
106
+ # =========================
107
+ # CLEANUP
108
+ # =========================
109
  def cleanup_old_backups():
110
+ contents = list_repo_files(REPO_ID, repo_type="dataset", token=TOKEN)
111
+
112
+ backups = sorted([
113
+ f for f in contents
114
+ if f.startswith(PREFIX) and f.endswith(".zip")
115
+ ])
116
+
117
+ if len(backups) <= KEEP_LAST:
118
+ return
119
 
120
+ for old in backups[:-KEEP_LAST]:
121
+ try:
122
+ api.delete_file(
123
+ path_in_repo=old,
124
+ repo_id=REPO_ID,
125
+ repo_type="dataset",
126
+ token=TOKEN
127
+ )
128
+ log(f"🗑️ Deleted: {old}")
129
+ except Exception as e:
130
+ log(f"⚠️ delete fail {old}: {e}")
131
+
132
+ # =========================
133
+ # RESTORE
134
+ # =========================
135
  def restore():
136
  if not REPO_ID or not TOKEN:
137
+ log("❌ Missing config")
138
  return False
139
 
140
+ contents = list_repo_files(REPO_ID, repo_type="dataset", token=TOKEN)
141
+
142
+ # 1. Restore Full Backup (ZIP)
143
+ backups = sorted([f for f in contents if f.startswith(PREFIX) and f.endswith(".zip")])
144
+ if backups:
145
+ latest = backups[-1]
146
+ log(f"📥 Step 1: Restoring full backup from {latest}")
147
+ zip_path = hf_hub_download(
148
+ repo_id=REPO_ID, filename=latest, repo_type="dataset", token=TOKEN
149
+ )
150
+ if BASE_DIR.exists():
151
+ shutil.rmtree(BASE_DIR)
152
+ BASE_DIR.mkdir(parents=True)
153
+ log("📦 Extracting full backup...")
154
+ with zipfile.ZipFile(zip_path, "r") as z:
155
+ z.extractall(BASE_DIR)
156
+ log("✅ Full backup restored.")
157
+ else:
158
+ log("⚠️ No full backup found. Skipping Step 1.")
159
+ BASE_DIR.mkdir(parents=True, exist_ok=True)
160
+
161
+ # 2. Restore openclaw.json (แยกไฟล์)
162
+ config_exists_in_repo = CONFIG_FILE_NAME in contents
163
+ if config_exists_in_repo:
164
+ log(f"📥 Step 2: Restoring openclaw.json from Dataset")
165
+ try:
166
+ config_path = hf_hub_download(
167
+ repo_id=REPO_ID, filename=CONFIG_FILE_NAME, repo_type="dataset", token=TOKEN
168
  )
169
+ shutil.copy(config_path, BASE_DIR / CONFIG_FILE_NAME)
170
+ log("✅ Config file restored.")
171
+ except Exception as e:
172
+ log(f"⚠️ Failed to restore config: {e}")
173
+ else:
174
+ log("⚠️ No separate openclaw.json found in Dataset. Step 2 skipped.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
+ return True
177
+
178
+ # =========================
179
+ # LOOP: ทุกชั่วโมงที่ :00
180
+ # =========================
181
+ def backup_loop_every_1hour_at_minute_00():
182
+ log("🔄 Backup every hour at :00")
183
+
184
+ while True:
185
+ now = time.localtime()
186
+ wait_sec = (60 - now.tm_min) * 60 - now.tm_sec
187
+
188
+ log(f"⏳ Waiting {wait_sec}s...")
189
+ time.sleep(wait_sec)
190
+
191
+ backup()
192
+
193
+ # =========================
194
+ # LOOP: TEST 5 MIN
195
+ # =========================
196
+ def backup_loop_5min():
197
+ log("🧪 TEST MODE: every 5 minutes")
198
+
199
+ while True:
200
+ backup()
201
+ log("⏳ Waiting 5 minutes...")
202
+ time.sleep(300)
203
 
204
  # =========================
205
  # MAIN
206
  # =========================
207
  if __name__ == "__main__":
 
208
  action = sys.argv[1].lower() if len(sys.argv) > 1 else "restore"
209
 
210
  if action == "backup":
211
+ backup_loop_every_1hour_at_minute_00()
212
+ # backup_loop_5min()
 
 
 
 
 
 
 
 
213
  else:
214
+ restore()