mirror of
https://github.com/dredx/prole.git
synced 2026-09-24 11:34:31 +00:00
- 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.
411 lines
13 KiB
Bash
Executable File
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
|