Commit ·
f072685
1
Parent(s): 577eec6
Rework OpenClaw native runtime
Browse files- Dockerfile +26 -23
- README.md +45 -5
- nginx.conf.template +59 -0
- start-openclaw.sh +0 -271
- start.sh +40 -0
- sync-root-data.sh +60 -0
Dockerfile
CHANGED
|
@@ -1,33 +1,36 @@
|
|
| 1 |
FROM node:22-slim
|
| 2 |
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
| 13 |
&& rm -rf /var/lib/apt/lists/*
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
# 3. Install OpenClaw
|
| 19 |
-
RUN npm install -g openclaw@latest --unsafe-perm
|
| 20 |
|
| 21 |
-
|
| 22 |
-
RUN curl -fsSL https://code-server.dev/install.sh | sh
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
COPY
|
| 27 |
-
RUN chmod +x start-openclaw.sh
|
| 28 |
|
| 29 |
-
|
| 30 |
-
ENV PORT=7860 HOME=/root
|
| 31 |
|
| 32 |
EXPOSE 7860
|
| 33 |
-
|
|
|
|
|
|
| 1 |
FROM node:22-slim
|
| 2 |
|
| 3 |
+
ENV DEBIAN_FRONTEND=noninteractive \
|
| 4 |
+
HOME=/root \
|
| 5 |
+
PROXY_PORT=7860 \
|
| 6 |
+
OPENCLAW_PORT=8080 \
|
| 7 |
+
SYNC_INTERVAL=300 \
|
| 8 |
+
PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
|
| 9 |
+
|
| 10 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 11 |
+
ca-certificates \
|
| 12 |
+
curl \
|
| 13 |
+
gettext-base \
|
| 14 |
+
git \
|
| 15 |
+
nginx \
|
| 16 |
+
procps \
|
| 17 |
+
python3 \
|
| 18 |
+
rsync \
|
| 19 |
+
unzip \
|
| 20 |
+
wget \
|
| 21 |
&& rm -rf /var/lib/apt/lists/*
|
| 22 |
|
| 23 |
+
RUN npm install -g openclaw@latest playwright \
|
| 24 |
+
&& playwright install --with-deps chromium
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
WORKDIR /root/workspace
|
|
|
|
| 27 |
|
| 28 |
+
COPY start.sh /app/start.sh
|
| 29 |
+
COPY sync-root-data.sh /app/sync-root-data.sh
|
| 30 |
+
COPY nginx.conf.template /app/nginx.conf.template
|
|
|
|
| 31 |
|
| 32 |
+
RUN chmod +x /app/start.sh /app/sync-root-data.sh
|
|
|
|
| 33 |
|
| 34 |
EXPOSE 7860
|
| 35 |
+
|
| 36 |
+
CMD ["/app/start.sh"]
|
README.md
CHANGED
|
@@ -1,11 +1,51 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: indigo
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
license: apache-2.0
|
|
|
|
| 9 |
---
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: OpenClaw
|
| 3 |
+
emoji: 🦀
|
| 4 |
+
colorFrom: purple
|
| 5 |
colorTo: indigo
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
license: apache-2.0
|
| 9 |
+
app_port: 7860
|
| 10 |
---
|
| 11 |
+
|
| 12 |
+
# OpenClaw Space
|
| 13 |
+
|
| 14 |
+
Native OpenClaw runtime for Hugging Face Spaces. VS Code/code-server is intentionally not installed.
|
| 15 |
+
|
| 16 |
+
## Routing
|
| 17 |
+
|
| 18 |
+
```text
|
| 19 |
+
/ -> OpenClaw control UI
|
| 20 |
+
/{port}/ -> localhost:{port}
|
| 21 |
+
/proxy/{port}/ -> localhost:{port}
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
## Persistence
|
| 25 |
+
|
| 26 |
+
On startup, `/data/root` is restored into `/root` when `/data` is available.
|
| 27 |
+
|
| 28 |
+
During runtime, `/root` is synced back to `/data/root` every 5 minutes by default.
|
| 29 |
+
|
| 30 |
+
```text
|
| 31 |
+
SYNC_INTERVAL=300
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
Large generated folders such as `node_modules`, `.next`, `dist`, `build`, and cache directories are excluded.
|
| 35 |
+
|
| 36 |
+
## Environment
|
| 37 |
+
|
| 38 |
+
```text
|
| 39 |
+
PROXY_PORT=7860
|
| 40 |
+
OPENCLAW_PORT=8080
|
| 41 |
+
SYNC_INTERVAL=300
|
| 42 |
+
OPENCLAW_CMD=
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
Use `OPENCLAW_CMD` only if your installed OpenClaw CLI needs a different start command.
|
| 46 |
+
|
| 47 |
+
Example:
|
| 48 |
+
|
| 49 |
+
```text
|
| 50 |
+
OPENCLAW_CMD=openclaw --host 127.0.0.1 --port 8080
|
| 51 |
+
```
|
nginx.conf.template
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
worker_processes auto;
|
| 2 |
+
pid /tmp/nginx.pid;
|
| 3 |
+
|
| 4 |
+
events {
|
| 5 |
+
worker_connections 1024;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
http {
|
| 9 |
+
access_log /dev/stdout;
|
| 10 |
+
error_log /dev/stderr info;
|
| 11 |
+
include /etc/nginx/mime.types;
|
| 12 |
+
default_type application/octet-stream;
|
| 13 |
+
sendfile on;
|
| 14 |
+
tcp_nodelay on;
|
| 15 |
+
|
| 16 |
+
map $http_upgrade $connection_upgrade {
|
| 17 |
+
default upgrade;
|
| 18 |
+
'' close;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
server {
|
| 22 |
+
listen ${PROXY_PORT};
|
| 23 |
+
server_name _;
|
| 24 |
+
client_max_body_size 0;
|
| 25 |
+
|
| 26 |
+
proxy_http_version 1.1;
|
| 27 |
+
proxy_set_header Host $host;
|
| 28 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 29 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 30 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
| 31 |
+
proxy_set_header Upgrade $http_upgrade;
|
| 32 |
+
proxy_set_header Connection $connection_upgrade;
|
| 33 |
+
proxy_read_timeout 86400;
|
| 34 |
+
proxy_send_timeout 86400;
|
| 35 |
+
proxy_buffering off;
|
| 36 |
+
|
| 37 |
+
location ~ ^/proxy/([0-9]+)/(.*)$ {
|
| 38 |
+
proxy_set_header X-Forwarded-Prefix /proxy/$1;
|
| 39 |
+
proxy_pass http://127.0.0.1:$1/$2$is_args$args;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
location ~ ^/proxy/([0-9]+)$ {
|
| 43 |
+
return 308 /proxy/$1/;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
location ~ ^/([0-9]+)/(.*)$ {
|
| 47 |
+
proxy_set_header X-Forwarded-Prefix /$1;
|
| 48 |
+
proxy_pass http://127.0.0.1:$1/$2$is_args$args;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
location ~ ^/([0-9]+)$ {
|
| 52 |
+
return 308 /$1/;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
location / {
|
| 56 |
+
proxy_pass http://127.0.0.1:${OPENCLAW_PORT};
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
}
|
start-openclaw.sh
DELETED
|
@@ -1,271 +0,0 @@
|
|
| 1 |
-
#!/bin/bash
|
| 2 |
-
|
| 3 |
-
# ─────────────────────────────────────────────────────────────
|
| 4 |
-
# start-openclaw.sh — VS Code Proxy & OpenClaw Bucket Setup
|
| 5 |
-
# ─────────────────────────────────────────────────────────────
|
| 6 |
-
|
| 7 |
-
set +e
|
| 8 |
-
|
| 9 |
-
echo "===== VS Code Proxy & Storage Setup ====="
|
| 10 |
-
|
| 11 |
-
# ── 1. Setup Persistent Data Bucket (/data) ──────────────────
|
| 12 |
-
mkdir -p /data/.openclaw/agents/main/sessions
|
| 13 |
-
mkdir -p /data/.openclaw/credentials
|
| 14 |
-
mkdir -p /data/.openclaw/sessions
|
| 15 |
-
mkdir -p /data/.openclaw/browsers
|
| 16 |
-
mkdir -p /data/workspace
|
| 17 |
-
|
| 18 |
-
# Link persistent store ke /root agar terbaca OpenClaw default
|
| 19 |
-
ln -sfn /data/.openclaw /root/.openclaw
|
| 20 |
-
|
| 21 |
-
# Link workspace code-server untuk kemudahan akses
|
| 22 |
-
mkdir -p /home/coder
|
| 23 |
-
ln -sfn /data/workspace /home/coder/workspace
|
| 24 |
-
|
| 25 |
-
echo ">>> Persistent directories under /data ready."
|
| 26 |
-
|
| 27 |
-
# ── 2. Fix DNS ────────────────────────────────────────────────
|
| 28 |
-
echo "nameserver 1.1.1.1" >> /etc/resolv.conf
|
| 29 |
-
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
|
| 30 |
-
echo "nameserver 8.8.4.4" >> /etc/resolv.conf
|
| 31 |
-
echo ">>> DNS fixed."
|
| 32 |
-
|
| 33 |
-
# ── 3. Chromium Persistent Install ──────────────────────────────
|
| 34 |
-
export PLAYWRIGHT_BROWSERS_PATH=/data/.openclaw/browsers
|
| 35 |
-
CHROMIUM_PATH=$(find /data/.openclaw/browsers -name "chrome" -type f 2>/dev/null | head -1)
|
| 36 |
-
|
| 37 |
-
if [ -z "$CHROMIUM_PATH" ]; then
|
| 38 |
-
echo ">>> Installing Chromium to persistent storage /data..."
|
| 39 |
-
if timeout 300 npx playwright install chromium; then
|
| 40 |
-
echo ">>> Chromium OK"
|
| 41 |
-
else
|
| 42 |
-
if timeout 300 npx playwright-core install chromium; then
|
| 43 |
-
echo ">>> Chromium OK (via playwright-core)"
|
| 44 |
-
else
|
| 45 |
-
echo ">>> WARN: Chromium install failed"
|
| 46 |
-
fi
|
| 47 |
-
fi
|
| 48 |
-
# Re-check path after install
|
| 49 |
-
CHROMIUM_PATH=$(find /data/.openclaw/browsers -name "chrome" -type f 2>/dev/null | head -1)
|
| 50 |
-
else
|
| 51 |
-
echo ">>> Chromium found in persistent storage: $CHROMIUM_PATH"
|
| 52 |
-
fi
|
| 53 |
-
|
| 54 |
-
# ── 4. OpenClaw Configuration ──────────────────────────────────
|
| 55 |
-
CLEAN_BASE=$(echo "$OPENAI_API_BASE" | sed "s|/chat/completions||g" | sed "s|/v1/|/v1|g" | sed "s|/v1$|/v1|g")
|
| 56 |
-
ALLOW_ID_SAFE=${TELEGRAM_ALLOW_ID:-0}
|
| 57 |
-
|
| 58 |
-
generate_safe_json() {
|
| 59 |
-
cat > /data/.openclaw/openclaw.json <<JSONEOF
|
| 60 |
-
{
|
| 61 |
-
"models": {
|
| 62 |
-
"providers": {
|
| 63 |
-
"kilo_gateway": {
|
| 64 |
-
"baseUrl": "https://api.kilo.ai/api/gateway",
|
| 65 |
-
"apiKey": "anonymous",
|
| 66 |
-
"api": "openai-completions",
|
| 67 |
-
"models": [
|
| 68 |
-
{ "id": "kilo-auto/free", "name": "Kilo Auto Free", "contextWindow": 128000, "maxTokens": 16000 }
|
| 69 |
-
]
|
| 70 |
-
},
|
| 71 |
-
"nvidia": {
|
| 72 |
-
"baseUrl": "$CLEAN_BASE",
|
| 73 |
-
"apiKey": "$OPENAI_API_KEY",
|
| 74 |
-
"api": "openai-completions",
|
| 75 |
-
"models": [
|
| 76 |
-
{ "id": "$MODEL", "name": "$MODEL", "contextWindow": 128000 },
|
| 77 |
-
{ "id": "${VISION_MODEL:-$MODEL}", "name": "Nvidia Vision", "contextWindow": 128000, "input": ["text", "image"] }
|
| 78 |
-
]
|
| 79 |
-
}
|
| 80 |
-
}
|
| 81 |
-
},
|
| 82 |
-
"agents": {
|
| 83 |
-
"defaults": {
|
| 84 |
-
"model": {
|
| 85 |
-
"primary": "nvidia/$MODEL",
|
| 86 |
-
"fallbacks": ["kilo_gateway/kilo-auto/free"]
|
| 87 |
-
},
|
| 88 |
-
"imageModel": {
|
| 89 |
-
"primary": "nvidia/${VISION_MODEL:-$MODEL}"
|
| 90 |
-
},
|
| 91 |
-
"llm": {
|
| 92 |
-
"idleTimeoutSeconds": 0,
|
| 93 |
-
"maxTokens": 16384
|
| 94 |
-
}
|
| 95 |
-
}
|
| 96 |
-
},
|
| 97 |
-
"commands": { "restart": true },
|
| 98 |
-
"browser": {
|
| 99 |
-
"enabled": true,
|
| 100 |
-
"requirePairing": false,
|
| 101 |
-
"headless": true,
|
| 102 |
-
"noSandbox": true,
|
| 103 |
-
"executablePath": "$CHROMIUM_PATH",
|
| 104 |
-
"defaultProfile": "openclaw",
|
| 105 |
-
"ssrfPolicy": { "dangerouslyAllowPrivateNetwork": true },
|
| 106 |
-
"profiles": {
|
| 107 |
-
"openclaw": {
|
| 108 |
-
"cdpPort": 18800,
|
| 109 |
-
"color": "0088FF"
|
| 110 |
-
}
|
| 111 |
-
}
|
| 112 |
-
},
|
| 113 |
-
"channels": {
|
| 114 |
-
"telegram": {
|
| 115 |
-
"enabled": true,
|
| 116 |
-
"botToken": "$TELEGRAM_BOT_TOKEN",
|
| 117 |
-
"dmPolicy": "allowlist",
|
| 118 |
-
"allowFrom": [$ALLOW_ID_SAFE],
|
| 119 |
-
"apiRoot": "https://tg-relay.markdevil11.workers.dev",
|
| 120 |
-
"webhookUrl": "https://elysiadev11-openclaw.hf.space/tg-webhook",
|
| 121 |
-
"webhookSecret": "$OPENCLAW_GATEWAY_PASSWORD",
|
| 122 |
-
"webhookPath": "/tg-webhook",
|
| 123 |
-
"webhookHost": "0.0.0.0",
|
| 124 |
-
"webhookPort": 8787,
|
| 125 |
-
"streaming": {
|
| 126 |
-
"mode": "partial"
|
| 127 |
-
}
|
| 128 |
-
}
|
| 129 |
-
},
|
| 130 |
-
"gateway": {
|
| 131 |
-
"mode": "local",
|
| 132 |
-
"bind": "lan",
|
| 133 |
-
"port": 7862,
|
| 134 |
-
"trustedProxies": ["0.0.0.0/0"],
|
| 135 |
-
"auth": { "mode": "token", "token": "$OPENCLAW_GATEWAY_PASSWORD" },
|
| 136 |
-
"http": {
|
| 137 |
-
"endpoints": {
|
| 138 |
-
"chatCompletions": { "enabled": true }
|
| 139 |
-
}
|
| 140 |
-
},
|
| 141 |
-
"controlUi": {
|
| 142 |
-
"enabled": true,
|
| 143 |
-
"allowInsecureAuth": true,
|
| 144 |
-
"dangerouslyDisableDeviceAuth": true,
|
| 145 |
-
"dangerouslyAllowHostHeaderOriginFallback": true
|
| 146 |
-
}
|
| 147 |
-
}
|
| 148 |
-
}
|
| 149 |
-
JSONEOF
|
| 150 |
-
}
|
| 151 |
-
|
| 152 |
-
# Cek integritas JSON
|
| 153 |
-
echo ">>> Checking /data/.openclaw/openclaw.json..."
|
| 154 |
-
if [ ! -f /data/.openclaw/openclaw.json ] || ! python3 -c 'import json; json.load(open("/data/.openclaw/openclaw.json"))' 2>/dev/null; then
|
| 155 |
-
echo ">>> Generating /data/.openclaw/openclaw.json..."
|
| 156 |
-
generate_safe_json
|
| 157 |
-
else
|
| 158 |
-
echo ">>> Valid openclaw.json found in /data. Keeping it intact."
|
| 159 |
-
fi
|
| 160 |
-
|
| 161 |
-
# ── 5. Start code-server ─────────────────────────────────────
|
| 162 |
-
echo ">>> Starting code-server on port 8443..."
|
| 163 |
-
mkdir -p /root/.config/code-server
|
| 164 |
-
VSCODE_PASSWORD="${OPENCLAW_GATEWAY_PASSWORD:-openclaw}"
|
| 165 |
-
|
| 166 |
-
cat > /root/.config/code-server/config.yaml <<EOF
|
| 167 |
-
bind-addr: 0.0.0.0:8443
|
| 168 |
-
auth: password
|
| 169 |
-
password: ${VSCODE_PASSWORD}
|
| 170 |
-
cert: false
|
| 171 |
-
EOF
|
| 172 |
-
|
| 173 |
-
# Start VS Code explicit dengan PORT=8443 agar mengabaikan global PORT=7860
|
| 174 |
-
PORT=8443 code-server --bind-addr 0.0.0.0:8443 \
|
| 175 |
-
--auth password \
|
| 176 |
-
--disable-telemetry \
|
| 177 |
-
--disable-update-check \
|
| 178 |
-
/data/workspace &
|
| 179 |
-
CODE_SERVER_PID=$!
|
| 180 |
-
echo ">>> code-server started (PID: $CODE_SERVER_PID)"
|
| 181 |
-
|
| 182 |
-
# ── 6. Node.js Reverse Proxy ─────────────────────────────────
|
| 183 |
-
# Routes:
|
| 184 |
-
# /code/* → 8443 (VS Code)
|
| 185 |
-
# /tg-webhook → 8787 (OpenClaw Telegram)
|
| 186 |
-
# /* → 7862 (OpenClaw UI)
|
| 187 |
-
node -e "
|
| 188 |
-
const http = require('http');
|
| 189 |
-
const net = require('net');
|
| 190 |
-
|
| 191 |
-
function proxyHttp(req, res, targetPort, rewritePath) {
|
| 192 |
-
const opts = {
|
| 193 |
-
hostname: '127.0.0.1',
|
| 194 |
-
port: targetPort,
|
| 195 |
-
path: rewritePath || req.url,
|
| 196 |
-
method: req.method,
|
| 197 |
-
headers: { ...req.headers, host: req.headers.host },
|
| 198 |
-
};
|
| 199 |
-
const pr = http.request(opts, (r) => {
|
| 200 |
-
const headers = { ...r.headers };
|
| 201 |
-
if (targetPort === 8443 && headers.location) {
|
| 202 |
-
if (headers.location === '/' || headers.location === '') {
|
| 203 |
-
headers.location = '/code/';
|
| 204 |
-
} else if (!headers.location.startsWith('/code') && headers.location.startsWith('/')) {
|
| 205 |
-
headers.location = '/code' + headers.location;
|
| 206 |
-
}
|
| 207 |
-
}
|
| 208 |
-
res.writeHead(r.statusCode, headers);
|
| 209 |
-
r.pipe(res, { end: true });
|
| 210 |
-
});
|
| 211 |
-
pr.on('error', (e) => {
|
| 212 |
-
res.writeHead(502);
|
| 213 |
-
if (targetPort === 7862) {
|
| 214 |
-
res.end('<h1>OpenClaw Gateway</h1><p>The UI is not reachable. You must start OpenClaw manually via the VS Code terminal (available at /code/).</p>');
|
| 215 |
-
} else {
|
| 216 |
-
res.end('502 Bad Gateway');
|
| 217 |
-
}
|
| 218 |
-
});
|
| 219 |
-
req.pipe(pr, { end: true });
|
| 220 |
-
}
|
| 221 |
-
|
| 222 |
-
function proxyWs(req, socket, head, targetPort, rewritePath) {
|
| 223 |
-
const conn = net.connect(targetPort, '127.0.0.1', () => {
|
| 224 |
-
const path = rewritePath || req.url;
|
| 225 |
-
conn.write(
|
| 226 |
-
'GET ' + path + ' HTTP/1.1\r\n' +
|
| 227 |
-
Object.entries(req.headers).map(([k,v]) => k+': '+v).join('\r\n') +
|
| 228 |
-
'\r\n\r\n'
|
| 229 |
-
);
|
| 230 |
-
if (head && head.length) conn.write(head);
|
| 231 |
-
socket.pipe(conn);
|
| 232 |
-
conn.pipe(socket);
|
| 233 |
-
});
|
| 234 |
-
conn.on('error', () => { socket.destroy(); });
|
| 235 |
-
socket.on('error', () => conn.destroy());
|
| 236 |
-
}
|
| 237 |
-
|
| 238 |
-
const server = http.createServer((req, res) => {
|
| 239 |
-
if (req.url.startsWith('/code')) {
|
| 240 |
-
let newPath = req.url.replace(/^\/code\/?/, '/');
|
| 241 |
-
if (newPath === '') newPath = '/';
|
| 242 |
-
proxyHttp(req, res, 8443, newPath);
|
| 243 |
-
} else if (req.url.startsWith('/tg-webhook')) {
|
| 244 |
-
proxyHttp(req, res, 8787);
|
| 245 |
-
} else {
|
| 246 |
-
proxyHttp(req, res, 7862);
|
| 247 |
-
}
|
| 248 |
-
});
|
| 249 |
-
|
| 250 |
-
server.on('upgrade', (req, socket, head) => {
|
| 251 |
-
if (req.url.startsWith('/code')) {
|
| 252 |
-
let newPath = req.url.replace(/^\/code\/?/, '/');
|
| 253 |
-
if (newPath === '') newPath = '/';
|
| 254 |
-
proxyWs(req, socket, head, 8443, newPath);
|
| 255 |
-
} else {
|
| 256 |
-
proxyWs(req, socket, head, 7862);
|
| 257 |
-
}
|
| 258 |
-
});
|
| 259 |
-
|
| 260 |
-
server.listen(7860, '0.0.0.0', () => {
|
| 261 |
-
console.log('Proxy routing: /code -> VSCode (8443), /tg-webhook -> Telegram (8787), /* -> OpenClaw (7862)');
|
| 262 |
-
});
|
| 263 |
-
" &
|
| 264 |
-
|
| 265 |
-
echo "============================================="
|
| 266 |
-
echo "READY! Open HF space at \`/code/\` to access VS Code,"
|
| 267 |
-
echo "then start openclaw manually from the terminal."
|
| 268 |
-
echo "============================================="
|
| 269 |
-
|
| 270 |
-
# Keep container running
|
| 271 |
-
sleep infinity
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
start.sh
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
export PROXY_PORT="${PROXY_PORT:-7860}"
|
| 5 |
+
export OPENCLAW_PORT="${OPENCLAW_PORT:-8080}"
|
| 6 |
+
export SYNC_INTERVAL="${SYNC_INTERVAL:-300}"
|
| 7 |
+
export PLAYWRIGHT_BROWSERS_PATH="${PLAYWRIGHT_BROWSERS_PATH:-/ms-playwright}"
|
| 8 |
+
|
| 9 |
+
mkdir -p /root/workspace
|
| 10 |
+
|
| 11 |
+
/app/sync-root-data.sh restore
|
| 12 |
+
/app/sync-root-data.sh loop &
|
| 13 |
+
|
| 14 |
+
run_openclaw() {
|
| 15 |
+
while true; do
|
| 16 |
+
echo "Starting OpenClaw on 127.0.0.1:${OPENCLAW_PORT}..."
|
| 17 |
+
if [ -n "${OPENCLAW_CMD:-}" ]; then
|
| 18 |
+
sh -lc "$OPENCLAW_CMD"
|
| 19 |
+
else
|
| 20 |
+
openclaw --host 127.0.0.1 --port "${OPENCLAW_PORT}"
|
| 21 |
+
fi
|
| 22 |
+
echo "OpenClaw exited; restarting in 2 seconds..."
|
| 23 |
+
sleep 2
|
| 24 |
+
done
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
run_nginx() {
|
| 28 |
+
while true; do
|
| 29 |
+
envsubst '${PROXY_PORT} ${OPENCLAW_PORT}' < /app/nginx.conf.template > /tmp/nginx.conf
|
| 30 |
+
nginx -c /tmp/nginx.conf -g 'daemon off;'
|
| 31 |
+
echo "Nginx exited; restarting in 2 seconds..."
|
| 32 |
+
sleep 2
|
| 33 |
+
done
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
run_openclaw &
|
| 37 |
+
run_nginx &
|
| 38 |
+
|
| 39 |
+
wait -n
|
| 40 |
+
exit 1
|
sync-root-data.sh
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
DATA_ROOT="${DATA_ROOT:-/data/root}"
|
| 5 |
+
SYNC_INTERVAL="${SYNC_INTERVAL:-300}"
|
| 6 |
+
|
| 7 |
+
EXCLUDES=(
|
| 8 |
+
"--exclude=.cache"
|
| 9 |
+
"--exclude=.npm/_cacache"
|
| 10 |
+
"--exclude=.npm/_update-notifier-last-checked*"
|
| 11 |
+
"--exclude=.pnpm-store"
|
| 12 |
+
"--exclude=node_modules"
|
| 13 |
+
"--exclude=.next"
|
| 14 |
+
"--exclude=dist"
|
| 15 |
+
"--exclude=build"
|
| 16 |
+
"--exclude=coverage"
|
| 17 |
+
"--exclude=tmp"
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
data_available() {
|
| 21 |
+
[ -d /data ] || mkdir -p /data 2>/dev/null || return 1
|
| 22 |
+
mkdir -p "$DATA_ROOT" 2>/dev/null || return 1
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
restore_root() {
|
| 26 |
+
if data_available; then
|
| 27 |
+
echo "Restoring persistent data from $DATA_ROOT to /root..."
|
| 28 |
+
rsync -rlptD --no-specials --no-devices "${EXCLUDES[@]}" "$DATA_ROOT/" /root/ || true
|
| 29 |
+
else
|
| 30 |
+
echo "Persistent /data is not available; skipping restore."
|
| 31 |
+
fi
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
persist_root() {
|
| 35 |
+
if data_available; then
|
| 36 |
+
echo "Syncing /root to $DATA_ROOT..."
|
| 37 |
+
rsync -rlptD --no-specials --no-devices --delete "${EXCLUDES[@]}" /root/ "$DATA_ROOT/" || true
|
| 38 |
+
else
|
| 39 |
+
echo "Persistent /data is not available; skipping sync."
|
| 40 |
+
fi
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
case "${1:-loop}" in
|
| 44 |
+
restore)
|
| 45 |
+
restore_root
|
| 46 |
+
;;
|
| 47 |
+
persist)
|
| 48 |
+
persist_root
|
| 49 |
+
;;
|
| 50 |
+
loop)
|
| 51 |
+
while true; do
|
| 52 |
+
persist_root
|
| 53 |
+
sleep "$SYNC_INTERVAL"
|
| 54 |
+
done
|
| 55 |
+
;;
|
| 56 |
+
*)
|
| 57 |
+
echo "Usage: $0 {restore|persist|loop}"
|
| 58 |
+
exit 2
|
| 59 |
+
;;
|
| 60 |
+
esac
|