Skip to Content
v1.33.8 · shipped One-screen report · Looks like your app Read the changelog →
Self-hostingSupabase setup

Supabase setup

cd packages/server npx supabase login npx supabase link --project-ref <your-ref> npx supabase db push # applies every migration in supabase/migrations/

Run these commands from packages/server/ — the Supabase CLI looks for supabase/ relative to the current directory.

Required extensions

Enable these extensions in your Supabase project (Database → Extensions):

ExtensionPurpose
vectorEmbeddings for dedup + semantic search (commonly called pgvector)
pg_cronScheduled MV refreshes, nightly judge runs, retention cron
pgcryptoUUIDs and HMAC
pg_netAsync HTTP from cron jobs and triggers (plugin dispatcher, edge-function crons)
citextCase-insensitive text columns
supabase_vaultBYOK + plugin-secret storage (on by default in Supabase projects)
age (optional)Apache AGE graph backend for the knowledge graph

The migrations run create extension if not exists for vector, pgcrypto, pg_cron, pg_net, citext and age. Vault is part of every Supabase project. Report columns are not encrypted with pgsodium.

Required custom functions

The migration sequence creates the following custom Postgres functions:

FunctionPurpose
mushi_rls_coverage_snapshot()Lists every public table and whether RLS is enabled on it
mushi_apply_retention()Applies project_retention_policies (also scheduled by pg_cron)

DSARs are handled via the data_subject_requests table + the api edge function (POST /v1/admin/compliance/dsars), not a stored procedure. SOC 2 evidence is generated by the soc2-evidence edge function triggered from the admin console — no soc2_evidence_snapshot() RPC exists. Both routes are documented in the Security & Compliance section.

RLS pattern

Every table ships with RLS enabled and project-scoped policies. The canonical policy pattern is:

-- Use the subquery form — Postgres caches via initPlan, not per-row create policy "users see own project data" on public.reports for select using (project_id in ( select project_id from public.project_members where user_id = (select auth.uid()) ));

Do not use bare auth.uid(): the subquery form is evaluated once per query instead of once per row, which matters on large tables.

Seed data

There is no separate seed file. The migrations insert the lookup rows themselves (pricing plans, the plugin registry, the mushi_runtime_config defaults), so db push is enough.

Verifying the setup

-- Tables with RLS disabled (should return no rows) select * from mushi_rls_coverage_snapshot() where not rls_enabled; -- Check extensions select name, default_version, installed_version from pg_available_extensions where name in ('vector', 'pg_cron', 'pg_net', 'pgcrypto', 'supabase_vault');
Last updated on