-- Gebos Postgres bootstrap. -- Runs once via services.postgresql.initialScript on db-host. -- -- Sets up: -- * Extensions Supabase + TimescaleDB need -- * Supabase's expected schemas and admin roles -- * The gebos_ingest role (BYPASSRLS) used by the MQTT ingester -- * A telemetry hypertable with tenant_id column for RLS -- -- TODO: split into ordered files once it grows. For the skeleton, one file is fine. create extension if not exists timescaledb; create extension if not exists pgcrypto; create extension if not exists pgjwt; create extension if not exists pg_graphql; create extension if not exists pgsodium; -- create extension if not exists supabase_vault; -- TODO: vendor or package -- Supabase schemas create schema if not exists auth; create schema if not exists storage; create schema if not exists _analytics; create schema if not exists _supavisor; -- Roles Supabase services connect as. Passwords come from secrets.env, not here. do $$ begin create role anon nologin noinherit; exception when duplicate_object then null; end $$; do $$ begin create role authenticated nologin noinherit; exception when duplicate_object then null; end $$; do $$ begin create role service_role nologin noinherit bypassrls; exception when duplicate_object then null; end $$; do $$ begin create role authenticator noinherit login; grant anon, authenticated, service_role to authenticator; exception when duplicate_object then null; end $$; do $$ begin create role supabase_admin login createrole createdb replication bypassrls; exception when duplicate_object then null; end $$; do $$ begin create role supabase_auth_admin login createrole; grant usage on schema auth to supabase_auth_admin; exception when duplicate_object then null; end $$; -- Ingester writes telemetry; bypasses RLS because at write time no user is -- authenticated. Trust boundary is the broker ACL ("who can publish to what -- tenant's topic"), not the database. do $$ begin create role gebos_ingest login bypassrls; exception when duplicate_object then null; end $$; -- Telemetry hypertable. Tenant lives on the row so standard RLS works. create table if not exists public.telemetry ( ts timestamptz not null, tenant_id uuid not null, device_id uuid not null, metric text not null, value double precision, payload jsonb ); select create_hypertable('public.telemetry', 'ts', if_not_exists => true); create index if not exists telemetry_tenant_device_ts_idx on public.telemetry (tenant_id, device_id, ts desc); alter table public.telemetry enable row level security; create policy telemetry_tenant_isolation on public.telemetry for select using (tenant_id = current_setting('app.tenant_id', true)::uuid); grant select on public.telemetry to authenticated; grant insert on public.telemetry to gebos_ingest;