2026-03-21 10:13:43 +08:00
|
|
|
#!/bin/bash
|
2026-03-22 22:42:47 +08:00
|
|
|
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
|
2026-03-21 10:13:43 +08:00
|
|
|
|
|
|
|
|
echo "=========================================="
|
2026-03-22 22:42:47 +08:00
|
|
|
echo " Jarvis - Quick Start"
|
2026-03-21 10:13:43 +08:00
|
|
|
echo "=========================================="
|
2026-03-22 22:42:47 +08:00
|
|
|
echo
|
2026-03-21 10:13:43 +08:00
|
|
|
|
2026-03-22 22:42:47 +08:00
|
|
|
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
|
2026-03-21 10:13:43 +08:00
|
|
|
fi
|
|
|
|
|
|
2026-03-22 22:42:47 +08:00
|
|
|
if [[ ! -f "$FRONTEND_VITE" ]]; then
|
|
|
|
|
echo "[ERROR] frontend/node_modules/.bin/vite.cmd was not found."
|
|
|
|
|
echo "[ERROR] Install frontend dependencies first."
|
|
|
|
|
exit 1
|
2026-03-21 10:13:43 +08:00
|
|
|
fi
|
|
|
|
|
|
2026-03-22 22:42:47 +08:00
|
|
|
if ! command -v powershell.exe >/dev/null 2>&1; then
|
|
|
|
|
echo "[ERROR] powershell.exe was not found."
|
|
|
|
|
exit 1
|
2026-03-21 10:13:43 +08:00
|
|
|
fi
|
|
|
|
|
|
2026-03-22 22:42:47 +08:00
|
|
|
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."
|
2026-03-21 10:13:43 +08:00
|
|
|
|
2026-03-22 22:42:47 +08:00
|
|
|
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..."
|
2026-03-21 10:13:43 +08:00
|
|
|
sleep 5
|
|
|
|
|
|
2026-03-22 22:42:47 +08:00
|
|
|
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')"
|
2026-03-21 10:13:43 +08:00
|
|
|
|
2026-03-22 22:42:47 +08:00
|
|
|
echo
|
2026-03-21 10:13:43 +08:00
|
|
|
echo "=========================================="
|
2026-03-22 22:42:47 +08:00
|
|
|
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"
|
2026-03-21 10:13:43 +08:00
|
|
|
echo "=========================================="
|
2026-03-22 22:42:47 +08:00
|
|
|
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
|