cbc8958b55
- aws-1 pooler (not aws-0) - pg_dump 17 required for Supabase (PG 17.6 server) - extensions schema + stubs needed for local PG 16 - auth.users stub for RLS policy references - Re-apply is idempotent (errors are 'already exists')
206 lines
7.4 KiB
Markdown
206 lines
7.4 KiB
Markdown
# Supabase Dump & Restore Guide
|
|
|
|
This guide is the runbook for capturing the real Supabase schema and data
|
|
and restoring it to a local Postgres database. It documents the exact
|
|
steps that worked when the spend cap was removed on 2026-06-05.
|
|
|
|
## Connection (this works from the dev box)
|
|
|
|
The Supabase project `wnzkhezyhnfzhkhiflrp` (route-commerce) is hosted in
|
|
**East US (North Virginia)**. The dev box has no IPv6, so we must use
|
|
the **Supavisor pooler** (IPv4 only) — and it's on **`aws-1`**, not `aws-0`.
|
|
|
|
```bash
|
|
# Working pooler URL (from supabase/.temp/pooler-url)
|
|
postgresql://postgres.wnzkhezyhnfzhkhiflrp:YLKzP9jz2yqop7jr@aws-1-us-east-1.pooler.supabase.com:5432/postgres
|
|
```
|
|
|
|
The `aws-0-us-east-1.pooler.supabase.com` hostname resolves over IPv4
|
|
but Supavisor returns "tenant/user not found" because the project lives
|
|
on `aws-1`. The correct region number is non-obvious — always check
|
|
`supabase/.temp/pooler-url` for the actual endpoint.
|
|
|
|
The direct hostname `db.wnzkhezyhnfzhkhiflrp.supabase.co` only resolves
|
|
over IPv6, which is unreachable from this dev box.
|
|
|
|
## pg_dump version
|
|
|
|
Supabase runs **PostgreSQL 17.6**. Local pg must be ≥ 17 to dump cleanly.
|
|
The dev box had pg 16 by default — install pg 17 client:
|
|
|
|
```bash
|
|
echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt/ noble-pgdg main" \
|
|
| sudo tee /etc/apt/sources.list.d/pgdg.list
|
|
sudo apt-get update
|
|
sudo apt-get install -y --allow-unauthenticated postgresql-client-17
|
|
# Use /usr/lib/postgresql/17/bin/pg_dump explicitly (or update PATH)
|
|
```
|
|
|
|
## Capture Schema
|
|
|
|
```bash
|
|
export PGPASSWORD="YLKzP9jz2yqop7jr"
|
|
export PATH="/usr/lib/postgresql/17/bin:$PATH"
|
|
|
|
mkdir -p supabase/captured
|
|
pg_dump \
|
|
--host=aws-1-us-east-1.pooler.supabase.com \
|
|
--port=5432 \
|
|
--username=postgres.wnzkhezyhnfzhkhiflrp \
|
|
--dbname=postgres \
|
|
--schema-only \
|
|
--no-owner \
|
|
--no-privileges \
|
|
--no-acl \
|
|
--exclude-schema=auth \
|
|
--exclude-schema=storage \
|
|
--exclude-schema=realtime \
|
|
--exclude-schema=supabase_functions \
|
|
--exclude-schema=graphql \
|
|
--exclude-schema=graphql_public \
|
|
--exclude-schema=pgsodium \
|
|
--exclude-schema=pgsodium_masks \
|
|
--exclude-schema=extensions \
|
|
--exclude-schema=pgbouncer \
|
|
--exclude-schema=supabase_migrations \
|
|
--exclude-schema=net \
|
|
--exclude-schema=vault \
|
|
--file=supabase/captured/captured_schema.sql
|
|
```
|
|
|
|
Captured schema is ~540KB, 65 tables, 252 functions.
|
|
|
|
## Capture Data
|
|
|
|
```bash
|
|
pg_dump \
|
|
--host=aws-1-us-east-1.pooler.supabase.com \
|
|
--port=5432 \
|
|
--username=postgres.wnzkhezyhnfzhkhiflrp \
|
|
--dbname=postgres \
|
|
--data-only \
|
|
--no-owner \
|
|
--no-privileges \
|
|
--no-acl \
|
|
--disable-triggers \
|
|
--exclude-schema=auth \
|
|
--exclude-schema=storage \
|
|
--exclude-schema=realtime \
|
|
--exclude-schema=supabase_functions \
|
|
--exclude-schema=graphql \
|
|
--exclude-schema=graphql_public \
|
|
--exclude-schema=pgsodium \
|
|
--exclude-schema=pgsodium_masks \
|
|
--exclude-schema=extensions \
|
|
--exclude-schema=pgbouncer \
|
|
--exclude-schema=supabase_migrations \
|
|
--exclude-schema=net \
|
|
--exclude-schema=vault \
|
|
--file=supabase/captured/captured_data.sql
|
|
```
|
|
|
|
Captured data is ~130KB for this project's current size.
|
|
|
|
## Restore to Local DB (PostgreSQL 16)
|
|
|
|
PostgreSQL 16 doesn't support `transaction_timeout` (added in PG 17) and
|
|
doesn't have the `supabase_vault` extension. Pre-create the stub objects:
|
|
|
|
```bash
|
|
export PGPASSWORD=routecommerce_dev_password
|
|
psql -U routecommerce -h 127.0.0.1 -d route_commerce <<'SQL'
|
|
DROP SCHEMA public CASCADE;
|
|
CREATE SCHEMA public;
|
|
GRANT ALL ON SCHEMA public TO routecommerce;
|
|
|
|
-- extensions schema (referenced by dump as extensions.uuid_generate_v4())
|
|
CREATE SCHEMA extensions;
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" SCHEMA extensions;
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto" SCHEMA extensions;
|
|
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" SCHEMA extensions;
|
|
|
|
-- Stub functions in extensions schema (real Supabase has pgcrypto.crypt etc.)
|
|
CREATE OR REPLACE FUNCTION extensions.uuid_generate_v4()
|
|
RETURNS uuid LANGUAGE sql AS $$ SELECT gen_random_uuid(); $$;
|
|
CREATE OR REPLACE FUNCTION extensions.gen_salt(text)
|
|
RETURNS text LANGUAGE sql AS $$ SELECT '$2a$06$' || repeat('A', 53); $$;
|
|
CREATE OR REPLACE FUNCTION extensions.crypt(text, text)
|
|
RETURNS text LANGUAGE sql AS $$ SELECT $1; $$;
|
|
|
|
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA extensions TO routecommerce;
|
|
|
|
-- auth stub (we excluded auth schema from dump, but RLS policies reference auth.uid())
|
|
CREATE SCHEMA IF NOT EXISTS auth;
|
|
CREATE TABLE IF NOT EXISTS auth.users (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email TEXT,
|
|
raw_user_meta_data JSONB,
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
CREATE OR REPLACE FUNCTION auth.uid() RETURNS UUID
|
|
LANGUAGE sql STABLE AS $$ SELECT NULL::UUID; $$;
|
|
GRANT USAGE ON SCHEMA auth TO routecommerce;
|
|
GRANT ALL ON auth.users TO routecommerce;
|
|
GRANT EXECUTE ON FUNCTION auth.uid() TO routecommerce;
|
|
|
|
-- Drop Supabase-specific publications (we don't have realtime / vault)
|
|
DROP PUBLICATION IF EXISTS supabase_realtime;
|
|
DROP PUBLICATION IF EXISTS supabase_realtime_messages_publication;
|
|
SQL
|
|
```
|
|
|
|
Strip the PG17-only `SET transaction_timeout` line from the dump:
|
|
|
|
```bash
|
|
sed -i 's/^SET transaction_timeout = 0;$/-- transaction_timeout requires PG17 (we are on PG16); skipped/' \
|
|
supabase/captured/captured_schema.sql
|
|
```
|
|
|
|
Apply schema and data (idempotent — re-runs are safe):
|
|
|
|
```bash
|
|
psql -U routecommerce -h 127.0.0.1 -d route_commerce -v ON_ERROR_STOP=0 \
|
|
-f supabase/captured/captured_schema.sql 2>&1 | tail -20
|
|
|
|
psql -U routecommerce -h 127.0.0.1 -d route_commerce -v ON_ERROR_STOP=0 \
|
|
-f supabase/captured/captured_data.sql 2>&1 | tail -20
|
|
```
|
|
|
|
Most errors on re-run are "already exists" — that's expected because the
|
|
dump is idempotent for CREATE statements (we used `IF NOT EXISTS` where
|
|
possible, but pg_dump doesn't add it for everything).
|
|
|
|
## Verify
|
|
|
|
```bash
|
|
psql -U routecommerce -h 127.0.0.1 -d route_commerce -c "SELECT count(*) FROM pg_tables WHERE schemaname='public';"
|
|
# Expect: 65
|
|
|
|
psql -U routecommerce -h 127.0.0.1 -d route_commerce -c "SELECT count(*) FROM pg_proc WHERE pronamespace=(SELECT oid FROM pg_namespace WHERE nspname='public');"
|
|
# Expect: ~250+
|
|
|
|
psql -U routecommerce -h 127.0.0.1 -d route_commerce -c "SELECT name, slug, created_at FROM brands ORDER BY created_at;"
|
|
# Expect 5 brands: Tuxedo Corn, Indian River Direct, Sunrise Farms, Green Valley Organics, Orchard Fresh
|
|
```
|
|
|
|
## What Didn't Work (don't try these)
|
|
|
|
| Approach | Why it failed |
|
|
|---|---|
|
|
| `psql ... db.wnzkhezyhnfzhkhiflrp.supabase.co:5432` | Hostname is IPv6-only, dev box has no IPv6 |
|
|
| `aws-0-us-east-1.pooler.supabase.com` | "tenant/user not found" — wrong region (project is on `aws-1`) |
|
|
| `pg_dump 16.x` against PG 17 server | "server version mismatch" — fatal error |
|
|
| `supabase db dump --linked` | Requires Docker (we don't have it) |
|
|
|
|
## Notes
|
|
|
|
- The captured data includes 3 test brands (Sunrise, Green Valley, Orchard)
|
|
created on 2026-06-03 — these were test data the user added during the
|
|
migration work, not real customers. They can be deleted safely.
|
|
- Tuxedo Corn and Indian River Direct are the real production brands.
|
|
- The schema dump is committed to git at `supabase/captured/captured_schema.sql.gz`
|
|
for reproducibility. The data dump is gitignored (regenerate as needed).
|
|
- After dump, the local PostgREST needs a schema cache reload — restart
|
|
the postgrest process or it'll serve stale metadata for ~30 seconds.
|