feat(tenant): single-tenant mode core (config SSOT + server/UI guards + branding)

Add lib/tenant.ts as the single source of truth for multi- vs single-tenant
deployment. Gate space creation (server + signup UI), add open-join to the one
space, hide the marketing shell, and route brand name + base URL through config.
Also commit the _migrations_applied table into the canonical schema so a fresh
instance is reproducible from schema.sql alone.

- lib/tenant.ts: resolveTenantConfig (pure) + tenantConfig() + appBaseUrl()
- lib/auth-actions.ts: createSpace refused when !allowSpaceCreation; joinSpace
  open-join resolves the configured/sole space with no invite code
- proxy.ts: marketing paths redirect to /login when showMarketing is off
- app/signup/page.tsx: single-tenant hides create/join chooser, brand=siteName
- app/layout.tsx + 4 link builders: brand/base-URL via config (no hackerspace.sh literal)
- scripts/schema.sql: public._migrations_applied tracking table
- .env.example: documented single-tenant + first-admin bootstrap vars
- package.json: pnpm setup
This commit is contained in:
Moheeb Zara
2026-08-05 12:43:11 -07:00
parent 85f3655e6f
commit 646d2e542c
12 changed files with 301 additions and 29 deletions
+50 -1
View File
@@ -14,9 +14,58 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOi...replace-with-anon-key
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOi...replace-with-service-role-key
# App URL (required)
# Public-facing base URL of this deployment. Used in OAuth redirects.
# Public-facing base URL of this deployment. Used in OAuth redirects and in every
# absolute link the app emits (dues emails, form-result links, the /me portal).
# Set this to your own domain: it replaces the old hackerspace.sh fallback, so a
# single-tenant instance never leaks the platform domain. No trailing slash.
NEXT_PUBLIC_APP_URL=http://localhost:3000
# -----------------------------------------------------------------------------
# Single-tenant mode (optional). See docs/SINGLE_TENANT.md for the full guide.
# -----------------------------------------------------------------------------
# Leave everything below unset to run the default MULTI-TENANT platform (one
# deployment hosts many spaces, users create/join spaces at signup — the
# hackerspace.sh model). Set NEXT_PUBLIC_SINGLE_TENANT=true to run a dedicated
# instance for exactly ONE space:
# - the "create a space" flow is disabled (server + UI),
# - signup becomes "join THE space",
# - the hackerspace.sh marketing/landing shell is hidden.
# These are NEXT_PUBLIC_ because the signup page (a client component) reads them;
# the server re-enforces every rule, so they are safe to expose.
NEXT_PUBLIC_SINGLE_TENANT=
# Brand name shown throughout the UI (logo wordmark, page titles). White-label
# your instance here. Defaults to "hackerspace.sh" when unset.
NEXT_PUBLIC_SITE_NAME=
# The slug of your one space (matches spaces.slug, created by `pnpm setup`).
# In single-tenant mode new signups join this space. If unset, the app falls
# back to "the only space in the database".
NEXT_PUBLIC_SINGLE_TENANT_SPACE_SLUG=
# Set to "true" to let people sign up and join your space WITHOUT an invite code
# (still subject to the space's require_approval setting — approve them from the
# members page). Leave unset to require an invite code even in single-tenant mode.
NEXT_PUBLIC_SINGLE_TENANT_OPEN_JOIN=
# Override the marketing shell visibility. Defaults to ON for the platform and
# OFF in single-tenant mode. Set "true" to keep the /resources subsite + landing
# page, or "false" to hide them on a multi-tenant deploy.
NEXT_PUBLIC_SHOW_MARKETING=
# -----------------------------------------------------------------------------
# First-admin bootstrap (used ONLY by `pnpm setup`, never read at runtime).
# `pnpm setup` provisions your one space and creates the first admin login from
# these values (or from interactive prompts / CLI flags). Fill them in, run the
# CLI once, then you can clear the password. See docs/SINGLE_TENANT.md.
# -----------------------------------------------------------------------------
SETUP_SPACE_NAME=
SETUP_SPACE_SLUG=
SETUP_SPACE_CITY=
SETUP_ADMIN_EMAIL=
SETUP_ADMIN_PASSWORD=
SETUP_ADMIN_NAME=
# Secrets vault encryption key (required for the /ops Secrets feature).
# 32 bytes of cryptographically random data, hex-encoded (64 hex chars).
# Generate with: openssl rand -hex 32