Upload 4 files
Browse files- Dockerfile +39 -0
- openclaw.json +107 -0
- start-openclaw.sh +21 -0
- sync.py +64 -0
Dockerfile
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:22-slim
|
| 2 |
+
|
| 3 |
+
# 1. 基础依赖
|
| 4 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 5 |
+
git openssh-client build-essential python3 python3-pip \
|
| 6 |
+
g++ make ca-certificates curl gettext \
|
| 7 |
+
# Playwright/Chrome 依赖
|
| 8 |
+
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
|
| 9 |
+
libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 \
|
| 10 |
+
libxrandr2 libgbm1 libasound2 libpango-1.0-0 libcairo2 \
|
| 11 |
+
# FFmpeg 用于音频处理
|
| 12 |
+
ffmpeg \
|
| 13 |
+
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
| 14 |
+
|
| 15 |
+
# 安装 Playwright 和 Chromium(Playwright 会自动下载匹配的 Chromium)
|
| 16 |
+
RUN npx playwright install chromium --with-deps && \
|
| 17 |
+
mv /root/.cache/ms-playwright/chromium-* /root/.cache/ms-playwright/chromium
|
| 18 |
+
|
| 19 |
+
# 安装 huggingface_hub (系统不允许直接 pip 装到系统 Python,--break-system-packages强制绕过限制)
|
| 20 |
+
RUN pip3 install --no-cache-dir huggingface_hub --break-system-packages
|
| 21 |
+
# 安装 whisper 和 edge-tts
|
| 22 |
+
RUN pip3 install --no-cache-dir faster-whisper edge-tts --break-system-packages
|
| 23 |
+
|
| 24 |
+
# 2. 安装 OpenClaw
|
| 25 |
+
RUN npm install -g openclaw@latest --unsafe-perm
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# 3. 设置工作目录并拷贝脚本
|
| 29 |
+
WORKDIR /app
|
| 30 |
+
COPY sync.py .
|
| 31 |
+
COPY start-openclaw.sh .
|
| 32 |
+
COPY openclaw.json .
|
| 33 |
+
RUN chmod +x start-openclaw.sh
|
| 34 |
+
|
| 35 |
+
# 4. 环境变量
|
| 36 |
+
ENV PORT=7860 HOME=/root
|
| 37 |
+
|
| 38 |
+
EXPOSE 7860
|
| 39 |
+
CMD ["./start-openclaw.sh"]
|
openclaw.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"models": {
|
| 3 |
+
"providers": {
|
| 4 |
+
"nvidia": {
|
| 5 |
+
"baseUrl": "https://integrate.api.nvidia.com/v1",
|
| 6 |
+
"apiKey": "${NVIDIA_API_KEY}",
|
| 7 |
+
"api": "openai-completions",
|
| 8 |
+
"models": [
|
| 9 |
+
{ "id": "qwen/qwen3.5-397b-a17b", "name": "qwen/qwen3.5-397b-a17b", "input": ["text", "image"], "contextWindow": 128000, "maxTokens": 8192 },
|
| 10 |
+
{ "id": "stepfun-ai/step-3.5-flash", "name": "stepfun-ai/step-3.5-flash", "input": ["text", "image"], "contextWindow": 256000, "maxTokens": 8192 },
|
| 11 |
+
{ "id": "moonshotai/kimi-k2.5", "name": "moonshotai/kimi-k2.5", "input": ["text", "image"], "contextWindow": 256000, "maxTokens": 8192 },
|
| 12 |
+
{ "id": "z-ai/glm4.7", "name": "z-ai/glm4.7", "input": ["text", "image"], "contextWindow": 128000, "maxTokens": 8192 },
|
| 13 |
+
{ "id": "z-ai/glm5", "name": "z-ai/glm5", "input": ["text", "image"], "contextWindow": 128000, "maxTokens": 8192 },
|
| 14 |
+
{ "id": "minimaxai/minimax-m2.5", "name": "minimaxai/minimax-m2.5", "input": ["text", "image"], "contextWindow": 192000, "maxTokens": 8192 }
|
| 15 |
+
]
|
| 16 |
+
},
|
| 17 |
+
"qiniu": {
|
| 18 |
+
"baseUrl": "https://api.qnaigc.com/v1",
|
| 19 |
+
"apiKey": "${QINIU_API_KEY}",
|
| 20 |
+
"api": "openai-completions",
|
| 21 |
+
"models": [
|
| 22 |
+
{ "id": "minimax/minimax-m2.5", "name": "minimax/minimax-m2.5", "input": ["text", "image"], "contextWindow": 128000, "maxTokens": 8192 }
|
| 23 |
+
]
|
| 24 |
+
},
|
| 25 |
+
"silicon": {
|
| 26 |
+
"baseUrl": "https://api.siliconflow.cn/v1",
|
| 27 |
+
"apiKey": "${SF_API_KEY}",
|
| 28 |
+
"api": "openai-completions",
|
| 29 |
+
"models": [
|
| 30 |
+
{ "id": "Pro/MiniMaxAI/MiniMax-M2.5", "name": "Pro/MiniMaxAI/MiniMax-M2.5", "input": ["text", "image"], "contextWindow": 128000, "maxTokens": 8192 }
|
| 31 |
+
]
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
},
|
| 35 |
+
"agents": { "defaults": { "model": { "primary": "${MODEL}" } } },
|
| 36 |
+
"channels": {
|
| 37 |
+
"feishu": {
|
| 38 |
+
"enabled": true,
|
| 39 |
+
"groupChat": "enabled",
|
| 40 |
+
"requireMention": true,
|
| 41 |
+
"accounts": {
|
| 42 |
+
"default": {
|
| 43 |
+
"dmPolicy": "all"
|
| 44 |
+
},
|
| 45 |
+
"main": {
|
| 46 |
+
"appId": "${FEISHU_APP_ID}",
|
| 47 |
+
"appSecret": "${FEISHU_APP_SECRET}"
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
},
|
| 52 |
+
"gateway": {
|
| 53 |
+
"mode": "local", "bind": "lan", "port": 7860,
|
| 54 |
+
"trustedProxies": ["0.0.0.0/0"],
|
| 55 |
+
"auth": { "mode": "token", "token": "${OPENCLAW_GATEWAY_PASSWORD}" },
|
| 56 |
+
"controlUi": {
|
| 57 |
+
"allowInsecureAuth": true,
|
| 58 |
+
"dangerouslyDisableDeviceAuth": true,
|
| 59 |
+
"allowedOrigins": ["*"],
|
| 60 |
+
"dangerouslyAllowHostHeaderOriginFallback": true
|
| 61 |
+
},
|
| 62 |
+
"nodes": {
|
| 63 |
+
"browser": {
|
| 64 |
+
"mode": "auto"
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
},
|
| 68 |
+
"tools": {
|
| 69 |
+
"profile": "full",
|
| 70 |
+
"web": {
|
| 71 |
+
"search": {
|
| 72 |
+
"enabled": true,
|
| 73 |
+
"provider": "brave",
|
| 74 |
+
"apiKey": "${BRAVE_KEY}",
|
| 75 |
+
"maxResults": 5
|
| 76 |
+
},
|
| 77 |
+
"fetch": {
|
| 78 |
+
"enabled": true
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
"agentToAgent": {
|
| 82 |
+
"enabled": true
|
| 83 |
+
}
|
| 84 |
+
},
|
| 85 |
+
"browser": {
|
| 86 |
+
"enabled": true,
|
| 87 |
+
"headless": true,
|
| 88 |
+
"noSandbox": true,
|
| 89 |
+
"defaultProfile": "openclaw",
|
| 90 |
+
"executablePath": "/root/.cache/ms-playwright/chromium/chrome-linux64/chrome"
|
| 91 |
+
},
|
| 92 |
+
"commands": {
|
| 93 |
+
"native": "auto",
|
| 94 |
+
"nativeSkills": "auto",
|
| 95 |
+
"restart": true,
|
| 96 |
+
"ownerDisplay": "raw"
|
| 97 |
+
},
|
| 98 |
+
"session": {
|
| 99 |
+
"maintenance": {
|
| 100 |
+
"mode": "enforce",
|
| 101 |
+
"resetArchiveRetention": "1d"
|
| 102 |
+
}
|
| 103 |
+
},
|
| 104 |
+
"plugins": {
|
| 105 |
+
"entries": {}
|
| 106 |
+
}
|
| 107 |
+
}
|
start-openclaw.sh
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -ex # 打开调试输出
|
| 3 |
+
|
| 4 |
+
# 1. 补全目录
|
| 5 |
+
mkdir -p /root/.openclaw/
|
| 6 |
+
|
| 7 |
+
# 2. 执行恢复
|
| 8 |
+
python3 /app/sync.py restore
|
| 9 |
+
|
| 10 |
+
# 清理旧的 Chrome 锁文件(防止容器重启后 Chrome 无法启动)
|
| 11 |
+
rm -rf /root/.openclaw/browser/*/user-data/Singleton* /tmp/org.chromium.Chromium.* 2>/dev/null || true
|
| 12 |
+
|
| 13 |
+
# 3. 复制配置文件并替换环境变量
|
| 14 |
+
envsubst < /app/openclaw.json > /root/.openclaw/openclaw.json
|
| 15 |
+
|
| 16 |
+
# 4. 启动定时备份 (每 6 小时)
|
| 17 |
+
(while true; do sleep 21600; python3 /app/sync.py backup; done) &
|
| 18 |
+
|
| 19 |
+
# 5. 后台运行
|
| 20 |
+
openclaw gateway run --port $PORT &
|
| 21 |
+
wait
|
sync.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import tarfile
|
| 4 |
+
from huggingface_hub import HfApi, hf_hub_download
|
| 5 |
+
|
| 6 |
+
api = HfApi()
|
| 7 |
+
repo_id = os.getenv("HF_DATASET")
|
| 8 |
+
token = os.getenv("HF_TOKEN")
|
| 9 |
+
FILENAME = "openclaw_space.tar.gz"
|
| 10 |
+
BACKUP_ROOT = "/root/.openclaw"
|
| 11 |
+
|
| 12 |
+
def restore():
|
| 13 |
+
try:
|
| 14 |
+
if not repo_id or not token:
|
| 15 |
+
print("Skip Restore: HF_DATASET or HF_TOKEN not set")
|
| 16 |
+
return
|
| 17 |
+
|
| 18 |
+
# 直接下载最新文件
|
| 19 |
+
print(f"Downloading {FILENAME} from {repo_id}...")
|
| 20 |
+
path = hf_hub_download(repo_id=repo_id, filename=FILENAME, repo_type="dataset", token=token)
|
| 21 |
+
os.makedirs(BACKUP_ROOT, exist_ok=True)
|
| 22 |
+
with tarfile.open(path, "r:gz") as tar:
|
| 23 |
+
tar.extractall(path=BACKUP_ROOT)
|
| 24 |
+
print(f"Success: Restored from {FILENAME}")
|
| 25 |
+
return True
|
| 26 |
+
except Exception as e:
|
| 27 |
+
# 如果是第一次运行,仓库里没文件,报错是正常的
|
| 28 |
+
print(f"Restore Note: No existing backup found or error: {e}")
|
| 29 |
+
|
| 30 |
+
def backup():
|
| 31 |
+
try:
|
| 32 |
+
if not repo_id or not token:
|
| 33 |
+
print("Skip Backup: HF_DATASET or HF_TOKEN not set")
|
| 34 |
+
return
|
| 35 |
+
|
| 36 |
+
temp_tar_path = f"/tmp/{FILENAME}"
|
| 37 |
+
with tarfile.open(temp_tar_path, "w:gz") as tar:
|
| 38 |
+
# 打包整个 .openclaw 文件夹
|
| 39 |
+
if os.path.exists(BACKUP_ROOT):
|
| 40 |
+
# arcname="" 确保解压后直接是文件夹内容,不会多一层目录
|
| 41 |
+
tar.add(BACKUP_ROOT, arcname=".")
|
| 42 |
+
else:
|
| 43 |
+
print(f"Warning: {BACKUP_ROOT} does not exist")
|
| 44 |
+
return
|
| 45 |
+
|
| 46 |
+
# 上传并覆盖
|
| 47 |
+
api.upload_file(
|
| 48 |
+
path_or_fileobj=temp_tar_path,
|
| 49 |
+
path_in_repo=FILENAME,
|
| 50 |
+
repo_id=repo_id,
|
| 51 |
+
repo_type="dataset",
|
| 52 |
+
token=token
|
| 53 |
+
)
|
| 54 |
+
print(f"Backup {FILENAME} Success (Overwritten).")
|
| 55 |
+
# 清理临时文件
|
| 56 |
+
os.remove(temp_tar_path)
|
| 57 |
+
except Exception as e:
|
| 58 |
+
print(f"Backup Error: {e}")
|
| 59 |
+
|
| 60 |
+
if __name__ == "__main__":
|
| 61 |
+
if len(sys.argv) > 1 and sys.argv[1] == "backup":
|
| 62 |
+
backup()
|
| 63 |
+
else:
|
| 64 |
+
restore()
|