124 lines
5.4 KiB
Bash
Executable File
124 lines
5.4 KiB
Bash
Executable File
#!/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"
|
||
export REACT_APP_ENV_NAME="$ENV_NAME"
|
||
|
||
|
||
# ───────────────────────── 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"
|