#!/bin/bash set -e # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # Load environment variables from backend/.env if [ -f "backend/.env" ]; then export $(grep -v '^#' backend/.env | grep -v '^$' | xargs) fi # Configuration POSTGRES_PORT=5432 REDIS_PORT=6379 BACKEND_PORT=${BACKEND_PORT:-8028} FRONTEND_PORT=${FRONTEND_PORT:-84488} VENV_DIR="backend/.venv" # Database credentials DB_USER="stockai" DB_PASSWORD="stockai2025" DB_NAME="stockai" print_msg() { echo -e "${GREEN}[INFO]${NC} $1"; } print_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } print_error() { echo -e "${RED}[ERROR]${NC} $1"; } print_step() { echo -e "\n${BLUE}==>${NC} ${CYAN}$1${NC}"; } print_success() { echo -e "${GREEN}✓${NC} $1"; } detect_os() { if [[ "$OSTYPE" == "darwin"* ]]; then OS="macos" elif [[ "$OSTYPE" == "linux-gnu"* ]]; then OS="linux" else print_error "Unsupported OS: $OSTYPE" exit 1 fi } command_exists() { command -v "$1" >/dev/null 2>&1; } find_python() { local python_cmd="" # Explicitly prioritize Python 3.12 as the default for ver in 3.12 3.11; do if command_exists "python$ver"; then python_cmd="python$ver" break fi done if [ -z "$python_cmd" ] && command_exists python3; then python_cmd="python3" fi if [ -z "$python_cmd" ]; then print_error "No Python installation found" return 1 fi local py_version=$($python_cmd --version 2>&1 | cut -d' ' -f2) local py_major_minor=$(echo $py_version | cut -d'.' -f1,2) # Check for incompatible Python versions (3.14 and above, 3.13) if [[ "$py_major_minor" == "3.14"* ]] || [[ "$py_major_minor" == "3.13"* ]]; then print_warn "Python $py_version may have compatibility issues" # Explicitly look for Python 3.12 as the preferred alternative if command_exists "python3.12"; then local alt_version=$(python3.12 --version 2>&1 | cut -d' ' -f2) echo -e "Found preferred: ${GREEN}Python $alt_version${NC} (python3.12)" echo -n "Use Python 3.12? (Y/n) " read -r response if [[ ! "$response" =~ ^[Nn]$ ]]; then python_cmd="python3.12" py_version=$alt_version else print_error "Please install Python 3.12: brew install python@3.12" exit 1 fi elif command_exists "python3.11"; then local alt_version=$(python3.11 --version 2>&1 | cut -d' ' -f2) echo -e "Found alternative: ${GREEN}Python $alt_version${NC} (python3.11)" echo -n "Use Python 3.11? (Y/n) " read -r response if [[ ! "$response" =~ ^[Nn]$ ]]; then python_cmd="python3.11" py_version=$alt_version else print_error "Please install Python 3.12: brew install python@3.12" exit 1 fi else print_error "Please install Python 3.12: brew install python@3.12" exit 1 fi fi PYTHON_CMD=$python_cmd PYTHON_VERSION=$py_version } check_port() { local port=$1 lsof -Pi :$port -sTCP:LISTEN -t >/dev/null 2>&1 } kill_port() { local port=$1 local service=$2 if check_port $port; then print_warn "Port $port is in use by $service" echo -n "Kill process? (y/N) " read -r response if [[ "$response" =~ ^[Yy]$ ]]; then lsof -ti:$port | xargs kill -9 2>/dev/null || true sleep 1 print_success "Process killed" else return 1 fi fi return 0 } check_prerequisites() { print_step "Checking prerequisites..." local missing_deps=() if ! find_python; then missing_deps+=("python3.12") else print_success "Python $PYTHON_VERSION" fi if [ "$OS" = "macos" ]; then if ! command_exists cmake; then missing_deps+=("cmake") fi if ! brew list libomp &>/dev/null; then missing_deps+=("libomp") fi fi for cmd in node npm psql redis-cli; do if ! command_exists $cmd; then missing_deps+=($cmd) else print_success "$cmd installed" fi done if [ ${#missing_deps[@]} -ne 0 ]; then print_error "Missing: ${missing_deps[*]}" if [ "$OS" = "macos" ]; then echo "Install: ${CYAN}brew install python@3.12 postgresql@16 redis node cmake libomp${NC}" fi exit 1 fi } start_postgres() { print_step "Starting PostgreSQL..." if check_port $POSTGRES_PORT; then print_msg "PostgreSQL already running" return 0 fi if [ "$OS" = "macos" ]; then brew services start postgresql@16 2>/dev/null || brew services start postgresql else sudo systemctl start postgresql fi for i in {1..30}; do if pg_isready -h localhost -p $POSTGRES_PORT >/dev/null 2>&1; then print_success "PostgreSQL ready" return 0 fi sleep 1 done print_error "PostgreSQL failed to start" exit 1 } start_redis() { print_step "Starting Redis..." if check_port $REDIS_PORT; then print_msg "Redis already running" return 0 fi if [ "$OS" = "macos" ]; then brew services start redis else sudo systemctl start redis || sudo systemctl start redis-server fi for i in {1..30}; do if redis-cli ping >/dev/null 2>&1; then print_success "Redis ready" return 0 fi sleep 1 done print_error "Redis failed to start" exit 1 } setup_database() { print_step "Setting up database..." if psql -h localhost -U $USER -lqt 2>/dev/null | cut -d \| -f 1 | grep -qw $DB_NAME; then print_msg "Database exists" else psql -h localhost -U $USER postgres -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';" 2>/dev/null || true psql -h localhost -U $USER postgres -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;" || exit 1 print_success "Database created" fi if [ -f "backend/database/schema.sql" ]; then if ! PGPASSWORD=$DB_PASSWORD psql -h localhost -U $DB_USER -d $DB_NAME -c "SELECT 1 FROM stocks LIMIT 1;" >/dev/null 2>&1; then print_msg "Applying schema..." PGPASSWORD=$DB_PASSWORD psql -h localhost -U $DB_USER -d $DB_NAME < backend/database/schema.sql >/dev/null fi fi } setup_venv() { print_step "Setting up Python environment..." if [ -d "$VENV_DIR" ]; then local venv_python="$VENV_DIR/bin/python" if [ -f "$venv_python" ]; then local venv_ver=$($venv_python --version 2>&1 | cut -d' ' -f2 | cut -d'.' -f1,2) if [[ "$venv_ver" == "3.14" ]] || [[ "$venv_ver" == "3.13" ]]; then print_warn "Incompatible venv Python $venv_ver" rm -rf "$VENV_DIR" fi fi fi if [ ! -d "$VENV_DIR" ]; then # Ensure we're using Python 3.12 for the virtual environment if [[ "$PYTHON_CMD" == *"python3.12"* ]]; then print_msg "Creating venv with $PYTHON_CMD..." $PYTHON_CMD -m venv $VENV_DIR else # If the detected Python isn't 3.12, try to explicitly use python3.12 if command_exists "python3.12"; then print_msg "Creating venv with python3.12..." python3.12 -m venv $VENV_DIR else print_msg "Creating venv with $PYTHON_CMD (Python 3.12 recommended)..." $PYTHON_CMD -m venv $VENV_DIR fi fi fi print_success "Virtual environment ready" } install_python_deps() { print_step "Installing Python dependencies..." local requirements_hash="$VENV_DIR/.requirements.hash" local current_hash if command -v md5 >/dev/null 2>&1; then current_hash=$(md5 -q backend/requirements.txt) else current_hash=$(md5sum backend/requirements.txt | cut -d' ' -f1) fi if [ -f "$requirements_hash" ]; then local saved_hash=$(cat $requirements_hash) if [ "$current_hash" = "$saved_hash" ]; then print_msg "Dependencies up to date" # 仍需验证关键包 source $VENV_DIR/bin/activate if python -c "import pydantic_settings" 2>/dev/null; then print_success "Dependencies verified" return 0 else print_warn "Dependencies corrupted, reinstalling..." rm -f "$requirements_hash" fi fi fi # 使用独立安装脚本 print_msg "Running dependency installer..." if ./install_deps.sh; then print_success "Dependencies installed" else print_error "Dependency installation failed" echo "" echo "Try manual installation:" echo " ${CYAN}source backend/.venv/bin/activate${NC}" echo " ${CYAN}pip install pydantic==2.5.0 pydantic-settings==2.1.0${NC}" echo " ${CYAN}pip install -r backend/requirements.txt${NC}" exit 1 fi } install_frontend_deps() { print_step "Installing frontend dependencies..." cd frontend local package_hash="node_modules/.package.hash" local current_hash if command -v md5 >/dev/null 2>&1; then current_hash=$(md5 -q package.json) else current_hash=$(md5sum package.json | cut -d' ' -f1) fi if [ -f "$package_hash" ] && [ "$(cat $package_hash)" = "$current_hash" ]; then print_msg "Frontend deps up to date" cd .. return 0 fi npm install echo $current_hash > $package_hash print_success "Frontend deps installed" cd .. } setup_env() { print_step "Setting up environment..." if [ ! -f "backend/.env" ]; then cat > backend/.env << ENV_EOF DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@localhost:${POSTGRES_PORT}/${DB_NAME} REDIS_URL=redis://localhost:${REDIS_PORT}/0 OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-your-key} TUSHARE_TOKEN=${TUSHARE_TOKEN:-your-token} ALPHA_VANTAGE_API_KEY=${ALPHA_VANTAGE_API_KEY:-your-key} OLLAMA_BASE_URL=http://localhost:11434 DEEPSEEK_MODEL=qwen3-coder:30b-a3b-q8_0 LLM_MODEL=anthropic/claude-4.5-sonnet OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 CACHE_TTL_QUOTE=60 CACHE_TTL_FEATURES=300 CACHE_TTL_RECOMMENDATIONS=300 CACHE_TTL_LLM=3600 ENVIRONMENT=development LOG_LEVEL=info TZ=Asia/Shanghai ENV_EOF print_success "Created .env" print_warn "Update backend/.env with your API keys!" fi } start_backend() { print_step "Starting backend..." source $VENV_DIR/bin/activate if ! kill_port $BACKEND_PORT "backend"; then print_error "Port $BACKEND_PORT in use" exit 1 fi cd backend print_msg "FastAPI: http://localhost:$BACKEND_PORT" print_msg "Docs: http://localhost:$BACKEND_PORT/docs" echo "" uvicorn main:app --reload --host 0.0.0.0 --port $BACKEND_PORT } start_frontend_bg() { print_step "Starting frontend..." if ! kill_port $FRONTEND_PORT "frontend"; then exit 1 fi cd frontend mkdir -p ../logs npm run dev > ../logs/frontend.log 2>&1 & echo $! > ../.frontend.pid print_success "Frontend started (PID: $(cat ../.frontend.pid))" cd .. } cleanup() { echo "" print_msg "Shutting down..." if [ -f ".frontend.pid" ]; then kill $(cat .frontend.pid) 2>/dev/null || true rm -f .frontend.pid fi print_msg "Use ./stop_app.sh --all to stop databases" exit 0 } trap cleanup INT TERM print_banner() { echo -e "${CYAN}" echo "╔════════════════════════════════════════════════════════╗" echo "║ Claude Stock - Development Environment ║" echo "╚════════════════════════════════════════════════════════╝" echo -e "${NC}" } main() { print_banner detect_os check_prerequisites start_postgres start_redis setup_database setup_venv setup_env install_python_deps install_frontend_deps if [ "${START_FRONTEND:-true}" = "true" ]; then start_frontend_bg fi print_step "Environment ready!" echo "" echo -e "${GREEN}Services:${NC}" echo " ✓ PostgreSQL: localhost:$POSTGRES_PORT" echo " ✓ Redis: localhost:$REDIS_PORT" [ "${START_FRONTEND:-true}" = "true" ] && echo " ✓ Frontend: http://localhost:$FRONTEND_PORT" echo " ⟳ Backend: http://localhost:$BACKEND_PORT" echo "" echo -e "${YELLOW}Press Ctrl+C to stop${NC}" echo "" start_backend } while [[ $# -gt 0 ]]; do case $1 in --no-frontend) START_FRONTEND=false; shift ;; --help) echo "Usage: $0 [--no-frontend] [--help]" exit 0 ;; *) print_error "Unknown: $1"; exit 1 ;; esac done main