#!/bin/bash # Centralized Logging Configuration (Bug #18) export LOG_LEVEL=${LOG_LEVEL:-"info"} export LOG_FILE=${LOG_FILE:-"/app/verdant_claw.log"} # Mask sensitive env vars in logs (Bug #13) 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 (Bug #12) 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 (Bug #9) # Supports multi-key rotation: "nvidia_2" → NVIDIA_API_KEY_2 if [ -n "$API_KEYS_JSON" ]; then log "Parsing API keys from JSON..." # Primary keys 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') # Rotation keys (nvidia_2, openai_2, etc.) for provider in nvidia openai anthropic google groq; do for i in $(seq 2 5); do val=$(echo $API_KEYS_JSON | jq -r ".${provider}_${i} // empty") if [ -n "$val" ] && [ "$val" != "null" ]; then envname=$(echo "${provider}_API_KEY_${i}" | tr '[:lower:]' '[:upper:]') export "$envname=$val" log "✓ Loaded rotation key: $envname" fi done done log "✓ API keys loaded from JSON" else log "⚠️ WARNING: API_KEYS_JSON not set!" fi # Function to start service with retry (Bug #15) 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 (Bug #4) 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; do check_port $port || exit 1 done # 🚀 Start Services # Locate memgraph binary (common paths in memgraph-mage) 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_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 alternate port (7861) log "Starting 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..." # Launch Marimo as the primary UI (port 7860 for HF Spaces healthcheck) cd /app nohup marimo run /app/app.py --host 0.0.0.0 --port 7860 --headless > /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 # Start Pi Agent in background with EOF persistence fix log "Starting Pi Agent in background..." cd /app/mom-infra start_with_retry "Pi Agent" "bash -c 'tail -f /dev/null | npm run pi -- --mode rpc --extensions /app/agent-pi/pi-extensions --skills /app/agent-pi/skills --prompts /app/agent-pi/commands'" || log "Pi Agent finished or failed." log "All services active!" log " - Memgraph: port 7687" log " - ZeroClaw: gateway" log " - OmniRoute: port 20128" log " - Graph Viewer: port 7861" log " - Marimo UI: port 7860 (primary)" log " - Pi Agent: Background RPC" # Keep container alive by waiting for all background processes log "Holding container open. Access the UI at https://your-space.hf.space" wait