Enables the auth providers and transactional email flows the self-hosted Supabase was missing compared to the cloud instance: - GoTrue now accepts Google and Apple OAuth (web flow); Apple client-secret JWT is signed fresh on every activation from the SOPS-stored .p8 so there's no 6-month rotation ritual. - SMTP points at mail.cloonar.com:587 with SASL auth via a new `supabase` LDAP account; a `noreply@fueltide.io` mailAlias lets that account send as the fueltide.io address. - rspamd on mail.cloonar.com gets a per-domain DKIM key for fueltide.io (selector `default`) so outbound mail is signed. - MAILER_AUTOCONFIRM is off so signup confirmation + password reset actually go through email. - SITE_URL + URI_ALLOW_LIST point at app.fueltide.io / stage so links in emails and OAuth redirects land in the right app. FUELTIDE_AUTH_SETUP.md documents the manual steps (LDAP entries, SOPS additions, DNS records, Google/Apple console setup) that must be completed before merging.
132 lines
4.5 KiB
Bash
132 lines
4.5 KiB
Bash
set -euo pipefail
|
|
umask 077
|
|
mkdir -p /run/supabase
|
|
|
|
set -a
|
|
source "$1"
|
|
set +a
|
|
|
|
# URL-encode password for use in connection strings
|
|
PG_PASS_ENCODED=$(printf '%s' "$POSTGRES_PASSWORD" | jq -sRr @uri)
|
|
|
|
cat > /run/supabase/db.env <<EOF
|
|
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
|
PGPASSWORD=$POSTGRES_PASSWORD
|
|
JWT_SECRET=$JWT_SECRET
|
|
EOF
|
|
|
|
cat > /run/supabase/analytics.env <<EOF
|
|
DB_PASSWORD=$POSTGRES_PASSWORD
|
|
LOGFLARE_PUBLIC_ACCESS_TOKEN=$LOGFLARE_PUBLIC_ACCESS_TOKEN
|
|
LOGFLARE_PRIVATE_ACCESS_TOKEN=$LOGFLARE_PRIVATE_ACCESS_TOKEN
|
|
POSTGRES_BACKEND_URL=postgresql://supabase_admin:$PG_PASS_ENCODED@db:5432/_supabase
|
|
EOF
|
|
|
|
# Apple client-secret is a short-lived JWT signed with the .p8 key downloaded
|
|
# from Apple Developer. Re-sign on every activation (lifetime 180 days, Apple's
|
|
# cap) so there is no manual rotation ritual. The SOPS-sourced APPLE_PRIVATE_KEY
|
|
# is stored as a single line with literal \n separators; python un-escapes it.
|
|
APPLE_SECRET=""
|
|
if [ -n "${APPLE_TEAM_ID:-}" ] && [ -n "${APPLE_KEY_ID:-}" ] \
|
|
&& [ -n "${APPLE_SERVICES_ID:-}" ] && [ -n "${APPLE_PRIVATE_KEY:-}" ]; then
|
|
APPLE_SECRET=$(
|
|
APPLE_TEAM_ID="$APPLE_TEAM_ID" \
|
|
APPLE_KEY_ID="$APPLE_KEY_ID" \
|
|
APPLE_SERVICES_ID="$APPLE_SERVICES_ID" \
|
|
APPLE_PRIVATE_KEY="$APPLE_PRIVATE_KEY" \
|
|
python3 - <<'PY'
|
|
import base64, json, os, time
|
|
from cryptography.hazmat.primitives import serialization, hashes
|
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
|
|
def b64u(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
|
|
now = int(time.time())
|
|
header = {"alg": "ES256", "kid": os.environ["APPLE_KEY_ID"], "typ": "JWT"}
|
|
payload = {"iss": os.environ["APPLE_TEAM_ID"], "iat": now, "exp": now + 86400 * 180,
|
|
"aud": "https://appleid.apple.com", "sub": os.environ["APPLE_SERVICES_ID"]}
|
|
parts = (b64u(json.dumps(header, separators=(",", ":")).encode())
|
|
+ "." + b64u(json.dumps(payload, separators=(",", ":")).encode())).encode()
|
|
pem = os.environ["APPLE_PRIVATE_KEY"].encode().decode("unicode_escape").encode()
|
|
key = serialization.load_pem_private_key(pem, password=None)
|
|
der = key.sign(parts, ec.ECDSA(hashes.SHA256()))
|
|
r, s = decode_dss_signature(der)
|
|
raw = r.to_bytes(32, "big") + s.to_bytes(32, "big")
|
|
print(parts.decode() + "." + b64u(raw))
|
|
PY
|
|
)
|
|
fi
|
|
|
|
cat > /run/supabase/auth.env <<EOF
|
|
GOTRUE_JWT_SECRET=$JWT_SECRET
|
|
GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:$PG_PASS_ENCODED@db:5432/postgres
|
|
GOTRUE_SMTP_USER=${SMTP_USER:-}
|
|
GOTRUE_SMTP_PASS=${SMTP_PASS:-}
|
|
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
|
|
GOTRUE_EXTERNAL_GOOGLE_SECRET=${GOOGLE_SECRET:-}
|
|
GOTRUE_EXTERNAL_APPLE_CLIENT_ID=${APPLE_SERVICES_ID:-}
|
|
GOTRUE_EXTERNAL_APPLE_SECRET=$APPLE_SECRET
|
|
EOF
|
|
|
|
cat > /run/supabase/rest.env <<EOF
|
|
PGRST_JWT_SECRET=$JWT_SECRET
|
|
PGRST_APP_SETTINGS_JWT_SECRET=$JWT_SECRET
|
|
PGRST_DB_URI=postgres://authenticator:$PG_PASS_ENCODED@db:5432/postgres
|
|
EOF
|
|
|
|
cat > /run/supabase/realtime.env <<EOF
|
|
DB_PASSWORD=$POSTGRES_PASSWORD
|
|
API_JWT_SECRET=$JWT_SECRET
|
|
SECRET_KEY_BASE=$SECRET_KEY_BASE
|
|
METRICS_JWT_SECRET=$JWT_SECRET
|
|
EOF
|
|
|
|
cat > /run/supabase/storage.env <<EOF
|
|
ANON_KEY=$ANON_KEY
|
|
SERVICE_KEY=$SERVICE_ROLE_KEY
|
|
AUTH_JWT_SECRET=$JWT_SECRET
|
|
DATABASE_URL=postgres://supabase_storage_admin:$PG_PASS_ENCODED@db:5432/postgres
|
|
S3_PROTOCOL_ACCESS_KEY_ID=$S3_PROTOCOL_ACCESS_KEY_ID
|
|
S3_PROTOCOL_ACCESS_KEY_SECRET=$S3_PROTOCOL_ACCESS_KEY_SECRET
|
|
EOF
|
|
|
|
cat > /run/supabase/meta.env <<EOF
|
|
PG_META_DB_PASSWORD=$POSTGRES_PASSWORD
|
|
CRYPTO_KEY=$PG_META_CRYPTO_KEY
|
|
EOF
|
|
|
|
cat > /run/supabase/studio.env <<EOF
|
|
POSTGRES_PASSWORD=$PG_PASS_ENCODED
|
|
PG_META_CRYPTO_KEY=$PG_META_CRYPTO_KEY
|
|
SUPABASE_ANON_KEY=$ANON_KEY
|
|
SUPABASE_SERVICE_KEY=$SERVICE_ROLE_KEY
|
|
AUTH_JWT_SECRET=$JWT_SECRET
|
|
LOGFLARE_API_KEY=$LOGFLARE_PUBLIC_ACCESS_TOKEN
|
|
LOGFLARE_PUBLIC_ACCESS_TOKEN=$LOGFLARE_PUBLIC_ACCESS_TOKEN
|
|
LOGFLARE_PRIVATE_ACCESS_TOKEN=$LOGFLARE_PRIVATE_ACCESS_TOKEN
|
|
EOF
|
|
|
|
cat > /run/supabase/kong.env <<EOF
|
|
SUPABASE_ANON_KEY=$ANON_KEY
|
|
SUPABASE_SERVICE_KEY=$SERVICE_ROLE_KEY
|
|
DASHBOARD_USERNAME=supabase
|
|
DASHBOARD_PASSWORD=$DASHBOARD_PASSWORD
|
|
EOF
|
|
|
|
cat > /run/supabase/vector.env <<EOF
|
|
LOGFLARE_PUBLIC_ACCESS_TOKEN=$LOGFLARE_PUBLIC_ACCESS_TOKEN
|
|
EOF
|
|
|
|
cat > /run/supabase/pooler.env <<EOF
|
|
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
|
DATABASE_URL=ecto://supabase_admin:$PG_PASS_ENCODED@db:5432/_supabase
|
|
SECRET_KEY_BASE=$SECRET_KEY_BASE
|
|
VAULT_ENC_KEY=$VAULT_ENC_KEY
|
|
API_JWT_SECRET=$JWT_SECRET
|
|
METRICS_JWT_SECRET=$JWT_SECRET
|
|
EOF
|
|
|
|
cat > /run/supabase/functions.env <<EOF
|
|
JWT_SECRET=$JWT_SECRET
|
|
SUPABASE_ANON_KEY=$ANON_KEY
|
|
SUPABASE_SERVICE_ROLE_KEY=$SERVICE_ROLE_KEY
|
|
EOF
|