Tier 2 productization for single-tenant instances, plus fixes from an adversarial review pass. New: - scripts/setup.mjs: 'pnpm setup' CLI (doctor/provision/create-admin/all), idempotent, creates the first admin via the Supabase Admin API (email_confirm), flag > env > .env.local precedence, masked interactive prompts, --yes/--force. - scripts/apply-migrations.sh + deploy/deploy.sh: reproducible, idempotent CD glue (previously only on the prod droplet), tracked via public._migrations_applied. - docker-compose.full.yml + .env.full.example: one-command app + self-hosted Supabase bundle (compose config validated). - docs/SINGLE_TENANT.md (deploy + CLI guide) and docs/CUSTOMIZE.md (the documented lib/tenant.ts config pattern for white-labeling); README pointer. - __tests__/tenant.test.ts: 19 unit tests for resolveTenantConfig + isMarketingPath. Security (found by review, pre-existing): - Drop the stale handle_space_signup() auth trigger, which derived member role and space from client-controlled raw_user_meta_data and could self-insert an admin. Membership is created only by the vetted server actions. Migration scripts/056_drop_stale_signup_trigger.sql + folded into schema.sql. Fixes: - setup.mjs masked-password prompt hung (callback passed to readline/promises question); use the awaited form. - notifications-enqueue.test: base-URL default is now localhost (platform-domain fallback removed), not hackerspace.sh. - docs accuracy (logo/BrandMark, apply-migrations does not restart PostgREST, --yes). Gates: tsc 0, lint 0 errors, 664 unit tests, build ok.
138 lines
7.2 KiB
Bash
138 lines
7.2 KiB
Bash
# =============================================================================
|
|
# .env.full.example - config for docker-compose.full.yml (the convenience
|
|
# bundle: app + self-hosted Supabase in one command).
|
|
#
|
|
# cp .env.full.example .env.full
|
|
# # fill in the secrets below, then:
|
|
# docker compose -f docker-compose.full.yml --env-file .env.full up -d
|
|
#
|
|
# Every variable docker-compose.full.yml references is documented here. This is
|
|
# the fast path. For the hardened, internet-facing deployment (Nginx + TLS,
|
|
# backups, Studio behind auth) follow docs/DEPLOY_DO_SELFHOSTED.md; that guide
|
|
# is the source of truth for real deployments.
|
|
#
|
|
# The four critical secrets are POSTGRES_PASSWORD, JWT_SECRET, ANON_KEY, and
|
|
# SERVICE_ROLE_KEY. ANON_KEY and SERVICE_ROLE_KEY are JWTs SIGNED WITH the same
|
|
# JWT_SECRET you set below: if you change JWT_SECRET you MUST regenerate both.
|
|
# Never commit .env.full. Lock it down: chmod 600 .env.full
|
|
# =============================================================================
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Postgres
|
|
# -----------------------------------------------------------------------------
|
|
# Superuser password for Postgres. Also used by auth/rest/realtime/meta to
|
|
# connect. Generate: openssl rand -base64 24
|
|
POSTGRES_PASSWORD=replace-with-openssl-rand-base64-24
|
|
# Database name. Keep "postgres" unless you have a reason not to.
|
|
POSTGRES_DB=postgres
|
|
# Host port the Postgres admin port is published on (bound to 127.0.0.1 only,
|
|
# for psql / pg_dump from the host). Container always listens on 5432.
|
|
POSTGRES_PORT=5432
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# JWT / API keys (the god-mode secrets)
|
|
# -----------------------------------------------------------------------------
|
|
# Symmetric secret GoTrue/PostgREST/Realtime use to sign and verify JWTs.
|
|
# 32+ random bytes. Generate: openssl rand -hex 32
|
|
JWT_SECRET=replace-with-openssl-rand-hex-32
|
|
# Access-token lifetime in seconds (1 hour). Also used as the JWT expiry hint.
|
|
JWT_EXPIRY=3600
|
|
|
|
# Public "anon" API key. A JWT with role=anon signed by JWT_SECRET above.
|
|
# This is the NEXT_PUBLIC_SUPABASE_ANON_KEY the app/browser use. Generate with
|
|
# JWT_SECRET exported, on your local machine:
|
|
# export JWT_SECRET='the-value-above'
|
|
# npx -y -p jsonwebtoken@9 node -e "const jwt=require('jsonwebtoken');const iat=Math.floor(Date.now()/1000);console.log(jwt.sign({role:'anon',iss:'supabase',iat,exp:iat+60*60*24*365*10},process.env.JWT_SECRET))"
|
|
ANON_KEY=replace-with-anon-jwt-signed-by-JWT_SECRET
|
|
|
|
# Service-role API key. A JWT with role=service_role signed by JWT_SECRET.
|
|
# Bypasses RLS (god mode) - this is the app's SUPABASE_SERVICE_ROLE_KEY. Same
|
|
# command as above with role:'service_role'. Treat like root credentials.
|
|
# npx -y -p jsonwebtoken@9 node -e "const jwt=require('jsonwebtoken');const iat=Math.floor(Date.now()/1000);console.log(jwt.sign({role:'service_role',iss:'supabase',iat,exp:iat+60*60*24*365*10},process.env.JWT_SECRET))"
|
|
SERVICE_ROLE_KEY=replace-with-service-role-jwt-signed-by-JWT_SECRET
|
|
|
|
# Realtime needs its own long random base for signing/cookies. Independent of
|
|
# JWT_SECRET. Generate: openssl rand -hex 32
|
|
SECRET_KEY_BASE=replace-with-openssl-rand-hex-32
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Public URLs
|
|
# -----------------------------------------------------------------------------
|
|
# Where GoTrue sends users back after auth flows, and the app's own public URL.
|
|
# For a local bundle these are localhost; for a server put your real domains
|
|
# here (and terminate TLS in a reverse proxy in front of Kong / the app).
|
|
SITE_URL=http://localhost:3000
|
|
# Comma-separated extra redirect URLs GoTrue will accept.
|
|
ADDITIONAL_REDIRECT_URLS=http://localhost:3000
|
|
# The externally reachable base URL of the Supabase API gateway (Kong). GoTrue
|
|
# embeds this in email links. Local bundle: http://localhost:8000
|
|
API_EXTERNAL_URL=http://localhost:8000
|
|
# Same gateway URL, shown by Studio. Usually identical to API_EXTERNAL_URL.
|
|
SUPABASE_PUBLIC_URL=http://localhost:8000
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Kong (API gateway) host ports (bound to 127.0.0.1 by the compose file)
|
|
# -----------------------------------------------------------------------------
|
|
KONG_HTTP_PORT=8000
|
|
KONG_HTTPS_PORT=8443
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Studio (admin dashboard) (bound to 127.0.0.1 by the compose file)
|
|
# -----------------------------------------------------------------------------
|
|
# Host port Studio is published on. Keep it off 3000 (the app owns that).
|
|
STUDIO_PORT=3001
|
|
STUDIO_DEFAULT_ORGANIZATION=hackerspace
|
|
STUDIO_DEFAULT_PROJECT=hackerspace
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# PostgREST
|
|
# -----------------------------------------------------------------------------
|
|
# Schemas PostgREST exposes over /rest/v1. The app only needs public; the rest
|
|
# match the Supabase defaults.
|
|
PGRST_DB_SCHEMAS=public,storage,graphql_public
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Auth behavior
|
|
# -----------------------------------------------------------------------------
|
|
# Allow email/password signup (the app's join flow needs this).
|
|
ENABLE_EMAIL_SIGNUP=true
|
|
# Auto-confirm new emails WITHOUT sending a verification mail. Handy for local
|
|
# dev when you have no SMTP configured; set false in production.
|
|
ENABLE_EMAIL_AUTOCONFIRM=true
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# SMTP for auth emails (invites, confirmations, password resets).
|
|
# Optional for local dev if ENABLE_EMAIL_AUTOCONFIRM=true. Required in prod.
|
|
# Use any provider (Resend, SendGrid, SES, Postmark, Mailgun).
|
|
# -----------------------------------------------------------------------------
|
|
SMTP_ADMIN_EMAIL=[email protected]
|
|
SMTP_HOST=smtp.resend.com
|
|
SMTP_PORT=587
|
|
SMTP_USER=resend
|
|
SMTP_PASS=replace-with-smtp-api-key
|
|
SMTP_SENDER_NAME=hackerspace.sh
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# The app (Next.js) - these feed both build args and runtime env.
|
|
# -----------------------------------------------------------------------------
|
|
# Browser-reachable Supabase (Kong) URL. Baked into the client bundle at build
|
|
# time, so it must be what the BROWSER can reach: http://localhost:8000 locally,
|
|
# or https://supabase.yourdomain.com behind a proxy. Keep it equal to
|
|
# API_EXTERNAL_URL. (The app's anon key = ANON_KEY above; service key =
|
|
# SERVICE_ROLE_KEY above - the compose wires those through automatically.)
|
|
NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000
|
|
# Public base URL of the app itself (OAuth redirects, emailed links). No
|
|
# trailing slash.
|
|
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Single-tenant mode (optional; see docs/SINGLE_TENANT.md).
|
|
# Leave blank for the default multi-tenant platform. Set NEXT_PUBLIC_SINGLE_TENANT=true
|
|
# to dedicate this instance to exactly one space.
|
|
# -----------------------------------------------------------------------------
|
|
NEXT_PUBLIC_SINGLE_TENANT=
|
|
NEXT_PUBLIC_SITE_NAME=
|
|
NEXT_PUBLIC_SINGLE_TENANT_SPACE_SLUG=
|
|
NEXT_PUBLIC_SINGLE_TENANT_OPEN_JOIN=
|
|
NEXT_PUBLIC_SHOW_MARKETING=
|