174 lines
5.4 KiB
Python
Executable File
174 lines
5.4 KiB
Python
Executable File
#!/Users/stwhite/CODE/chatterbox-ui/.venv/bin/python
|
|
"""
|
|
Startup script that launches both the backend and frontend servers concurrently.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import signal
|
|
import subprocess
|
|
import threading
|
|
from pathlib import Path
|
|
|
|
# Try to load environment variables, but don't fail if dotenv is not available
|
|
try:
|
|
from dotenv import load_dotenv
|
|
load_dotenv()
|
|
except ImportError:
|
|
print("python-dotenv not installed, using system environment variables only")
|
|
|
|
# Configuration
|
|
BACKEND_PORT = int(os.getenv('BACKEND_PORT', '8000'))
|
|
BACKEND_HOST = os.getenv('BACKEND_HOST', '0.0.0.0')
|
|
FRONTEND_PORT = int(os.getenv('FRONTEND_PORT', '8001'))
|
|
FRONTEND_HOST = os.getenv('FRONTEND_HOST', '127.0.0.1')
|
|
|
|
def find_free_port(start_port, host='127.0.0.1'):
|
|
"""Find a free port starting from start_port"""
|
|
import socket
|
|
|
|
for port in range(start_port, start_port + 10):
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
sock.settimeout(1)
|
|
result = sock.connect_ex((host, port))
|
|
if result != 0: # Port is free
|
|
return port
|
|
|
|
raise RuntimeError(f"Could not find a free port starting from {start_port}")
|
|
|
|
def check_port_available(port, host='127.0.0.1'):
|
|
"""Check if a port is available"""
|
|
import socket
|
|
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
sock.settimeout(1)
|
|
result = sock.connect_ex((host, port))
|
|
return result != 0 # True if port is free
|
|
|
|
# Get project root directory
|
|
PROJECT_ROOT = Path(__file__).parent.absolute()
|
|
|
|
def run_backend():
|
|
"""Run the backend FastAPI server"""
|
|
os.chdir(PROJECT_ROOT / "backend")
|
|
cmd = [
|
|
sys.executable, "-m", "uvicorn",
|
|
"app.main:app",
|
|
"--reload",
|
|
f"--host={BACKEND_HOST}",
|
|
f"--port={BACKEND_PORT}"
|
|
]
|
|
|
|
print(f"\n{'='*50}")
|
|
print(f"Starting Backend Server at http://{BACKEND_HOST}:{BACKEND_PORT}")
|
|
print(f"API docs available at http://{BACKEND_HOST}:{BACKEND_PORT}/docs")
|
|
print(f"{'='*50}\n")
|
|
|
|
return subprocess.Popen(
|
|
cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
universal_newlines=True,
|
|
bufsize=1
|
|
)
|
|
|
|
def run_frontend():
|
|
"""Run the frontend development server"""
|
|
frontend_dir = PROJECT_ROOT / "frontend"
|
|
os.chdir(frontend_dir)
|
|
|
|
cmd = [sys.executable, "start_dev_server.py"]
|
|
env = os.environ.copy()
|
|
env["VITE_DEV_SERVER_HOST"] = FRONTEND_HOST
|
|
env["VITE_DEV_SERVER_PORT"] = str(FRONTEND_PORT)
|
|
|
|
print(f"\n{'='*50}")
|
|
print(f"Starting Frontend Server at http://{FRONTEND_HOST}:{FRONTEND_PORT}")
|
|
print(f"{'='*50}\n")
|
|
|
|
return subprocess.Popen(
|
|
cmd,
|
|
env=env,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
universal_newlines=True,
|
|
bufsize=1
|
|
)
|
|
|
|
def print_process_output(process, prefix):
|
|
"""Print process output with a prefix"""
|
|
for line in iter(process.stdout.readline, ''):
|
|
if not line:
|
|
break
|
|
print(f"{prefix} | {line}", end='')
|
|
|
|
def main():
|
|
"""Main function to start both servers"""
|
|
print("\n🚀 Starting Chatterbox UI Development Environment")
|
|
|
|
# Check and adjust ports if needed
|
|
global BACKEND_PORT, FRONTEND_PORT
|
|
|
|
if not check_port_available(BACKEND_PORT, '127.0.0.1'):
|
|
original_backend_port = BACKEND_PORT
|
|
BACKEND_PORT = find_free_port(BACKEND_PORT + 1)
|
|
print(f"⚠️ Backend port {original_backend_port} is in use, using port {BACKEND_PORT} instead")
|
|
|
|
if not check_port_available(FRONTEND_PORT, FRONTEND_HOST):
|
|
original_frontend_port = FRONTEND_PORT
|
|
FRONTEND_PORT = find_free_port(FRONTEND_PORT + 1)
|
|
print(f"⚠️ Frontend port {original_frontend_port} is in use, using port {FRONTEND_PORT} instead")
|
|
|
|
# Start the backend server
|
|
backend_process = run_backend()
|
|
|
|
# Give the backend a moment to start
|
|
time.sleep(2)
|
|
|
|
# Start the frontend server
|
|
frontend_process = run_frontend()
|
|
|
|
# Create threads to monitor and print output
|
|
backend_monitor = threading.Thread(
|
|
target=print_process_output,
|
|
args=(backend_process, "BACKEND"),
|
|
daemon=True
|
|
)
|
|
frontend_monitor = threading.Thread(
|
|
target=print_process_output,
|
|
args=(frontend_process, "FRONTEND"),
|
|
daemon=True
|
|
)
|
|
|
|
backend_monitor.start()
|
|
frontend_monitor.start()
|
|
|
|
# Setup signal handling for graceful shutdown
|
|
def signal_handler(sig, frame):
|
|
print("\n\n🛑 Shutting down servers...")
|
|
backend_process.terminate()
|
|
frontend_process.terminate()
|
|
# Threads are daemon, so they'll exit when the main thread exits
|
|
print("✅ Servers stopped successfully")
|
|
sys.exit(0)
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
|
|
# Print access information
|
|
print("\n📋 Access Information:")
|
|
print(f" • Frontend: http://{FRONTEND_HOST}:{FRONTEND_PORT}")
|
|
print(f" • Backend API: http://{BACKEND_HOST}:{BACKEND_PORT}/api")
|
|
print(f" • API Documentation: http://{BACKEND_HOST}:{BACKEND_PORT}/docs")
|
|
print("\n⚠️ Press Ctrl+C to stop both servers\n")
|
|
|
|
# Keep the main process running
|
|
try:
|
|
while True:
|
|
time.sleep(1)
|
|
except KeyboardInterrupt:
|
|
signal_handler(None, None)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|