FROM node:22-slim # Install system dependencies including Chromium for browser support RUN apt-get update && apt-get install -y \ git \ bash \ curl \ jq \ python3 \ python3-pip \ python3-venv \ chromium \ chromium-driver \ fonts-liberation \ libasound2 \ libatk-bridge2.0-0 \ libatk1.0-0 \ libatspi2.0-0 \ libcups2 \ libdbus-1-3 \ libdrm2 \ libgbm1 \ libgtk-3-0 \ libnspr4 \ libnss3 \ libwayland-client0 \ libxcomposite1 \ libxdamage1 \ libxfixes3 \ libxkbcommon0 \ libxrandr2 \ xdg-utils \ && rm -rf /var/lib/apt/lists/* RUN npm install -g openclaw@latest # Create Python virtual environment and install PostgreSQL driver RUN python3 -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" RUN pip install --no-cache-dir psycopg2-binary RUN mkdir -p /root/.openclaw EXPOSE 18789 # Python script for DB operations with Neon support RUN cat > /usr/local/bin/db-manager.py << 'PYSCRIPT' #!/usr/bin/env python3 import psycopg2 import json import sys import os def get_connection(): # Use DATABASE_URL if provided (Neon), otherwise use individual params database_url = os.environ.get('DATABASE_URL') if database_url: return psycopg2.connect(database_url) else: return psycopg2.connect( host=os.environ.get('POSTGRES_HOST', 'localhost'), port=os.environ.get('POSTGRES_PORT', '5432'), database=os.environ.get('POSTGRES_DB', 'openclaw_db'), user=os.environ.get('POSTGRES_USER', 'openclaw'), password=os.environ.get('POSTGRES_PASSWORD') ) def load_config(): """Load OpenClaw config from PostgreSQL""" try: conn = get_connection() cur = conn.cursor() cur.execute("SELECT config_value FROM openclaw_config WHERE config_key = 'openclaw_main'") row = cur.fetchone() cur.close() conn.close() if row: return row[0] return None except Exception as e: print(f"Error loading config from DB: {e}", file=sys.stderr) return None def save_config(config_json): """Save OpenClaw config to PostgreSQL""" try: conn = get_connection() cur = conn.cursor() cur.execute(""" INSERT INTO openclaw_config (config_key, config_value, updated_at) VALUES ('openclaw_main', %s::jsonb, CURRENT_TIMESTAMP) ON CONFLICT (config_key) DO UPDATE SET config_value = EXCLUDED.config_value, updated_at = CURRENT_TIMESTAMP """, (config_json,)) conn.commit() cur.close() conn.close() return True except Exception as e: print(f"Error saving config to DB: {e}", file=sys.stderr) return False def init_db(): """Initialize database with default config if not exists""" try: conn = get_connection() cur = conn.cursor() cur.execute("SELECT COUNT(*) FROM openclaw_config WHERE config_key = 'openclaw_main'") count = cur.fetchone()[0] cur.close() conn.close() return count > 0 except Exception as e: print(f"Error checking DB: {e}", file=sys.stderr) return False if __name__ == '__main__': action = sys.argv[1] if len(sys.argv) > 1 else 'load' if action == 'load': config = load_config() if config: print(json.dumps(config)) else: sys.exit(1) elif action == 'save': config_json = sys.stdin.read() if save_config(config_json): sys.exit(0) else: sys.exit(1) elif action == 'init': if init_db(): print("DB already initialized") else: print("DB not initialized") sys.exit(1) PYSCRIPT RUN chmod +x /usr/local/bin/db-manager.py # Config sync daemon RUN cat > /usr/local/bin/sync-config.sh << 'SYNCSCRIPT' #!/bin/bash CONFIG_FILE="/root/.openclaw/openclaw.json" LAST_HASH="" echo "[sync-daemon] Config sync daemon started" sleep 10 while true; do if [ -f "$CONFIG_FILE" ]; then CURRENT_HASH=$(md5sum "$CONFIG_FILE" | awk '{print $1}') if [ "$CURRENT_HASH" != "$LAST_HASH" ] && [ -n "$LAST_HASH" ]; then echo "[sync-daemon] Config changed detected, syncing to DB..." if cat "$CONFIG_FILE" | /opt/venv/bin/python3 /usr/local/bin/db-manager.py save; then echo "[sync-daemon] òÜÓ Config synced at $(date -Iseconds)" else echo "[sync-daemon] òÜ× Failed to sync config" fi fi LAST_HASH="$CURRENT_HASH" fi sleep 30 done SYNCSCRIPT RUN cat > /usr/local/bin/start-openclaw.sh << 'SCRIPT' #!/bin/bash set -e echo "=== OpenClaw Startup with Browser Support ===" if [ -z "$OPENCLAW_GATEWAY_PASSWORD" ]; then echo "ERROR: OPENCLAW_GATEWAY_PASSWORD not set!" exit 1 fi if [ -z "$KIRO_API_KEY" ]; then echo "ERROR: KIRO_API_KEY not set!" exit 1 fi if [ -z "$DATABASE_URL" ]; then echo "ERROR: DATABASE_URL not set!" exit 1 fi if [ -n "$TELEGRAM_STEVE_TOKEN" ]; then echo "Telegram Steve bot token found" fi if [ -n "$TELEGRAM_DEN_TOKEN" ]; then echo "Telegram Den bot token found" fi if [ -n "$TELEGRAM_OLYA_TOKEN" ]; then echo "Telegram Olya bot token found" fi if [ -n "$SPACE_HOST" ]; then ALLOWED_ORIGIN="https://${SPACE_HOST}" else ALLOWED_ORIGIN="*" fi echo "Loading config from Neon PostgreSQL..." if ! /opt/venv/bin/python3 /usr/local/bin/db-manager.py load > /root/.openclaw/openclaw.json; then echo "ERROR: Failed to load config from Neon DB!" echo "Please ensure config exists in openclaw_config table with key 'openclaw_main'" exit 1 fi echo "òÜÓ Config loaded successfully from Neon" chmod 700 /root/.openclaw chmod 600 /root/.openclaw/openclaw.json echo "Starting config sync daemon..." /usr/local/bin/sync-config.sh & echo "òÜÓ Sync daemon started (PID: $!)" echo "Starting OpenClaw Gateway with browser support..." exec openclaw gateway run --port 18789 SCRIPT RUN chmod +x /usr/local/bin/start-openclaw.sh RUN chmod +x /usr/local/bin/sync-config.sh CMD ["/usr/local/bin/start-openclaw.sh"]