#!/usr/bin/env bash # dev.sh - Idempotent local development setup script # This script can be run multiple times safely # # Usage: # ./dev.sh - Setup only (PostgreSQL + .env files + migrations) # ./dev.sh --start - Setup and start backend + frontend # ./dev.sh --stop - Stop running services set -e # Exit on error # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration DB_URL="postgres://dev:devpass@localhost:5432/paragliding?sslmode=disable" CONTAINER_NAME="paragliding-postgres" BACKEND_DIR="backend" FRONTEND_DIR="frontend" BACKEND_PID_FILE=".backend.pid" FRONTEND_PID_FILE=".frontend.pid" BACKEND_LOG_FILE="backend.log" FRONTEND_LOG_FILE="frontend.log" # Helper functions info() { echo -e "${BLUE}ℹ${NC} $1" } success() { echo -e "${GREEN}✓${NC} $1" } warning() { echo -e "${YELLOW}⚠${NC} $1" } error() { echo -e "${RED}✗${NC} $1" } # Check if a command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Check if PostgreSQL container is running is_postgres_running() { if command_exists podman; then podman ps --filter "name=$CONTAINER_NAME" --filter "status=running" --format "{{.Names}}" | grep -q "$CONTAINER_NAME" elif command_exists docker; then docker ps --filter "name=$CONTAINER_NAME" --filter "status=running" --format "{{.Names}}" | grep -q "$CONTAINER_NAME" else return 1 fi } # Check if PostgreSQL is ready to accept connections is_postgres_ready() { if command_exists podman; then podman exec "$CONTAINER_NAME" pg_isready -U dev -d paragliding >/dev/null 2>&1 elif command_exists docker; then docker exec "$CONTAINER_NAME" pg_isready -U dev -d paragliding >/dev/null 2>&1 else return 1 fi } # Start PostgreSQL if not running start_postgres() { if is_postgres_running; then success "PostgreSQL is already running" return 0 fi info "Starting PostgreSQL container..." if command_exists podman; then if podman ps -a --filter "name=$CONTAINER_NAME" --format "{{.Names}}" | grep -q "$CONTAINER_NAME"; then # Container exists but is not running info "Starting existing PostgreSQL container..." podman start "$CONTAINER_NAME" >/dev/null else # Container doesn't exist, use docker-compose if command_exists podman-compose; then podman-compose -f docker-compose.dev.yml up -d else error "podman-compose not found. Please install it or use 'make dev-db'" return 1 fi fi elif command_exists docker; then if docker ps -a --filter "name=$CONTAINER_NAME" --format "{{.Names}}" | grep -q "$CONTAINER_NAME"; then info "Starting existing PostgreSQL container..." docker start "$CONTAINER_NAME" >/dev/null else if command_exists docker-compose; then docker-compose -f docker-compose.dev.yml up -d else error "docker-compose not found. Please install it or use 'make dev-db'" return 1 fi fi else error "Neither podman nor docker found. Please install one of them." return 1 fi # Wait for PostgreSQL to be ready info "Waiting for PostgreSQL to be ready..." for i in {1..30}; do if is_postgres_ready; then success "PostgreSQL is ready!" return 0 fi sleep 1 done error "PostgreSQL failed to become ready in time" return 1 } # Setup backend environment setup_backend_env() { if [ -f "$BACKEND_DIR/.env" ]; then success "Backend .env file already exists" else if [ -f "$BACKEND_DIR/.env.example" ]; then info "Creating backend/.env from .env.example..." cp "$BACKEND_DIR/.env.example" "$BACKEND_DIR/.env" success "Backend .env file created" else warning "Backend .env.example not found, creating default .env..." cat > "$BACKEND_DIR/.env" < "$FRONTEND_DIR/.env.local" <&1 | grep -q "no change"; then success "Migrations are up to date" else success "Migrations applied" fi else warning "Go not found, skipping migrations. Install Go to run migrations." fi cd - >/dev/null } # Check dependencies check_dependencies() { info "Checking dependencies..." local missing_deps=() if ! command_exists go; then missing_deps+=("go (for backend)") fi if ! command_exists node && ! command_exists bun; then missing_deps+=("node or bun (for frontend)") fi if ! command_exists podman && ! command_exists docker; then missing_deps+=("podman or docker (for PostgreSQL)") fi if [ ${#missing_deps[@]} -gt 0 ]; then warning "Missing dependencies:" for dep in "${missing_deps[@]}"; do echo " - $dep" done echo "" else success "All dependencies found" fi } # Start backend service in background start_backend() { if [ -f "$BACKEND_PID_FILE" ] && kill -0 "$(cat $BACKEND_PID_FILE)" 2>/dev/null; then warning "Backend is already running (PID: $(cat $BACKEND_PID_FILE))" return 0 fi if ! command_exists go; then error "Go is required to run the backend. Please install Go first." return 1 fi if [ ! -f "$BACKEND_DIR/.env" ]; then error "Backend .env file not found. Run './dev.sh' first to set up." return 1 fi info "Starting backend server..." # Start backend in subshell to isolate environment ( cd "$BACKEND_DIR" # Export environment variables from .env file set -a source .env set +a exec go run ./cmd/api ) > "$BACKEND_LOG_FILE" 2>&1 & echo $! > "$BACKEND_PID_FILE" success "Backend started (PID: $(cat $BACKEND_PID_FILE), logs: $BACKEND_LOG_FILE)" } # Start frontend service in background start_frontend() { if [ -f "$FRONTEND_PID_FILE" ] && kill -0 "$(cat $FRONTEND_PID_FILE)" 2>/dev/null; then warning "Frontend is already running (PID: $(cat $FRONTEND_PID_FILE))" return 0 fi local runner="npm" if command_exists bun; then runner="bun" elif ! command_exists npm; then error "Neither npm nor bun found. Please install Node.js or Bun." return 1 fi info "Starting frontend server with $runner..." # Start frontend in subshell to isolate environment ( cd "$FRONTEND_DIR" exec $runner run dev ) > "$FRONTEND_LOG_FILE" 2>&1 & echo $! > "$FRONTEND_PID_FILE" success "Frontend started (PID: $(cat $FRONTEND_PID_FILE), logs: $FRONTEND_LOG_FILE)" } # Stop running services stop_services() { echo "" info "Stopping services..." local stopped=0 if [ -f "$BACKEND_PID_FILE" ]; then local pid=$(cat "$BACKEND_PID_FILE") if kill -0 "$pid" 2>/dev/null; then kill "$pid" success "Backend stopped (was PID: $pid)" stopped=1 fi rm -f "$BACKEND_PID_FILE" fi if [ -f "$FRONTEND_PID_FILE" ]; then local pid=$(cat "$FRONTEND_PID_FILE") if kill -0 "$pid" 2>/dev/null; then kill "$pid" success "Frontend stopped (was PID: $pid)" stopped=1 fi rm -f "$FRONTEND_PID_FILE" fi if [ $stopped -eq 0 ]; then info "No services were running" fi echo "" } # Show service status show_status() { echo "" info "Service Status:" echo "" # PostgreSQL if is_postgres_running; then echo " PostgreSQL: ${GREEN}●${NC} Running" else echo " PostgreSQL: ${RED}●${NC} Stopped" fi # Backend if [ -f "$BACKEND_PID_FILE" ] && kill -0 "$(cat $BACKEND_PID_FILE)" 2>/dev/null; then echo " Backend: ${GREEN}●${NC} Running (PID: $(cat $BACKEND_PID_FILE))" else echo " Backend: ${RED}●${NC} Stopped" [ -f "$BACKEND_PID_FILE" ] && rm -f "$BACKEND_PID_FILE" fi # Frontend if [ -f "$FRONTEND_PID_FILE" ] && kill -0 "$(cat $FRONTEND_PID_FILE)" 2>/dev/null; then echo " Frontend: ${GREEN}●${NC} Running (PID: $(cat $FRONTEND_PID_FILE))" else echo " Frontend: ${RED}●${NC} Stopped" [ -f "$FRONTEND_PID_FILE" ] && rm -f "$FRONTEND_PID_FILE" fi echo "" } # Main setup flow main() { echo "" echo "🚀 Paragliding Local Development Setup" echo "======================================" echo "" # Check dependencies check_dependencies echo "" # Step 1: Start PostgreSQL info "Step 1: PostgreSQL" start_postgres || exit 1 echo "" # Step 2: Setup environment files info "Step 2: Environment Configuration" setup_backend_env setup_frontend_env echo "" # Step 3: Run migrations info "Step 3: Database Migrations" run_migrations echo "" # Final instructions success "Setup complete! 🎉" echo "" } # Main entry point case "${1:-}" in --start) main info "Step 4: Starting Services" echo "" start_backend sleep 2 # Give backend a moment to start start_frontend echo "" success "All services started! 🚀" echo "" echo "Services are running in the background:" echo " - Frontend: ${BLUE}http://localhost:3000${NC}" echo " - Backend: ${BLUE}http://localhost:8080${NC}" echo " - API: ${BLUE}http://localhost:8080/api/v1${NC}" echo "" echo "View logs:" echo " ${YELLOW}tail -f $BACKEND_LOG_FILE${NC}" echo " ${YELLOW}tail -f $FRONTEND_LOG_FILE${NC}" echo "" echo "Stop services:" echo " ${YELLOW}./dev.sh --stop${NC}" echo "" ;; --stop) stop_services ;; --status) show_status ;; --help) echo "Usage: ./dev.sh [OPTION]" echo "" echo "Options:" echo " (none) Setup only (PostgreSQL + .env files + migrations)" echo " --start Setup and start backend + frontend services" echo " --stop Stop running backend + frontend services" echo " --status Show status of all services" echo " --help Show this help message" echo "" ;; "") main echo "Next steps:" echo "" echo " Option 1 - Start services in background:" echo " ${GREEN}./dev.sh --start${NC}" echo "" echo " Option 2 - Run in separate terminals:" echo " Terminal 1: ${GREEN}make run-backend${NC}" echo " Terminal 2: ${GREEN}make run-frontend${NC}" echo "" echo "Services will be available at:" echo " - Frontend: ${BLUE}http://localhost:3000${NC}" echo " - Backend: ${BLUE}http://localhost:8080${NC}" echo " - API: ${BLUE}http://localhost:8080/api/v1${NC}" echo "" ;; *) error "Unknown option: $1" echo "Run './dev.sh --help' for usage information" exit 1 ;; esac