#!/usr/bin/env python3
"""
IRIS Bot Startup Script
Performs all necessary checks and starts the bot properly
"""
import sys
import os
import time
import signal
import subprocess
from pathlib import Path
from typing import Optional, Tuple
# Add project root to path
PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
try:
import redis.asyncio as redis
import asyncio
from dotenv import load_dotenv
except ImportError:
print("❌ Missing dependencies. Installing...")
subprocess.run([sys.executable, "-m", "pip", "install", "-q", "redis", "python-dotenv"])
import redis.asyncio as redis
import asyncio
from dotenv import load_dotenv
# Colors
class Colors:
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
BLUE = '\033[0;34m'
BOLD = '\033[1m'
NC = '\033[0m'
def print_header(text: str):
print("━" * 60)
print(f"{Colors.BOLD}{text}{Colors.NC}")
print("━" * 60)
print()
def print_step(step: int, total: int, text: str):
print(f"{Colors.BLUE}[{step}/{total}]{Colors.NC} {text}")
def print_success(text: str):
print(f"{Colors.GREEN}✓ {text}{Colors.NC}")
def print_warning(text: str):
print(f"{Colors.YELLOW}⚠️ {text}{Colors.NC}")
def print_error(text: str):
print(f"{Colors.RED}✗ {text}{Colors.NC}")
def get_pid_file() -> Path:
return PROJECT_ROOT / ".bot.pid"
def get_log_file() -> Path:
return PROJECT_ROOT / "bot.log"
def check_existing_process() -> Optional[int]:
"""Check if bot is already running"""
pid_file = get_pid_file()
if not pid_file.exists():
return None
try:
pid = int(pid_file.read_text().strip())
# Check if process exists
os.kill(pid, 0)
return pid
except (ProcessLookupError, ValueError):
# Process doesn't exist or invalid PID
pid_file.unlink(missing_ok=True)
return None
def stop_process(pid: int, timeout: int = 10) -> bool:
"""Stop a process gracefully, force kill if needed"""
try:
# Send SIGTERM
os.kill(pid, signal.SIGTERM)
# Wait for graceful shutdown
for _ in range(timeout):
try:
os.kill(pid, 0) # Check if still alive
time.sleep(1)
except ProcessLookupError:
return True
# Force kill if still running
try:
os.kill(pid, signal.SIGKILL)
return True
except ProcessLookupError:
return True
except ProcessLookupError:
return True
except Exception as e:
print_error(f"Failed to stop process: {e}")
return False
def check_env_file() -> Tuple[bool, dict]:
"""Check if .env file exists and load variables"""
env_file = PROJECT_ROOT / ".env"
if not env_file.exists():
return False, {}
load_dotenv(env_file)
env_vars = {
'TELEGRAM_BOT_TOKEN': os.getenv('TELEGRAM_BOT_TOKEN'),
'ANTHROPIC_API_KEY': os.getenv('ANTHROPIC_API_KEY'),
'OPENAI_API_KEY': os.getenv('OPENAI_API_KEY'),
'REDIS_HOST': os.getenv('REDIS_HOST', 'localhost'),
'REDIS_PORT': os.getenv('REDIS_PORT', '6379'),
'REDIS_DB': os.getenv('REDIS_DB', '0'),
}
return True, env_vars
async def check_redis_connection(host: str, port: int, db: int) -> bool:
"""Check if Redis is accessible"""
try:
client = redis.Redis(
host=host,
port=port,
db=db,
socket_connect_timeout=3,
decode_responses=True
)
await client.ping()
await client.aclose()
return True
except Exception:
return False
def check_dependencies() -> bool:
"""Check if required Python packages are installed"""
try:
import telegram
import anthropic
import openai
return True
except ImportError:
return False
def rotate_log_file(max_size_mb: int = 10):
"""Rotate log file if too large"""
log_file = get_log_file()
if not log_file.exists():
return
size_mb = log_file.stat().st_size / (1024 * 1024)
if size_mb > max_size_mb:
print_warning(f"Log file is {size_mb:.1f}MB, rotating...")
log_file.rename(log_file.with_suffix('.log.old'))
def start_bot() -> Optional[int]:
"""Start the bot process"""
log_file = get_log_file()
# Start bot in background
cmd = [sys.executable, "-m", "src.telegram_bot.bot"]
with open(log_file, 'a') as log:
process = subprocess.Popen(
cmd,
stdout=log,
stderr=log,
cwd=PROJECT_ROOT,
start_new_session=True # Detach from parent
)
# Save PID
pid_file = get_pid_file()
pid_file.write_text(str(process.pid))
return process.pid
def main():
print_header("🚀 IRIS BOT - STARTUP SCRIPT")
# Step 1: Check existing process
print_step(1, 6, "Checking for existing bot process...")
existing_pid = check_existing_process()
if existing_pid:
print_warning(f"Bot is already running (PID: {existing_pid})")
response = input("\nDo you want to restart it? (y/n): ").strip().lower()
if response == 'y':
print_warning("Stopping old bot process...")
if stop_process(existing_pid):
print_success("Old process stopped")
get_pid_file().unlink(missing_ok=True)
else:
print_error("Failed to stop old process")
return 1
else:
print_warning("Keeping existing bot process")
return 0
else:
print_success("No existing bot process")
print()
# Step 2: Check environment file
print_step(2, 6, "Checking environment configuration...")
env_exists, env_vars = check_env_file()
if not env_exists:
print_error(".env file not found!")
print("Please create .env file with required variables")
return 1
print_success(".env file found")
# Check required variables
missing_vars = [k for k, v in env_vars.items() if not v]
if missing_vars:
print_error("Missing required environment variables:")
for var in missing_vars:
print(f" - {var}")
return 1
print_success("All required environment variables set")
print()
# Step 3: Check Redis connection
print_step(3, 6, "Checking Redis connection...")
redis_host = env_vars['REDIS_HOST']
redis_port = int(env_vars['REDIS_PORT'])
redis_db = int(env_vars['REDIS_DB'])
if asyncio.run(check_redis_connection(redis_host, redis_port, redis_db)):
print_success(f"Redis connected at {redis_host}:{redis_port}/{redis_db}")
else:
print_error(f"Cannot connect to Redis at {redis_host}:{redis_port}")
print("Please ensure Redis is running and accessible")
return 1
print()
# Step 4: Check dependencies
print_step(4, 6, "Checking Python dependencies...")
if check_dependencies():
print_success("Dependencies OK")
else:
print_warning("Some dependencies might be missing")
print("Installing dependencies...")
result = subprocess.run(
[sys.executable, "-m", "pip", "install", "-q", "-r", str(PROJECT_ROOT / "requirements.txt")],
capture_output=True
)
if result.returncode == 0:
print_success("Dependencies installed")
else:
print_warning("Some dependencies might be missing, but continuing...")
print()
# Step 5: Prepare log file
print_step(5, 6, "Preparing log file...")
rotate_log_file()
print_success(f"Log file: {get_log_file()}")
print()
# Step 6: Start the bot
print_step(6, 6, "Starting IRIS bot...")
bot_pid = start_bot()
if not bot_pid:
print_error("Failed to start bot!")
return 1
# Wait and verify
time.sleep(3)
try:
os.kill(bot_pid, 0) # Check if process is alive
print_success("Bot started successfully!")
except ProcessLookupError:
print_error("Bot failed to start!")
print(f"Check logs: tail -n 50 {get_log_file()}")
get_pid_file().unlink(missing_ok=True)
return 1
# Print summary
print()
print_header("✅ IRIS BOT IS RUNNING")
print()
print("📊 Bot Information:")
print(f" • PID: {bot_pid}")
print(f" • Redis: {redis_host}:{redis_port}/{redis_db}")
print(f" • Log: {get_log_file()}")
print(f" • Provider: {os.getenv('LLM_PROVIDER', 'anthropic')}")
print()
print("🛠️ Useful Commands:")
print(" • View logs: python scripts/logs_bot.py")
print(" • Check status: python scripts/status_bot.py")
print(" • Stop bot: python scripts/stop_bot.py")
print()
print("━" * 60)
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
print("\n\n❌ Interrupted by user")
sys.exit(1)
except Exception as e:
print_error(f"Unexpected error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)