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.
What init needs from any host
Section titled “What init needs from any host”- 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=requireto 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.
Which role installs the guard
Section titled “Which role installs the guard”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
Section titled “Row-level security”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.
Poolers
Section titled “Poolers”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.
Your host
Section titled “Your host”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 5432succeeded 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.
postgresis the admin. It is not a superuser and it hasCREATEROLE, 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
yfor tables the agent should see. See Row-level security. - Schemas other than
public.authandstoragehold 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 --remotewants the string without its password. Supabase shows it aspostgresql://postgres:[YOUR-PASSWORD]@.... Remove the placeholder and the colon before it. See A bot on its own computer. - Default privileges. Supabase’s
postgresrole grants new tables inpublictoanon,authenticatedandservice_role. BasaltDB’s own objects are made by its own role inbasalt_meta, so none of that applies, and an agent gets no default privileges either.
Use the instance endpoint from the RDS console, and the master user or another role that can create roles.
postgresql://postgres:PASSWORD@NAME.xxxx.REGION.rds.amazonaws.com:5432/DBNAME?sslmode=require- Role. The master user can create roles, which is all init needs. It does not have to be a superuser.
- Reach. The security group must let in the machine that runs
basalt initand the machine that runs the engine. They are often two machines. - Use the instance endpoint. A proxy in front of the database, such as RDS Proxy, may pool by session or by transaction and may route by user. Connect the engine to the instance endpoint, or to a session-mode pooler you control.
- TLS. Keep
?sslmode=require. - RLS. Postgres leaves RLS off unless you or your tooling turn it on. If a table has it on and no policy for the agent, init asks the row-level security question. See Row-level security.
- A bot. The bot’s
--database-urlis the same endpoint without the password:postgresql://postgres@NAME.xxxx.REGION.rds.amazonaws.com:5432/DBNAME. See A bot on its own computer.
Use the host and port your Postgres listens on.
postgresql://postgres:PASSWORD@HOST:5432/DBNAME?sslmode=requireA local Postgres without TLS needs no sslmode. The Docker database from the Quickstart uses postgres://postgres:demo@localhost:5432/postgres.
- Role. The
postgressuperuser works. A role that owns the database and hasCREATEROLEis enough, and is the safer choice for a database you keep. - Reach. Check
listen_addressesandpg_hba.conf. The machine that runsbasalt initand the machine that runs the engine must both be allowed to connect, and so must the agent’s role,basalt_agent_<name>, from the engine’s machine. - PgBouncer. Use session mode, or connect to Postgres directly.
- RLS. Postgres leaves RLS off unless you or your tooling turn it on. If a table has it on and no policy for the agent, init asks the question. See Row-level security.
- The engine’s machine. The engine can run on the same server as Postgres or on another machine. As a background service, init asks for
sudoonce to create the engine’s OS user. - A bot. The bot’s
--database-urlis the string without the password:postgresql://postgres@HOST:5432/DBNAME. See A bot on its own computer.
- Install: guard your own tables.
- Connect an agent: point an agent at the engine.
- When something is refused: if the agent reads 0 rows or cannot connect.
BasaltDB · Apache-2.0