#!/usr/bin/env python3 import os import sys import tarfile from huggingface_hub import HfApi, hf_hub_download # 配置常量 BACKUP_DIR = "/root/.openclaw" BACKUP_FILENAME = "backup.tar.gz" def restore(): repo_id = os.getenv("HF_DATASET") token = os.getenv("HF_TOKEN") if not repo_id or not token: print("--- [SYNC] 跳过恢复: 未配置 HF_DATASET 或 HF_TOKEN ---") return True # 返回 True 表示流程继续,只是没恢复数据 try: print(f"--- [SYNC] 启动恢复流程, 目标仓库: {repo_id} ---") api = HfApi() # 获取文件列表 files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token) if BACKUP_FILENAME in files: print(f"--- [SYNC] 发现备份文件: {BACKUP_FILENAME}, 正在下载... ---") # 下载到本地临时位置 downloaded_path = hf_hub_download( repo_id=repo_id, filename=BACKUP_FILENAME, repo_type="dataset", token=token, force_download=True # 确保每次都是最新的 ) extract_path = BACKUP_DIR print(f"--- [SYNC] 正在解压至 {extract_path} ... ---") with tarfile.open(downloaded_path, "r:gz") as tar: # 安全解压逻辑 (兼容旧版本 Python) def is_within_directory(directory, target): abs_directory = os.path.abspath(directory) abs_target = os.path.abspath(target) prefix = os.path.commonprefix([abs_directory, abs_target]) return prefix == abs_directory for member in tar.getmembers(): member_path = os.path.join(extract_path, member.name) if not is_within_directory(extract_path, member_path): raise Exception(f"尝试跳出目录的攻击行为: {member.name}") tar.extractall(path=extract_path) print(f"--- [SYNC] 恢复成功! 数据已覆盖至 {extract_path} ---") return True else: # 修正日志:不再谎称检查了时间 print("--- [SYNC] 未在仓库中找到备份文件,将使用空状态启动 ---") return True except Exception as e: print(f"--- [SYNC] 恢复异常: {e} ---") import traceback traceback.print_exc() return True # 即使失败也返回 True,避免阻塞启动 def backup(): repo_id = os.getenv("HF_DATASET") token = os.getenv("HF_TOKEN") if not repo_id or not token: print("--- [SYNC] 跳过备份: 未配置 HF_DATASET 或 HF_TOKEN ---") return try: print(f"--- [SYNC] 正在执行全量备份: {BACKUP_FILENAME} ---") base_path = BACKUP_DIR if not os.path.exists(base_path): print(f"--- [SYNC] 警告: 备份目录 {base_path} 不存在,跳过打包 ---") # 即使目录不存在,也可以上传一个空的标记文件或者直接返回,这里选择返回 return # 创建临时压缩包 with tarfile.open(BACKUP_FILENAME, "w:gz") as tar: # 修复点:将 arcname 设置为 '.',这样解压后直接是 .openclaw 的内容, # 或者设置为 os.path.basename(base_path) 保留文件夹名。 # 通常为了还原方便,我们希望在 .openclaw 目录下直接看到内容,所以用 '.' # 如果希望解压出来是 /root/.openclaw/.openclaw/... 则用 basename arcname = "." tar.add(base_path, arcname=arcname) print(f"--- [SYNC] 本地打包完成,大小: {os.path.getsize(BACKUP_FILENAME) / 1024 / 1024:.2f} MB ---") api = HfApi() print(f"--- [SYNC] 正在上传至 Hugging Face ({repo_id}) ... ---") api.upload_file( path_or_fileobj=BACKUP_FILENAME, path_in_repo=BACKUP_FILENAME, repo_id=repo_id, repo_type="dataset", token=token ) print(f"--- [SYNC] 备份上传成功! ---") # 清理本地临时备份文件以节省空间 if os.path.exists(BACKUP_FILENAME): os.remove(BACKUP_FILENAME) print("--- [SYNC] 本地临时文件已清理 ---") except Exception as e: print(f"--- [SYNC] 备份失败: {e} ---") import traceback traceback.print_exc() if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == "backup": backup() else: restore()