finish local dev setup
This commit is contained in:
+190
-10
@@ -15,17 +15,25 @@ let
|
||||
pgPassword = "dev";
|
||||
pgHost = "127.0.0.1";
|
||||
pgPort = 5432;
|
||||
# Unix socket dir, bind-mounted into the compose network's `db` proxy
|
||||
# (see composeDevOverride below). Relative to the process-compose cwd,
|
||||
# like services-flake's ./data/db data dir.
|
||||
pgSocketDir = "./data/db-socket";
|
||||
|
||||
mqttHost = "127.0.0.1";
|
||||
mqttPort = 1883;
|
||||
|
||||
# Pre-generated dev JWT pair, signed with `jwt_secret = "dev-jwt-secret-32chars-minimum-xxxxx"`.
|
||||
# Anyone with this token can read/write the local dev stack only — not prod.
|
||||
jwtSecret = "dev-jwt-secret-32chars-minimum-xxxxx";
|
||||
anonKey = "dev-anon-key-placeholder-regenerate-with-supabase-self-host-script";
|
||||
serviceRoleKey = "dev-service-role-key-placeholder-regenerate-with-supabase-self-host-script";
|
||||
# Pre-generated dev JWT pair, signed HS256 with jwtSecret. Regenerate
|
||||
# with any JWT tool if jwtSecret ever changes; payload is
|
||||
# {"role":"<anon|service_role>","iss":"supabase-dev","iat":1751328000,"exp":2082758400}.
|
||||
# Anyone with these tokens can read/write the local dev stack only — not prod.
|
||||
jwtSecret = "dev-jwt-secret-32chars-minimum-xxxxx";
|
||||
anonKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLWRldiIsImlhdCI6MTc1MTMyODAwMCwiZXhwIjoyMDgyNzU4NDAwfQ.M6U-nZq9dySoUJEfWHsiB0qabhATWobGaTM1LF86HHU";
|
||||
serviceRoleKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UtZGV2IiwiaWF0IjoxNzUxMzI4MDAwLCJleHAiOjIwODI3NTg0MDB9.dNeWNshQ9e1pIjxTDYseURlbSPPArVIKa9OuTRZyIH8";
|
||||
|
||||
supabaseUrl = "http://127.0.0.1:8000";
|
||||
|
||||
frontendPort = 5173; # vite default
|
||||
};
|
||||
|
||||
ingesterEnv = {
|
||||
@@ -45,6 +53,133 @@ let
|
||||
listener ${toString dev.mqttPort} ${dev.mqttHost}
|
||||
allow_anonymous true
|
||||
'';
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Supabase — the vendored prod compose bundle (nix/supabase/), unchanged,
|
||||
# plus this dev overlay.
|
||||
#
|
||||
# The overlay's job is wiring the containers to the services-flake postgres.
|
||||
# Bridge→host TCP is a dead end on a typical NixOS dev machine (the host
|
||||
# firewall's INPUT chain rejects it), so instead a socat `db` service
|
||||
# forwards TCP 5432 inside the compose network onto the host postgres unix
|
||||
# socket, bind-mounted from pgSocketDir. Conveniently that restores the
|
||||
# upstream compose convention of a service literally named `db`, so the
|
||||
# vendored file works with just POSTGRES_HOST=db. Socket connections match
|
||||
# pg_hba's `local … trust`, so the dev password is never actually checked.
|
||||
# ---------------------------------------------------------------------------
|
||||
composeDevOverride = pkgs.writeText "docker-compose.dev-override.yml" ''
|
||||
services:
|
||||
db:
|
||||
container_name: supabase-dev-db
|
||||
# Unpinned minor: dev-only TCP→unix-socket shim, nothing depends on
|
||||
# socat internals.
|
||||
image: alpine/socat:1.8.0.3
|
||||
restart: unless-stopped
|
||||
command: TCP-LISTEN:${toString dev.pgPort},fork,reuseaddr UNIX-CONNECT:/host-postgres/.s.PGSQL.${toString dev.pgPort}
|
||||
volumes:
|
||||
- ''${PG_SOCKET_DIR:?set by gebos-dev-supabase-compose}:/host-postgres
|
||||
|
||||
studio:
|
||||
environment:
|
||||
SUPABASE_PUBLIC_URL: ${dev.supabaseUrl}
|
||||
|
||||
auth:
|
||||
environment:
|
||||
API_EXTERNAL_URL: ${dev.supabaseUrl}
|
||||
GOTRUE_SITE_URL: http://localhost:${toString dev.frontendPort}
|
||||
GOTRUE_JWT_ISSUER: ${dev.supabaseUrl}
|
||||
# No SMTP locally, so magic-link invites can't send. Allow plain
|
||||
# self-signup with auto-confirm instead; prod keeps signup disabled.
|
||||
GOTRUE_DISABLE_SIGNUP: "false"
|
||||
GOTRUE_MAILER_AUTOCONFIRM: "true"
|
||||
'';
|
||||
|
||||
# One wrapper for `up`/`down` so both resolve the socket-dir bind mount the
|
||||
# same way (compose needs an absolute path).
|
||||
supabaseCompose = pkgs.writeShellApplication {
|
||||
name = "gebos-dev-supabase-compose";
|
||||
runtimeInputs = [ pkgs.docker pkgs.coreutils ];
|
||||
text = ''
|
||||
PG_SOCKET_DIR="$(readlink -f ${dev.pgSocketDir})"
|
||||
export PG_SOCKET_DIR
|
||||
exec docker compose \
|
||||
--project-name gebos-supabase-dev \
|
||||
-f nix/supabase/docker-compose.yml \
|
||||
-f ${composeDevOverride} \
|
||||
"$@"
|
||||
'';
|
||||
};
|
||||
|
||||
supabaseEnv = {
|
||||
POSTGRES_HOST = "db"; # the socat proxy above
|
||||
POSTGRES_PORT = toString dev.pgPort;
|
||||
POSTGRES_DB = dev.pgDB;
|
||||
POSTGRES_PASSWORD = dev.pgPassword; # unchecked — see overlay comment
|
||||
JWT_SECRET = dev.jwtSecret;
|
||||
ANON_KEY = dev.anonKey;
|
||||
SERVICE_ROLE_KEY = dev.serviceRoleKey;
|
||||
STUDIO_BIND = "127.0.0.1";
|
||||
};
|
||||
|
||||
# Everything prod splits across nix/supabase/init.sql (first initdb),
|
||||
# the gebos-postgres-passwords oneshot, and a manual `supabase db push`,
|
||||
# collapsed into one idempotent script that runs on every stack start.
|
||||
dbMigrate = pkgs.writeShellApplication {
|
||||
name = "gebos-dev-db-migrate";
|
||||
runtimeInputs = [ pkgs.supabase-cli pkgs.postgresql_17 ];
|
||||
text = ''
|
||||
export PGHOST=${dev.pgHost} PGPORT=${toString dev.pgPort} PGDATABASE=${dev.pgDB} PGSSLMODE=disable
|
||||
|
||||
# 1. Cluster bootstrap: Supabase roles/schemas the compose services
|
||||
# expect (mirrors nix/supabase/init.sql, but idempotent because the
|
||||
# dev data dir may predate this script). Password 'dev' everywhere.
|
||||
psql -v ON_ERROR_STOP=1 --quiet <<'SQL'
|
||||
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 LOGIN NOINHERIT; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
GRANT anon, authenticated, service_role TO authenticator;
|
||||
DO $$ BEGIN CREATE ROLE supabase_admin LOGIN CREATEROLE CREATEDB BYPASSRLS; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
DO $$ BEGIN CREATE ROLE supabase_auth_admin LOGIN CREATEROLE; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
-- On db-host the NixOS postgresql module creates the `postgres`
|
||||
-- superuser; the services-flake cluster's superuser is the OS user
|
||||
-- instead, and GoTrue's own migrations GRANT to `postgres` by name.
|
||||
DO $$ BEGIN CREATE ROLE postgres LOGIN SUPERUSER; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
ALTER ROLE postgres PASSWORD 'dev';
|
||||
ALTER ROLE supabase_admin SUPERUSER PASSWORD 'dev';
|
||||
ALTER ROLE supabase_auth_admin PASSWORD 'dev';
|
||||
ALTER ROLE authenticator PASSWORD 'dev';
|
||||
|
||||
-- GoTrue owns `auth` and runs its own migrations there on startup. Its
|
||||
-- migrator creates tables in the role's default schema, so without the
|
||||
-- search_path it lands in `public` and dies on PG15+'s revoked CREATE
|
||||
-- (upstream's supabase/postgres image sets the same role search_path).
|
||||
CREATE SCHEMA IF NOT EXISTS auth;
|
||||
ALTER SCHEMA auth OWNER TO supabase_auth_admin;
|
||||
ALTER ROLE supabase_auth_admin SET search_path TO auth, extensions;
|
||||
|
||||
-- The upstream supabase/postgres image keeps shared extensions in an
|
||||
-- `extensions` schema on the search path; the services assume that.
|
||||
CREATE SCHEMA IF NOT EXISTS extensions;
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA extensions;
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA extensions;
|
||||
GRANT USAGE ON SCHEMA extensions TO anon, authenticated, service_role, authenticator, supabase_auth_admin;
|
||||
ALTER DATABASE gebos SET search_path TO "$user", public, extensions;
|
||||
SQL
|
||||
|
||||
# 2. Schema migrations — same mechanism as prod (supabase/README.md),
|
||||
# non-interactive, as the local superuser ($USER).
|
||||
supabase db push --yes --db-url "postgres://$USER@${dev.pgHost}:${toString dev.pgPort}/${dev.pgDB}"
|
||||
|
||||
# 3. The schema-v1 migration created gebos_ingest; in prod its password
|
||||
# comes from the gebos-postgres-passwords oneshot.
|
||||
psql -v ON_ERROR_STOP=1 --quiet -c "ALTER ROLE ${dev.pgUser} PASSWORD '${dev.pgPassword}'"
|
||||
|
||||
# 4. Fixture data — idempotent, dev/tests only (supabase/README.md).
|
||||
psql -v ON_ERROR_STOP=1 --quiet -f supabase/seed.sql
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ services-flake.processComposeModules.default ];
|
||||
@@ -53,7 +188,17 @@ in
|
||||
postgres."db" = {
|
||||
enable = true;
|
||||
port = dev.pgPort;
|
||||
# TCP stays loopback-only; the compose containers come in through the
|
||||
# unix socket instead (see composeDevOverride).
|
||||
socketDir = dev.pgSocketDir;
|
||||
initialDatabases = [{ name = dev.pgDB; }];
|
||||
# The schema-v1 migration calls create_hypertable; without the
|
||||
# extension `supabase db push` rolls back entirely on the dev db.
|
||||
# The apache variant lacks only TSL features (compression, CAggs
|
||||
# policies) — hypertables are enough here, and it keeps dev free
|
||||
# of unfree packages.
|
||||
extensions = extensions: [ extensions.timescaledb-apache ];
|
||||
settings.shared_preload_libraries = "timescaledb";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -67,20 +212,55 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
# Oneshot: bootstrap roles/schemas, apply migrations, seed. Idempotent,
|
||||
# so it simply re-runs on every stack start.
|
||||
db-migrate = {
|
||||
command = "${dbMigrate}/bin/gebos-dev-db-migrate";
|
||||
depends_on."db".condition = "process_healthy";
|
||||
};
|
||||
|
||||
# Auth (GoTrue) + REST (PostgREST) + Studio + postgres-meta behind Kong
|
||||
# on ${dev.supabaseUrl} — the vendored prod bundle, see composeDevOverride.
|
||||
# Needs a running docker daemon on the dev machine.
|
||||
supabase = {
|
||||
command = "${supabaseCompose}/bin/gebos-dev-supabase-compose up --remove-orphans";
|
||||
environment = supabaseEnv;
|
||||
# `restart: unless-stopped` containers outlive a hard-killed
|
||||
# process-compose; an explicit `down` covers the clean path.
|
||||
shutdown.command = "${supabaseCompose}/bin/gebos-dev-supabase-compose down --remove-orphans";
|
||||
depends_on = {
|
||||
# GoTrue's own migrations need the auth schema + roles from db-migrate.
|
||||
"db".condition = "process_healthy";
|
||||
"db-migrate".condition = "process_completed_successfully";
|
||||
};
|
||||
readiness_probe = {
|
||||
# Kong → key-auth → GoTrue → Postgres: healthy = the whole API path works.
|
||||
exec.command = "${pkgs.curl}/bin/curl -fsS -H 'apikey: ${dev.anonKey}' ${dev.supabaseUrl}/auth/v1/health";
|
||||
initial_delay_seconds = 5;
|
||||
period_seconds = 5;
|
||||
failure_threshold = 60; # first start pulls the images
|
||||
};
|
||||
};
|
||||
|
||||
ingester = {
|
||||
command = "${pkgs.go}/bin/go run ./ingester/cmd/gebos-ingester";
|
||||
# -C: the go module root is ingester/, not the repo root.
|
||||
command = "${pkgs.go}/bin/go run -C ingester ./cmd/gebos-ingester";
|
||||
environment = ingesterEnv;
|
||||
depends_on = {
|
||||
"db".condition = "process_healthy";
|
||||
# db-migrate (implies db healthy) creates the gebos_ingest role and
|
||||
# the telemetry tables.
|
||||
"db-migrate".condition = "process_completed_successfully";
|
||||
"broker".condition = "process_started";
|
||||
};
|
||||
};
|
||||
|
||||
frontend = {
|
||||
command = "${pkgs.nodejs_24}/bin/npm --prefix frontend install && ${pkgs.nodejs_24}/bin/npm --prefix frontend run dev";
|
||||
environment = frontendEnv;
|
||||
};
|
||||
# TODO: supabase compose (point POSTGRES_HOST at the services-flake db)
|
||||
# TODO: kong (use the vendored kong.yml from nix/supabase/)
|
||||
# TODO: caddy (mirror prod routes locally, no TLS)
|
||||
|
||||
# No caddy locally: prod's Caddy config is generated by the gebos-caddy
|
||||
# NixOS module, so a hand-mirrored dev caddyfile wouldn't test it — the
|
||||
# frontend talks to Kong directly (dev.supabaseUrl).
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user