#!/usr/bin/env bash set -euo pipefail # ───────────────────────── config ───────────────────────── ENV="${1:-${ENV:-dev}}" case "$ENV" in dev|staging|prod) ;; *) echo "❌ Unknown ENV='$ENV'"; exit 1 ;; esac PROJECT="aptivaai-${ENV}" REG="us-central1-docker.pkg.dev/${PROJECT}/aptiva-repo" ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="${ROOT}/.env" echo "🔧 Deploying environment: $ENV (GCP: $PROJECT)" SECRETS=( ENV_NAME PROJECT CORS_ALLOWED_ORIGINS TOKEN_MAX_AGE_MS COOKIE_SECURE COOKIE_SAMESITE ACCESS_COOKIE_NAME SERVER1_PORT SERVER2_PORT SERVER3_PORT JWT_SECRET OPENAI_API_KEY ONET_USERNAME ONET_PASSWORD STRIPE_SECRET_KEY STRIPE_PUBLISHABLE_KEY STRIPE_WH_SECRET STRIPE_PRICE_PREMIUM_MONTH STRIPE_PRICE_PREMIUM_YEAR STRIPE_PRICE_PRO_MONTH STRIPE_PRICE_PRO_YEAR DB_HOST DB_NAME DB_PORT DB_USER DB_PASSWORD DB_SSL_CERT DB_SSL_KEY DB_SSL_CA SUPPORT_SENDGRID_API_KEY EMAIL_INDEX_SECRET APTIVA_API_BASE TWILIO_ACCOUNT_SID TWILIO_AUTH_TOKEN TWILIO_MESSAGING_SERVICE_SID GOOGLE_MAPS_API_KEY KMS_KEY_NAME DEK_PATH ) cd "$ROOT" # ───────────── pull runtime secrets (BEFORE build) ───────────── echo "🔐 Pulling secrets from Secret Manager" for S in "${SECRETS[@]}"; do export "$S"="$(gcloud secrets versions access latest --secret="${S}_${ENV}" --project="$PROJECT")" done export FROM_SECRETS_MANAGER=true # React needs the prefixed var at BUILD time export REACT_APP_GOOGLE_MAPS_API_KEY="$GOOGLE_MAPS_API_KEY" # ───────────────────────── node + npm ci cache ───────────────────────── echo "🛠 Building front-end bundle (skips when unchanged)" export npm_config_cache="${HOME}/.npm" # persist npm cache export CI=false # don’t treat warnings as errors NODE_VER="$(node -v 2>/dev/null || echo 'none')" if [[ ! -f .last-node || "$(cat .last-node 2>/dev/null || echo)" != "$NODE_VER" ]]; then echo "♻️ Node changed → cleaning node_modules (was '$(cat .last-node 2>/dev/null || echo none)', now '${NODE_VER}')" rm -rf node_modules .build.hash fi echo "$NODE_VER" > .last-node if [[ ! -f package-lock.json ]]; then echo "⚠️ package-lock.json missing; running npm ci" npm ci --silent --no-audit --no-fund else LOCK_HASH="$(sha1sum package-lock.json | awk '{print $1}')" if [[ -d node_modules && -f .last-lock && "$(cat .last-lock)" == "$LOCK_HASH" ]]; then echo "📦 node_modules up-to-date; skipping npm ci" else echo "📦 installing deps…" npm ci --silent --no-audit --no-fund echo "$LOCK_HASH" > .last-lock echo "$LOCK_HASH" > .lock.hash # legacy compat fi fi # ───────────────────────── npm run build cache ───────────────────────── SRC_HASH="$(find src public -type f -print0 2>/dev/null | sort -z | xargs -0 sha1sum | sha1sum | awk '{print $1}')" PKG_HASH="$(sha1sum package.json package-lock.json 2>/dev/null | sha1sum | awk '{print $1}')" BUILD_ENV_HASH="$(printf '%s' "${REACT_APP_GOOGLE_MAPS_API_KEY}-${REACT_APP_API_URL:-}" | sha1sum | awk '{print $1}')" COMBINED_HASH="${SRC_HASH}-${PKG_HASH}-${BUILD_ENV_HASH}" if [[ -f .build.hash && "$(cat .build.hash)" == "$COMBINED_HASH" && -d build ]]; then echo "🏗 static bundle up-to-date; skipping npm run build" else echo "🏗 Building static bundle…" GENERATE_SOURCEMAP=false NODE_OPTIONS="--max-old-space-size=4096" npm run build echo "$COMBINED_HASH" > .build.hash fi # ───────────────────── build & push images (SEQUENTIAL) ───────────────────── export DOCKER_BUILDKIT=1 export COMPOSE_DOCKER_CLI_BUILD=1 export BUILDKIT_PROGRESS=plain # stable progress output TAG="$(git rev-parse --short HEAD)-$(date -u +%Y%m%d%H%M)" echo "🔨 Building & pushing containers (tag = ${TAG})" build_and_push () { local svc="$1" echo "🧱 Building ${svc}…" docker build --progress=plain -f "Dockerfile.${svc}" -t "${REG}/${svc}:${TAG}" . echo "⏫ Pushing ${svc}…" docker push "${REG}/${svc}:${TAG}" } # Build servers first, then nginx (needs ./build) for svc in server1 server2 server3 nginx; do build_and_push "$svc" done # ───────────────────── write IMG_TAG locally ───────────────────── export IMG_TAG="${TAG}" echo "🔖 Using IMG_TAG=${IMG_TAG} (not writing to .env)" # ───────────────────── publish IMG_TAG to Secret Manager ───────────────────── printf "%s" "${TAG}" | gcloud secrets versions add IMG_TAG --data-file=- --project="$PROJECT" >/dev/null echo "📦 IMG_TAG pushed to Secret Manager" # ───────────────────── docker compose up ───────────────────── preserve=IMG_TAG,FROM_SECRETS_MANAGER,REACT_APP_API_URL,REACT_APP_GOOGLE_MAPS_API_KEY,$(IFS=,; echo "${SECRETS[*]}") echo "🚀 docker compose up -d (env: $preserve)" sudo --preserve-env="$preserve" docker compose up -d --force-recreate \ 2> >(grep -v 'WARN \[0000\]') echo "✅ Deployment finished"