#!/usr/bin/env bash # ============================================================================== # GoodGo Platform — Manual Production Deploy Script # Backup for CI/CD pipeline. Use when GitHub Actions is unavailable. # # Usage (from the server): # cd ~/goodgo # ./deploy-production.sh [image-tag] # # Usage (from local machine): # ssh ubuntu@185.225.232.65 'cd ~/goodgo && ./deploy-production.sh abc1234' # ============================================================================== set -euo pipefail # ── Configuration ───────────────────────────────────────────────────────────── COMPOSE_FILE="docker-compose.prod.yml" IMAGE_TAG="${1:-latest}" HEALTH_URL="http://127.0.0.1:3001/health" HEALTH_RETRIES=15 HEALTH_INTERVAL=5 ROLLBACK_ON_FAIL=true # ── Colors ──────────────────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' log() { echo -e "${GREEN}[DEPLOY]${NC} $(date +%H:%M:%S) $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $(date +%H:%M:%S) $*"; } err() { echo -e "${RED}[ERROR]${NC} $(date +%H:%M:%S) $*" >&2; } info() { echo -e "${CYAN}[INFO]${NC} $(date +%H:%M:%S) $*"; } # ── Pre-flight Checks ──────────────────────────────────────────────────────── if [ ! -f "$COMPOSE_FILE" ]; then err "Compose file not found: $COMPOSE_FILE" err "Are you in the ~/goodgo directory?" exit 1 fi if [ ! -f ".env" ]; then err ".env file not found. Copy from infra/env.production.example" exit 1 fi log "==========================================" log " GoodGo Platform — Production Deploy" log " Image tag: ${IMAGE_TAG}" log " Compose: ${COMPOSE_FILE}" log "==========================================" echo "" # ── Step 1: Record Current State (for rollback) ────────────────────────────── log "Step 1/6: Recording current state for rollback..." PREV_API=$(docker inspect --format='{{.Config.Image}}' goodgo-api 2>/dev/null || echo "none") PREV_WEB=$(docker inspect --format='{{.Config.Image}}' goodgo-web 2>/dev/null || echo "none") PREV_AI=$(docker inspect --format='{{.Config.Image}}' goodgo-ai-services 2>/dev/null || echo "none") info "Previous API: ${PREV_API}" info "Previous Web: ${PREV_WEB}" info "Previous AI: ${PREV_AI}" # ── Step 2: Pull New Images ────────────────────────────────────────────────── log "Step 2/6: Pulling new images (tag: ${IMAGE_TAG})..." export IMAGE_TAG docker compose -f "$COMPOSE_FILE" pull api web ai-services log "Images pulled successfully." # ── Step 3: Rolling Update ─────────────────────────────────────────────────── log "Step 3/6: Rolling update (zero-downtime)..." info "Updating API..." docker compose -f "$COMPOSE_FILE" up -d --no-deps --wait api info "API updated and healthy." info "Updating Web..." docker compose -f "$COMPOSE_FILE" up -d --no-deps --wait web info "Web updated and healthy." info "Updating AI Services..." docker compose -f "$COMPOSE_FILE" up -d --no-deps --wait ai-services info "AI Services updated and healthy." log "Rolling update complete." # ── Step 4: Database Migrations ────────────────────────────────────────────── log "Step 4/6: Running database migrations..." docker compose -f "$COMPOSE_FILE" exec -T api npx prisma migrate deploy log "Migrations complete." # ── Step 5: Health Check Verification ──────────────────────────────────────── log "Step 5/6: Verifying deployment health..." HEALTHY=false for i in $(seq 1 "$HEALTH_RETRIES"); do if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then HEALTHY=true break fi info "Waiting for health check... (${i}/${HEALTH_RETRIES})" sleep "$HEALTH_INTERVAL" done if $HEALTHY; then log "Health check passed!" else err "Health check failed after ${HEALTH_RETRIES} attempts!" if $ROLLBACK_ON_FAIL; then warn "Initiating rollback..." # Rollback: stop current, docker compose will use previously cached images docker compose -f "$COMPOSE_FILE" stop api web ai-services docker compose -f "$COMPOSE_FILE" up -d --wait api web ai-services warn "Rollback complete. Verifying..." sleep 5 if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then warn "Services recovered after rollback." else err "CRITICAL: Services still unhealthy after rollback!" err "Manual intervention required." fi fi exit 1 fi # ── Step 6: Cleanup ────────────────────────────────────────────────────────── log "Step 6/6: Cleaning up old images..." docker image prune -f log "Cleanup complete." # ── Summary ────────────────────────────────────────────────────────────────── echo "" log "==========================================" log " Deployment successful!" log "==========================================" log "" log " Services:" info " API: $(docker inspect --format='{{.Config.Image}}' goodgo-api)" info " Web: $(docker inspect --format='{{.Config.Image}}' goodgo-web)" info " AI: $(docker inspect --format='{{.Config.Image}}' goodgo-ai-services)" log "" log " Endpoints:" info " Web: https://platform.goodgo.vn" info " API: https://api.goodgo.vn" info " Grafana: https://grafana.goodgo.vn" log "" log " Run smoke tests:" info " ./scripts/smoke-test.sh https://api.goodgo.vn" log "=========================================="