# Production env for ambrosio — copy to .env on the server and fill in secrets. # Generate JWT triplet: bash infra/scripts/rotate-jwt.sh # ── App + Supabase API (single domain, routed by Traefik on ambrosio) ────── PUBLIC_APP_URL=https://erosi.limonia.net PUBLIC_SUPABASE_URL=https://erosi.limonia.net # ── External Keycloak ────────────────────────────────────────────────────── # The client must be confidential, with `openid` as a default client scope # (GoTrue v2.158.1 doesn't send `openid` in the OAuth scope param — Keycloak # must inject it), redirectUris including https://erosi.limonia.net/* and # https://erosi.limonia.net/auth/v1/callback, and webOrigins https://erosi.limonia.net. # # NOTE: if your Keycloak uses the legacy `/auth/` base path (Keycloak ≤ 16 and # some later distributions), PUBLIC_KEYCLOAK_URL must include it — e.g. # https://auth.example.com/auth. Verify before deploying: a GET on # ${PUBLIC_KEYCLOAK_URL}/realms/${PUBLIC_KEYCLOAK_REALM}/.well-known/openid-configuration # must return 200 JSON. 404 = wrong base path. PUBLIC_KEYCLOAK_URL=https://your-keycloak.example.com PUBLIC_KEYCLOAK_REALM=your-realm PUBLIC_KEYCLOAK_CLIENT_ID=your-client-id KEYCLOAK_CLIENT_SECRET=CHANGEME-paste-from-external-keycloak # ── Reverse proxy ────────────────────────────────────────────────────────── # TLS + outermost routing live outside this repo. The stack exposes a single # host port (3000) where an internal Caddy fans out kong (Supabase API) vs # app (SvelteKit). Configure whatever external proxy you use to terminate # HTTPS for PUBLIC_APP_URL and forward plain HTTP to ambrosio:3000. # ── Postgres (single password for all internal roles) ─────────────────────── POSTGRES_PASSWORD=CHANGEME-strong-random-password # ── Supabase JWT triplet (regenerate every 90 days) ───────────────────────── # Produce all three with: bash infra/scripts/rotate-jwt.sh SUPABASE_JWT_SECRET=CHANGEME-openssl-rand-base64-48 PUBLIC_SUPABASE_ANON_KEY=CHANGEME-sign-with-above SUPABASE_SERVICE_ROLE_KEY=CHANGEME-sign-with-above # ── Realtime encryption ───────────────────────────────────────────────────── # DB_ENC_KEY: exactly 16 characters. SECRET_KEY_BASE: ≥ 64 characters. REALTIME_ENC_KEY=CHANGEME16charkey REALTIME_SECRET_KEY_BASE=CHANGEME-openssl-rand-base64-64-at-minimum-so-phoenix-is-happy # ── Server admin bootstrap (Fase 13) ──────────────────────────────────────── # Email of the user who should be promoted to `server_admin` on first volume # init. The bootstrap script (infra/db-init/10-server-admin-seed.sh) reads # this and, if `public.server_admins` is empty AND a matching public.users row # exists, inserts that user as the first admin. Idempotent — re-running with a # different email after the first admin is created does NOTHING. # # IMPORTANT: this fires from `docker-entrypoint-initdb.d`, which only runs on # a FRESH volume. On prod first deploy: # 1. Spin up the stack (this will likely log "no user matches" — the user # hasn't completed their first Keycloak login yet). # 2. Have the operator sign in once via Keycloak so public.users picks them up. # 3. Manually re-run: `docker compose exec -T db bash /docker-entrypoint-initdb.d/10-server-admin-seed.sh