| #!/usr/bin/env bash |
| 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://azils-openclaws.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 & |
|
|
| |
| 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 & |
|
|
| |
| echo "Waiting for services to start..." |
| sleep 10 |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| 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 |