#!/bin/bash # Centralized Logging Configuration export LOG_LEVEL=${LOG_LEVEL:-"info"} export LOG_FILE=${LOG_FILE:-"/app/verdant_claw.log"} # Mask sensitive env vars in logs log() { local msg=$(echo "$1" | sed -E 's/(nvapi|sk|gsk)[a-zA-Z0-9]+/\1*****/g') echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] [$LOG_LEVEL] $msg" | tee -a $LOG_FILE } # Trap SIGTERM for graceful shutdown cleanup() { log "Shutting down services gracefully..." kill $(jobs -p) 2>/dev/null sleep 2 log "Shutdown complete" exit 0 } trap cleanup SIGTERM SIGINT # Parse API_KEYS_JSON secret if [ -n "$API_KEYS_JSON" ]; then log "Parsing API keys from JSON..." export NVIDIA_API_KEY=$(echo $API_KEYS_JSON | jq -r '.nvidia // empty') export OPENAI_API_KEY=$(echo $API_KEYS_JSON | jq -r '.openai // empty') export ANTHROPIC_API_KEY=$(echo $API_KEYS_JSON | jq -r '.anthropic // empty') export GOOGLE_API_KEY=$(echo $API_KEYS_JSON | jq -r '.google // empty') export GROQ_API_KEY=$(echo $API_KEYS_JSON | jq -r '.groq // empty') log "✓ API keys loaded from JSON" else log "⚠️ WARNING: API_KEYS_JSON not set!" fi # Function to start service with retry start_with_retry() { local name=$1 local cmd=$2 local max_attempts=3 for i in $(seq 1 $max_attempts); do log "Starting $name (attempt $i/$max_attempts)..." eval "$cmd" & local pid=$! sleep 5 if ps -p $pid > /dev/null; then log "✓ $name started (PID: $pid)" return 0 fi log "⚠️ $name failed to start, retrying..." done log "❌ ERROR: $name failed after $max_attempts attempts" return 1 } # Check if ports are available check_port() { local port=$1 if netstat -tlnp 2>/dev/null | grep -q ":$port"; then log "❌ ERROR: Port $port already in use!" return 1 fi return 0 } log "Verifying infrastructure ports..." for port in 7860 7861 7687 20128 7862; do check_port $port || exit 1 done # 🚀 Start Services MEMGRAPH_BIN=$(command -v memgraph || echo "/usr/bin/memgraph") if [ ! -f "$MEMGRAPH_BIN" ]; then MEMGRAPH_BIN="/usr/lib/memgraph/memgraph" fi start_with_retry "Memgraph" "$MEMGRAPH_BIN --bolt-port 7687" || exit 1 # Start ZeroClaw on port 42617 (NOT 7860 - Marimo needs that) # Use environment variable to set port export ZEROCLAW_GATEWAY_PORT=42617 start_with_retry "ZeroClaw" "zeroclaw gateway" || exit 1 start_with_retry "OmniRoute" "node /app/scripts/omniroute.js --config /app/config/omniroute-masterfile.json --port 20128" || exit 1 # Start Graph Viewer on port 7861 GRAPH_VIEWER_PORT=7861 start_with_retry "Graph Viewer" "node /app/scripts/graph-viewer.js" || exit 1 log "All infrastructure ready! Launching Marimo UI on port 7860..." # Setup Ollama config (proxy to ZeroClaw) mkdir -p /home/node/.ollama cp /app/ollama-config.json /home/node/.ollama/config.json 2>/dev/null || true log "✓ Ollama configured to proxy to ZeroClaw" # Start Ollama serve (in background) log "Starting Ollama..." nohup ollama serve > /tmp/ollama.log 2>&1 & OLLAMA_PID=$! sleep 3 if ps -p $OLLAMA_PID > /dev/null; then log "✓ Ollama started (PID: $OLLAMA_PID)" # Pull a default model log "Pulling default model (this may take a minute)..." ollama pull llama3.2 || log "⚠️ Could not pull model, will use ZeroClaw models" else log "⚠️ Ollama failed to start" fi # Setup Marimo config (pre-configured with Ollama as AI provider) mkdir -p /home/node/.config/marimo cat > /home/node/.config/marimo/marimo.toml << 'TOML' [ai] enabled = true [ai.providers.ollama] name = "Ollama" base_url = "http://localhost:11434/v1" api_key = "ollama-local-key" [[ai.models]] provider = "ollama" model = "zeroclaw/default" TOML log "✓ Marimo AI config created with Ollama provider" # Initialize Pi (accepts config, creates necessary files) log "Initializing Pi agent..." echo "y" | pi --mode rpc --extensions /app/agent-pi/pi-extensions --skills /app/agent-pi/skills --prompts /app/agent-pi/commands /tmp/pi-init.log 2>&1 || true sleep 2 # Start pi-acp for ACP support (Marimo AI integration) through stdio-to-ws bridge # Name it "opencode" so Marimo recognizes it! log "Starting pi-acp ACP server via stdio-to-ws bridge (as 'opencode')..." nohup npx stdio-to-ws "pi-acp --terminal-login" --port 3017 --name "opencode" > /tmp/pi-acp.log 2>&1 & PI_ACP_PID=$! sleep 3 if ps -p $PI_ACP_PID > /dev/null; then log "✓ pi-acp started on ws://localhost:3017 as 'opencode' (PID: $PI_ACP_PID)" else log "⚠️ pi-acp failed to start, check /tmp/pi-acp.log" cat /tmp/pi-acp.log fi # Launch Marimo in EDIT MODE (not app mode) cd /app nohup marimo edit app.py --host 0.0.0.0 --port 7860 --no-token > /tmp/marimo.log 2>&1 & MARIMO_PID=$! sleep 3 if ps -p $MARIMO_PID > /dev/null; then log "✓ Marimo started (PID: $MARIMO_PID)" log " Marimo logs: /tmp/marimo.log" else log "❌ ERROR: Marimo failed to start!" cat /tmp/marimo.log exit 1 fi log "All services active!" log " - Memgraph: port 7687" log " - ZeroClaw: port 7862" log " - OmniRoute: port 20128" log " - Graph Viewer: port 7861" log " - Marimo UI: port 7860 (primary)" # Keep container alive log "Holding container open. Access the UI at https://your-space.hf.space" wait