#!/usr/bin/env bash # ============================================================================= # GoodGo Platform — One-Command Bootstrap # ============================================================================= # Sets up a complete local development environment from a clean clone. # # Usage: # ./scripts/bootstrap.sh # Full setup # ./scripts/bootstrap.sh --skip-seed # Skip database seeding # ./scripts/bootstrap.sh --no-docker # Skip Docker services (BYO database) # # Idempotent — safe to run multiple times. # Works on macOS and Linux. # ============================================================================= set -euo pipefail # ── Colours & Helpers ──────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m' # No Color info() { printf "${BLUE}ℹ${NC} %s\n" "$1"; } success() { printf "${GREEN}✔${NC} %s\n" "$1"; } warn() { printf "${YELLOW}⚠${NC} %s\n" "$1"; } fail() { printf "${RED}✖${NC} %s\n" "$1" >&2; exit 1; } step() { printf "\n${BOLD}── %s ──${NC}\n" "$1"; } # ── Parse Arguments ────────────────────────────────────────────────────────── SKIP_SEED=false NO_DOCKER=false for arg in "$@"; do case "$arg" in --skip-seed) SKIP_SEED=true ;; --no-docker) NO_DOCKER=true ;; --help|-h) echo "Usage: ./scripts/bootstrap.sh [--skip-seed] [--no-docker]" echo "" echo "Options:" echo " --skip-seed Skip database seeding (pnpm db:seed)" echo " --no-docker Skip Docker Compose services (bring your own DB)" echo " --help, -h Show this help message" exit 0 ;; *) warn "Unknown argument: $arg (ignored)" ;; esac done # ── Navigate to Project Root ───────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$PROJECT_ROOT" echo "" printf "${BOLD}╔══════════════════════════════════════════════════╗${NC}\n" printf "${BOLD}║ GoodGo Platform — Developer Bootstrap ║${NC}\n" printf "${BOLD}╚══════════════════════════════════════════════════╝${NC}\n" echo "" info "Project root: $PROJECT_ROOT" # ── Step 1: Check Prerequisites ────────────────────────────────────────────── step "Checking prerequisites" MISSING=() # Node.js >= 22 if command -v node &>/dev/null; then NODE_VERSION=$(node -v | sed 's/v//') NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d. -f1) if [ "$NODE_MAJOR" -ge 22 ]; then success "Node.js $NODE_VERSION (>= 22 required)" else fail "Node.js $NODE_VERSION found but >= 22 is required. Install via nvm: nvm install 22" fi else MISSING+=("Node.js >= 22 — https://nodejs.org or 'nvm install 22'") fi # pnpm if command -v pnpm &>/dev/null; then PNPM_VERSION=$(pnpm -v) success "pnpm $PNPM_VERSION" else MISSING+=("pnpm — install with 'corepack enable' or 'npm install -g pnpm'") fi # Docker (only if not --no-docker) if [ "$NO_DOCKER" = false ]; then if command -v docker &>/dev/null; then DOCKER_VERSION=$(docker --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) success "Docker $DOCKER_VERSION" else MISSING+=("Docker — https://docs.docker.com/get-docker/") fi # Docker Compose (v2 plugin or standalone) if docker compose version &>/dev/null 2>&1; then COMPOSE_VERSION=$(docker compose version --short 2>/dev/null || docker compose version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) success "Docker Compose $COMPOSE_VERSION" elif command -v docker-compose &>/dev/null; then COMPOSE_VERSION=$(docker-compose --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) success "Docker Compose $COMPOSE_VERSION (standalone)" else MISSING+=("Docker Compose — https://docs.docker.com/compose/install/") fi # Check Docker daemon is running if ! docker info &>/dev/null 2>&1; then fail "Docker daemon is not running. Please start Docker Desktop or the Docker service." fi fi if [ ${#MISSING[@]} -gt 0 ]; then echo "" printf "${RED}Missing prerequisites:${NC}\n" for dep in "${MISSING[@]}"; do echo " • $dep" done echo "" fail "Install the missing tools above and re-run this script." fi success "All prerequisites met" # ── Step 2: Environment File ───────────────────────────────────────────────── step "Setting up environment" if [ -f .env ]; then success ".env already exists (skipping copy)" else if [ -f .env.example ]; then cp .env.example .env # Generate secure defaults for development if command -v openssl &>/dev/null; then JWT_SECRET_VAL=$(openssl rand -base64 48) JWT_REFRESH_VAL=$(openssl rand -base64 48) KYC_KEY_VAL=$(openssl rand -hex 32) # Platform-safe sed in-place (macOS vs Linux) if [[ "$OSTYPE" == "darwin"* ]]; then SED_INPLACE=(sed -i '') else SED_INPLACE=(sed -i) fi "${SED_INPLACE[@]}" "s|DB_PASSWORD=CHANGE_ME|DB_PASSWORD=goodgo_secret|" .env "${SED_INPLACE[@]}" "s|TYPESENSE_API_KEY=CHANGE_ME|TYPESENSE_API_KEY=ts_dev_key_change_me|" .env "${SED_INPLACE[@]}" "s|MINIO_ACCESS_KEY=CHANGE_ME|MINIO_ACCESS_KEY=minioadmin|" .env "${SED_INPLACE[@]}" "s|MINIO_SECRET_KEY=CHANGE_ME|MINIO_SECRET_KEY=minioadmin|" .env "${SED_INPLACE[@]}" "s|PGBOUNCER_ADMIN_PASSWORD=CHANGE_ME|PGBOUNCER_ADMIN_PASSWORD=pgbouncer_admin|" .env "${SED_INPLACE[@]}" "s|PGBOUNCER_STATS_PASSWORD=CHANGE_ME|PGBOUNCER_STATS_PASSWORD=pgbouncer_stats|" .env "${SED_INPLACE[@]}" "s|JWT_SECRET=.*|JWT_SECRET=$JWT_SECRET_VAL|" .env "${SED_INPLACE[@]}" "s|JWT_REFRESH_SECRET=.*|JWT_REFRESH_SECRET=$JWT_REFRESH_VAL|" .env "${SED_INPLACE[@]}" "s|KYC_ENCRYPTION_KEY=.*|KYC_ENCRYPTION_KEY=$KYC_KEY_VAL|" .env else warn "openssl not found — .env copied but secrets are placeholders. Update them manually." fi success ".env created from .env.example with generated dev secrets" else fail ".env.example not found. Is this the project root?" fi fi # ── Step 3: Install Dependencies ───────────────────────────────────────────── step "Installing dependencies" pnpm install --frozen-lockfile 2>/dev/null || pnpm install success "Dependencies installed" # ── Step 4: Start Docker Services ──────────────────────────────────────────── if [ "$NO_DOCKER" = false ]; then step "Starting Docker services" # Start only core dev services (not monitoring/backup stack) CORE_SERVICES=(postgres redis typesense minio) info "Starting: ${CORE_SERVICES[*]}" docker compose up -d "${CORE_SERVICES[@]}" # Wait for services to be healthy info "Waiting for services to be healthy..." MAX_WAIT=120 ELAPSED=0 INTERVAL=5 wait_for_service() { local service="$1" local container="goodgo-$service" while [ "$ELAPSED" -lt "$MAX_WAIT" ]; do local health health=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "not_found") case "$health" in healthy) success "$service is healthy" return 0 ;; unhealthy) fail "$service is unhealthy. Check logs: docker compose logs $service" ;; not_found) fail "Container $container not found. Check: docker compose ps" ;; *) # starting or no status yet ;; esac sleep "$INTERVAL" ELAPSED=$((ELAPSED + INTERVAL)) done fail "$service did not become healthy within ${MAX_WAIT}s. Check logs: docker compose logs $service" } for svc in "${CORE_SERVICES[@]}"; do ELAPSED=0 wait_for_service "$svc" done success "All Docker services are healthy" else warn "Skipping Docker services (--no-docker). Ensure PostgreSQL, Redis, Typesense, and MinIO are available." fi # ── Step 5: Generate Prisma Client ─────────────────────────────────────────── step "Setting up database" info "Generating Prisma client..." pnpm db:generate success "Prisma client generated" # ── Step 6: Run Migrations ─────────────────────────────────────────────────── info "Running database migrations..." pnpm db:migrate:dev --name init 2>/dev/null || pnpm db:migrate:dev 2>/dev/null || { # If migrate dev fails (e.g., migrations already applied), try deploy instead warn "migrate dev had issues, trying migrate deploy..." pnpm db:migrate:deploy } success "Database migrations applied" # ── Step 7: Seed Database ──────────────────────────────────────────────────── if [ "$SKIP_SEED" = false ]; then info "Seeding database..." pnpm db:seed || { warn "Seed command returned non-zero (data may already exist). Continuing..." } success "Database seeded" else info "Skipping database seed (--skip-seed)" fi # ── Done! ──────────────────────────────────────────────────────────────────── echo "" printf "${BOLD}${GREEN}╔══════════════════════════════════════════════════╗${NC}\n" printf "${BOLD}${GREEN}║ Setup Complete! ║${NC}\n" printf "${BOLD}${GREEN}╚══════════════════════════════════════════════════╝${NC}\n" echo "" echo "Next steps:" echo "" echo " ${BOLD}pnpm dev${NC} Start all apps (API :3001, Web :3000)" echo " ${BOLD}pnpm db:studio${NC} Open Prisma Studio to browse data" echo " ${BOLD}pnpm test${NC} Run unit tests" echo " ${BOLD}pnpm lint${NC} Run ESLint" echo "" echo "Optional services (monitoring stack):" echo "" echo " ${BOLD}docker compose up -d loki promtail prometheus grafana${NC}" echo " Grafana: http://localhost:3002 (admin/admin)" echo "" echo "Documentation:" echo "" echo " README.md Project overview" echo " CONTRIBUTING.md Contribution guidelines" echo " CLAUDE.md AI assistant instructions" echo ""