Files
goodgo-platform/scripts/bootstrap.sh
Ho Ngoc Hai b7f9664709 feat(devops): add one-command bootstrap dev setup script
Streamline developer onboarding with a single bootstrap.sh that checks
prerequisites, configures .env with generated secrets, installs deps,
starts Docker services, and runs migrations + seeding.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-11 00:18:36 +07:00

298 lines
11 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 ""