448 lines
12 KiB
Bash
Executable File
448 lines
12 KiB
Bash
Executable File
#!/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" <<EOF
|
||
DATABASE_URL=$DB_URL
|
||
PORT=8080
|
||
LOCATION_LAT=32.8893
|
||
LOCATION_LON=-117.2519
|
||
LOCATION_NAME=Torrey Pines Gliderport
|
||
TIMEZONE=America/Los_Angeles
|
||
FETCH_INTERVAL=15m
|
||
CACHE_TTL=10m
|
||
EOF
|
||
success "Backend .env file created with defaults"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Setup frontend environment
|
||
setup_frontend_env() {
|
||
if [ -f "$FRONTEND_DIR/.env.local" ]; then
|
||
success "Frontend .env.local file already exists"
|
||
else
|
||
info "Creating frontend/.env.local..."
|
||
cat > "$FRONTEND_DIR/.env.local" <<EOF
|
||
NEXT_PUBLIC_API_URL=http://localhost:8080/api/v1
|
||
EOF
|
||
success "Frontend .env.local file created"
|
||
fi
|
||
}
|
||
|
||
# Run database migrations
|
||
run_migrations() {
|
||
info "Checking database migrations..."
|
||
|
||
if [ ! -d "$BACKEND_DIR/migrations" ]; then
|
||
warning "No migrations directory found, skipping..."
|
||
return 0
|
||
fi
|
||
|
||
# Check if migrations have been run by trying to query a table
|
||
# This is a simple check - you might want to use a more robust migration tool
|
||
cd "$BACKEND_DIR"
|
||
|
||
# Try to run migrations
|
||
if command_exists go; then
|
||
info "Running database migrations..."
|
||
if DATABASE_URL="$DB_URL" go run -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest -path ./migrations -database "$DB_URL" up 2>&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
|