#!/bin/bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" BACKEND_DIR="$SCRIPT_DIR/backend" FRONTEND_DIR="$SCRIPT_DIR/frontend" LOG_DIR="$SCRIPT_DIR/logs" PROJECT_ENV="$SCRIPT_DIR/.env" FRONTEND_ENV_LOCAL="$FRONTEND_DIR/.env.local" BACKEND_PYTHON="$BACKEND_DIR/.venv/Scripts/python.exe" FRONTEND_VITE="$FRONTEND_DIR/node_modules/.bin/vite.cmd" RUN_ID="$(date +%Y%m%d-%H%M%S)" BACKEND_LOG="$LOG_DIR/backend-start-${RUN_ID}.log" BACKEND_ERR_LOG="$LOG_DIR/backend-start-${RUN_ID}.err.log" FRONTEND_LOG="$LOG_DIR/frontend-start-${RUN_ID}.log" FRONTEND_ERR_LOG="$LOG_DIR/frontend-start-${RUN_ID}.err.log" KILL_PORT=false if [[ "${1:-}" == "--kill-port" ]]; then KILL_PORT=true elif [[ $# -gt 0 ]]; then echo "[ERROR] Unsupported argument: $1" echo "[ERROR] Usage: bash start.sh [--kill-port]" exit 1 fi mkdir -p "$LOG_DIR" port_in_use() { "$BACKEND_PYTHON" - "$1" <<'PY' import socket, sys port = int(sys.argv[1]) with socket.socket() as sock: sock.settimeout(0.2) sys.exit(0 if sock.connect_ex(("127.0.0.1", port)) == 0 else 1) PY } to_windows_path() { local path="$1" if [[ "$path" =~ ^/mnt/([a-zA-Z])/(.*)$ ]]; then printf '%s:/%s' "${BASH_REMATCH[1]^}" "${BASH_REMATCH[2]}" elif [[ "$path" =~ ^/([a-zA-Z])/(.*)$ ]]; then printf '%s:/%s' "${BASH_REMATCH[1]^}" "${BASH_REMATCH[2]}" else printf '%s' "$path" fi } kill_port_process() { local port="$1" local pids pids="$(powershell.exe -NoProfile -Command '& { Get-NetTCPConnection -LocalPort '"$1"' -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique }' | tr -d '\r' || true)" if [[ -z "$pids" ]]; then return 0 fi while IFS= read -r pid; do [[ -z "$pid" ]] && continue powershell.exe -NoProfile -Command "Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue" >/dev/null 2>&1 || true cmd.exe /c "taskkill /PID $pid /F" >/dev/null 2>&1 || true done <<< "$pids" } kill_process_tree() { local pid="$1" [[ -z "$pid" ]] && return 0 cmd.exe /c "taskkill /PID $pid /T /F" >/dev/null 2>&1 || true } cleanup() { local exit_code=$? trap - INT TERM EXIT if [[ -n "${FRONTEND_PID:-}" ]]; then kill_process_tree "$FRONTEND_PID" fi if [[ -n "${BACKEND_PID:-}" ]]; then kill_process_tree "$BACKEND_PID" fi if [[ -n "${BACKEND_PORT:-}" ]]; then kill_port_process "$BACKEND_PORT" fi exit "$exit_code" } trap cleanup INT TERM EXIT echo "==========================================" echo " Jarvis - Quick Start" echo "==========================================" echo if [[ ! -x "$BACKEND_PYTHON" ]]; then echo "[ERROR] backend/.venv/Scripts/python.exe was not found." echo "[ERROR] Create the backend virtual environment first." exit 1 fi if [[ ! -f "$FRONTEND_VITE" ]]; then echo "[ERROR] frontend/node_modules/.bin/vite.cmd was not found." echo "[ERROR] Install frontend dependencies first." exit 1 fi if ! command -v powershell.exe >/dev/null 2>&1; then echo "[ERROR] powershell.exe was not found." exit 1 fi if [[ ! -f "$PROJECT_ENV" ]]; then echo "[INFO] .env was not found in the project root." echo "[INFO] Create it before first run." echo fi echo "[1/3] Check backend environment..." echo "[OK] Backend virtual environment is available." echo echo "[2/3] Check frontend dependencies..." if [[ ! -x "$FRONTEND_DIR/node_modules/.bin/vite" && ! -x "$FRONTEND_DIR/node_modules/.bin/vite.cmd" ]]; then echo "[ERROR] frontend dependencies are missing." echo "[ERROR] Run: cd frontend && npm install" exit 1 fi echo "[OK] Frontend dependencies are available." BACKEND_HOST="127.0.0.1" BACKEND_PORT="" if [[ -f "$PROJECT_ENV" ]]; then ENV_HOST="$(grep '^HOST=' "$PROJECT_ENV" | cut -d'=' -f2- | tr -d '\r' || true)" ENV_PORT="$(grep '^PORT=' "$PROJECT_ENV" | cut -d'=' -f2- | tr -d '\r' || true)" if [[ -n "$ENV_HOST" ]]; then BACKEND_HOST="$ENV_HOST"; fi if [[ -n "$ENV_PORT" ]]; then BACKEND_PORT="$ENV_PORT"; fi fi if [[ -z "$BACKEND_PORT" ]]; then echo "[ERROR] PORT was not found in .env." echo "[ERROR] Set PORT in the project root .env and run start.sh again." exit 1 fi FRONTEND_API_URL="http://${BACKEND_HOST}:${BACKEND_PORT}" printf 'VITE_API_URL=%s\n' "$FRONTEND_API_URL" > "$FRONTEND_ENV_LOCAL" echo echo "[3/3] Start services..." if port_in_use "$BACKEND_PORT"; then if [[ "$KILL_PORT" == true ]]; then echo "[INFO] Port ${BACKEND_PORT} is in use. Killing existing process..." kill_port_process "$BACKEND_PORT" sleep 2 fi fi if port_in_use "$BACKEND_PORT"; then echo "[ERROR] Port ${BACKEND_PORT} is already in use." echo "[ERROR] Stop the existing service first, then run start.sh again." exit 1 fi BACKEND_WIN_DIR="$(to_windows_path "$BACKEND_DIR")" BACKEND_WIN_LOG="$(to_windows_path "$BACKEND_LOG")" BACKEND_WIN_ERR_LOG="$(to_windows_path "$BACKEND_ERR_LOG")" BACKEND_WIN_PYTHON="$(to_windows_path "$BACKEND_PYTHON")" BACKEND_PID="$(powershell.exe -NoProfile -Command "& { \$process = Start-Process -FilePath '$BACKEND_WIN_PYTHON' -ArgumentList '-m','uvicorn','app.main:app','--reload','--host','$BACKEND_HOST','--port','$BACKEND_PORT' -WorkingDirectory '$BACKEND_WIN_DIR' -RedirectStandardOutput '$BACKEND_WIN_LOG' -RedirectStandardError '$BACKEND_WIN_ERR_LOG' -PassThru \$process.Id }" | tr -d '\r')" echo "Waiting for backend..." sleep 5 if ! port_in_use "$BACKEND_PORT"; then echo "[ERROR] Backend did not start successfully." echo "[ERROR] Check log: $BACKEND_LOG" exit 1 fi echo "Starting frontend on port 5173..." FRONTEND_WIN_DIR="$(to_windows_path "$FRONTEND_DIR")" FRONTEND_WIN_LOG="$(to_windows_path "$FRONTEND_LOG")" FRONTEND_WIN_ERR_LOG="$(to_windows_path "$FRONTEND_ERR_LOG")" FRONTEND_WIN_VITE="$(to_windows_path "$FRONTEND_VITE")" FRONTEND_PID="$(powershell.exe -NoProfile -Command "& { \$process = Start-Process -FilePath '$FRONTEND_WIN_VITE' -ArgumentList '--host','0.0.0.0' -WorkingDirectory '$FRONTEND_WIN_DIR' -RedirectStandardOutput '$FRONTEND_WIN_LOG' -RedirectStandardError '$FRONTEND_WIN_ERR_LOG' -PassThru \$process.Id }" | tr -d '\r')" echo echo "==========================================" echo " Started" echo echo " Backend: http://${BACKEND_HOST}:${BACKEND_PORT}" echo " Frontend: http://localhost:5173" echo " API docs: http://${BACKEND_HOST}:${BACKEND_PORT}/docs" echo echo " Logs:" echo " - $BACKEND_LOG" echo " - $FRONTEND_LOG" echo "==========================================" echo echo "Press Ctrl+C to stop both services." echo while true; do backend_alive="$(powershell.exe -NoProfile -Command "Get-Process -Id $BACKEND_PID -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id" | tr -d '\r')" frontend_alive="$(powershell.exe -NoProfile -Command "Get-Process -Id $FRONTEND_PID -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id" | tr -d '\r')" if [[ -z "$backend_alive" ]]; then echo "[ERROR] Backend process exited." echo "[ERROR] Check log: $BACKEND_LOG" exit 1 fi if [[ -z "$frontend_alive" ]]; then echo "[ERROR] Frontend process exited." echo "[ERROR] Check log: $FRONTEND_LOG" exit 1 fi sleep 2 done