2d57db8182
check / flake-check (pull_request) Has been cancelled
Lays out the agreed shape from the design discussion: one flake at the root with three nixosConfigurations (mqtt-ingest, db-host, app-host), vendored Supabase docker-compose pointed at the external db-host, Caddy → Kong → PostgREST/GoTrue, a Go MQTT→Postgres ingester stub, deploy-rs node map, and a Gitea Actions workflow that deploys in db → app → ingest order on push to main. No real implementation yet — every host module has TODOs marking the gaps (real hardware config, actual hostnames, HiveMQ packaging, vendor the full upstream Supabase compose, etc.).
82 lines
2.8 KiB
SQL
82 lines
2.8 KiB
SQL
-- 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;
|