caterium-app/ops/supabase-selfhosted/README.md
pavlov346346 88b7f55db4 Ops: add self-hosted Supabase stack for Timeweb Cloud migration
Full production-ready docker-compose stack (db, kong, auth, rest,
realtime, storage, imgproxy, meta, functions, studio) targeting
api.caterium.ru, plus bootstrap script for a fresh Cloud Server,
Caddy reverse-proxy config (HTTPS, WebSocket, upload limits), and
dump/restore/verify/storage-sync scripts for moving off the managed
Supabase project (cksuehzcimitsxmeloes). Does not touch public/ or
any live runtime behavior — frontend cutover is documented separately
in frontend-cutover.md and only applied after Etap 8 verification.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 16:20:45 +03:00

5.8 KiB

Self-hosted Supabase for Caterium (Timeweb Cloud Server)

Target: https://api.caterium.ru — replaces https://cksuehzcimitsxmeloes.supabase.co.

This directory is deployable as-is on a fresh Ubuntu 22.04/24.04 Timeweb Cloud Server (VPS with root). It CANNOT run on the existing timeweb-caterium shared-hosting SSH account (ci503744@vh470.timeweb.ru) — that account has no root, sudo is disabled (no_new_privileges), and there is no Docker daemon. Shared hosting is fine for serving the static frontend (public/) but architecturally cannot host Postgres/Kong/ GoTrue/PostgREST/Realtime/Storage containers.

What's here

File Purpose
bootstrap-server.sh Run once (as root) on a fresh Cloud Server. Installs Docker, Docker Compose plugin, Caddy, ufw rules, clones this config, brings the stack up.
docker-compose.yml The Supabase stack: db, kong, auth, rest, realtime, storage, meta, functions, studio, imgproxy.
.env.example Every secret/setting the stack needs. Copy to .env, fill in, chmod 600. Never commit the real .env.
volumes/api/kong.yml Kong declarative routing for /rest/v1, /auth/v1, /storage/v1, /realtime/v1, /functions/v1.
volumes/db/init/ One-time SQL run by the Postgres container on first boot (roles, pg_cron/extensions, JWT hook wiring).
Caddyfile Reverse proxy for api.caterium.ru: automatic HTTPS (Let's Encrypt), WebSocket upgrade for Realtime, large upload limits for Storage, header forwarding.
scripts/01-dump-old.sh Runs against the OLD Supabase project. Produces roles.sql, schema.sql, data.sql via supabase db dump.
scripts/02-restore-new.sh Restores those dumps into the new self-hosted Postgres via psql.
scripts/03-verify-counts.sql Row-count comparison old vs new (auth.users, orders, order_items, clients, catalog_items, storage.objects).
scripts/04-deploy-edge-function.sh Copies supabase/functions/caterium-create-employee and caterium-platform-auth-admin into the running functions container's mounted volume and reloads it.
scripts/05-storage-sync.sh Copies Storage objects (binary files) from the old project's Storage S3-compatible/API to the new bucket, preserving paths.

Order of operations

  1. Provision the Cloud Server (see "Manual step required" below). Point DNS api.caterium.ru A-record at its IP.
  2. scp this whole ops/supabase-selfhosted/ directory to the server, or git clone the repo there.
  3. sudo bash bootstrap-server.sh — installs Docker/Caddy, brings up the stack with placeholder secrets.
  4. Fill in real secrets in .env (JWT secret, DB password, SMTP, dashboard user/pass) — generate fresh ones with openssl rand -base64 32 unless told to reuse the old JWT secret. docker compose up -d again to apply.
  5. Run scripts/01-dump-old.sh (needs OLD_DB_URL — the old project's Postgres connection string with password, from Supabase Dashboard → Project Settings → Database → Connection string → URI, "session pooler" disabled, direct connection).
  6. Run scripts/02-restore-new.sh against the new stack's db container.
  7. Run scripts/03-verify-counts.sql old vs new and compare against the expected counts (14 / 11 / 53 / 52 / 460 / 18).
  8. Run scripts/05-storage-sync.sh to copy the 18 Storage objects and their metadata/policies.
  9. Run scripts/04-deploy-edge-function.sh to bring caterium-create-employee online, verify with a test call.
  10. Only after 5-9 pass: flip DEFAULT_SUPABASE_URL in public/app-runtime.js, ship the localStorage migration shim (see ops/supabase-selfhosted/frontend-cutover.md), commit to main, let CI promote to production.

Manual step required right now (nothing beyond this is blocked)

I do not have a Timeweb Cloud API token or root SSH credentials to any Timeweb Cloud Server (VPS) — only to the existing shared-hosting account used for the static frontend, which cannot run Docker. To unblock steps 1-10:

Either

  • (A) In the Timeweb Cloud panel (https://timeweb.cloud/), create a Cloud Server: Ubuntu 22.04, minimum 2 vCPU / 4 GB RAM / 50 GB NVMe (recommended: 4 vCPU / 8 GB for headroom), public IPv4, and add my SSH public key as an authorized key OR give me the root password so I can add my own key. Then tell me the server's IP.
  • (B) Generate a Timeweb Cloud API token (panel → API → "Токены доступа" → create token with Cloud Servers write scope) and give it to me; I will provision the server via the Timeweb API myself (POST https://api.timeweb.cloud/api/v1/servers).

Whichever you pick, also add a DNS A-record: api.caterium.ru → the new server's IP (in whatever DNS provider currently hosts caterium.ru's zone — outside Cloudflare per your instruction not to put Cloudflare in the backend path).

Separately, to actually migrate the data (step 5), I need the OLD Supabase project's direct Postgres connection string with the real database password (Supabase Dashboard → Project Settings → Database → Connection string → URI, non-pooled). I cannot retrieve this myself — it's not in any file on this machine and I won't ask you to paste a password into a browser form on my behalf. Paste the connection string here in chat, or set it as OLD_DB_URL in the shell before I run scripts/01-dump-old.sh for you. If you'd rather I don't see the raw password in chat, run scripts/01-dump-old.sh yourself with OLD_DB_URL exported and send me the three resulting .sql files (or their location on this machine) and I'll take it from there.

I will use a freshly generated JWT secret for the new stack (per your instruction that this is acceptable) — existing sessions will be invalidated and all 14 users will need to log in once more with their existing email+password (which migrate unchanged, since auth.users.encrypted_password is copied byte-for-byte from the dump).