# OpenClaw on Hugging Face Spaces 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 \ && rm -rf /var/lib/apt/lists/* # 2. 安装 Hugging Face Hub RUN pip3 install --no-cache-dir huggingface_hub --break-system-packages # 3. Git 配置 RUN update-ca-certificates && \ git config --global http.sslVerify false && \ git config --global url."https://github.com/".insteadOf ssh://git@github.com/ # 4. 安装 OpenClaw RUN npm install -g openclaw@latest --unsafe-perm # 5. 环境变量预设 - 所有可配置的环境变量,使用 ${VAR:-default} 格式 ENV \ # 基础配置 PORT=${PORT:-7860} \ NODE_ENV=${NODE_ENV:-production} \ HOME=${HOME:-/root} \ \ # OpenClaw 核心配置 OPENCLAW_GATEWAY_MODE=${OPENCLAW_GATEWAY_MODE:-local} \ OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN:-} \ \ # 模型配置 MODEL_PROVIDER=${MODEL_PROVIDER:-nvidia} \ MODEL_ID=${MODEL_ID:-moonshotai/kimi-k2.5} \ MODEL_CONTEXT_WINDOW=${MODEL_CONTEXT_WINDOW:-256000} \ MODEL_DISPLAY_NAME=${MODEL_DISPLAY_NAME:-"Kimi K2.5"} \ \ # API 配置 API_BASE_URL=${API_BASE_URL:-https://integrate.api.nvidia.com/v1} \ API_TYPE=${API_TYPE:-openai-completions} \ OPENAI_API_KEY=${OPENAI_API_KEY:-} \ \ # 网关配置 GATEWAY_AUTH_MODE=${GATEWAY_AUTH_MODE:-token} \ GATEWAY_BIND=${GATEWAY_BIND:-lan} \ GATEWAY_TRUSTED_PROXIES=${GATEWAY_TRUSTED_PROXIES:-0.0.0.0/0,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16} \ GATEWAY_ALLOWED_ORIGINS=${GATEWAY_ALLOWED_ORIGINS:-https://control.example.com} \ \ # 控制UI配置 CONTROLUI_ALLOW_INSECURE_AUTH=${CONTROLUI_ALLOW_INSECURE_AUTH:-true} \ CONTROLUI_DANGEROUS_HOST_HEADER=${CONTROLUI_DANGEROUS_HOST_HEADER:-true} \ CONTROLUI_DANGEROUS_DISABLE_DEVICE_AUTH=${CONTROLUI_DANGEROUS_DISABLE_DEVICE_AUTH:-true} \ \ # Telegram 配置 TELEGRAM_ENABLED=${TELEGRAM_ENABLED:-false} \ TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-} \ TELEGRAM_DM_POLICY=${TELEGRAM_DM_POLICY:-allowlist} \ TELEGRAM_ALLOW_FROM=${TELEGRAM_ALLOW_FROM:-} \ \ # 备份配置 BACKUP_ENABLED=${BACKUP_ENABLED:-true} \ BACKUP_INTERVAL=${BACKUP_INTERVAL:-21600} \ BACKUP_RETENTION_DAYS=${BACKUP_RETENTION_DAYS:-5} \ HF_DATASET=${HF_DATASET:-} \ HF_TOKEN=${HF_TOKEN:-} # 6. 同步脚本 RUN cat > /usr/local/bin/sync.py << 'SYNC_EOF' import os, sys, tarfile from huggingface_hub import HfApi, hf_hub_download from datetime import datetime, timedelta api = HfApi() repo_id = os.getenv("HF_DATASET", "") token = os.getenv("HF_TOKEN", "") retention_days = int(os.getenv("BACKUP_RETENTION_DAYS", "5")) def restore(): if not repo_id or not token: print("No HF_DATASET or HF_TOKEN, skipping restore") return False try: files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token) now = datetime.now() for i in range(retention_days): day = (now - timedelta(days=i)).strftime("%Y-%m-%d") name = f"backup_{day}.tar.gz" if name in files: path = hf_hub_download(repo_id=repo_id, filename=name, repo_type="dataset", token=token) with tarfile.open(path, "r:gz") as tar: tar.extractall(path="/root/.openclaw/") print(f"Restored from {name}") return True print("No backup found") except Exception as e: print(f"Restore warning: {e}") return False def backup(): if not repo_id or not token: return try: day = datetime.now().strftime("%Y-%m-%d") name = f"backup_{day}.tar.gz" with tarfile.open(name, "w:gz") as tar: if os.path.exists("/root/.openclaw/sessions"): tar.add("/root/.openclaw/sessions", arcname="sessions") if os.path.exists("/root/.openclaw/openclaw.json"): tar.add("/root/.openclaw/openclaw.json", arcname="openclaw.json") api.upload_file(path_or_fileobj=name, path_in_repo=name, repo_id=repo_id, repo_type="dataset", token=token) print(f"Backup {name} done") os.remove(name) except Exception as e: print(f"Backup warning: {e}") if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == "backup": backup() else: restore() SYNC_EOF RUN chmod +x /usr/local/bin/sync.py # 7. 启动脚本 RUN cat > /usr/local/bin/start-openclaw << 'START_EOF' #!/bin/bash set -e mkdir -p /root/.openclaw/sessions # 尝试恢复数据(可选) if [ -n "$HF_DATASET" ] && [ -n "$HF_TOKEN" ]; then python3 /usr/local/bin/sync.py restore || true fi # 准备 API 配置 CLEAN_BASE="${API_BASE_URL}" CLEAN_BASE=$(echo "$CLEAN_BASE" | sed 's|/chat/completions||g' | sed 's|/v1/|/v1|g' | sed 's|/v1$|/v1|') # 生成令牌(如果没设置) if [ -z "$OPENCLAW_GATEWAY_TOKEN" ]; then OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 16) fi # 转换可信代理列表为JSON数组 TRUSTED_PROXIES_JSON=$(echo "$GATEWAY_TRUSTED_PROXIES" | tr ',' '\n' | awk '{ printf "\"%s\",", $0 }' | sed 's/,$//' | sed 's/^/[/' | sed 's/$/]/') # 转换允许的源列表为JSON数组 ALLOWED_ORIGINS_JSON=$(echo "$GATEWAY_ALLOWED_ORIGINS" | tr ',' '\n' | awk '{ printf "\"%s\",", $0 }' | sed 's/,$//' | sed 's/^/[/' | sed 's/$/]/') # 转换Telegram允许列表为JSON数组 if [ -n "$TELEGRAM_ALLOW_FROM" ]; then # 规范化ID,添加tg:前缀(如果没有) TELEGRAM_ALLOW_JSON=$(echo "$TELEGRAM_ALLOW_FROM" | tr ',' '\n' | while read id; do id=$(echo "$id" | xargs) # 去除空格 if [[ "$id" =~ ^[0-9]+$ ]]; then echo "\"tg:$id\"" elif [[ "$id" =~ ^tg: ]]; then echo "\"$id\"" elif [[ "$id" =~ ^@ ]]; then # 注意:@username需要在运行时通过doctor --fix解析 echo "\"$id\"" else echo "\"$id\"" fi done | paste -sd ',' | sed 's/^/[/' | sed 's/$/]/') else TELEGRAM_ALLOW_JSON="[]" fi # 创建 OpenClaw 配置 cat > /root/.openclaw/openclaw.json <