背景

OpenClaw 是一个强大的 AI 助手框架,但自建服务器成本不低。HuggingFace Space 提供免费 Docker 容器环境(2vCPU + 16GB RAM),足够跑起 OpenClaw 配合 MiniMax 模型,实现零成本 7×24 在线部署。

本文记录完整部署流程,包含数据持久化(备份/恢复)和关键的 HF Spaces 适配修改。

整体架构

层级 方案 成本
模型层 MiniMax API(api.xins.live 参考 MiniMax 额度
部署层 HuggingFace Space(Docker) $0
持久化层 HuggingFace Dataset 备份 $0
保活层 UptimeRobot 定时请求 $0

第一步:创建 HuggingFace Space

访问 hf.co/spacesNew Space

  • SDK:选择 Docker
  • Template:选择 Blank
  • Privacy:选择 Private(保护个人数据)

第二步:创建 HuggingFace Dataset

在头像菜单 → New Dataset,创建私有数据集。
记下数据集名称,格式为 username/dataset-name

第三步:生成 Access Token

头像菜单 → Access TokensNew token

  • Token Type:选择 Write(需要写入 Dataset)

复制保存好 Token。

第四步:配置环境变量

回到 Space 页面 → Settings → 找到 Variables and Secrets,添加以下变量:

变量名 取值 说明
OPENCLAW_GATEWAY_TOKEN 自定义 Token 用于登录 Web UI
HF_DATASET username/dataset-name 私有 Dataset 名称
HF_TOKEN 你的 HF Access Token Secret

第五步:上传部署文件

在 Space 的 Files 页面创建以下三个文件。

sync.py(数据持久化脚本)

负责每次 Space 重启时从 HuggingFace Dataset 恢复数据,以及定期备份。支持 SHA256 校验、Gateway 重启跳过无变化备份、20 分钟超时。

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
import os
import sys
import tarfile
import shutil
import hashlib
import subprocess
from huggingface_hub import HfApi, hf_hub_download

api = HfApi()
repo_id = os.getenv("HF_DATASET")
token = os.getenv("HF_TOKEN")
FILENAME = "latest_backup.tar.gz"
CHECKSUM_FILE = "latest_backup.sha256"
BACKUP_TIMEOUT = 1200 # 20 分钟


def calc_sha256(filepath):
h = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
return h.hexdigest()


def restore():
print(f"[sync] restore() 开始")
print(f" HF_DATASET: {repo_id}")
print(f" HF_TOKEN: {'已设置' if token else '未设置'}")
if not repo_id or not token:
print("[sync] Skip Restore: HF_DATASET or HF_TOKEN not set")
return

# ── 阶段 1:下载 & 校验 ──
print(f"[sync] 阶段 1/3:下载 & 校验")
try:
print(f" 下载 {FILENAME} ...")
path = hf_hub_download(
repo_id=repo_id,
filename=FILENAME,
repo_type="dataset",
token=token,
)
print(f" 下载完成: {os.path.getsize(path) / 1024 / 1024:.1f} MB")
except Exception as e:
print(f" 远程无备份或下载失败 ({type(e).__name__}),跳过恢复")
return

# tar 包完整性校验
print(f" 校验 tar 包完整性 ...")
try:
with tarfile.open(path, "r:gz") as tar:
members = tar.getmembers()
print(f" ✓ tar 包完整,{len(members)} 个条目")
except tarfile.TarError as e:
print(f" ✗ tar 包损坏: {e}")
return

# SHA256 二次校验
print(f" 校验 SHA256 ...")
try:
cs_path = hf_hub_download(repo_id=repo_id, filename=CHECKSUM_FILE,
repo_type="dataset", token=token)
expected = open(cs_path).read().strip()
actual = calc_sha256(path)
if expected != actual:
print(f" ✗ SHA256 不匹配!expected={expected[:16]}..., actual={actual[:16]}...")
return
print(f" ✓ SHA256 匹配")
except Exception as e:
print(f" ! SHA256 校验跳过: {e}")

# ── 阶段 2:清空 & 解压 ──
print(f"[sync] 阶段 2/3:清空 & 解压")
target = "/root/.openclaw"
if os.path.exists(target):
before = set(os.listdir(target))
shutil.rmtree(target)
print(f" 已清空 {target}{len(before)} 项)")
os.makedirs(target)

print(f" 解压到 /root/ ...")
with tarfile.open(path, "r:gz") as tar:
tar.extractall(path="/root")
print(f" 解压完成")

# 恢复后文件列表
restored = sorted(os.listdir("/root/.openclaw"))
print(f"[sync] ✓ 恢复成功,共 {len(restored)} 项:")
for item in restored:
m = os.stat(os.path.join("/root/.openclaw", item)).st_mode & 0o777
print(f" {oct(m)[-3:]} {item}")


def backup():
print(f"[sync] backup() 开始")
print(f" HF_DATASET: {repo_id}")
print(f" HF_TOKEN: {'已设置' if token else '未设置'}")
if not repo_id or not token:
print("[sync] Skip Backup: HF_DATASET or HF_TOKEN not set")
return

src = "/root/.openclaw"
tmp_tar = "/tmp/latest_backup.tar.gz"
tmp_sha = "/tmp/latest_backup.sha256"

print(f"[sync] 打包 {src} ...")
tar_cmd = (
f"cd /root && tar --numeric-owner -czf {tmp_tar} "
f".openclaw > /dev/null 2>&1 && echo OK"
)
print(f" tar 命令执行中 (timeout={BACKUP_TIMEOUT}s) ...")
proc = subprocess.run(
tar_cmd,
shell=True,
capture_output=True,
text=True,
timeout=BACKUP_TIMEOUT
)
if proc.returncode != 0:
print(f"[sync] ✗ tar 失败: {proc.stderr[:200]}")
return

size_mb = os.path.getsize(tmp_tar) / 1024 / 1024
print(f" 打包完成: {size_mb:.1f} MB")

# SHA256
checksum = calc_sha256(tmp_tar)
with open(tmp_sha, "w") as f:
f.write(checksum)
print(f" SHA256: {checksum[:16]}...")

# 上传
print(f"[sync] 上传 {FILENAME} ({size_mb:.1f} MB) ...")
try:
api.upload_file(
path_or_fileobj=tmp_tar,
path_in_repo=FILENAME,
repo_id=repo_id,
repo_type="dataset",
token=token,
)
print(f" ✓ {FILENAME} 上传成功")
except Exception as e:
print(f"[sync] ✗ {FILENAME} 上传失败: {e}")
for f in [tmp_tar, tmp_sha]:
if os.path.exists(f):
os.remove(f)
print("[sync] Backup aborted")
return

try:
api.upload_file(
path_or_fileobj=tmp_sha,
path_in_repo=CHECKSUM_FILE,
repo_id=repo_id,
repo_type="dataset",
token=token,
)
print(f" ✓ {CHECKSUM_FILE} 上传成功")
except Exception as e:
print(f"[sync] ✗ {CHECKSUM_FILE} 上传失败: {e}")

# 清理
for f in [tmp_tar, tmp_sha]:
if os.path.exists(f):
os.remove(f)
print(f"[sync] ✓ 备份完成: {FILENAME} ({size_mb:.1f} MB)")


if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "backup":
backup()
else:
restore()

关键特性:

  • SHA256 校验:备份上传 latest_backup.sha256,恢复时校验完整性
  • tar 输出屏蔽> /dev/null 2>&1 避免干扰日志,进程退出码判断成功与否
  • 20 分钟超时tar 打包设置 1200s 超时,适合大备份包
  • 恢复跳过机制:远程无备份时直接跳过(except 捕获),不报错
  • 恢复前清空shutil.rmtree + os.makedirs 确保旧文件不残留

start-openclaw.sh(启动脚本)

1
2
3
4
5
6
7
#!/bin/bash
set -e

python3 /app/sync.py restore

# 备份中已包含 openclaw.json,直接用它启动
exec openclaw gateway

关键适配点(相对于通用部署):

  1. dangerouslyDisableDeviceAuth: true
    HF Spaces 是完全托管的环境,用户无法 SSH 进入容器运行 openclaw pairing approve 命令。开启此选项后,Web UI 配对验证被禁用,直接用 Token 登录。

  2. allowedOrigins 限制来源
    只允许指定的 HF Space 域名访问控制台,防止 Token 泄露被滥用。需在 openclaw.json 中手动添加:

    1
    2
    3
    4
    5
    6
    7
    "gateway": {
    "controlUi": {
    "allowedOrigins": [
    "https://[username]-[spacename].hf.space"
    ]
    }
    }
  3. OPENCLAW_GATEWAY_TOKEN 替代密码
    不使用传统的 OPENCLAW_GATEWAY_PASSWORD,改用 Token 模式,值通过环境变量注入。

  4. MiniMax 模型配置
    openclaw.jsonmodels.providers 中配置 baseUrl: https://api.xins.live,完整示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    "models": {
    "mode": "replace",
    "providers": {
    "cloud": {
    "api": "anthropic-messages",
    "apiKey": "你的 MiniMax API Key",
    "baseUrl": "https://api.xins.live",
    "models": [{
    "id": "MiniMax",
    "name": "MiniMax",
    "reasoning": true,
    "contextWindow": 200000,
    "maxTokens": 131072,
    "cost": {
    "input": 4.2,
    "output": 16.8,
    "cacheRead": 0.42,
    "cacheWrite": 2.625
    }
    }]
    }
    }
    }
  5. bind: "lan"
    HF Space 容器内必须绑定 LAN 接口才能被外部访问。

  6. 控制台模型白名单
    openclaw.jsonagents.defaults 中通过 models 字段指定 Control UI 可选的模型列表:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    "agents": {
    "defaults": {
    "models": {
    "cloud/MiniMax": {},
    "minimax/MiniMax-M2.7-highspeed": {},
    "loss/MiniMax": {}
    }
    }
    }

Dockerfile(容器镜像配置)

完整的容器镜像配置,包含 .NET SDK(minimax-docx skill 用 OpenXML SDK)、Python 和 Node.js 依赖。

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
FROM node:22-slim

# ============================================================
# 1. 系统依赖
# ============================================================
RUN apt-get update && apt-get install -y --no-install-recommends \
git openssh-client build-essential python3 python3-pip \
g++ make ca-certificates curl wget gnupg \
&& rm -rf /var/lib/apt/lists/*

# 设置时区(上海)
RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone

# ============================================================
# 2. .NET SDK 8.0(minimax-docx skill 用 OpenXML SDK)
# ============================================================
RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
| gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg \
&& curl -fsSL https://packages.microsoft.com/config/debian/12/prod.list \
| tee /etc/apt/sources.list.d/microsoft-prod.list \
| grep -v '^#' | grep -v '^$' > /dev/null \
&& apt-get update \
&& apt-get install -y --no-install-recommends dotnet-sdk-8.0 \
&& rm -rf /var/lib/apt/lists/*

# ============================================================
# 3. Python 依赖(skill 运行时所需)
# ============================================================
RUN pip3 install --no-cache-dir --break-system-packages \
"huggingface_hub>=0.23" \
openpyxl reportlab pypdf beautifulsoup4 lxml requests uv

# ============================================================
# 4. Node.js 全局包(skill 运行时所需)
# ============================================================
RUN npm install -g openclaw@latest --unsafe-perm \
&& npm install -g pptxgenjs --unsafe-perm \
&& npm install -g mmx-cli --unsafe-perm \
&& ln -sf /usr/local/lib/node_modules/openclaw/openclaw.mjs /usr/local/bin/openclaw

# ============================================================
# 5. Hexo 博客(可选)
# ============================================================
RUN npm install -g hexo --unsafe-perm \
&& npm install -g hexo-deployer-aliyun-oss --unsafe-perm \
&& npm install -g hexo-generator-archive --unsafe-perm \
&& npm install -g hexo-generator-category --unsafe-perm \
&& npm install -g hexo-generator-index --unsafe-perm \
&& npm install -g hexo-generator-tag --unsafe-perm \
&& npm install -g hexo-renderer-marked --unsafe-perm \
&& npm install -g hexo-renderer-pug --unsafe-perm \
&& npm install -g hexo-renderer-ejs --unsafe-perm \
&& npm install -g hexo-renderer-stylus --unsafe-perm \
&& npm install -g probe-image-size --unsafe-perm

# ============================================================
# 6. OpenClaw 工作目录
# ============================================================
WORKDIR /app
COPY sync.py .
COPY start-openclaw.sh .
RUN chmod +x start-openclaw.sh

ENV PORT=7860 HOME=/root

EXPOSE 7860

CMD ["./start-openclaw.sh"]

⚠️ Microsoft APT 源添加时不要用 sed 删除 arch=amd64,否则 prod.list 会变成 deb [] https://... 导致 apt 解析失败。直接 tee 原始文件即可,node:22-slim 默认支持多架构。

⚠️ HF Spaces 容器内 npm 全局包的 .bin 目录不会自动创建,导致 openclaw 命令找不到。必须显式 ln -sf 创建符号链接到 /usr/local/bin/,这一步不能省略。

⚠️ Hexo 阿里云 OSS 凭证不要写在 Dockerfile 里,改为在 HF Spaces Settings → Variables and Secrets 中配置以下环境变量(对应 _config.ymldeploy: 节点):

  • OSS_REGIONoss-cn-wuhan-lr
  • OSS_ACCESS_KEY_ID → 你的 Access Key ID
  • OSS_ACCESS_KEY_SECRET → 你的 Access Key Secret
    部署完成后手动运行 hexo g && hexo deploy 即可。

第六步:提交并验证

  • 在 Space 的 Files 页面添加上述三个文件后 Commit,HF 自动触发构建
  • 点击 Logs 查看构建和运行状态
  • 日志出现以下信息说明部署成功:
1
2
3
4
5
◇ Gateway connection ─────────────────────────────
│ Gateway target: ws://10.108.65.42:7860
│ Source: local lan 10.108.65.42
│ Config: /root/.openclaw/openclaw.json
│ Bind: lan

第七步:配置 UptimeRobot 保活

HF Space 在 48 小时无访问 时会自动休眠。用 UptimeRobot 定时 ping 你的 Space URL 即可保持在线:

  • Monitor Type:HTTP(s)
  • URL:https://[username]-[spacename].hf.space/
  • Monitoring Interval:5 分钟(免费版最小间隔)

登录与使用

在浏览器打开你的 Space URL,首次访问时 UI 显示”未连接”状态。
在登录界面输入 OPENCLAW_GATEWAY_TOKEN 设置的值,确认后状态变为 “Connected”

之后就可以正常与 AI 对话了。

常见问题

Q: 部署后 Token 登录一直失败?

检查环境变量 OPENCLAW_GATEWAY_TOKEN 是否正确配置。另外确认 Space 日志里 config 是否写入了正确的 token。

Q: HF Space 重启后数据丢失?

确认 HF_DATASETHF_TOKEN 环境变量配置正确。Space 首次部署时 sync.py restore 找不到备份文件是正常的(显示 Restore Note 即可),重新部署后下次重启就会自动恢复。

Q: openclaw: command not found

这是 HF Spaces 容器内 npm 全局包路径问题,表现为 Dockerfile 构建成功但启动脚本运行 openclaw gateway 时报错。原因是 npm 全局包的 .bin 目录在容器内没有自动创建,CLI 符号链接丢失。

修复方法:在 Dockerfile 第 4 步末尾加上:

1
&& ln -sf /usr/local/lib/node_modules/openclaw/openclaw.mjs /usr/local/bin/openclaw

然后重新 Rebuild Space 即可。

Q: 国内访问 HF Space 速度慢?

可以用 Cloudflare Tunnel 或类似方案做反向代理改善访问速度。

记录于 2026-04-14 00:14