#!/usr/bin/env bash # Build and deploy Cerebrus services (backend, frontend) and the scanner Cloud Run # job to Cloud Run via Cloud Build. # # The backend config is baked into its image from apps/backend/.env.production. # The frontend runtime proxy reads BACKEND_URL from apps/frontend/.env.production, # which this script sets automatically on the Cloud Run service. # # ./deploy.sh # # Optional env overrides: REGION (default us-central1), AR_REPO (default cerebrus), # DEPLOY_SCANNER (default 1). set -euo pipefail PROJECT_ID="${1:-}" REGION="${REGION:-us-central1}" AR_REPO="${AR_REPO:-cerebrus}" DEPLOY_SCANNER="${DEPLOY_SCANNER:-1}" if [[ -z "$PROJECT_ID" ]]; then echo "Usage: ./deploy.sh (REGION / AR_REPO / DEPLOY_SCANNER overridable via env)" >&2 exit 1 fi if [[ ! -f "apps/backend/.env.production" ]]; then echo "ERROR: apps/backend/.env.production is missing — the backend image bakes it in." >&2 exit 1 fi if [[ ! -f "apps/frontend/.env.production" ]]; then echo "ERROR: apps/frontend/.env.production is missing." >&2 exit 1 fi # Read a key=value line from a .env file, stripping optional surrounding quotes. read_env() { local file="$1" key="$2" local value value="$(grep "^${key}=" "$file" 2>/dev/null | head -n1 | cut -d= -f2- | sed -E "s/^['\"]//;s/['\"]$//")" || true printf '%s' "$value" } FRONTEND_BACKEND_URL="$(read_env apps/frontend/.env.production BACKEND_URL)" if [[ -z "$FRONTEND_BACKEND_URL" ]]; then echo "ERROR: BACKEND_URL is not set in apps/frontend/.env.production" >&2 exit 1 fi # Sanity-check the backend env that trips people up. NODE_ENV="$(read_env apps/backend/.env.production NODE_ENV)" if [[ "$NODE_ENV" != "production" ]]; then echo "WARNING: NODE_ENV in apps/backend/.env.production is '${NODE_ENV:-(unset)}', expected 'production'." >&2 fi SCAN_CALLBACK_URL="$(read_env apps/backend/.env.production SCAN_CALLBACK_URL)" if [[ -n "$SCAN_CALLBACK_URL" && "$SCAN_CALLBACK_URL" == *"cerebrus-frontend"* ]]; then echo "WARNING: SCAN_CALLBACK_URL points to the frontend ($SCAN_CALLBACK_URL)." >&2 echo " It must point to the backend URL or scanner /internal callbacks will fail." >&2 fi echo "==> Project: $PROJECT_ID Region: $REGION Repo: $AR_REPO" gcloud config set project "$PROJECT_ID" >/dev/null echo "==> Enabling required APIs" gcloud services enable run.googleapis.com artifactregistry.googleapis.com cloudbuild.googleapis.com echo "==> Ensuring Artifact Registry repo '$AR_REPO'" gcloud artifacts repositories describe "$AR_REPO" --location="$REGION" >/dev/null 2>&1 || \ gcloud artifacts repositories create "$AR_REPO" \ --repository-format=docker --location="$REGION" --description="Cerebrus images" echo "==> Granting Cloud Build permission to deploy to Cloud Run" PROJECT_NUMBER="$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')" CB_SA="${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" gcloud projects add-iam-policy-binding "$PROJECT_ID" \ --member="serviceAccount:${CB_SA}" --role="roles/run.admin" --condition=None >/dev/null gcloud projects add-iam-policy-binding "$PROJECT_ID" \ --member="serviceAccount:${CB_SA}" --role="roles/iam.serviceAccountUser" --condition=None >/dev/null # Build + push + deploy backend and frontend. for app in backend frontend; do IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO}/${app}" echo "==> Building & deploying cerebrus-${app} (${IMAGE})" SUBS="_REGION=${REGION},_IMAGE=${IMAGE}" if [[ "$app" == "frontend" ]]; then SUBS="${SUBS},_BACKEND_URL=${FRONTEND_BACKEND_URL}" fi gcloud builds submit \ --config "apps/${app}/cloudbuild.yaml" \ --substitutions="$SUBS" \ . done # Build + push + deploy the scanner Cloud Run job (used when SCAN_RUNNER=cloudrun). if [[ "$DEPLOY_SCANNER" == "1" ]]; then SCANNER_IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO}/scanner" echo "==> Building & deploying scanner job (${SCANNER_IMAGE})" gcloud builds submit \ --config "apps/cli/cloudbuild.yaml" \ --substitutions="_REGION=${REGION},_IMAGE=${SCANNER_IMAGE}" \ . fi echo "==> Done. Cerebrus services:" gcloud run services list --platform=managed --region="$REGION" \ --filter="metadata.name~^cerebrus-(backend|frontend)$" \ --format='table(metadata.name, status.url)'