#!/bin/bash set -e # Entrypoint for HuggingClaw - Cain # Ensure frontend directory exists (defensive: created at build time, but verify at runtime) mkdir -p /app/frontend mkdir -p /data/frontend # Ensure /data is writable and exists mkdir -p /data chmod 777 /data 2>/dev/null || true echo "==========================================" echo "Cain Starting..." echo "==========================================" echo "Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")" echo "PORT: ${PORT:-7860}" echo "Working Directory: $(pwd)" echo "Python Version: $(python --version)" echo "Data directory: $(ls -la /data 2>&1 | head -5)" echo "==========================================" # Change to app directory cd /app # Set data directory to dataset mount point export OPENCLAW_DATA_DIR=/data # Verify app.py exists if [ ! -f "/app/app.py" ]; then echo "ERROR: app.py not found in /app" exit 1 fi echo "✓ app.py found" # List key files for debugging echo "" echo "Files in /app:" ls -la /app/*.py 2>/dev/null || echo " (no .py files in /app root)" # Create logs directory mkdir -p /app/logs # Ensure cain_status.json directory exists mkdir -p /data # CRITICAL: Clear stale error field from cain_status.json before startup # This prevents "unknown" errors from persisting across container restarts # Clean ALL possible status file locations to ensure consistency echo "Cleaning stale error field from status files..." python3 -c " import json from pathlib import Path status_files = [ Path('/data/cain_status.json'), # Primary (OPENCLAW_DATA_DIR) Path('/app/openclaw/.openclaw/agents/cain_status.json'), # Nested structure Path('/app/.openclaw/agents/cain_status.json'), # Legacy flat structure Path('/app/cain_status.json'), # App root Path('/app/data/cain_status.json'), # App data subdirectory Path('/app/memory/cain_status.json'), # Memory directory (CRITICAL: was missing!) Path('/data/memory/cain_status.json'), # Data memory directory ] cleaned = 0 for status_file in status_files: try: if status_file.exists(): with open(status_file, 'r') as f: data = json.load(f) error = data.get('error') # Fix: if error is string 'unknown' or contains 'unknown', set to null if isinstance(error, str) and error.strip().lower() in ('unknown', 'none', 'null', ''): data['error'] = None data['_cleaned_at'] = 'entrypoint' with open(status_file, 'w') as f: json.dump(data, f, indent=2) cleaned += 1 print(f' Cleaned: {status_file}') elif error is None: print(f' OK: {status_file} (error already null)') except Exception as e: print(f' Could not clean {status_file}: {e}') print(f'Cleaned {cleaned} status file(s)') " 2>/dev/null || echo "(Could not clean status files - continuing)" echo "" echo "" echo "==========================================" echo "Starting uvicorn..." echo "==========================================" # Run uvicorn directly in foreground (no forking, no piping) exec uvicorn app:app --host 0.0.0.0 --port "${PORT:-7860}"