#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" RUN_DIR="$ROOT_DIR/.run" LOG_DIR="$RUN_DIR/logs" BACKEND_PID_FILE="$RUN_DIR/backend.pid" FRONTEND_PID_FILE="$RUN_DIR/frontend.pid" BACKEND_LOG="$LOG_DIR/backend.log" FRONTEND_LOG="$LOG_DIR/frontend.log" BACKEND_HOST="${BACKEND_HOST:-127.0.0.1}" BACKEND_PORT="${BACKEND_PORT:-8001}" FRONTEND_HOST="${FRONTEND_HOST:-127.0.0.1}" FRONTEND_PORT="${FRONTEND_PORT:-3001}" API_BASE_URL="${NEXT_PUBLIC_API_BASE_URL:-http://${BACKEND_HOST}:${BACKEND_PORT}}" is_running() { local pid_file="$1" if [[ -f "$pid_file" ]]; then local pid pid="$(cat "$pid_file")" if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then return 0 fi rm -f "$pid_file" fi return 1 } require_file() { local path="$1" local label="$2" if [[ ! -e "$path" ]]; then echo "$label not found: $path" >&2 exit 1 fi } cmd_start() { require_file "$ROOT_DIR/backend/.venv/bin/uvicorn" "Backend virtualenv executable" require_file "$ROOT_DIR/frontend/node_modules" "Frontend dependencies" mkdir -p "$LOG_DIR" if is_running "$BACKEND_PID_FILE"; then echo "Backend already running on PID $(cat "$BACKEND_PID_FILE")" else ( cd "$ROOT_DIR/backend" exec .venv/bin/uvicorn app.main:app --reload --host "$BACKEND_HOST" --port "$BACKEND_PORT" ) >"$BACKEND_LOG" 2>&1 & echo $! >"$BACKEND_PID_FILE" echo "Started backend on http://${BACKEND_HOST}:${BACKEND_PORT} (PID $(cat "$BACKEND_PID_FILE"))" fi if is_running "$FRONTEND_PID_FILE"; then echo "Frontend already running on PID $(cat "$FRONTEND_PID_FILE")" else ( cd "$ROOT_DIR/frontend" export NEXT_PUBLIC_API_BASE_URL="$API_BASE_URL" exec npm run dev -- --hostname "$FRONTEND_HOST" --port "$FRONTEND_PORT" ) >"$FRONTEND_LOG" 2>&1 & echo $! >"$FRONTEND_PID_FILE" echo "Started frontend on http://${FRONTEND_HOST}:${FRONTEND_PORT} (PID $(cat "$FRONTEND_PID_FILE"))" fi cat </dev/null; then kill "$pid" 2>/dev/null || true for _ in {1..20}; do if ! kill -0 "$pid" 2>/dev/null; then break fi sleep 0.25 done if kill -0 "$pid" 2>/dev/null; then kill -9 "$pid" 2>/dev/null || true fi echo "Stopped $name (PID $pid)" else echo "$name pid file was stale" fi rm -f "$pid_file" } cmd_stop() { stop_process "frontend" "$FRONTEND_PID_FILE" stop_process "backend" "$BACKEND_PID_FILE" } cmd_restart() { cmd_stop cmd_start } cmd_status() { if is_running "$BACKEND_PID_FILE"; then echo "backend running PID $(cat "$BACKEND_PID_FILE") http://${BACKEND_HOST}:${BACKEND_PORT}" else echo "backend stopped" fi if is_running "$FRONTEND_PID_FILE"; then echo "frontend running PID $(cat "$FRONTEND_PID_FILE") http://${FRONTEND_HOST}:${FRONTEND_PORT}" else echo "frontend stopped" fi } case "${1:-}" in start) cmd_start ;; stop) cmd_stop ;; restart) cmd_restart ;; status) cmd_status ;; *) echo "Usage: $(basename "$0") {start|stop|restart|status}" >&2 exit 1 ;; esac