#!/bin/sh # dev.sh — Start Docker backend + local Vite web dev server # # Usage: # ./scripts/dev.sh # uses backend/.env for backend, web/.env.local for Vite # REBUILD=1 ./scripts/dev.sh # force Docker rebuild before starting # # Requires: # - Docker running # - pnpm installed # - backend/.env populated (copy from backend/.env.example) # - web/.env.local populated (copy from web/.env.example) # # Ports: # Backend → http://localhost:4018 # Web → http://localhost:5173 (Vite default) set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd)" # --------------------------------------------------------------------------- # Colour helpers (skip if not a TTY) # --------------------------------------------------------------------------- if [ -t 1 ]; then BOLD='\033[1m'; CYAN='\033[0;36m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; RESET='\033[0m' else BOLD=''; CYAN=''; GREEN=''; YELLOW=''; RESET='' fi log() { printf "${CYAN}[dev]${RESET} %s\n" "$*"; } ok() { printf "${GREEN}[dev]${RESET} %s\n" "$*"; } warn() { printf "${YELLOW}[dev]${RESET} %s\n" "$*"; } # --------------------------------------------------------------------------- # Pre-flight checks # --------------------------------------------------------------------------- if ! docker info >/dev/null 2>&1; then printf "Error: Docker is not running.\n" >&2 exit 1 fi if [ ! -f "$ROOT/backend/.env" ]; then warn "backend/.env not found — copy backend/.env.example and fill in credentials." warn " cp backend/.env.example backend/.env" exit 1 fi if [ ! -f "$ROOT/web/.env.local" ]; then warn "web/.env.local not found — creating from web/.env.example with defaults." cp "$ROOT/web/.env.example" "$ROOT/web/.env.local" fi # --------------------------------------------------------------------------- # Cleanup on exit — always stop the backend container # --------------------------------------------------------------------------- cleanup() { log "Shutting down backend container..." cd "$ROOT" docker compose stop backend >/dev/null 2>&1 || true } trap cleanup INT TERM EXIT # --------------------------------------------------------------------------- # Start backend in Docker (backend service only, not web) # --------------------------------------------------------------------------- cd "$ROOT" BUILD_FLAG="" if [ "${REBUILD:-}" = "1" ]; then BUILD_FLAG="--build" log "Rebuilding backend Docker image..." fi log "Starting backend container..." # Run detached; logs are tailed below docker compose up backend --detach $BUILD_FLAG # --------------------------------------------------------------------------- # Wait for backend health # --------------------------------------------------------------------------- log "Waiting for backend to become healthy..." ATTEMPTS=0 MAX_ATTEMPTS=30 until docker compose exec -T backend wget -qO- http://localhost:4018/health/live >/dev/null 2>&1; do ATTEMPTS=$((ATTEMPTS + 1)) if [ "$ATTEMPTS" -ge "$MAX_ATTEMPTS" ]; then printf "Error: backend did not become healthy after %s attempts.\n" "$MAX_ATTEMPTS" >&2 docker compose logs --tail=40 backend >&2 exit 1 fi sleep 2 done ok "Backend healthy at http://localhost:4018" # --------------------------------------------------------------------------- # Tail backend logs in background # --------------------------------------------------------------------------- docker compose logs --follow --tail=20 backend & LOGS_PID=$! # --------------------------------------------------------------------------- # Start Vite web dev server (foreground — Ctrl+C stops everything via trap) # --------------------------------------------------------------------------- log "Starting Vite web dev server..." printf "\n${BOLD} Web → http://localhost:5173${RESET}\n" printf "${BOLD} API → http://localhost:4018${RESET}\n\n" cd "$ROOT/web" pnpm dev # Kill log tail if Vite exits cleanly kill "$LOGS_PID" 2>/dev/null || true