#!/usr/bin/env bash # ============================================================================= # 04-deploy.sh (Linux) # Builds the Docker image, pushes it to Artifact Registry, and deploys it # to Cloud Run — all in one command. # # Usage: # ./GCR/scripts/04-deploy.sh # deploy with tag = git short SHA # ./GCR/scripts/04-deploy.sh my-tag # deploy with a custom tag # # Prerequisites: # 1. GCR/.env exists and is filled in (copy from GCR/.env.example) # 2. 01-login.sh has been run (gcloud auth + Docker configured) # 3. 02-setup-project.sh has been run (APIs enabled, repo created) # 4. 03-create-secrets.sh has been run (MongoDB secret created) # 5. Docker daemon is running locally # # Windows users: run GCR/scripts/04-deploy.ps1 in PowerShell instead. # ============================================================================= set -euo pipefail if [[ "$(uname -s)" != "Linux" ]]; then echo "ERROR: This script is for Linux only." echo "Windows users: run GCR/scripts/04-deploy.ps1" exit 1 fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # ── Load .env ───────────────────────────────────────────────────────────────── ENV_FILE="$SCRIPT_DIR/../.env" if [[ ! -f "$ENV_FILE" ]]; then echo "ERROR: $ENV_FILE not found." echo "Copy GCR/.env.example to GCR/.env and fill in your values first." exit 1 fi # shellcheck disable=SC1090 source "$ENV_FILE" : "${GCP_PROJECT_ID:?GCP_PROJECT_ID is not set in .env}" : "${GCP_REGION:?GCP_REGION is not set in .env}" : "${GCP_REPOSITORY:?GCP_REPOSITORY is not set in .env}" : "${SERVICE_NAME:?SERVICE_NAME is not set in .env}" : "${MONGODB_DATABASE_NAME:?MONGODB_DATABASE_NAME is not set in .env}" # Note: MONGODB_CONNECTION_STRING is stored in Secret Manager (mongodb-connection-string) # See GCR/README.md for Secret Manager setup secret_setup_ready() { local service_account service_account="serviceAccount:${GCP_PROJECT_ID}@appspot.gserviceaccount.com" gcloud secrets describe "mongodb-connection-string" --project="$GCP_PROJECT_ID" >/dev/null 2>&1 || return 1 gcloud secrets get-iam-policy "mongodb-connection-string" \ --project="$GCP_PROJECT_ID" \ --flatten="bindings[].members" \ --filter="bindings.role=roles/secretmanager.secretAccessor AND bindings.members=${service_account}" \ --format="value(bindings.members)" 2>/dev/null \ | grep -Fxq "$service_account" } if ! secret_setup_ready; then echo "" echo ">>> Required secrets are not fully configured yet." read -rp " Run GCR/scripts/03-create-secrets.sh now? [y/N]: " RUN_SECRET_SETUP if [[ "$RUN_SECRET_SETUP" =~ ^[Yy]$ ]]; then bash "$SCRIPT_DIR/03-create-secrets.sh" else echo "" echo "ERROR: Deployment requires secret setup first." echo "Run: bash GCR/scripts/03-create-secrets.sh" exit 1 fi if ! secret_setup_ready; then echo "" echo "ERROR: Secret setup check still failing after running 03-create-secrets.sh." exit 1 fi fi # ── Determine image tag ──────────────────────────────────────────────────────── TAG="${1:-}" if [[ -z "$TAG" ]]; then # Default to git short SHA if inside a git repo; otherwise use timestamp if git -C "$REPO_ROOT" rev-parse --short HEAD &>/dev/null; then TAG="$(git -C "$REPO_ROOT" rev-parse --short HEAD)" else TAG="$(date +%Y%m%d%H%M%S)" fi fi REGISTRY="${GCP_REGION}-docker.pkg.dev" IMAGE_URI="${REGISTRY}/${GCP_PROJECT_ID}/${GCP_REPOSITORY}/${SERVICE_NAME}:${TAG}" echo "================================================================" echo " Htmx → Cloud Run deployment" echo "================================================================" echo " Project: $GCP_PROJECT_ID" echo " Region: $GCP_REGION" echo " Service: $SERVICE_NAME" echo " Image: $IMAGE_URI" echo "================================================================" echo "" # ── Step 1: Ensure package-lock.json exists (required for `npm ci`) ─────────── LOCKFILE="$REPO_ROOT/Htmx.ApiDemo/package-lock.json" if [[ ! -f "$LOCKFILE" ]]; then echo ">>> package-lock.json not found. Generating it now..." echo " (This requires node + npm to be installed locally)" (cd "$REPO_ROOT/Htmx.ApiDemo" && npm install --package-lock-only) echo " package-lock.json generated. Commit it to the repository." echo "" fi # ── Step 2: Build the Docker image ──────────────────────────────────────────── echo ">>> Building Docker image..." echo " Context: $REPO_ROOT" echo " Dockerfile: GCR/Dockerfile" echo "" # Build from repo root so the COPY instructions can reach both # Htmx.ApiDemo/ and Htmx.SourceGenerator/ directories. docker build \ --file "$REPO_ROOT/GCR/Dockerfile" \ --tag "$IMAGE_URI" \ "$REPO_ROOT" echo "" echo ">>> Image built: $IMAGE_URI" # ── Step 3: Push image to Artifact Registry ─────────────────────────────────── echo "" echo ">>> Pushing image to Artifact Registry..." docker push "$IMAGE_URI" echo ">>> Push complete." # ── Step 4: Deploy to Cloud Run via docker-compose.yml ─────────────────────── echo "" echo ">>> Deploying to Cloud Run..." # Export variables consumed by docker-compose.yml substitution export IMAGE_URI export MONGODB_DATABASE_NAME gcloud run services replace "$REPO_ROOT/GCR/docker-compose.yml" \ --region="$GCP_REGION" \ --project="$GCP_PROJECT_ID" # ── Step 4b: Inject MongoDB connection string from Secret Manager ──────────── echo "" echo ">>> Injecting MongoDB connection string from Secret Manager..." gcloud run services update "$SERVICE_NAME" \ --region="$GCP_REGION" \ --project="$GCP_PROJECT_ID" \ --set-secrets="ConnectionStrings__DefaultConnection=mongodb-connection-string:latest" # ── Step 5: Make the service publicly accessible ────────────────────────────── # Remove this block if you want the service to require authentication. echo "" echo ">>> Allowing public (unauthenticated) access to the service..." gcloud run services add-iam-policy-binding "$SERVICE_NAME" \ --region="$GCP_REGION" \ --project="$GCP_PROJECT_ID" \ --member="allUsers" \ --role="roles/run.invoker" # ── Print service URL ───────────────────────────────────────────────────────── echo "" SERVICE_URL=$(gcloud run services describe "$SERVICE_NAME" \ --region="$GCP_REGION" \ --project="$GCP_PROJECT_ID" \ --format="value(status.url)") echo "================================================================" echo " Deployment complete!" echo " Service URL: $SERVICE_URL" echo "================================================================"