#!/usr/bin/env bash set -euo pipefail # init_supabase.sh # Purpose: # - Deploy Supabase stack in Kubernetes using the official supabase/docker compose # - Rewire Supabase to use the CNPG Postgres service # - Disable image pulls (imagePullPolicy: Never) for now SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # Load environment and config via prole_cfg.sh # shellcheck disable=SC1090 source "$SCRIPT_DIR/prole_cfg.sh" ACTION=${1:-} CNPG_CLUSTER_NAME=${CNPG_CLUSTER_NAME:-prole-db} SUPABASE_HOME=${SUPABASE_HOME:-} SUPABASE_USE_DEV_COMPOSE=${SUPABASE_USE_DEV_COMPOSE:-0} SUPABASE_IMAGE_PULL_POLICY=${SUPABASE_IMAGE_PULL_POLICY:-Never} SUPABASE_K8S_DIR=${SUPABASE_K8S_DIR:-${PROLE_HOME:-$SCRIPT_DIR/..}/build/supabase-k8s} SUPABASE_POSTGRES_HOST=${SUPABASE_POSTGRES_HOST:-db} SUPABASE_POSTGRES_DB=${SUPABASE_POSTGRES_DB:-postgres} SUPABASE_POSTGRES_PORT=${SUPABASE_POSTGRES_PORT:-5432} SUPABASE_APPLY_DB_MIGRATIONS=${SUPABASE_APPLY_DB_MIGRATIONS:-1} usage() { cat <&2; } ensure_tools() { for t in kubectl kompose python3 sed awk base64; do command -v "$t" >/dev/null || { err "Missing required tool: $t"; exit 1; } done } ensure_namespace() { if ! kubectl get namespace "supabase" >/dev/null 2>&1; then err "ERROR: namespace 'supabase' not found. Supabase must be deployed in its own namespace." exit 1 fi } ensure_prereqs() { ensure_namespace # We still want to ensure prole-db-superuser secret is in the CURRENT namespace (where prole-db is) # but supabase itself will be in 'supabase' namespace. # The issue description says: "connect supabase postgres network ports to our new namespace" # This implies we might need to create services in the CURRENT namespace that point to supabase. if ! kubectl get secret prole-db-superuser -n "$NAMESPACE" >/dev/null 2>&1; then err "ERROR: Secret 'prole-db-superuser' not found in namespace '$NAMESPACE'." exit 1 fi } detect_supabase_home() { if [[ -n "$SUPABASE_HOME" && -d "$SUPABASE_HOME" ]]; then echo "$SUPABASE_HOME" return 0 fi if [[ -d "$HOME/prole/supabase" ]]; then echo "$HOME/prole/supabase" return 0 fi if [[ -d "$HOME/dev/supabase" ]]; then echo "$HOME/dev/supabase" return 0 fi if [[ -n "${PROLE_HOME:-}" && -d "$PROLE_HOME/../supabase" ]]; then echo "$PROLE_HOME/../supabase" return 0 fi return 1 } get_db_password() { kubectl -n "$NAMESPACE" get secret prole-db-superuser -o jsonpath='{.data.password}' 2>/dev/null | base64 -d } set_env_kv() { local file="$1" key="$2" value="$3" # Ensure value is quoted if it contains spaces and is not already quoted if [[ "$value" == *" "* && ! "$value" =~ ^\".*\"$ && ! "$value" =~ ^\'.*\'$ ]]; then value="\"$value\"" fi if grep -q "^${key}=" "$file" 2>/dev/null; then sed -i.bak "s|^${key}=.*|${key}=${value}|" "$file" else printf "%s=%s\n" "$key" "$value" >> "$file" fi } build_env_file() { local supa_home="$1" local docker_dir="$supa_home/docker" local env_base="$docker_dir/.env" local env_example="$docker_dir/.env.example" local env_out="$SUPABASE_K8S_DIR/.env" local db_pass db_pass=$(get_db_password || true) mkdir -p "$SUPABASE_K8S_DIR" if [[ -f "$env_base" ]]; then cp "$env_base" "$env_out" elif [[ -f "$env_example" ]]; then cp "$env_example" "$env_out" else err "ERROR: Supabase .env or .env.example not found in $docker_dir" exit 1 fi # If we used .env.example, generate required secrets if [[ ! -f "$env_base" ]]; then if [[ -x "$docker_dir/utils/generate-keys.sh" ]]; then log "Generating Supabase secrets from utils/generate-keys.sh ..." local gen_out gen_out=$(bash "$docker_dir/utils/generate-keys.sh" /dev/null || true # Enforce imagePullPolicy python3 - </dev/null || true) if [[ -z "$primary" ]]; then primary=$(kubectl -n "$NAMESPACE" get pods -l "cnpg.io/cluster=$CNPG_CLUSTER_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true) fi if [[ -z "$primary" ]]; then err "WARN: Unable to locate primary CNPG pod; skipping migrations." return fi local db_pass db_pass=$(get_db_password || true) if [[ -z "$db_pass" ]]; then err "WARN: Unable to read prole-db-superuser password; skipping migrations." return fi log "Applying Supabase SQL migrations to CNPG (${SUPABASE_POSTGRES_DB}) ..." local files=( "$sql_dir/_supabase.sql" "$sql_dir/webhooks.sql" "$sql_dir/realtime.sql" "$sql_dir/logs.sql" "$sql_dir/pooler.sql" "$sql_dir/jwt.sql" "$sql_dir/roles.sql" ) for f in "${files[@]}"; do if [[ -f "$f" ]]; then log "Applying $(basename "$f") ..." kubectl -n "$NAMESPACE" exec -i "$primary" -c postgres -- \ env PGPASSWORD="$db_pass" psql -U postgres -d "$SUPABASE_POSTGRES_DB" -v ON_ERROR_STOP=1 -f - < "$f" || true fi done } delete_k8s_resources() { if [[ -d "$SUPABASE_K8S_DIR" ]]; then kubectl delete -n "supabase" -f "$SUPABASE_K8S_DIR" --ignore-not-found || true fi kubectl delete -n "$NAMESPACE" svc/supabase-db --ignore-not-found || true kubectl delete -n "supabase" svc/db --ignore-not-found || true } status() { ensure_tools log "Supabase status in namespace 'supabase':" kubectl get deploy,svc -n "supabase" | grep -E "supabase|kong|auth|rest|realtime|storage|meta|analytics|vector|imgproxy|functions|edge|pooler|db" || true log "Federated services in namespace '$NAMESPACE':" kubectl get svc -n "$NAMESPACE" | grep supabase-db || true } case "$ACTION" in start) ensure_tools ensure_prereqs SUPABASE_HOME="$(detect_supabase_home)" || { err "ERROR: Supabase repo not found. Set SUPABASE_HOME or symlink ~/prole/supabase."; exit 1; } log "Using Supabase repo: $SUPABASE_HOME" log "Using namespace: $NAMESPACE" convert_compose_to_k8s "$SUPABASE_HOME" apply_k8s_resources apply_db_migrations "$SUPABASE_HOME" ;; stop) ensure_tools ensure_namespace delete_k8s_resources ;; restart) ensure_tools ensure_prereqs SUPABASE_HOME="$(detect_supabase_home)" || { err "ERROR: Supabase repo not found. Set SUPABASE_HOME or symlink ~/prole/supabase."; exit 1; } convert_compose_to_k8s "$SUPABASE_HOME" delete_k8s_resources apply_k8s_resources apply_db_migrations "$SUPABASE_HOME" ;; status) status ;; *) usage ;; esac