#!/usr/bin/env bash # # generate-env.sh: copy .env.example to .env and fill in every secret. # # Fills POSTGRES_PASSWORD, JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, # SECRET_KEY_BASE, SECRETS_ENCRYPTION_KEY and CRON_SECRET. ANON_KEY and # SERVICE_ROLE_KEY are HS256 JWTs signed with the generated JWT_SECRET. # # Usage: scripts/generate-env.sh [--output FILE] [--yes] # --output FILE write here instead of .env # --yes, --force skip the confirmation prompt (required when stdin is not a # terminal) # # Always asks before writing; the default answer is N. # # The default target is .env (git-ignored), not .env.example, because # .env.example is committed and must never hold real secrets. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" TEMPLATE="$ROOT/.env.example" OUT="$ROOT/.env" FORCE=0 while [[ $# -gt 0 ]]; do case "$1" in --output) OUT="$2"; shift 2 ;; --yes|--force) FORCE=1; shift ;; -h|--help) sed -n '2,18p' "${BASH_SOURCE[0]}"; exit 0 ;; *) echo "error: unknown argument: $1" >&2; exit 1 ;; esac done command -v openssl >/dev/null || { echo "error: openssl is required" >&2; exit 1; } [[ -f "$TEMPLATE" ]] || { echo "error: $TEMPLATE not found" >&2; exit 1; } if [[ $FORCE -ne 1 ]]; then if [[ ! -t 0 ]]; then echo "error: not a terminal; pass --yes to run non-interactively" >&2 exit 1 fi if [[ -e "$OUT" ]]; then echo "$OUT already exists. Continuing replaces it and rotates every secret." else echo "This will create $OUT with newly generated secrets." fi read -r -p "Continue? [y/N] " answer case "$answer" in y|Y|yes|YES) ;; *) echo "aborted"; exit 1 ;; esac fi b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; } # sign_jwt ROLE SECRET -> HS256 JWT valid for 10 years sign_jwt() { local role="$1" secret="$2" iat header payload sig iat="$(date +%s)" header="$(printf '{"alg":"HS256","typ":"JWT"}' | b64url)" payload="$(printf '{"role":"%s","iss":"supabase","iat":%d,"exp":%d}' \ "$role" "$iat" "$((iat + 315360000))" | b64url)" sig="$(printf '%s.%s' "$header" "$payload" \ | openssl dgst -sha256 -hmac "$secret" -binary | b64url)" printf '%s.%s.%s' "$header" "$payload" "$sig" } POSTGRES_PASSWORD="$(openssl rand -hex 24)" JWT_SECRET="$(openssl rand -hex 32)" ANON_KEY="$(sign_jwt anon "$JWT_SECRET")" SERVICE_ROLE_KEY="$(sign_jwt service_role "$JWT_SECRET")" SECRET_KEY_BASE="$(openssl rand -hex 32)" SECRETS_ENCRYPTION_KEY="$(openssl rand -hex 32)" CRON_SECRET="$(openssl rand -hex 32)" umask 077 cp "$TEMPLATE" "$OUT" for name in POSTGRES_PASSWORD JWT_SECRET ANON_KEY SERVICE_ROLE_KEY \ SECRET_KEY_BASE SECRETS_ENCRYPTION_KEY CRON_SECRET; do # Values are hex or base64url, so they contain no sed-special characters. sed -i "s|^${name}=.*|${name}=${!name}|" "$OUT" done chmod 600 "$OUT" echo "wrote $OUT"