#!/usr/bin/env python3 """ RFCP Installer — Detects hardware, installs dependencies, sets up GPU acceleration. Usage: python install_rfcp.py The installer handles: - Python dependency installation - GPU detection (NVIDIA/Intel/AMD) - GPU acceleration setup (CuPy for CUDA, PyOpenCL for Intel/AMD) - Frontend build (if Node.js available) - Verification of installation """ import subprocess import sys import platform import os import shutil def print_header(text: str): """Print section header.""" print(f"\n{'=' * 60}") print(f" {text}") print('=' * 60) def print_step(text: str): """Print step indicator.""" print(f"\n>>> {text}") def check_python() -> bool: """Verify Python 3.10+ is available.""" version = sys.version_info if version.major < 3 or version.minor < 10: print(f"[X] Python 3.10+ required, found {version.major}.{version.minor}") return False print(f"[OK] Python {version.major}.{version.minor}.{version.micro}") return True def check_node() -> bool: """Verify Node.js 18+ is available.""" try: result = subprocess.run( ["node", "--version"], capture_output=True, text=True, timeout=10 ) version = result.stdout.strip().lstrip('v') major = int(version.split('.')[0]) if major < 18: print(f"[!] Node.js 18+ recommended, found {version}") return False print(f"[OK] Node.js {version}") return True except FileNotFoundError: print("[!] Node.js not found (frontend build will be skipped)") return False except Exception as e: print(f"[!] Node.js check failed: {e}") return False def detect_gpu() -> dict: """Detect available GPU hardware.""" gpus = { "nvidia": False, "nvidia_name": "", "nvidia_memory_mb": 0, "intel": False, "intel_name": "", "amd": False, "amd_name": "" } # Check NVIDIA via nvidia-smi try: result = subprocess.run( ["nvidia-smi", "--query-gpu=name,driver_version,memory.total", "--format=csv,noheader"], capture_output=True, text=True, timeout=10 ) if result.returncode == 0 and result.stdout.strip(): info = result.stdout.strip() parts = info.split(",") gpus["nvidia"] = True gpus["nvidia_name"] = parts[0].strip() if len(parts) >= 3: mem_str = parts[2].strip().replace(" MiB", "").replace(" MB", "") try: gpus["nvidia_memory_mb"] = int(mem_str) except ValueError: pass print(f"[OK] NVIDIA GPU: {gpus['nvidia_name']}") except FileNotFoundError: pass except subprocess.TimeoutExpired: print("[!] nvidia-smi timed out") except Exception as e: print(f"[!] NVIDIA detection error: {e}") # Check Intel/AMD via WMI (Windows) or lspci (Linux) if platform.system() == "Windows": try: result = subprocess.run( ["wmic", "path", "win32_videocontroller", "get", "name", "/format:csv"], capture_output=True, text=True, timeout=10 ) for line in result.stdout.strip().split('\n'): line_lower = line.lower() if 'intel' in line_lower and ('uhd' in line_lower or 'iris' in line_lower or 'hd graphics' in line_lower): gpus["intel"] = True # Extract name from CSV parts = line.split(',') for part in parts: if 'Intel' in part: gpus["intel_name"] = part.strip() break if gpus["intel_name"]: print(f"[OK] Intel GPU: {gpus['intel_name']}") elif 'amd' in line_lower or 'radeon' in line_lower: gpus["amd"] = True parts = line.split(',') for part in parts: if 'AMD' in part or 'Radeon' in part: gpus["amd_name"] = part.strip() break if gpus["amd_name"]: print(f"[OK] AMD GPU: {gpus['amd_name']}") except Exception: pass else: # Linux: use lspci try: result = subprocess.run( ["lspci"], capture_output=True, text=True, timeout=10 ) for line in result.stdout.split('\n'): if 'VGA' in line or 'Display' in line or '3D' in line: if 'Intel' in line: gpus["intel"] = True gpus["intel_name"] = line.split(':')[-1].strip() if ':' in line else "Intel GPU" print(f"[OK] Intel GPU: {gpus['intel_name']}") elif 'AMD' in line or 'Radeon' in line: gpus["amd"] = True gpus["amd_name"] = line.split(':')[-1].strip() if ':' in line else "AMD GPU" print(f"[OK] AMD GPU: {gpus['amd_name']}") except Exception: pass if not gpus["nvidia"] and not gpus["intel"] and not gpus["amd"]: print("[i] No GPU detected - will use CPU (NumPy)") return gpus def install_core_dependencies() -> bool: """Install core Python dependencies.""" print_step("Installing core dependencies...") req_file = os.path.join(os.path.dirname(__file__), "backend", "requirements.txt") if not os.path.exists(req_file): print(f"[X] requirements.txt not found at {req_file}") return False try: subprocess.run( [sys.executable, "-m", "pip", "install", "-r", req_file, "--quiet", "--no-warn-script-location"], check=True, timeout=600 ) print("[OK] Core dependencies installed") return True except subprocess.CalledProcessError as e: print(f"[X] pip install failed: {e}") return False except subprocess.TimeoutExpired: print("[X] pip install timed out (10 min)") return False def install_gpu_dependencies(gpus: dict) -> bool: """Install GPU-specific dependencies based on detected hardware.""" print_step("Setting up GPU acceleration...") gpu_installed = False # NVIDIA - install CuPy (includes CUDA runtime) if gpus["nvidia"]: print(f" Installing CuPy for {gpus['nvidia_name']}...") try: # Try CUDA 12 first (newer cards, RTX 30xx/40xx) subprocess.run( [sys.executable, "-m", "pip", "install", "cupy-cuda12x", "--quiet", "--no-warn-script-location"], check=True, timeout=600 ) print(f" [OK] CuPy (CUDA 12) installed") gpu_installed = True except (subprocess.CalledProcessError, subprocess.TimeoutExpired): try: # Fallback to CUDA 11 (older cards) print(" [!] CUDA 12 failed, trying CUDA 11...") subprocess.run( [sys.executable, "-m", "pip", "install", "cupy-cuda11x", "--quiet", "--no-warn-script-location"], check=True, timeout=600 ) print(f" [OK] CuPy (CUDA 11) installed") gpu_installed = True except Exception as e: print(f" [X] CuPy installation failed: {e}") print(f" Manual install: pip install cupy-cuda12x") # Intel/AMD - install PyOpenCL if gpus["intel"] or gpus["amd"]: gpu_name = gpus["intel_name"] or gpus["amd_name"] print(f" Installing PyOpenCL for {gpu_name}...") try: subprocess.run( [sys.executable, "-m", "pip", "install", "pyopencl", "--quiet", "--no-warn-script-location"], check=True, timeout=300 ) print(f" [OK] PyOpenCL installed") gpu_installed = True except Exception as e: print(f" [X] PyOpenCL installation failed: {e}") print(f" Manual install: pip install pyopencl") if not gpu_installed and not gpus["nvidia"] and not gpus["intel"] and not gpus["amd"]: print(" [i] No GPU acceleration - using CPU (NumPy)") print(" This is fine! GPU just makes large calculations faster.") return gpu_installed def install_frontend(has_node: bool) -> bool: """Install frontend dependencies and build.""" if not has_node: print_step("Skipping frontend build (Node.js not available)") return False print_step("Setting up frontend...") frontend_dir = os.path.join(os.path.dirname(__file__), "frontend") if not os.path.exists(os.path.join(frontend_dir, "package.json")): print("[!] Frontend directory not found") return False try: print(" Installing npm packages...") subprocess.run( ["npm", "install"], cwd=frontend_dir, check=True, timeout=300, capture_output=True ) print(" Building frontend...") subprocess.run( ["npm", "run", "build"], cwd=frontend_dir, check=True, timeout=300, capture_output=True ) print("[OK] Frontend built") return True except subprocess.CalledProcessError as e: print(f"[X] Frontend build failed: {e}") return False except subprocess.TimeoutExpired: print("[X] Frontend build timed out") return False def create_launcher() -> bool: """Create launcher scripts.""" print_step("Creating launcher scripts...") base_dir = os.path.dirname(os.path.abspath(__file__)) if platform.system() == "Windows": # Create RFCP.bat launcher_path = os.path.join(base_dir, "RFCP.bat") with open(launcher_path, 'w') as f: f.write('@echo off\n') f.write('title RFCP - RF Coverage Planner\n') f.write(f'cd /d "{base_dir}"\n') f.write('echo Starting RFCP...\n') f.write('echo Open http://localhost:8090 in your browser\n') f.write('echo Press Ctrl+C to stop\n') f.write('echo.\n') f.write(f'cd backend\n') f.write(f'"{sys.executable}" -m uvicorn app.main:app --host 0.0.0.0 --port 8090\n') print(f" [OK] Created: RFCP.bat") # Create install.bat for first-time setup install_bat_path = os.path.join(base_dir, "install.bat") with open(install_bat_path, 'w') as f: f.write('@echo off\n') f.write('title RFCP - First Time Setup\n') f.write('echo ============================================\n') f.write('echo RFCP - RF Coverage Planner - Setup\n') f.write('echo ============================================\n') f.write('echo.\n') f.write('python --version >nul 2>&1\n') f.write('if errorlevel 1 (\n') f.write(' echo ERROR: Python not found!\n') f.write(' echo Please install Python 3.10+ from python.org\n') f.write(' pause\n') f.write(' exit /b 1\n') f.write(')\n') f.write(f'cd /d "{base_dir}"\n') f.write('python install_rfcp.py\n') f.write('echo.\n') f.write('echo Setup complete! Run RFCP.bat to start.\n') f.write('pause\n') print(f" [OK] Created: install.bat") else: # Linux/macOS launcher_path = os.path.join(base_dir, "rfcp.sh") with open(launcher_path, 'w') as f: f.write('#!/bin/bash\n') f.write(f'cd "{base_dir}"\n') f.write('echo "Starting RFCP..."\n') f.write('echo "Open http://localhost:8090 in your browser"\n') f.write('echo "Press Ctrl+C to stop"\n') f.write('cd backend\n') f.write(f'{sys.executable} -m uvicorn app.main:app --host 0.0.0.0 --port 8090\n') os.chmod(launcher_path, 0o755) print(f" [OK] Created: rfcp.sh") return True def verify_installation() -> bool: """Run quick verification tests.""" print_step("Verifying installation...") checks = [] critical_fail = False # Check core imports try: import numpy as np checks.append(f"[OK] NumPy {np.__version__}") except ImportError: checks.append("[X] NumPy missing") critical_fail = True try: import scipy checks.append(f"[OK] SciPy {scipy.__version__}") except ImportError: checks.append("[X] SciPy missing") critical_fail = True try: import fastapi checks.append(f"[OK] FastAPI {fastapi.__version__}") except ImportError: checks.append("[X] FastAPI missing") critical_fail = True try: import uvicorn checks.append(f"[OK] Uvicorn {uvicorn.__version__}") except ImportError: checks.append("[X] Uvicorn missing") critical_fail = True # Check GPU acceleration try: import cupy as cp device_count = cp.cuda.runtime.getDeviceCount() if device_count > 0: props = cp.cuda.runtime.getDeviceProperties(0) name = props["name"] if isinstance(name, bytes): name = name.decode() mem_mb = props["totalGlobalMem"] // (1024 * 1024) checks.append(f"[OK] CuPy (CUDA) -> {name} ({mem_mb} MB)") else: checks.append("[i] CuPy installed but no CUDA devices found") except ImportError: checks.append("[i] CuPy not available (NVIDIA GPU acceleration disabled)") except Exception as e: checks.append(f"[!] CuPy error: {e}") try: import pyopencl as cl devices = [] for p in cl.get_platforms(): for d in p.get_devices(): devices.append(d.name.strip()) if devices: checks.append(f"[OK] PyOpenCL -> {', '.join(devices[:2])}") else: checks.append("[i] PyOpenCL installed but no devices found") except ImportError: checks.append("[i] PyOpenCL not available (Intel/AMD GPU acceleration disabled)") except Exception as e: checks.append(f"[!] PyOpenCL error: {e}") for check in checks: print(f" {check}") return not critical_fail def main(): """Main installer entry point.""" print_header("RFCP - RF Coverage Planner - Installer") # Step 1: Check prerequisites print_step("Checking prerequisites...") if not check_python(): print("\n[X] Python 3.10+ is required. Please install from python.org") sys.exit(1) has_node = check_node() # Step 2: Detect GPU print_step("Detecting GPU hardware...") gpus = detect_gpu() # Step 3: Install core dependencies if not install_core_dependencies(): print("\n[X] Core dependency installation failed") sys.exit(1) # Step 4: Install GPU dependencies install_gpu_dependencies(gpus) # Step 5: Frontend (optional) install_frontend(has_node) # Step 6: Create launcher create_launcher() # Step 7: Verify success = verify_installation() # Summary print_header("Installation Summary") if success: print(" [OK] RFCP installed successfully!") print() print(" To start RFCP:") if platform.system() == "Windows": print(" Double-click RFCP.bat") print(" Or run: python -m uvicorn app.main:app --port 8090") else: print(" Run: ./rfcp.sh") print(" Or: python -m uvicorn app.main:app --port 8090") print() print(" Then open: http://localhost:8090") print() # GPU summary if gpus["nvidia"]: print(f" GPU: {gpus['nvidia_name']} (CUDA)") elif gpus["intel"]: print(f" GPU: {gpus['intel_name']} (OpenCL)") elif gpus["amd"]: print(f" GPU: {gpus['amd_name']} (OpenCL)") else: print(" Mode: CPU only (NumPy)") else: print(" [!] Installation completed with errors") print(" Some features may not work correctly") print() print('=' * 60) if __name__ == "__main__": main()