File size: 8,630 Bytes
664c278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7d64fe6
664c278
 
 
 
 
 
 
 
 
 
b2c5093
664c278
 
 
 
 
 
b2c5093
 
 
 
 
 
664c278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2c5093
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664c278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2c5093
 
664c278
 
 
 
b2c5093
 
 
 
 
 
 
 
664c278
 
 
 
 
 
 
 
 
 
b2c5093
 
 
 
 
 
664c278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# 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 <<OPENCLAW_CONFIG
{
  "models": {
    "providers": {
      "${MODEL_PROVIDER}": {
        "baseUrl": "${CLEAN_BASE}",
        "apiKey": "${OPENAI_API_KEY}",
        "api": "${API_TYPE}",
        "models": [{
          "id": "${MODEL_ID}",
          "name": "${MODEL_DISPLAY_NAME}",
          "contextWindow": ${MODEL_CONTEXT_WINDOW}
        }]
      }
    }
  },
  "agents": {
    "defaults": {
      "model": {
        "primary": "${MODEL_PROVIDER}/${MODEL_ID}"
      },
      "workspace": "~/.openclaw/workspace"
    }
  },
  "gateway": {
    "mode": "${OPENCLAW_GATEWAY_MODE}",
    "bind": "${GATEWAY_BIND}",
    "port": ${PORT},
    "trustedProxies": ${TRUSTED_PROXIES_JSON},
    "auth": {
      "mode": "${GATEWAY_AUTH_MODE}",
      "token": "${OPENCLAW_GATEWAY_TOKEN}"
    },
    "remote": {
      "token": "${OPENCLAW_GATEWAY_TOKEN}"
    },
    "controlUi": {
      "allowInsecureAuth": ${CONTROLUI_ALLOW_INSECURE_AUTH},
      "dangerouslyAllowHostHeaderOriginFallback": ${CONTROLUI_DANGEROUS_HOST_HEADER},
      "dangerouslyDisableDeviceAuth": ${CONTROLUI_DANGEROUS_DISABLE_DEVICE_AUTH},
      "allowedOrigins": ${ALLOWED_ORIGINS_JSON}
    }
  },
  "session": {
    "store": "/root/.openclaw/sessions/sessions.json"
  },
  "channels": {
    "telegram": {
      "enabled": ${TELEGRAM_ENABLED},
      "botToken": "${TELEGRAM_BOT_TOKEN}",
      "dmPolicy": "${TELEGRAM_DM_POLICY}",
      "allowFrom": ${TELEGRAM_ALLOW_JSON}
    }
  }
}
OPENCLAW_CONFIG

echo "========================================"
echo "OpenClaw Gateway Starting..."
echo "Model: ${MODEL_ID}"
echo "Provider: ${MODEL_PROVIDER}"
echo "Port: ${PORT}"
echo "Backup: ${BACKUP_ENABLED}"
echo "Allowed Origins: ${GATEWAY_ALLOWED_ORIGINS}"
echo "Telegram Enabled: ${TELEGRAM_ENABLED}"
if [ "${TELEGRAM_ENABLED}" = "true" ]; then
    echo "Telegram DM Policy: ${TELEGRAM_DM_POLICY}"
    echo "Telegram Allow From: ${TELEGRAM_ALLOW_FROM}"
fi
echo "========================================"

# 后台备份循环(如果启用)
if [ "${BACKUP_ENABLED}" = "true" ] && [ -n "$HF_DATASET" ] && [ -n "$HF_TOKEN" ]; then
    (
    while true; do
        sleep ${BACKUP_INTERVAL}
        python3 /usr/local/bin/sync.py backup || true
    done
    ) &
fi

# 启动网关
openclaw doctor --fix || true
exec openclaw gateway run --port $PORT
START_EOF
RUN chmod +x /usr/local/bin/start-openclaw

EXPOSE 7860

CMD ["/usr/local/bin/start-openclaw"]