prole/etc/init_supabase.sh
chrisfu fff18fdbe4 Update Prole-DB and improve Supabase integration
- Bumped Prole-DB image version to 17.7-053 in scripts, Dockerfile, and manifests.
- Replaced `prole-scan` with `prole-agent` throughout scripts and tests.
- Refined Kubernetes setup for Supabase to use namespace 'supabase'.
- Introduced conversion of Supabase Docker Compose to Kubernetes manifests with `kompose`.
- Added support for Kerberos toggle via environment variables in `init_kerberos.sh`.
- Improved error handling and logging in scripts for better maintainability.
2026-02-01 23:56:25 -08:00

411 lines
13 KiB
Bash
Executable File

#!/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 <<EOF
Usage: $0 [start|stop|restart|status]
Actions:
start Convert Supabase docker-compose to K8s and apply in namespace
stop Delete Supabase K8s resources from namespace
restart Re-apply Supabase manifests
status Show Supabase resources
Env overrides:
SUPABASE_HOME, SUPABASE_USE_DEV_COMPOSE, SUPABASE_K8S_DIR
SUPABASE_IMAGE_PULL_POLICY (default: Never)
SUPABASE_POSTGRES_HOST (default: db)
SUPABASE_POSTGRES_DB (default: postgres)
SUPABASE_POSTGRES_PORT (default: 5432)
SUPABASE_APPLY_DB_MIGRATIONS (default: 1)
EOF
exit 1
}
log() { printf '%s\n' "$*"; }
err() { printf '%s\n' "$*" >&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)
while IFS= read -r line; do
[[ "$line" =~ ^[A-Z0-9_]+= ]] || continue
local k="${line%%=*}"
local v="${line#*=}"
set_env_kv "$env_out" "$k" "$v"
done <<< "$gen_out"
fi
fi
if [[ -n "$db_pass" ]]; then
set_env_kv "$env_out" "POSTGRES_PASSWORD" "$db_pass"
fi
set_env_kv "$env_out" "POSTGRES_HOST" "$SUPABASE_POSTGRES_HOST"
set_env_kv "$env_out" "POSTGRES_DB" "$SUPABASE_POSTGRES_DB"
set_env_kv "$env_out" "POSTGRES_PORT" "$SUPABASE_POSTGRES_PORT"
}
convert_compose_to_k8s() {
local supa_home="$1"
local docker_dir="$supa_home/docker"
local compose_file="$docker_dir/docker-compose.yml"
local dev_compose="$docker_dir/dev/docker-compose.dev.yml"
if [[ ! -f "$compose_file" ]]; then
err "ERROR: docker-compose.yml not found at $compose_file"
exit 1
fi
rm -rf "$SUPABASE_K8S_DIR"
mkdir -p "$SUPABASE_K8S_DIR"
build_env_file "$supa_home"
# Export .env for kompose interpolation
set -a
# Fix unquoted values with spaces in the .env file before sourcing
# Use python for robust .env parsing and quoting
python3 - <<PY
import pathlib
import re
env_file = pathlib.Path("$SUPABASE_K8S_DIR/.env")
if env_file.exists():
content = env_file.read_text()
new_lines = []
for line in content.splitlines():
# Match KEY=VALUE where VALUE has spaces and is not quoted
m = re.match(r'^([A-Z0-9_]+)=([^"\'].* .*)$', line)
if m:
key, val = m.groups()
new_lines.append(f'{key}="{val}"')
else:
new_lines.append(line)
env_file.write_text("\n".join(new_lines) + "\n")
PY
# shellcheck disable=SC1090
source "$SUPABASE_K8S_DIR/.env"
set +a
local files=("-f" "$compose_file")
if [[ "$SUPABASE_USE_DEV_COMPOSE" == "1" && -f "$dev_compose" ]]; then
files+=("-f" "$dev_compose")
fi
log "Converting Supabase docker-compose to Kubernetes manifests for namespace 'supabase' ..."
kompose "${files[@]}" convert -n "supabase" -o "$SUPABASE_K8S_DIR" --volumes=configMap --suppress-warnings
# Remove the built-in Supabase DB workloads (we use CNPG instead)
rm -f "$SUPABASE_K8S_DIR"/db-*.yaml "$SUPABASE_K8S_DIR"/db*.yaml 2>/dev/null || true
# Enforce imagePullPolicy
python3 - <<PY
import pathlib
import re
k8s_dir = pathlib.Path("$SUPABASE_K8S_DIR")
policy = "$SUPABASE_IMAGE_PULL_POLICY"
def patch_file(path: pathlib.Path):
lines = path.read_text().splitlines()
out = []
for line in lines:
out.append(line)
m = re.match(r'^(\s*)image:\s*.+', line)
if m:
indent = m.group(1)
out.append(f"{indent}imagePullPolicy: {policy}")
path.write_text("\n".join(out) + "\n")
# Sanitize container names (must not contain dots)
def sanitize_container_names(path: pathlib.Path):
content = path.read_text()
# Find container names that contain dots and replace dots with hyphens
# Look for "name: some.name" within container blocks
def repl(match):
indent = match.group(1)
name = match.group(2)
sanitized = name.replace('.', '-')
return f"{indent}name: {sanitized}"
# Match name: value where value has dots, but only if it looks like a container name
# We can be more specific by looking for lines that start with - name: or just name: inside containers
# Updated regex to be more comprehensive for container names in various K8s objects
# 1. Matches '- name: realtime-dev.supabase-realtime'
new_content = re.sub(r'(^\s+-\s+name:\s+)([a-zA-Z0-9.-]+)', repl, content, flags=re.MULTILINE)
# 2. Matches 'name: realtime-dev.supabase-realtime' (if not already matched by 1)
new_content = re.sub(r'(^\s+name:\s+)([a-zA-Z0-9.-]+\.[a-zA-Z0-9.-]+)', repl, new_content, flags=re.MULTILINE)
# 3. Final safety check: if we are in a containers: block, dots are NOT allowed in names.
# This is a bit more aggressive but safer.
lines = new_content.splitlines()
final_lines = []
in_containers = False
for line in lines:
if 'containers:' in line:
in_containers = True
# If we are in containers block, look for - name: and sanitize it
if in_containers:
# Use a specific match for - name: to avoid mangling other things
if re.match(r'^\s+-\s+name:\s+', line):
line = re.sub(r'(\s+-\s+name:\s+)([a-zA-Z0-9.-]+)', repl, line)
# If we hit another top level key (no indent), we might be out of spec/containers
elif line and not line.startswith(' '):
in_containers = False
final_lines.append(line)
new_content = "\n".join(final_lines) + "\n"
if content != new_content:
path.write_text(new_content)
for path in k8s_dir.glob("*.yaml"):
patch_file(path)
sanitize_container_names(path)
PY
}
apply_k8s_resources() {
log "Applying Supabase resources to namespace 'supabase' ..."
kubectl apply -n "supabase" -f "$SUPABASE_K8S_DIR"
# Create an alias service 'supabase-db' in the CURRENT namespace that points to Supabase Postgres in 'supabase' namespace
# This allows prole-db (in current namespace) to connect to Supabase
cat <<EOF | kubectl apply -n "$NAMESPACE" -f -
apiVersion: v1
kind: Service
metadata:
name: supabase-db
labels:
app: supabase-db-federated
spec:
type: ExternalName
externalName: db.supabase.svc.cluster.local
EOF
# Also create an alias 'db' in 'supabase' namespace pointing to prole-db in CURRENT namespace
# This allows Supabase components to use prole-db as their primary DB
cat <<EOF | kubectl apply -n "supabase" -f -
apiVersion: v1
kind: Service
metadata:
name: db
labels:
app: prole-db-alias
spec:
type: ExternalName
externalName: prole-db-rw.${NAMESPACE}.svc.cluster.local
EOF
}
apply_db_migrations() {
if [[ "$SUPABASE_APPLY_DB_MIGRATIONS" != "1" ]]; then
log "SUPABASE_APPLY_DB_MIGRATIONS=0; skipping Supabase DB migrations."
return
fi
local supa_home="$1"
local sql_dir="$supa_home/docker/volumes/db"
if [[ ! -d "$sql_dir" ]]; then
err "WARN: Supabase SQL directory not found: $sql_dir"
return
fi
local primary
primary=$(kubectl -n "$NAMESPACE" get cluster "$CNPG_CLUSTER_NAME" -o jsonpath='{.status.currentPrimary}' 2>/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