Skip to content
BasaltDBBasaltDB
Star

Hosts

View as Markdown

BasaltDB works with Supabase, RDS and a Postgres you host yourself, when you can log in to it as an admin. This page covers what basalt init needs from the host, then the details that differ on Supabase, RDS and a self-hosted server.

  • An admin role that can create roles. The role that owns the database, with CREATEROLE. A superuser is not needed.
  • A direct connection, or a pooler in session mode. A transaction-mode pooler is not the way to connect. See Poolers.
  • A host your machine can reach. The engine and the agent use the same host and port as the string you give init, so the machine that runs the engine must be able to reach it too.
  • TLS on a hosted service. Add ?sslmode=require to the string. A local Postgres without TLS needs nothing.
  • Special characters encoded. If the password has @, /, : or #, URL-encode them.

The admin connection string is used once, by basalt init and basalt uninstall, typed at a hidden prompt. It is never stored. Do not export it, put it on a command line or paste it to anyone. If it ends up somewhere else, reset the admin role’s password with your host.

Init copies the string you typed, keeps its host, port, database and parameters, and swaps the user and password for the agent’s role and a new random password. The role is basalt_agent_<name>. The agent’s string is written to a file that only the engine’s user can read.

Init and basalt uninstall run as the admin role you give them. Init creates the basalt_meta schema, the guard role and the agent’s role, and attaches the guard to the tables you name.

The agent’s role is a plain role. Init refuses a role that has SUPERUSER, CREATEROLE, CREATEDB, REPLICATION or BYPASSRLS, that belongs to any role other than basalt_agents, or that owns anything in the database. It refuses before it changes anything.

Row-level security (RLS) filters rows by policy. If a table has RLS on and no policy names the agent’s role, Postgres hides every row from the agent without an error. The agent reads 0 rows from a table that has rows.

Init finds these tables before it shows the SQL. At a terminal it asks once per table:

public.leads hides its rows with row-level security. Let my_bot see all 30 rows of this table? [y/N]

y adds a policy for the agent’s role only. n skips it, and the agent will read 0 rows. In a script, --agent-rls=all-rows answers yes for every such table, and --agent-rls=none skips the question. Tables without RLS are never asked about.

If the agent already reads 0 rows, run basalt init again with the same flags and answer y. Re-running init keeps the agent’s password, so the engine needs no restart. basalt uninstall drops the policy.

Every BasaltDB connection sends each statement as a single unnamed statement and sets the reason with transaction-local settings. That is what a transaction pooler needs, but transaction mode is not yet verified, so connect directly or through a session pooler.

If your host’s pooler routes by user name, use a direct endpoint. Init keeps a routing suffix on the agent’s user only for Supabase’s pooler.

In the dashboard press Connect. Supabase shows three strings.

String Looks like Use it for
Direct connection postgresql://postgres:[PASSWORD]@db.<ref>.supabase.co:5432/postgres Init and the agent, if your network has IPv6. The direct host is IPv6-only without the add-on.
Session pooler postgresql://postgres.<ref>:[PASSWORD]@aws-0-<region>.pooler.supabase.com:5432/postgres Init and the agent otherwise. Works on IPv4 and IPv6.
Transaction pooler The same host, port 6543 Not checked. Use direct or session mode.

To check IPv6 to the direct host, run this. No secret is involved.

nc -6 -vz -w 5 db.<ref>.supabase.co 5432

succeeded means use the direct connection. A timeout or No route to host means use the session pooler. Add ?sslmode=require to whichever you use, and fill in [PASSWORD].

  • Role. postgres is the admin. It is not a superuser and it has CREATEROLE, which is enough.
  • The pooler user. On the session pooler your user is postgres.<ref>. The agent’s user keeps the project ref, basalt_agent_<name>.<ref>, so the pooler can route it. Init does this for you.
  • RLS. Tables you make in the dashboard have RLS on, and a project setting can turn it on for every new table, including tables made in the SQL editor. Expect the row-level security question, and answer y for tables the agent should see. See Row-level security.
  • Schemas other than public. auth and storage hold your users and files. The setup summary flags them more strongly than other schemas. Give an agent tables there only on purpose.
  • A bot. basalt init --remote wants the string without its password. Supabase shows it as postgresql://postgres:[YOUR-PASSWORD]@.... Remove the placeholder and the colon before it. See A bot on its own computer.
  • Default privileges. Supabase’s postgres role grants new tables in public to anon, authenticated and service_role. BasaltDB’s own objects are made by its own role in basalt_meta, so none of that applies, and an agent gets no default privileges either.

BasaltDB · Apache-2.0