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>
This commit is contained in:
297
scripts/bootstrap.sh
Executable file
297
scripts/bootstrap.sh
Executable file
@@ -0,0 +1,297 @@
|
|||||||
|
#!/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 ""
|
||||||
Reference in New Issue
Block a user