Skip to content

feat: supabase-only self-hosting with guided setup wizard - #108

Merged
iamjr15 merged 1 commit into
mainfrom
oss/phase-b-selfhost
Jul 31, 2026
Merged

feat: supabase-only self-hosting with guided setup wizard#108
iamjr15 merged 1 commit into
mainfrom
oss/phase-b-selfhost

Conversation

@iamjr15

@iamjr15 iamjr15 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase B of the OSS-ready program: self-hosting becomes real. Local development targets any Supabase project (free tier suffices) instead of hard-pinning the owner's production database, and pnpm dev:setup walks a contributor from a fresh clone to a verified running stack.

  • Un-pinned validator: scripts/dev-worker-config.ts accepts any Supabase session pooler (host pattern, port 5432, exact TLS params, <role>.<projectRef> usernames, host/port/db/ref equality across the three role URLs). The previous production coordinates pass unchanged.
  • Shared env contract (scripts/local-env-contract.ts) extracted from dev.ts behavior-neutrally; wizard key metadata is compile-time exhaustive.
  • Migration 0003_target_parity: installs vector + pg_stat_statements, revokes all Data-API-role and implicit-PUBLIC access (current + default ACLs) so the post-apply target assertion passes on fresh projects exactly as on production. Contract gains schema-privilege and effective-default-ACL checks scoped to postgres-owned ACLs.
  • pnpm dev:setup: preflight → guided prompts (Supabase/Clerk/Daytona/optional groups) with generated secrets → confirm → atomic 0600 env writes (bare values; #, $, quotes, edge whitespace, control chars rejected as unrepresentable across dotenv parsers) → migrations via sanitized child env → role passwords + Vault rows on the admin connection → pooler-login and signed-context probes (production signer reused) → optional force-recreated stack start. --check is a zero-mutation doctor.
  • README rewritten as a public front page (screenshot, architecture, quickstart, self-host, license); CONTRIBUTING added; env examples neutralized.

⚠️ One-time operator step at apply time (documented, deliberate)

The 0000 baseline was revised to delete twelve supabase_admin default-ACL recreations that are denied with SQLSTATE 42501 on every Supabase project (verified live on production AND on a fresh 2026 project) and that recreate platform state which already exists everywhere. Production baselined 0000 without executing it, so its ledger row must be updated once:

update drizzle.__drizzle_migrations set hash = '22b56335f7dc10125fb7bdebd46b053e4cceca0a732ddd9c410282d8430a5331' where created_at = 1784981026716;

followed immediately by pnpm db:migrate -- --dry-run proving no divergence and 0001/0002/0003 pending. The remedy for any stray environment is documented in packages/db/README.md. Alternatives (engine-side superseded-checksum compat map; reverting the baseline) were evaluated and declined — the revert resurrects the fresh-project blocker, and compat machinery violates the repo's no-legacy rule.

Verification notes

  • pnpm lint / pnpm typecheck / pnpm turbo build --force / pnpm deadcode / pnpm architecture:check / pnpm turbo skills:build — independent clean-install reruns, all green
  • Adversarial review: four rounds with live SELECT-only probes against production and a fresh throwaway project; 6 + 2 + 3 + 1 findings all fixed and re-verified; final verdict SAFE-TO-SHIP. Highlights: 42501 permission walls proven empirically on both project generations; env round-trip proven through the wizard's reader, wrangler's bundled dotenv+expand, and compose semantics.
  • Post-merge plan: prod ledger update + --dry-run review + --apply (0001/0002/0003), then a full pnpm dev:setup e2e against a throwaway free-tier project (already created) with an authenticated browser flow, re-run preservation test, and --check.

Phase B of the OSS-ready program. Local development now targets any Supabase
project, not the owner's:

- dev-worker-config validates a project-agnostic session-pooler shape (any
  project ref; host/port/database/ref equality across the three role URLs)
  instead of hard-pinning one production project
- scripts/local-env-contract.ts extracts the env contract shared by dev and
  setup; scripts/setup-keys.ts makes missing key metadata a compile error
- migration 0003_target_parity installs the vector and pg_stat_statements
  extensions and revokes all Data-API-role and implicit-PUBLIC access,
  current and default, so the post-apply target assertion passes on fresh
  projects exactly as on production; the supabase-target contract gains
  schema-privilege and effective-default-ACL checks scoped to postgres-owned
  ACLs (supabase_admin default ACLs are platform-managed)
- pnpm dev:setup: guided clack wizard — preflight, prompts with validation
  and generated secrets, atomic 0600 env writes (bare values; control chars,
  edge whitespace, leading quotes, '#', and '$' rejected as unrepresentable
  across dotenv parsers), confirm-before-write, migrations via a sanitized
  child env, role-password and Vault provisioning on the admin connection,
  pooler-login and signed-context probes reusing the production signer, and
  an optional force-recreated stack start; --check doctor mode is read-only
- 0000 baseline revised: the unrunnable supabase_admin default-ACL
  recreations (denied 42501 on every Supabase project; platform state that
  already exists everywhere) are removed with an explanatory comment; the
  one-time production ledger hash update and the remedy for any stray
  environment are documented in packages/db/README.md
- README rewritten as a public front page; CONTRIBUTING added; env examples
  neutralized (no production identifiers)
@iamjr15
iamjr15 force-pushed the oss/phase-b-selfhost branch from b62dece to ee04c4b Compare July 31, 2026 18:19
@iamjr15
iamjr15 merged commit 8180a42 into main Jul 31, 2026
4 checks passed
@iamjr15
iamjr15 deleted the oss/phase-b-selfhost branch July 31, 2026 18:22
iamjr15 added a commit that referenced this pull request Jul 31, 2026
Two checker corrections found by the first post-Wave-B `--apply` against
production (the assertion had not run since the contract was tightened).
Both live-probe-verified on production AND a fresh 2026 project:

1. **63-byte truncation**: the runtime-ACL union's first branch selected
`relname` (type `name`), so the union resolved `object_name` to `name`
and truncated every function signature past 63 bytes — the six
long-signature `webhooks_*` functions could never match the expected
set. One-line `::text` cast.
2. **supautils auto-membership**: every role `postgres` creates is
granted back to `postgres` (grantor `supabase_admin`) on every Supabase
project — verified by creating a probe role on a fresh project. The
zero-membership check now exempts exactly `app_* → postgres` and flags
all else.

## Verification notes

- [x] `pnpm lint` / `pnpm typecheck`
- [x] Live `pnpm db:migrate -- --dry-run` against production with the
fixed checker: **"Production schema contract verified."** with 0000
baselined and 0001/0002/0003 applied.

(DB-state note recorded in the PR trail: the one-time 0000 ledger-hash
update documented in #108 was executed before the apply; two stale
`max`-tier entitlement rows were normalized to `premium` to satisfy
0001's CHECK constraint.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant