Spaces:
Running
Running
| set -euo pipefail | |
| export PROXY_PORT="${PROXY_PORT:-7860}" | |
| export OPENCLAW_PORT="${OPENCLAW_PORT:-8080}" | |
| export CODE_PORT="${CODE_PORT:-8888}" | |
| export PORT="${OPENCLAW_PORT}" | |
| export HOST="0.0.0.0" | |
| export OPENCLAW_HOST="0.0.0.0" | |
| export OPENCLAW_PORT | |
| export OPENCLAW_GATEWAY_PASSWORD="${OPENCLAW_GATEWAY_PASSWORD:-773322}" | |
| export SYNC_INTERVAL="${SYNC_INTERVAL:-300}" | |
| export PLAYWRIGHT_BROWSERS_PATH="${PLAYWRIGHT_BROWSERS_PATH:-/ms-playwright}" | |
| export TELEGRAM_API_ROOT="${TELEGRAM_API_ROOT:-https://tg-relay.markdevil11.workers.dev}" | |
| export OPENCLAW_TELEGRAM_API_ROOT="${OPENCLAW_TELEGRAM_API_ROOT:-${TELEGRAM_API_ROOT}}" | |
| export SPACE_URL="${SPACE_URL:-https://elysiadev11-openclaw.hf.space}" | |
| export TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}" | |
| export TELEGRAM_ALLOW_ID="${TELEGRAM_ALLOW_ID:-0}" | |
| configure_dns() { | |
| if [ -w /etc/resolv.conf ]; then | |
| cat > /etc/resolv.conf <<'EOF' | |
| nameserver 1.1.1.1 | |
| nameserver 1.0.0.1 | |
| nameserver 8.8.8.8 | |
| nameserver 8.8.4.4 | |
| options timeout:2 attempts:3 rotate | |
| EOF | |
| echo "DNS configured with Cloudflare and Google resolvers." | |
| else | |
| echo "WARN: /etc/resolv.conf is not writable; keeping existing DNS." | |
| fi | |
| } | |
| mkdir -p /root/workspace | |
| configure_dns | |
| /app/sync-root-data.sh restore | |
| mkdir -p /root/.openclaw | |
| generate_openclaw_config() { | |
| local clean_base | |
| clean_base="$(printf '%s' "${OPENAI_API_BASE:-}" | sed 's|/chat/completions||g' | sed 's|/v1/|/v1|g' | sed 's|/v1$|/v1|g')" | |
| local allow_id | |
| allow_id="${TELEGRAM_ALLOW_ID:-0}" | |
| local primary_model="kilo_gateway/kilo-auto/free" | |
| local nvidia_provider="" | |
| if [ -n "$clean_base" ] && [ -n "${OPENAI_API_KEY:-}" ]; then | |
| primary_model="nvidia/${MODEL:-gpt-5.5}" | |
| nvidia_provider=', | |
| "nvidia": { | |
| "baseUrl": "'"${clean_base}"'", | |
| "apiKey": "'"${OPENAI_API_KEY:-}"'", | |
| "api": "openai-completions", | |
| "models": [ | |
| { "id": "'"${MODEL:-gpt-5.5}"'", "name": "'"${MODEL:-gpt-5.5}"'", "contextWindow": 128000 }, | |
| { "id": "'"${VISION_MODEL:-${MODEL:-gpt-5.5}}"'", "name": "Nvidia Vision", "contextWindow": 128000, "input": ["text", "image"] } | |
| ] | |
| }' | |
| fi | |
| cat > /root/.openclaw/openclaw.json <<JSONEOF | |
| { | |
| "models": { | |
| "providers": { | |
| "kilo_gateway": { | |
| "baseUrl": "https://api.kilo.ai/api/gateway", | |
| "apiKey": "anonymous", | |
| "api": "openai-completions", | |
| "models": [ | |
| { "id": "kilo-auto/free", "name": "Kilo Auto Free", "contextWindow": 128000, "maxTokens": 16000 } | |
| ] | |
| }${nvidia_provider} | |
| } | |
| }, | |
| "agents": { | |
| "defaults": { | |
| "model": { | |
| "primary": "${primary_model}", | |
| "fallbacks": ["kilo_gateway/kilo-auto/free"] | |
| }, | |
| "imageModel": { | |
| "primary": "${primary_model}" | |
| } | |
| } | |
| }, | |
| "commands": { "restart": true }, | |
| "browser": { | |
| "enabled": true, | |
| "headless": true, | |
| "noSandbox": true, | |
| "defaultProfile": "openclaw", | |
| "ssrfPolicy": { "dangerouslyAllowPrivateNetwork": true }, | |
| "profiles": { | |
| "openclaw": { | |
| "cdpPort": 18800, | |
| "color": "0088FF" | |
| } | |
| } | |
| }, | |
| "channels": { | |
| "telegram": { | |
| "enabled": true, | |
| "botToken": "${TELEGRAM_BOT_TOKEN:-}", | |
| "dmPolicy": "allowlist", | |
| "allowFrom": [${allow_id}], | |
| "apiRoot": "${TELEGRAM_API_ROOT}", | |
| "webhookUrl": "${SPACE_URL}/tg-webhook", | |
| "webhookSecret": "${OPENCLAW_GATEWAY_PASSWORD:-}", | |
| "webhookPath": "/tg-webhook", | |
| "webhookHost": "0.0.0.0", | |
| "webhookPort": 8787, | |
| "streaming": { | |
| "mode": "partial" | |
| } | |
| } | |
| }, | |
| "gateway": { | |
| "mode": "local", | |
| "bind": "lan", | |
| "port": ${OPENCLAW_PORT}, | |
| "trustedProxies": ["0.0.0.0/0"], | |
| "auth": { "mode": "token", "token": "${OPENCLAW_GATEWAY_PASSWORD:-}" }, | |
| "http": { | |
| "endpoints": { | |
| "chatCompletions": { "enabled": true } | |
| } | |
| }, | |
| "controlUi": { | |
| "enabled": true, | |
| "allowedOrigins": ["${SPACE_URL}"], | |
| "allowInsecureAuth": true, | |
| "dangerouslyDisableDeviceAuth": true, | |
| "dangerouslyAllowHostHeaderOriginFallback": true | |
| } | |
| } | |
| } | |
| JSONEOF | |
| } | |
| config_is_complete() { | |
| python3 - <<'PY' | |
| import json | |
| from pathlib import Path | |
| path = Path('/root/.openclaw/openclaw.json') | |
| if not path.exists(): | |
| raise SystemExit(1) | |
| try: | |
| data = json.loads(path.read_text()) | |
| except Exception: | |
| raise SystemExit(1) | |
| required_paths = [ | |
| ('models', 'providers'), | |
| ('models', 'providers', 'kilo_gateway'), | |
| ('agents', 'defaults'), | |
| ('browser',), | |
| ('gateway',), | |
| ('gateway', 'controlUi'), | |
| ('channels', 'telegram'), | |
| ] | |
| for parts in required_paths: | |
| cur = data | |
| for part in parts: | |
| if not isinstance(cur, dict) or part not in cur: | |
| raise SystemExit(1) | |
| cur = cur[part] | |
| raise SystemExit(0) | |
| PY | |
| } | |
| if ! config_is_complete; then | |
| echo "Generating full /root/.openclaw/openclaw.json before OpenClaw startup..." | |
| generate_openclaw_config | |
| else | |
| echo "Complete /root/.openclaw/openclaw.json found; keeping it." | |
| fi | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| from pathlib import Path | |
| path = Path('/root/.openclaw/openclaw.json') | |
| api_root = os.environ.get('TELEGRAM_API_ROOT', 'https://tg-relay.markdevil11.workers.dev') | |
| space_url = os.environ.get('SPACE_URL', 'https://elysiadev11-openclaw.hf.space') | |
| if not path.exists(): | |
| raise SystemExit(0) | |
| try: | |
| data = json.loads(path.read_text()) | |
| except Exception as exc: | |
| print(f'WARN: cannot update Telegram apiRoot in openclaw.json: {exc}') | |
| raise SystemExit(0) | |
| channels = data.setdefault('channels', {}) | |
| telegram = channels.setdefault('telegram', {}) | |
| old = telegram.get('apiRoot') | |
| providers = data.setdefault('models', {}).setdefault('providers', {}) | |
| nvidia = providers.get('nvidia') | |
| if isinstance(nvidia, dict) and not nvidia.get('baseUrl'): | |
| providers.pop('nvidia', None) | |
| agents = data.setdefault('agents', {}) | |
| defaults = agents.setdefault('defaults', {}) | |
| model = defaults.setdefault('model', {}) | |
| if model.get('primary', '').startswith('nvidia/') and 'nvidia' not in providers: | |
| model['primary'] = 'kilo_gateway/kilo-auto/free' | |
| fallbacks = model.setdefault('fallbacks', []) | |
| if 'kilo_gateway/kilo-auto/free' not in fallbacks: | |
| fallbacks.append('kilo_gateway/kilo-auto/free') | |
| image_model = defaults.setdefault('imageModel', {}) | |
| if image_model.get('primary', '').startswith('nvidia/') and 'nvidia' not in providers: | |
| image_model['primary'] = model['primary'] | |
| telegram['webhookUrl'] = f'{space_url}/tg-webhook' | |
| telegram['webhookPath'] = '/tg-webhook' | |
| telegram['webhookHost'] = '0.0.0.0' | |
| telegram['webhookPort'] = 8787 | |
| browser = data.get('browser') | |
| if isinstance(browser, dict): | |
| browser.pop('requirePairing', None) | |
| if isinstance(defaults, dict): | |
| defaults.pop('llm', None) | |
| gateway = data.setdefault('gateway', {}) | |
| auth = gateway.setdefault('auth', {}) | |
| auth['mode'] = 'token' | |
| auth['token'] = os.environ.get('OPENCLAW_GATEWAY_PASSWORD', '773322') | |
| control_ui = gateway.setdefault('controlUi', {}) | |
| control_ui['enabled'] = True | |
| control_ui['allowInsecureAuth'] = True | |
| control_ui['dangerouslyDisableDeviceAuth'] = True | |
| control_ui['dangerouslyAllowHostHeaderOriginFallback'] = True | |
| origins = control_ui.setdefault('allowedOrigins', []) | |
| if space_url not in origins: | |
| origins.append(space_url) | |
| if old != api_root: | |
| telegram['apiRoot'] = api_root | |
| print(f'Updated Telegram apiRoot: {old!r} -> {api_root!r}') | |
| else: | |
| print(f'Telegram apiRoot already set to {api_root}') | |
| print(f'Telegram webhookUrl set to {telegram.get("webhookUrl")}') | |
| path.write_text(json.dumps(data, indent=2) + '\n') | |
| PY | |
| /app/sync-root-data.sh reconcile | |
| /app/sync-root-data.sh loop & | |
| # ── code-server (VS Code) ───────────────────────────────────────────────────── | |
| run_code_server() { | |
| mkdir -p /root/.config/code-server | |
| cat > /root/.config/code-server/config.yaml <<YAML | |
| bind-addr: 127.0.0.1:${CODE_PORT} | |
| auth: password | |
| password: ${OPENCLAW_GATEWAY_PASSWORD} | |
| cert: false | |
| YAML | |
| while true; do | |
| echo "Starting code-server on 127.0.0.1:${CODE_PORT}..." | |
| code-server --config /root/.config/code-server/config.yaml /root/workspace | |
| echo "code-server exited; restarting in 2 seconds..." | |
| sleep 2 | |
| done | |
| } | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| run_openclaw() { | |
| while true; do | |
| echo "Starting OpenClaw on 127.0.0.1:${OPENCLAW_PORT}..." | |
| echo "OpenClaw config:" | |
| cat /root/.openclaw/openclaw.json | |
| if [ -n "${OPENCLAW_CMD:-}" ]; then | |
| sh -lc "$OPENCLAW_CMD" | |
| else | |
| openclaw gateway --port "${OPENCLAW_PORT}" --allow-unconfigured & | |
| OPENCLAW_PID=$! | |
| echo "OpenClaw started with PID: $OPENCLAW_PID" | |
| wait $OPENCLAW_PID | |
| fi | |
| echo "OpenClaw exited; restarting in 2 seconds..." | |
| sleep 2 | |
| done | |
| } | |
| run_nginx() { | |
| while true; do | |
| envsubst '${PROXY_PORT} ${OPENCLAW_PORT} ${OPENCLAW_GATEWAY_PASSWORD} ${CODE_PORT}' \ | |
| < /app/nginx.conf.template > /tmp/nginx.conf | |
| nginx -c /tmp/nginx.conf -g 'daemon off;' | |
| echo "Nginx exited; restarting in 2 seconds..." | |
| sleep 2 | |
| done | |
| } | |
| run_simple_webhook() { | |
| while true; do | |
| echo "Starting simple webhook server on port 8787..." | |
| python3 /app/webhook_server.py & | |
| WEBHOOK_PID=$! | |
| echo "Simple webhook server started with PID: $WEBHOOK_PID" | |
| wait $WEBHOOK_PID | |
| echo "Webhook server exited; restarting in 2 seconds..." | |
| sleep 2 | |
| done | |
| } | |
| run_openclaw & | |
| run_code_server & | |
| run_nginx & | |
| # Wait for services to start | |
| echo "Waiting for services to start..." | |
| sleep 10 | |
| # Check webhook port | |
| echo "Checking webhook port 8787..." | |
| if command -v ss &> /dev/null && ss -tuln 2>/dev/null | grep -q ":8787"; then | |
| echo "Webhook server is listening on port 8787" | |
| curl -s -o /dev/null -w "Webhook test status: %{http_code}\n" http://localhost:8787/tg-webhook || true | |
| elif curl -s http://0.0.0.0:8787/tg-webhook &>/dev/null; then | |
| echo "Webhook server is accessible on port 8787" | |
| else | |
| echo "WARNING: Webhook server is NOT listening on port 8787 — starting fallback..." | |
| run_simple_webhook & | |
| fi | |
| # Check OpenClaw port | |
| echo "Checking OpenClaw port ${OPENCLAW_PORT}..." | |
| if command -v ss &> /dev/null && ss -tuln 2>/dev/null | grep -q ":${OPENCLAW_PORT}"; then | |
| echo "OpenClaw is listening on port ${OPENCLAW_PORT}" | |
| elif curl -s http://127.0.0.1:${OPENCLAW_PORT}/health &>/dev/null; then | |
| echo "OpenClaw is accessible on port ${OPENCLAW_PORT}" | |
| else | |
| echo "WARNING: OpenClaw is NOT listening on port ${OPENCLAW_PORT}" | |
| fi | |
| # Check code-server port | |
| echo "Checking code-server port ${CODE_PORT}..." | |
| if command -v ss &> /dev/null && ss -tuln 2>/dev/null | grep -q ":${CODE_PORT}"; then | |
| echo "code-server is listening on port ${CODE_PORT}" | |
| else | |
| echo "WARNING: code-server is NOT listening on port ${CODE_PORT}" | |
| fi | |
| wait -n | |
| exit 1 |