备份与还原
概述
OpenClaw 使用 Hugging Face Dataset 作为备份存储后端,支持增量备份、分卷压缩、动态备份策略等企业级功能。
核心概念
备份类型
| 类型 | 说明 |
|---|---|
| 完整备份 (Full) | 备份所有指定文件和目录 |
| 增量备份 (Incremental) | 仅备份自上次备份以来变化的文件 |
| 分卷 (Split) | 将大备份分割成多个小文件(默认500MB),是完整/增量备份的可选处理方式 |
| 加密 (Encrypted) | 使用 AES-256-CBC 加密归档文件,是完整/增量备份的可选处理方式 |
两层元数据设计
系统使用两层元数据文件:
1. 备份文件名命名规则
备份文件名格式:openclaw-backup-{时间戳}-{类型}[-split].tar.gz
组成部分:
{时间戳}:格式YYYYMMDD-HHmmss,如20260413-120000{类型}:full:完整备份inc:增量备份
[-split]:可选后缀,表示分卷备份
类型后缀规则:
| 类型 | 后缀 | 示例 |
|---|---|---|
| 单文件完整备份 | full |
openclaw-backup-20260413-120000-full.tar.gz |
| 单文件增量备份 | inc |
openclaw-backup-20260414-060000-inc.tar.gz |
| 分卷完整备份 | full-split |
openclaw-backup-20260414-090000-full-split.tar.gz |
| 分卷增量备份 | inc-split |
openclaw-backup-20260414-150000-inc-split.tar.gz |
注意:
- 分卷文件在主文件名后加
.part-{aa,ab,...}后缀 - 分卷数量由归档后实际大小决定(超过
OPENCLAW_BACKUP_SPLIT_SIZE默认500MB时分卷) -split后缀仅用于区分分卷备份(完整和增量都适用)- 加密归档在原文件名后加
.enc后缀(如openclaw-backup-20260413-120000-full.tar.gz.enc) - 加密与分卷可同时使用,加密后分卷的文件名格式:
原名.tar.gz.enc.part-{aa,ab,...}
2. 备份索引文件(每个备份独立)
每个备份上传后都会在远端创建一个对应的元数据索引文件,命名格式为 {归档名}.meta.json。
示例远端存储结构(按文件名/时间排序):
Dataset根目录/
├── latest-backup.json ← 与backups/同级,必须在根目录下
└── backups/
├── openclaw-backup-20260413-120000-full.tar.gz.meta.json ← 单文件完整备份
├── openclaw-backup-20260413-120000-full.tar.gz
├── openclaw-backup-20260414-060000-inc.tar.gz.meta.json ← 单文件增量备份
├── openclaw-backup-20260414-060000-inc.tar.gz
├── openclaw-backup-20260414-072200-inc.tar.gz.meta.json ← 单文件增量备份
├── openclaw-backup-20260414-072200-inc.tar.gz
├── openclaw-backup-20260414-081100-full.tar.gz.meta.json ← 单文件完整备份
├── openclaw-backup-20260414-081100-full.tar.gz
├── openclaw-backup-20260414-090000-full-split.tar.gz.part-aa ← 分卷完整备份
├── openclaw-backup-20260414-090000-full-split.tar.gz.part-ab
├── openclaw-backup-20260414-090000-full-split.tar.gz.meta.json
├── openclaw-backup-20260414-150000-inc-split.tar.gz.part-aa ← 分卷增量备份
├── openclaw-backup-20260414-150000-inc-split.tar.gz.part-ab
├── openclaw-backup-20260414-150000-inc-split.tar.gz.meta.json
├── openclaw-backup-20260414-152400-inc-split.tar.gz.part-aa ← 分卷增量备份
├── openclaw-backup-20260414-152400-inc-split.tar.gz.part-ab
└── openclaw-backup-20260414-152400-inc-split.tar.gz.meta.json
说明:按文件名(时间)升序排列。每个分卷备份的文件组顺序为:分卷卷 → meta.json(分卷后原tar.gz不再保留)。
索引文件内容分场景示例:
场景A:单文件完整备份
// openclaw-backup-20260413-120000-full.tar.gz.meta.json
{
"volumes": ["openclaw-backup-20260413-120000-full.tar.gz"],
"is_split": false,
"backup_type": "full",
"chain_id": "abc123",
"parent": null,
"created_at_utc": "2026-04-13T12:00:00",
"file_count": 42,
"archive_size": 1258291200,
"checksum": "sha256:def456...",
"version": "2.1",
"created_by": "openclaw-backup"
}
// openclaw-backup-20260414-081100-full.tar.gz.meta.json
{
"volumes": ["openclaw-backup-20260414-081100-full.tar.gz"],
"is_split": false,
"backup_type": "full",
"chain_id": "xyz789",
"parent": null,
"created_at_utc": "2026-04-14T08:11:00",
"file_count": 42,
"archive_size": 1258291200,
"checksum": "sha256:def456...",
"version": "2.1",
"created_by": "openclaw-backup"
}
场景B:单文件增量备份
// openclaw-backup-20260414-060000-inc.tar.gz.meta.json
{
"volumes": ["openclaw-backup-20260414-060000-inc.tar.gz"],
"is_split": false,
"backup_type": "incremental",
"chain_id": "abc123",
"parent": "openclaw-backup-20260413-120000-full.tar.gz.meta.json",
"created_at_utc": "2026-04-14T06:00:00",
"file_count": 5,
"archive_size": 52428800,
"checksum": "sha256:ghi789...",
"version": "2.1",
"created_by": "openclaw-backup"
}
// openclaw-backup-20260414-072200-inc.tar.gz.meta.json
{
"volumes": ["openclaw-backup-20260414-072200-inc.tar.gz"],
"is_split": false,
"backup_type": "incremental",
"chain_id": "abc123",
"parent": "openclaw-backup-20260414-060000-inc.tar.gz.meta.json",
"created_at_utc": "2026-04-14T07:22:00",
"file_count": 3,
"archive_size": 52428800,
"checksum": "sha256:ghi789...",
"version": "2.1",
"created_by": "openclaw-backup"
}
场景C:分卷完整备份
// openclaw-backup-20260414-090000-full-split.tar.gz.meta.json
{
"volumes": [
"openclaw-backup-20260414-090000-full-split.tar.gz.part-aa",
"openclaw-backup-20260414-090000-full-split.tar.gz.part-ab"
],
"is_split": true,
"backup_type": "full",
"chain_id": "xyz789",
"parent": null,
"created_at_utc": "2026-04-14T09:00:00",
"file_count": 128,
"archive_size": 2147483648,
"checksum": "sha256:jkl012...",
"version": "2.1",
"created_by": "openclaw-backup"
}
场景D:分卷增量备份
// openclaw-backup-20260414-150000-inc-split.tar.gz.meta.json
{
"volumes": [
"openclaw-backup-20260414-150000-inc-split.tar.gz.part-aa",
"openclaw-backup-20260414-150000-inc-split.tar.gz.part-ab"
],
"is_split": true,
"backup_type": "incremental",
"chain_id": "xyz789",
"parent": "openclaw-backup-20260414-090000-full-split.tar.gz.meta.json",
"created_at_utc": "2026-04-14T15:00:00",
"file_count": 64,
"archive_size": 1073741824,
"checksum": "sha256:mno345...",
"version": "2.1",
"created_by": "openclaw-backup"
}
// openclaw-backup-20260414-152400-inc-split.tar.gz.meta.json
{
"volumes": [
"openclaw-backup-20260414-152400-inc-split.tar.gz.part-aa",
"openclaw-backup-20260414-152400-inc-split.tar.gz.part-ab"
],
"is_split": true,
"backup_type": "incremental",
"chain_id": "xyz789",
"parent": "openclaw-backup-20260414-150000-inc-split.tar.gz.meta.json",
"created_at_utc": "2026-04-14T15:24:00",
"file_count": 8,
"archive_size": 1073741824,
"checksum": "sha256:mno345...",
"version": "2.1",
"created_by": "openclaw-backup"
}
场景E:加密备份(单文件)
// openclaw-backup-20260414-160000-full.tar.gz.meta.json
{
"volumes": ["openclaw-backup-20260414-160000-full.tar.gz.enc"],
"is_split": false,
"backup_type": "full",
"chain_id": "enc001",
"parent": null,
"created_at_utc": "2026-04-14T16:00:00",
"file_count": 42,
"archive_size": 1300234240,
"checksum": "sha256:enc123...",
"version": "2.1",
"created_by": "openclaw-backup",
"encrypted": true,
"encryption_algorithm": "AES-256-CBC"
}
场景F:加密+分卷备份
// openclaw-backup-20260414-170000-full.tar.gz.meta.json
{
"volumes": [
"openclaw-backup-20260414-170000-full.tar.gz.enc.part-aa",
"openclaw-backup-20260414-170000-full.tar.gz.enc.part-ab",
"openclaw-backup-20260414-170000-full.tar.gz.enc.part-ac"
],
"is_split": true,
"backup_type": "full",
"chain_id": "enc002",
"parent": null,
"created_at_utc": "2026-04-14T17:00:00",
"file_count": 128,
"archive_size": 3221225472,
"checksum": "sha256:enc456...",
"version": "2.1",
"created_by": "openclaw-backup",
"encrypted": true,
"encryption_algorithm": "AES-256-CBC"
}
字段说明:
| 字段 | 说明 |
|---|---|
volumes |
归档文件列表(单文件时为单个元素,分卷时包含所有分卷) |
is_split |
是否分卷备份 |
backup_type |
full(完整)或 incremental(增量) |
chain_id |
备份链标识,同一链的所有备份共享此ID(详见下方生成规则) |
parent |
父备份的 .meta.json 文件路径(完整备份为null) |
created_at_utc |
备份创建时间(UTC) |
file_count |
归档内包含的文件数量 |
archive_size |
所有归档文件总大小(字节) |
checksum |
所有归档合并后的SHA256校验和 |
version |
元数据格式版本 |
created_by |
创建工具标识 |
encrypted |
是否加密(可选,默认为false) |
encryption_algorithm |
加密算法(可选,如 AES-256-CBC) |
chain_id 生成规则:
- 首次创建链:生成新的 UUID 前8位作为 chain_id
- 增量备份:继承父备份的 chain_id
- 独立完整备份:生成新的 chain_id(即使上一个链未完成)
- 分卷备份:继承父备份的 chain_id
2. latest-backup.json(最新备份软索引)
位于 Dataset 根目录(与 backups/ 同级).
{
"dataset": "GGSheng/page-backup",
"latest": "backups/openclaw-backup-20260414-152400-inc-split.tar.gz.meta.json",
"is_split": true,
"created_at_utc": "2026-04-14T15:24:00"
}
注意:此文件仅存储最新备份的名称,不包含完整信息。完整信息在 {归档名}.meta.json 中。
3. 归档内部元数据(last-backup-metadata.json)
在备份过程中,元数据会先写入本地 work_dir,然后打包进 tar.gz 归档内部,最后随归档上传到远端。归档内部元数据包含完整的备份信息(与 .meta.json 内容一致),用于归档自验证、链合并判断以及下一次增量备份的 parent 确定:
{
"version": "2.1",
"backup_type": "incremental",
"chain_id": "abc123",
"parent": "openclaw-backup-20260414-060000-inc.tar.gz.meta.json",
"volumes": ["openclaw-backup-20260414-060000-inc.tar.gz"],
"checksum": "sha256:abc123...",
"created_at_utc": "2026-04-14T15:24:00",
"last_backup_time": "2026-04-14T15:24:00",
"file_count": 10,
"archive_size": 5242880,
"is_latest": true,
"created_by": "openclaw-backup"
}
说明:
volumes:当前备份包含的所有卷文件(单文件为列表,分卷备份为part-*文件列表)checksum:归档的 SHA256 校验和file_count:备份的文件数量archive_size:归档原始大小(字节)is_latest:标记是否为最新备份(用于链管理)last_backup_time:上一次备份的时间戳(UTC),用于判断增量备份的时间间隔- 完整的备份信息同时存储在远端的
.meta.json中
重要说明:last-backup-metadata.json 存储在本地 work_dir(默认 /tmp/openclaw-backup),容器重启后可能丢失。因此系统设计为以远程元数据为真理源:每次备份启动时会从远程 latest-backup.json 和最新的 .meta.json 获取链信息,确保即使本地元数据丢失也能正确构建备份链。
增量备份链 (Backup Chain)
增量备份通过 parent 字段形成链表:
备份链路图(两条独立链):
【链 abc123】
完整备份 chain_id: "abc123"
openclaw-backup-20260413-120000-full.tar.gz.meta.json
parent: null
↑
│ parent
│
增量备份 #1 chain_id: "abc123"
openclaw-backup-20260414-060000-inc.tar.gz.meta.json
parent: "openclaw-backup-20260413-120000-full.tar.gz.meta.json"
↑
│ parent
│
增量备份 #2 chain_id: "abc123"
openclaw-backup-20260414-072200-inc.tar.gz.meta.json
parent: "openclaw-backup-20260414-060000-inc.tar.gz.meta.json"
【链 xyz789】(包含最新备份)
完整备份 #1 chain_id: "xyz789"
openclaw-backup-20260414-081100-full.tar.gz.meta.json
parent: null
↑
│ (独立链,无父子关系)
完整备份 #2 chain_id: "xyz789"
openclaw-backup-20260414-090000-full-split.tar.gz.meta.json
parent: null
↑
│ parent
│
增量备份 #1 chain_id: "xyz789"
openclaw-backup-20260414-150000-inc-split.tar.gz.meta.json
parent: "openclaw-backup-20260414-090000-full-split.tar.gz.meta.json"
↑
│ parent
│
增量备份 #2 (最新) chain_id: "xyz789"
openclaw-backup-20260414-152400-inc-split.tar.gz.meta.json
parent: "openclaw-backup-20260414-150000-inc-split.tar.gz.meta.json"
↑
│ latest
│
latest-backup.json → "backups/openclaw-backup-20260414-152400-inc-split.tar.gz.meta.json"
说明:
081100-full和090000-full都是完整备份(parent: null),分属同一 chain_id 但互无父子关系- 同一 chain_id 表示这些备份共享同一个备份链标识,用于分组和校验
- 恢复时沿
parent链路回溯即可
恢复流程:从 latest-backup.json 获取最新备份 → 下载其 meta.json → 沿 parent 回溯到完整备份 → 按顺序合并。
远端存储混合场景示例
假设存在以下远端存储结构(包含完整备份链、多次增量、分卷等):
Dataset: GGSheng/page-backup
│
├── latest-backup.json ← 指向最新备份(位于根目录)
│ {"latest": "backups/openclaw-backup-20260414-152400-inc-split.tar.gz.meta.json"}
│
└── backups/
├── openclaw-backup-20260413-120000-full.tar.gz.meta.json ← 单文件完整备份1
├── openclaw-backup-20260413-120000-full.tar.gz
├── openclaw-backup-20260414-060000-inc.tar.gz.meta.json ← 单文件增量备份1
├── openclaw-backup-20260414-060000-inc.tar.gz
├── openclaw-backup-20260414-072200-inc.tar.gz.meta.json ← 单文件增量备份2
├── openclaw-backup-20260414-072200-inc.tar.gz
├── openclaw-backup-20260414-081100-full.tar.gz.meta.json ← 单文件完整备份2
├── openclaw-backup-20260414-081100-full.tar.gz
├── openclaw-backup-20260414-090000-full-split.tar.gz.meta.json ← 分卷完整备份
├── openclaw-backup-20260414-090000-full-split.tar.gz.part-aa
├── openclaw-backup-20260414-090000-full-split.tar.gz.part-ab
├── openclaw-backup-20260414-150000-inc-split.tar.gz.meta.json ← 分卷增量备份1
├── openclaw-backup-20260414-150000-inc-split.tar.gz.part-aa
├── openclaw-backup-20260414-150000-inc-split.tar.gz.part-ab
├── openclaw-backup-20260414-152400-inc-split.tar.gz.meta.json ← 分卷增量备份2
├── openclaw-backup-20260414-152400-inc-split.tar.gz.part-aa
└── openclaw-backup-20260414-152400-inc-split.tar.gz.part-ab
场景说明:
| 文件名 | 类型 | chain_id | parent |
|---|---|---|---|
| 20260413-120000-full | 单文件完整备份 | abc123 | null |
| 20260414-060000-inc | 单文件增量备份 | abc123 | 20260413-120000-full.meta.json |
| 20260414-072200-inc | 单文件增量备份 | abc123 | 20260414-060000-inc.meta.json |
| 20260414-081100-full | 单文件完整备份 | xyz789 | null |
| 20260414-090000-full-split | 分卷完整备份 | xyz789 | null |
| 20260414-150000-inc-split | 分卷增量备份 | xyz789 | 20260414-090000-full-split.meta.json |
| 20260414-152400-inc-split | 分卷增量备份 | xyz789 | 20260414-150000-inc-split.meta.json |
恢复任意备份的流程:
- 确定目标备份(如
openclaw-backup-20260414-090000-full-split.tar.gz) - 下载其
.meta.json获取分卷信息和父备份引用 - 如有父备份,继续下载父备份的
.meta.json - 按顺序合并所有归档
- 恢复到目标目录
备份流程
整体架构
┌─────────────────────────────────────────────────────────────────┐
│ 外部 Cron 调度 (每30分钟,由cron定义) │
│ OPENCLAW_BACKUP_CRON="*/30 * * * *" │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ openclaw-backup-cron.sh │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────┐ │
│ │ 环境准备 │→ │ 判断备份类型│→ │ 创建归档 │→ │ 分卷处理│ │
│ │ 加载环境变量│ │ 完整/增量 │ │ (动态策略) │ │ (如需要)│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────────┐│
│ │ 清理旧备份 │← │ 上传文件 │← │ 创建meta.json ││
│ │ │ │ 分卷/归档 │ │ 更新latest-backup.json ││
│ └─────────────┘ └─────────────┘ └─────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
详细步骤
1. 环境准备
脚本从以下位置加载环境变量:
/etc/profile.d/openclaw-env.sh/root/.env.d/openclaw-backup.env
关键环境变量:
| 变量 | 默认值 | 说明 |
|---|---|---|
OPENCLAW_BACKUP_DATASET_REPO |
(必填) | 备份存储的Dataset仓库 |
OPENCLAW_BACKUP_SOURCE_DIR |
/root/.openclaw |
备份源目录 |
OPENCLAW_BACKUP_WORK_DIR |
/tmp/openclaw-backup |
临时工作目录 |
OPENCLAW_BACKUP_SPLIT_SIZE |
500 |
分卷大小(MB),超过此大小则分卷 |
OPENCLAW_BACKUP_KEEP_COUNT |
48 |
保留的备份数量 |
OPENCLAW_INCREMENTAL_BACKUP |
true |
是否启用增量备份功能(true=增量模式,false=每次完整备份) |
OPENCLAW_INCREMENTAL_INTERVAL_MINUTES |
10 |
增量备份间隔(分钟),距上次备份>=此时间时执行增量备份 |
OPENCLAW_BACKUP_ENCRYPTION_ENABLED |
false |
是否启用加密备份(true=启用,false=禁用) |
OPENCLAW_BACKUP_ENCRYPTION_PASSWORD |
(必填) | 加密密码(建议使用 HF Space Secrets 管理) |
注意:OPENCLAW_INCREMENTAL_INTERVAL_MINUTES 控制的是"每隔多久执行增量备份"。外部 cron 每30分钟触发一次,如果距上次备份时间未超过此间隔,则不执行任何备份;如果超过此间隔,则执行增量备份。
2. 获取远程链信息(真理源)
关键设计:由于本地 last-backup-metadata.json 存储在 /tmp/openclaw-backup(容器重启后可能丢失),系统以远程元数据为真理源。
备份启动时:
│
├─→ 下载 latest-backup.json
│
└─→ 下载 latest .meta.json
│
├─→ last_backup_time → 用于判断距上次备份的时间间隔
├─→ parent_meta_path → 用于增量备份链的 parent 引用
├─→ chain_id → 用于标识同一备份链
└─→ volumes → 用于分卷备份的文件列表
此步骤确保即使本地元数据丢失,也能正确构建备份链。
3. 动态备份策略
系统根据文件变化率和预估大小自动调整备份参数:
预估大小判断:
├── < 500MB → 小文件,快压缩(级别3),单文件备份
├── 500MB-2GB → 中等文件,平衡压缩(级别6),单文件备份
└── > 2GB → 大文件,最大压缩(级别9)
变化率调整:
├── > 10文件/分钟 → 高变化率,降低压缩级别优先速度
└── < 2文件/分钟 → 低变化率,提高压缩级别,可能跳过备份
分卷触发:归档完成后,检查归档实际大小是否超过 OPENCLAW_BACKUP_SPLIT_SIZE(默认500MB)。如果超过,则自动启用分卷。预估大小 > 2GB 只是提示系统采用最大压缩级别,但最终是否分卷由实际大小决定。
4. 判断备份类型
根据以下条件判断是否执行完整备份或增量备份:
INCREMENTAL_BACKUP = false?
├── YES → 执行完整备份(每次都备份所有文件,创建新链)
└── NO ↓
首次备份 或 手动触发完整备份?
├── YES → 执行完整备份(备份所有文件,创建新链)
└── NO ↓
距上次备份时间 >= INCREMENTAL_INTERVAL ?
├── YES → 执行增量备份(只打包变化的文件,继承链)
└── NO → 跳过本次备份(等待下次 cron 触发)
说明:
- 完整备份:创建新的备份链,生成新的
chain_id - 增量备份:继承父备份的
chain_id - 外部 cron 每30分钟触发一次,只有满足时间条件才执行增量备份
OPENCLAW_INCREMENTAL_BACKUP=false时,每次都执行完整备份- 归档完成后检查大小,超过
OPENCLAW_BACKUP_SPLIT_SIZE则自动分卷
5. 创建归档
根据策略创建 tar.gz 归档:
完整备份归档结构:
openclaw-backup-20260414-120000-full.tar.gz
├── openclaw-state/ # 主状态目录
│ ├── config.json
│ └── ...
├── root-config/ # 额外目录
├── root-ssh/ # SSH配置
└── last-backup-metadata.json # 备份链元数据
增量备份归档结构:
openclaw-backup-20260414-130000-inc.tar.gz
├── openclaw-state/ # 仅变化的文件
│ └── changed-file.txt
├── root-config/ # 仅变化的额外目录
└── last-backup-metadata.json # 包含chain_id和parent信息
分卷备份归档结构: 分卷备份的原始 tar.gz 归档结构与完整/增量相同,只是在归档完成后被分割成多个 part 文件。
# 归档前的原始结构(分卷完整备份为例)
openclaw-backup-20260414-090000-full.tar.gz
├── openclaw-state/ # 主状态目录
├── root-config/
├── root-ssh/
└── last-backup-metadata.json # 分卷前记录原始归档名
# 分割后的文件
openclaw-backup-20260414-090000-full.tar.gz.part-aa # 包含 last-backup-metadata.json
openclaw-backup-20204-090000-full.tar.gz.part-ab # 包含 last-backup-metadata.json
分卷文件中 last-backup-metadata.json 的内容说明:
每个分卷 part 文件内部都包含相同的 last-backup-metadata.json(因为是通过 split 分割原始 tar.gz 得到的)。其内容在分卷前确定:
{
"version": "2.1",
"backup_type": "full",
"chain_id": "xyz789",
"parent": null,
"volumes": ["openclaw-backup-20260414-090000-full.tar.gz"], // 分卷前为原始归档名
"checksum": "sha256:abc123...",
"created_at_utc": "2026-04-14T09:00:00",
"last_backup_time": "2026-04-14T09:00:00",
"file_count": 128,
"archive_size": 2147483648,
"is_latest": true,
"created_by": "openclaw-backup"
}
注意:volumes 字段在分卷前记录原始归档名,分卷后通过 _update_volumes_in_metadata() 更新为本地的 last-backup-metadata.json,但分卷文件内部的副本不会改变。上传到远端的 .meta.json 文件由程序生成,其 volumes 字段会被更新为所有分卷文件名列表:
{
"volumes": [
"openclaw-backup-20260414-090000-full.tar.gz.part-aa",
"openclaw-backup-20260414-090000-full.tar.gz.part-ab"
],
...
}
6. 分卷处理(如需要)
当备份超过 OPENCLAW_BACKUP_SPLIT_SIZE(默认500MB)时,使用 split 命令分割:
split -b 500M openclaw-backup-20260414-120000-full.tar.gz \
openclaw-backup-20260414-120000-full.tar.gz.part-
# 生成: part-aa, part-ab, part-ac...
7. 上传到 HuggingFace
分卷备份上传:
- 所有分卷卷文件(如
openclaw-backup-20260414-090000-full-split.tar.gz.part-aa,openclaw-backup-20260414-090000-full-split.tar.gz.part-ab) .meta.json元数据索引文件
单文件备份上传:
.tar.gz归档文件.meta.json元数据索引文件
统一上传顺序:
- 所有分卷卷文件(如有)
- 单文件归档文件(如有)
.meta.json元数据索引文件- 更新
latest-backup.json指向最新备份的.meta.json
注意:latest-backup.json 存储最新备份的 .meta.json 路径(如 backups/openclaw-backup-20260414-152400-inc-split.tar.gz.meta.json),恢复时直接下载它获取完整信息。
8. 清理旧备份
保留最近 OPENCLAW_BACKUP_KEEP_COUNT(默认48)个备份:
# 清理逻辑(简化)
all_backups = list_repo_files() # 获取所有备份文件
valid_backups = []
for backup in sorted(all_backups, key=timestamp, reverse=True):
if "-split.tar.gz" in backup:
# 分卷备份:检查所有卷是否都存在
if all_volumes_exist(backup):
valid_backups.append(backup)
elif backup.endswith(".meta.json"):
# 元数据文件:分卷备份直接保留(.tar.gz已被分割)
# 单文件备份需检查归档是否存在
main_archive = backup.replace(".meta.json", "")
if "-split" in main_archive or archive_exists(main_archive):
valid_backups.append(backup)
else:
valid_backups.append(backup)
# 删除超过keep_count的旧备份
for backup in valid_backups[keep_count:]:
delete(backup)
还原流程
整体流程
┌─────────────────────────────────────────────────────────────────┐
│ openclaw-restore.sh │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ 下载索引 │→ │ 解析备份链 │→ │ 按顺序下载归档 │ │
│ │ latest.json │ │ 下载meta.json│ │ (从完整到最新) │ │
│ │ │ │ 沿parent回溯 │ │ 合并分卷(如需要) │ │
│ └──────────────┘ └──────────────┘ └────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ 恢复到目标目录 │ │
│ │ │ │
│ └────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
详细步骤
1. 下载备份索引
从 HuggingFace Dataset 下载 latest-backup.json,获取最新备份的 .meta.json 路径:
{
"latest": "backups/openclaw-backup-20260414-152400-inc-split.tar.gz.meta.json",
"created_at_utc": "2026-04-14T15:24:00"
}
2. 解析备份链(下载所有 meta.json)
从最新备份开始,沿 parent 字段回溯到完整备份,下载所有需要的 .meta.json:
# 回溯下载流程(从新到旧收集)
hf download GGSheng/page-backup backups/openclaw-backup-20260414-152400-inc-split.tar.gz.meta.json
# → parent: "openclaw-backup-20260414-060000-inc.tar.gz.meta.json"
hf download GGSheng/page-backup backups/openclaw-backup-20260414-060000-inc.tar.gz.meta.json
# → parent: "openclaw-backup-20260413-120000-full.tar.gz.meta.json"
hf download GGSheng/page-backup backups/openclaw-backup-20260413-120000-full.tar.gz.meta.json
# → parent: null (完整备份)
回溯完成后,收集到的 meta.json 列表需要反转,得到完整备份链(从旧到新):
合并顺序: [20260413-120000(full), 20260414-060000(inc), 20260414-152400(inc-split)]
3. 按顺序下载归档文件
根据备份链从旧到新依次下载归档并合并:
- 分卷备份:下载所有分卷卷文件(如
part-aa,part-ab),然后合并 - 单文件备份:直接下载
.tar.gz文件
# 按顺序下载并合并归档
chain = [full_meta, inc1_meta, inc2_meta] # 从完整备份到最新
merged = {}
for meta in chain:
# 下载归档(分卷则先合并)
# volumes 格式: ["backups/xxx.part-aa", "backups/xxx.part-ab"] 或 ["backups/xxx.tar.gz"]
volumes = meta["volumes"]
if meta["is_split"]:
# 下载所有分卷,合并为一个临时文件
with open("temp.tar.gz", "wb") as out:
for vol in sorted(volumes):
download(vol)
with open(vol, "rb") as inp:
out.write(inp.read())
archive = "temp.tar.gz"
else:
archive = volumes[0]
download(archive)
# 校验完整性
assert calculate_checksum(archive) == meta["checksum"]
# 提取并合并到merged
for file in extract(archive):
merged[file.path] = file # 后者覆盖前者
4. 恢复文件
将合并后的文件恢复到配置的目标目录:
| 归档内容 | 恢复路径 |
|---|---|
openclaw-state/ |
OPENCLAW_BACKUP_SOURCE_DIR |
root-config/ |
OPENCLAW_BACKUP_ROOT_CONFIG_DIR |
root-ssh/ |
OPENCLAW_BACKUP_ROOT_SSH_DIR |
| 其他 | 按配置恢复 |
5. 指定备份恢复
恢复时可以指定任意备份,系统会自动完成备份链回溯和合并。
通过环境变量指定:
# 指定要恢复的归档文件名(不含路径,包含.tar.gz扩展名)
# 系统会自动添加 .meta.json 后缀
export OPENCLAW_RESTORE_ARCHIVE="openclaw-backup-20260414-090000-full-split.tar.gz"
# 执行恢复
python3 /opt/openclaw-hf/openclaw_hf/backup.py restore
恢复流程:
- 系统根据
OPENCLAW_BACKUP_PATH_PREFIX构造元数据文件路径 - 解析备份链:下载
.meta.json并沿parent回溯到完整备份 - 按顺序下载归档并合并(从完整备份到最新)
- 如果归档加密(
encrypted: true),使用OPENCLAW_BACKUP_ENCRYPTION_PASSWORD解密 - 恢复到目标目录
示例:假设指定恢复 openclaw-backup-20260414-090000-full-split.tar.gz(一个分卷完整备份):
指定归档 → openclaw-backup-20260414-090000-full-split.tar.gz
↓ 系统添加后缀 → openclaw-backup-20260414-090000-full-split.tar.gz.meta.json
↓ 下载 .meta.json: is_split=true, volumes=["backups/...part-aa", "backups/...part-ab"]
↓ parent: null (完整备份,无需回溯)
↓
Step 3:
(1)下载所有分卷: part-aa, part-ab
(2)合并分卷为完整归档
(3)如果 encrypted=true,解密归档
Step 4: 提取文件到目标目录
加密恢复注意事项:
- 如果备份是加密的但未提供
OPENCLAW_BACKUP_ENCRYPTION_PASSWORD,恢复会失败并报错 - 加密备份与普通备份可以共存于同一 Dataset 中,恢复时自动识别
备份调度
Cron 配置
默认每30分钟执行一次备份检查:
OPENCLAW_BACKUP_CRON="*/30 * * * *"
调度执行
由 openclaw-entrypoint.sh 启动:
# 在容器启动时设置cron
echo "$OPENCLAW_BACKUP_CRON root /usr/local/bin/openclaw-backup-cron.sh" >> /etc/crontab
执行保障
- 健康检查:备份前可选执行
--check,失败时尝试--repair - 重试机制:默认3次重试,间隔递增(10s, 20s, 30s)
- 看门狗:作为最后防线,确保备份按时执行
并发执行控制
备份使用文件锁机制防止并发执行:
lock_path = "/tmp/openclaw-backup/openclaw-backup.lock"
lock_fd = open(lock_path, "w")
try:
fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) # 非阻塞获取锁
except (OSError, IOError):
print("backup skipped: another backup is already in progress")
return None
流程:
- 尝试获取排他锁(
LOCK_EX | LOCK_NB) - 如果锁被占用,跳过本次备份
- 备份完成后释放锁
注意:如果备份任务执行时间超过 cron 间隔(例如30分钟),新的备份任务会跳过,确保不会同时执行两个备份。
环境变量参考
必需变量
| 变量 | 说明 |
|---|---|
OPENCLAW_BACKUP_DATASET_REPO |
HuggingFace Dataset 仓库ID (如 GGSheng/page-backup) |
备份配置
| 变量 | 默认值 | 说明 |
|---|---|---|
OPENCLAW_BACKUP_SOURCE_DIR |
/root/.openclaw |
备份的源目录 |
OPENCLAW_BACKUP_WORK_DIR |
/tmp/openclaw-backup |
临时工作目录 |
OPENCLAW_BACKUP_PATH_PREFIX |
backups |
仓库内的路径前缀 |
OPENCLAW_BACKUP_PRIVATE |
true |
是否创建为私有仓库 |
增量备份
| 变量 | 默认值 | 说明 |
|---|---|---|
OPENCLAW_INCREMENTAL_BACKUP |
true |
启用增量备份 |
OPENCLAW_INCREMENTAL_INTERVAL_MINUTES |
10 |
增量备份间隔(分钟),每隔此时间执行一次增量备份 |
注意:此变量控制增量备份的执行频率,非完整备份间隔。外部 cron 每30分钟触发一次,如果距上次备份未超过此间隔则跳过。超过此间隔则执行增量备份。
性能调优
| 变量 | 默认值 | 说明 |
|---|---|---|
OPENCLAW_BACKUP_COMPRESSION_LEVEL |
6 |
压缩级别 (1-9,9最大) |
OPENCLAW_BACKUP_SPLIT_SIZE |
500M |
分卷大小,空=不分卷 |
OPENCLAW_BACKUP_SIZE_WARNING_MB |
1500 |
备份大小警告阈值(MB) |
OPENCLAW_BACKUP_KEEP_COUNT |
48 |
保留备份数量 |
动态备份策略
| 变量 | 默认值 | 说明 |
|---|---|---|
OPENCLAW_DYNAMIC_BACKUP |
true |
启用动态策略 |
OPENCLAW_DYNAMIC_SMALL_THRESHOLD_MB |
500 |
小文件阈值 |
OPENCLAW_DYNAMIC_MEDIUM_THRESHOLD_MB |
2000 |
中等文件阈值 |
OPENCLAW_DYNAMIC_HIGH_CHANGE_RATE |
10 |
高变化率阈值(文件/分钟) |
OPENCLAW_DYNAMIC_LOW_CHANGE_RATE |
2 |
低变化率阈值(文件/分钟) |
额外目录和文件
通过环境变量配置额外的备份内容:
# 额外目录格式: "归档名:/路径"
OPENCLAW_BACKUP_EXTRA_DIRS="root-config:/root/.config,root-ssh:/root/.ssh"
# 额外文件格式: "归档名:/路径"
OPENCLAW_BACKUP_EXTRA_FILES="bt-default:/www/server/panel/default.pl,root-bashrc:/root/.bashrc"
健康检查
| 变量 | 默认值 | 说明 |
|---|---|---|
OPENCLAW_BACKUP_HEALTH_CHECK_ENABLED |
false |
启用健康检查 |
OPENCLAW_BACKUP_HEALTH_CHECK_BEFORE |
false |
备份前检查 |
OPENCLAW_BACKUP_HEALTH_CHECK_AFTER |
false |
备份后检查 |
OPENCLAW_BACKUP_MAX_RETRIES |
3 |
最大重试次数 |
恢复配置
| 变量 | 默认值 | 说明 |
|---|---|---|
OPENCLAW_RESTORE_ARCHIVE |
(空) | 指定恢复的归档文件名(不含路径),如 openclaw-backup-20260414-090000.tar.gz。默认为空时恢复最新备份。 |
注意:恢复时系统会自动查找 {OPENCLAW_RESTORE_ARCHIVE}.meta.json 获取备份的完整信息(volumes分卷列表、parent链路上游、chain_id等),无需单独指定元数据文件。
使用示例
首次部署
# 在 bootstrap-hf.sh 中配置
export OPENCLAW_BACKUP_DATASET_REPO="GGSheng/page-backup"
export OPENCLAW_INCREMENTAL_BACKUP="true"
export OPENCLAW_BACKUP_KEEP_COUNT="48"
手动触发备份
python3 /opt/openclaw-hf/openclaw_hf/backup.py backup
手动触发恢复
python3 /opt/openclaw-hf/openclaw_hf/backup.py restore
注意:backup.py 和 restore 操作均通过环境变量配置,不支持命令行参数指定备份文件或其他选项。相关配置通过 OPENCLAW_RESTORE_* 系列环境变量指定(详见环境变量参考)。
查看备份状态
# 查看最新备份索引
hf download GGSheng/page-backup backups/latest-backup.json
cat backups/latest-backup.json
故障排除
备份失败
- 检查网络连接和 HF_TOKEN 权限
- 查看日志:
/var/log/openclaw/backup.log - 确认 Dataset 仓库存在且有写入权限
恢复失败
- 确认备份元数据完整
- 检查恢复目标目录有足够空间
- 元数据版本不兼容时,系统会以兼容模式尝试恢复
大文件备份超时
- 启用分卷备份:
OPENCLAW_BACKUP_SPLIT_SIZE="500M" - 增加 HuggingFace 下载超时:
HF_HUB_DOWNLOAD_TIMEOUT=300
元数据版本兼容性
| 版本 | 说明 |
|---|---|
| 1.0 | 初始版本 |
| 2.0 | 支持增量备份链 |
| 2.1 | 增强兼容性检查 |
系统支持向后兼容,降级使用时会有警告但不影响基本功能。