openclaw / Dockerfile
quinnz's picture
Update Dockerfile
1825815 verified
# ========= Base =========
FROM node:22-slim
# ========= System deps =========
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
openssh-client \
build-essential \
python3 \
python3-pip \
ca-certificates \
curl \
jq \
&& rm -rf /var/lib/apt/lists/*
# ========= HF CLI & Python deps =========
RUN pip3 install --no-cache-dir huggingface_hub requests slack-sdk --break-system-packages
# ========= Git HTTPS fallback =========
RUN git config --global url."https://github.com/".insteadOf ssh://git@github.com/
# ========= OpenClaw =========
RUN npm install -g openclaw@latest --unsafe-perm
# ========= Copy scripts =========
COPY auto-approve.py /usr/local/bin/auto-approve.py
RUN chmod +x /usr/local/bin/auto-approve.py
# ========= ENV =========
ENV PORT=7860 \
OPENCLAW_GATEWAY_MODE=local \
HOME=/root
# ========= sync.py =========
RUN cat <<'PYEOF' > /usr/local/bin/sync.py
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")
def restore():
try:
print(f"--- [SYNC] Restore from {repo_id} ---")
if not repo_id or not token:
print("--- [SYNC] Skipped (no config) ---")
return
files = api.list_repo_files(repo_id=repo_id, repo_type="dataset", token=token)
now = datetime.now()
for i in range(5):
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("/root/.openclaw/")
print("--- [SYNC] Restore OK ---")
return
print("--- [SYNC] No backup found ---")
except Exception as e:
print("Restore error:", e)
def backup():
try:
day = datetime.now().strftime("%Y-%m-%d")
name = f"backup_{day}.tar.gz"
with tarfile.open(name, "w:gz") as tar:
for t in ["sessions","workspace","agents","memory","openclaw.json"]:
p=f"/root/.openclaw/{t}"
if os.path.exists(p):
tar.add(p, arcname=t)
api.upload_file(
path_or_fileobj=name,
path_in_repo=name,
repo_id=repo_id,
repo_type="dataset",
token=token
)
print("--- [SYNC] Backup OK ---")
except Exception as e:
print("Backup error:", e)
if __name__ == "__main__":
if len(sys.argv)>1 and sys.argv[1]=="backup":
backup()
else:
restore()
PYEOF
# ========= start-openclaw =========
RUN cat <<'SHEOF' > /usr/local/bin/start-openclaw
#!/bin/bash
set -e
mkdir -p /root/.openclaw/{sessions,workspace,credentials}
chmod 700 /root/.openclaw
# Restore state
python3 /usr/local/bin/sync.py
# Clean base url safely
if [ -n "$OPENAI_API_BASE" ]; then
CLEAN_BASE=$(echo "$OPENAI_API_BASE" \
| sed "s|/chat/completions||g" \
| sed "s|/v1/|/v1|g" \
| sed "s|/v1$|/v1|g")
else
CLEAN_BASE="https://api.siliconflow.cn/v1"
fi
# Slack credentials
if [ -n "$SLACK_BOT_TOKEN" ]; then
cat > /root/.openclaw/credentials/slack.json <<EOF
{
"botToken":"$SLACK_BOT_TOKEN",
"appToken":"$SLACK_APP_TOKEN"
}
EOF
fi
# OpenClaw config
cat > /root/.openclaw/openclaw.json <<EOF
{
"models":{
"providers":{
"siliconflow":{
"baseUrl":"$CLEAN_BASE",
"apiKey":"$OPENAI_API_KEY",
"api":"openai-completions",
"models":[
{"id":"$MODEL","name":"LLM","contextWindow":128000}
]
}
}
},
"agents":{
"defaults":{
"model":{"primary":"siliconflow/$MODEL"}
}
},
"gateway":{
"mode":"local",
"bind":"lan",
"port":$PORT,
"trustedProxies":["0.0.0.0/0"],
"auth":{
"mode":"token",
"token":"$OPENCLAW_GATEWAY_PASSWORD"
},
"controlUi":{
"allowInsecureAuth": true,
"allowedOrigins": [
"https://quinnz-openclaw.hf.space"
]
}
},
"channels":{
"slack": {
"mode": "socket",
"webhookPath": "/slack/events",
"enabled": true,
"botToken": "$SLACK_BOT_TOKEN",
"appToken": "$SLACK_APP_TOKEN",
"userTokenReadOnly": true,
"groupPolicy": "allowlist",
"streaming": "partial",
"nativeStreaming": true,
"actions": {
"reactions": true,
"messages": true,
"pins": true,
"memberInfo": true,
"channelInfo": true,
"emojiList": true
},
"dm": {
"enabled": true,
"policy": "allowlist",
"allowFrom": [
"$SLACK_USER_ID"
]
},
"channels": {
"$SLACK_CHANNEL_ID": {
"allow": true,
"requireMention": true
}
}
}
}
}
EOF
# Background backup loop
(
while true; do
sleep 10800
python3 /usr/local/bin/sync.py backup
done
) &
# Run doctor
openclaw doctor --fix || true
# Start Gateway
echo "--- Starting OpenClaw Gateway ---"
openclaw gateway run --port $PORT &
# Wait for Gateway ready
echo "--- Waiting for Gateway to be ready ---"
for i in {1..30}; do
if curl -s -H "Authorization: Bearer $OPENCLAW_GATEWAY_PASSWORD" \
http://127.0.0.1:$PORT/api/health >/dev/null 2>&1; then
echo "--- Gateway ready ---"
break
fi
sleep 1
done
# Auto approve Slack pairing
echo "--- Starting auto-approve.py ---"
python3 -u /usr/local/bin/auto-approve.py &
# Wait for frontend pairing
echo "--- Waiting for frontend pairing ---"
paired=0
for i in {1..60}; do
status=$(curl -s -H "Authorization: Bearer $OPENCLAW_GATEWAY_PASSWORD" \
http://127.0.0.1:$PORT/api/status || echo "")
if echo "$status" | grep -q '"connectedClients":[1-9]'; then
echo "✅ Gateway paired with frontend"
paired=1
break
fi
sleep 1
done
if [ "$paired" -ne 1 ]; then
echo "⚠️ Warning: no frontend connected after 60s"
fi
# Keep foreground
wait
SHEOF
RUN chmod +x /usr/local/bin/start-openclaw
# ========= Ports & CMD =========
EXPOSE 7860
CMD ["/usr/local/bin/start-openclaw"]