|
@@ -0,0 +1,127 @@
|
|
|
|
|
+#!/bin/sh
|
|
|
|
|
+# seed-realtime-tenant.sh
|
|
|
|
|
+#
|
|
|
|
|
+# One-shot initializer for the self-hosted Supabase Realtime tenant.
|
|
|
|
|
+#
|
|
|
|
|
+# Realtime v2.x is multi-tenant. Each "project" needs a row in
|
|
|
|
|
+# `_realtime.tenants` (with a paired `_realtime.extensions` row for
|
|
|
|
|
+# postgres_cdc_rls). When the realtime container is reached via Kong, the
|
|
|
|
|
+# upstream Host header becomes `supabase-realtime:4000`, which Realtime maps
|
|
|
|
|
+# to external_id=`supabase-realtime`. Without a matching tenant row, every WS
|
|
|
|
|
+# connect fails with `TenantNotFound` and no postgres_changes propagate.
|
|
|
|
|
+#
|
|
|
|
|
+# Despite SEED_SELF_HOST=true, the auto-seed inserts external_id=`realtime-dev`
|
|
|
|
|
+# (wrong slug for our setup) and never creates `supabase-realtime`. This
|
|
|
|
|
+# script is the deterministic fix.
|
|
|
|
|
+#
|
|
|
|
|
+# Steps:
|
|
|
|
|
+# 1. Ensure `_realtime` schema exists (Realtime's Ecto migrations target it
|
|
|
|
|
+# and bail with `no schema has been selected` if absent).
|
|
|
|
|
+# 2. Wait for the realtime HTTP API to come up.
|
|
|
|
|
+# 3. If a tenant with external_id=`supabase-realtime` already exists, exit 0.
|
|
|
|
|
+# 4. Otherwise mint a short-lived HS256 JWT signed with $JWT_SECRET (which
|
|
|
|
|
+# Realtime accepts as API_JWT_SECRET) and POST /api/tenants.
|
|
|
|
|
+#
|
|
|
|
|
+# Idempotent: safe to run on every `docker compose up`. Runs to completion
|
|
|
|
|
+# and exits; the container is `restart: "no"`.
|
|
|
|
|
+#
|
|
|
|
|
+# Required env: JWT_SECRET, POSTGRES_PASSWORD, (optionals with defaults)
|
|
|
|
|
+# POSTGRES_USER, POSTGRES_DB, DB_HOST, REALTIME_HOST, REALTIME_PORT.
|
|
|
|
|
+
|
|
|
|
|
+set -eu
|
|
|
|
|
+
|
|
|
|
|
+POSTGRES_USER="${POSTGRES_USER:-supabase_admin}"
|
|
|
|
|
+POSTGRES_DB="${POSTGRES_DB:-supabase}"
|
|
|
|
|
+DB_HOST="${DB_HOST:-supabase-db}"
|
|
|
|
|
+DB_PORT="${DB_PORT:-5432}"
|
|
|
|
|
+REALTIME_HOST="${REALTIME_HOST:-supabase-realtime}"
|
|
|
|
|
+REALTIME_PORT="${REALTIME_PORT:-4000}"
|
|
|
|
|
+TENANT_EXTERNAL_ID="${TENANT_EXTERNAL_ID:-supabase-realtime}"
|
|
|
|
|
+
|
|
|
|
|
+: "${JWT_SECRET:?JWT_SECRET must be set}"
|
|
|
|
|
+: "${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}"
|
|
|
|
|
+
|
|
|
|
|
+log() { printf '[seed-realtime] %s\n' "$*"; }
|
|
|
|
|
+
|
|
|
|
|
+# --- 1. Ensure _realtime schema exists ----------------------------------
|
|
|
|
|
+log "ensuring _realtime schema exists on ${DB_HOST}:${DB_PORT}/${POSTGRES_DB}"
|
|
|
|
|
+PGPASSWORD="$POSTGRES_PASSWORD" psql \
|
|
|
|
|
+ -h "$DB_HOST" -p "$DB_PORT" -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
|
|
|
|
|
+ -v ON_ERROR_STOP=1 \
|
|
|
|
|
+ -c "CREATE SCHEMA IF NOT EXISTS _realtime;" \
|
|
|
|
|
+ -c "GRANT ALL ON SCHEMA _realtime TO ${POSTGRES_USER};" >/dev/null
|
|
|
|
|
+
|
|
|
|
|
+# --- 2. Wait for realtime API ------------------------------------------
|
|
|
|
|
+log "waiting for realtime API at http://${REALTIME_HOST}:${REALTIME_PORT}"
|
|
|
|
|
+i=0
|
|
|
|
|
+until curl -sS -o /dev/null -w '%{http_code}' "http://${REALTIME_HOST}:${REALTIME_PORT}/api/health" 2>/dev/null | grep -qE '^(200|401|403|404)$'; do
|
|
|
|
|
+ i=$((i + 1))
|
|
|
|
|
+ if [ "$i" -gt 60 ]; then
|
|
|
|
|
+ log "realtime API did not become reachable within 60s; aborting"
|
|
|
|
|
+ exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ sleep 1
|
|
|
|
|
+done
|
|
|
|
|
+log "realtime API reachable"
|
|
|
|
|
+
|
|
|
|
|
+# --- 3. Mint short-lived API JWT (HS256) -------------------------------
|
|
|
|
|
+# Header/payload base64url, HMAC-SHA256 signature with $JWT_SECRET.
|
|
|
|
|
+b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
|
|
|
|
|
+NOW=$(date -u +%s)
|
|
|
|
|
+EXP=$((NOW + 300))
|
|
|
|
|
+HEADER='{"alg":"HS256","typ":"JWT"}'
|
|
|
|
|
+PAYLOAD="{\"iss\":\"supabase\",\"role\":\"supabase_admin\",\"iat\":${NOW},\"exp\":${EXP}}"
|
|
|
|
|
+H_B64=$(printf '%s' "$HEADER" | b64url)
|
|
|
|
|
+P_B64=$(printf '%s' "$PAYLOAD" | b64url)
|
|
|
|
|
+SIG=$(printf '%s' "${H_B64}.${P_B64}" \
|
|
|
|
|
+ | openssl dgst -sha256 -hmac "$JWT_SECRET" -binary \
|
|
|
|
|
+ | b64url)
|
|
|
|
|
+JWT="${H_B64}.${P_B64}.${SIG}"
|
|
|
|
|
+
|
|
|
|
|
+# --- 4. Check existing tenant; create if absent ------------------------
|
|
|
|
|
+TENANT_URL="http://${REALTIME_HOST}:${REALTIME_PORT}/api/tenants/${TENANT_EXTERNAL_ID}"
|
|
|
|
|
+HTTP=$(curl -sS -o /tmp/tenant_get.json -w '%{http_code}' \
|
|
|
|
|
+ -H "Authorization: Bearer ${JWT}" \
|
|
|
|
|
+ "$TENANT_URL" || echo "000")
|
|
|
|
|
+if [ "$HTTP" = "200" ]; then
|
|
|
|
|
+ log "tenant ${TENANT_EXTERNAL_ID} already exists; nothing to do"
|
|
|
|
|
+ exit 0
|
|
|
|
|
+fi
|
|
|
|
|
+log "tenant ${TENANT_EXTERNAL_ID} not found (GET=${HTTP}); creating"
|
|
|
|
|
+
|
|
|
|
|
+BODY=$(cat <<JSON
|
|
|
|
|
+{
|
|
|
|
|
+ "tenant": {
|
|
|
|
|
+ "name": "${TENANT_EXTERNAL_ID}",
|
|
|
|
|
+ "external_id": "${TENANT_EXTERNAL_ID}",
|
|
|
|
|
+ "jwt_secret": "${JWT_SECRET}",
|
|
|
|
|
+ "extensions": [{
|
|
|
|
|
+ "type": "postgres_cdc_rls",
|
|
|
|
|
+ "settings": {
|
|
|
|
|
+ "db_host": "${DB_HOST}",
|
|
|
|
|
+ "db_name": "${POSTGRES_DB}",
|
|
|
|
|
+ "db_user": "${POSTGRES_USER}",
|
|
|
|
|
+ "db_password": "${POSTGRES_PASSWORD}",
|
|
|
|
|
+ "db_port": "${DB_PORT}",
|
|
|
|
|
+ "region": "us-east-1",
|
|
|
|
|
+ "ssl_enforced": false,
|
|
|
|
|
+ "publication": "supabase_realtime"
|
|
|
|
|
+ }
|
|
|
|
|
+ }],
|
|
|
|
|
+ "postgres_cdc_default": "postgres_cdc_rls"
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+JSON
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+HTTP=$(curl -sS -o /tmp/tenant_post.json -w '%{http_code}' \
|
|
|
|
|
+ -X POST "http://${REALTIME_HOST}:${REALTIME_PORT}/api/tenants" \
|
|
|
|
|
+ -H "Authorization: Bearer ${JWT}" \
|
|
|
|
|
+ -H "Content-Type: application/json" \
|
|
|
|
|
+ -d "$BODY")
|
|
|
|
|
+if [ "$HTTP" != "201" ] && [ "$HTTP" != "200" ]; then
|
|
|
|
|
+ log "tenant creation failed (HTTP ${HTTP}):"
|
|
|
|
|
+ cat /tmp/tenant_post.json || true
|
|
|
|
|
+ exit 1
|
|
|
|
|
+fi
|
|
|
|
|
+log "tenant ${TENANT_EXTERNAL_ID} created (HTTP ${HTTP})"
|