Actions
46 actions, callable from the panel, the command palette and the API as
POST /api/v1/a/<id>. Internal actions used between modules are not listed.
| Action | What it does | Risk | Preview |
|---|---|---|---|
databases.engine.install | Install a database engine | critical | No dry run |
databases.engine.list | List installed engines (read) | low | No dry run |
databases.db.create | Create a database | medium | |
databases.db.list | List databases (read) | low | No dry run |
databases.db.get | Get a database (read) | low | No dry run |
databases.db.stats | Refresh database sizes | low | No dry run |
databases.db.delete | Delete a database | high | |
databases.db.export | Export a database | low | No dry run |
databases.db.import | Import a dump | medium | No dry run |
databases.user.create | Create a database user | medium | |
databases.user.list | List database users (read) | low | No dry run |
databases.user.delete | Delete a database user | medium | |
databases.user.password.set | Reset a user's password | medium | No dry run |
databases.user.password.reveal | Reveal a vaulted password (read) | high | No dry run |
databases.grant.set | Set a user's access to a database | medium | |
databases.remote.set | Allow remote connections | high | |
databases.valkey.create | Create a Valkey instance | medium | |
databases.valkey.list | List Valkey instances (read) | low | No dry run |
databases.valkey.update | Change a Valkey instance | medium | |
databases.valkey.reset | Reset a Valkey password | medium | No dry run |
databases.valkey.stats | Valkey usage (read) | low | No dry run |
databases.valkey.delete | Delete a Valkey instance | high | |
databases.server.create | Register an external database server | medium | No dry run |
databases.server.list | List external database servers (read) | low | No dry run |
databases.server.get | Get an external database server (read) | low | No dry run |
databases.server.update | Change an external database server | medium | No dry run |
databases.server.test | Test an external database server | low | No dry run |
databases.server.delete | Remove an external database server | high | No dry run |
databases.db.snippets | Connection snippets (read) | low | No dry run |
databases.admin.open | Open the database admin tool | medium | No dry run |
databases.reconcile | Re-apply all databases | medium | No dry run |
databases.config.reset_all | Reset the whole service to defaults | high | |
databases.config.snapshot.list | Snapshots before resets (read) | low | No dry run |
databases.config.snapshot.restore | Undo a reset (restore a snapshot) | high | |
databases.config.history.list | Earlier versions of a file (read) | low | No dry run |
databases.config.history.diff | Compare an earlier version (read) | low | No dry run |
databases.config.history.restore | Restore an earlier version of a file | high | |
databases.unmanaged.scan | Scan now for objects made outside the panel | low | No dry run |
databases.unmanaged.list | Objects made outside the panel (read) | low | No dry run |
databases.unmanaged.ignore | Ignore or un-ignore an unmanaged object | low | |
databases.unmanaged.adopt | Adopt an object made outside the panel | medium | |
databases.unmanaged.remove | Remove an object made outside the panel | high | |
databases.config.status | Hand-edited database server files (read) | low | No dry run |
databases.config.adopt | Keep a hand edit of a database server file | medium | |
databases.config.reapply | Render a database server file again | high | |
databases.config.unmanage | Hand a database server file off to the administrator | high |
Permissions and limits
Permissions
databases.db.viewView databases, users and instancesdatabases.db.manageCreate and delete databasesdatabases.user.manageManage database users and passwordsdatabases.grant.manageChange database grantsdatabases.remote.manageAllow remote connectionsdatabases.export.runExport (dump) databasesdatabases.import.runImport (restore) databasesdatabases.valkey.manageManage Valkey instancesdatabases.engine.manageInstall database engines on serversdatabases.reconcileRe-apply every database to its serverdatabases.server.viewView external database serversdatabases.server.manageRegister and change external database serversdatabases.admin.openOpen the database admin tool (single sign-on)
Plan limits
databases.countDatabasesdatabases.usersDatabase usersdatabases.size_mbTotal database sizedatabases.connectionsConnections per database userdatabases.remote_hostsRemote host rulesdatabases.valkey_instancesValkey instancesdatabases.valkey_mbValkey memory (all instances)databases.servers_sqlExternal MySQL/MariaDB/PostgreSQL servers (0 = not allowed)databases.servers_clickhouseExternal ClickHouse servers (0 = not allowed)databases.servers_redisExternal Redis/Valkey servers (0 = not allowed)databases.servers_mongodbExternal MongoDB servers (0 = not allowed)
Engineering notes
Generated from modules/databases/docs.md at build d90e9e2. These are the notes the engineers keep
next to the code: precise, technical, and honest about what is not done yet.
Customer database servers are installed on the node by the driver (internal/agent/databases*.go); the panel's own
Postgres is unrelated. Listeners are loopback-only until some user has remote hosts.
Model
- Names are
<account>_<name>(account-becomes_); database <= 48, user <= 32 characters. - Privileges (
readonly/readwrite/admin) replace each other ondatabases.grant.set. MariaDB: SELECT / DML / ALL on`acct\_db`.*(underscore escaped; no GRANT OPTION). PostgreSQL: per database three NOLOGIN group roles<db>_own(owner; admin users are members and log in withSET ROLE, so what they create is owned by the group),<db>_rw,<db>_ro, plus default privileges for new tables. - Passwords are generated (24 chars), returned once by create/reset, never stored, never in list/get. With
store_in_vaultthe vault keeps it anddatabases.user.password.revealreturns it (audited). Supplied passwords: 12-128 chars, letters+digits, no quotes/backslash/space/backtick. Top-levelpasswordkeys are redacted by the host audit. - Remote access (
databases.remote.set): IPv4/IPv6 addresses or IPv4 CIDR;%//0and ranges wider than /16 admin-only. MariaDB: extra'user'@'host'entries (CIDR becomes addr/netmask) +REQUIRE SSL; PostgreSQL:hostssl/hostlines inrc-hba.d/<user>.conf(included from pg_hba.conf, PG 16+). The engine listens on 0.0.0.0 only while some user has remote hosts (restart on change). TLS uses a self-signed cert generated by the agent (ssl module cert later). The firewall rule belongs to thesecuritymodule (not built): the plan warns about it. - Limits:
databases.count/users/valkey_instances/valkey_mb/remote_hostsReserve/Release;databases.connectionsbecomesMAX_USER_CONNECTIONS/ role CONNECTION LIMIT (read from the plan by probing Reserve, see follow-ups);databases.size_mbis enforced bydatabases.db.stats(reserves growth; when refused the database's grants are forced read-only, restored when it fits again; statusquota_exceeded). - Dump/restore:
databases.db.exportwrites.sql/.sql.gzinto the account's home,databases.db.importreads it. All file access runs as the account viarunuser(no symlink escape by a root agent). Import runs as an ephemeral user with rights on that database only (cannot CREATE USER / touch other DBs; verified). - Valkey:
rc-valkey@<account>_<name>from a template unit,User=<account>,Slice=rc-acct-<account>.slice, loopback port from a sequence + unix socket in~/valkey/<name>/,user default off, ACL user with a sha256 hash (no clear password on disk), dangerous commands denied,maxmemoryfrom the request (counted againstdatabases.valkey_mb),MemoryMax = 2x+64M. Anrc-agentACL user (token only in /etc/rc-valkey) feeds stats. - Desired state first, then apply (idempotent);
databases.reconcilere-applies everything keeping passwords;accounts.deletedsubscription removes everything of the account (durable, idempotent).
Verified in the lab (2026-10-09, Ubuntu 24.04: MariaDB 10.11, PostgreSQL 16, Valkey 7.2)
- Create db+user on both engines; connect from a second container over TLS with the generated password; wrong password
refused; before remote.set the port is closed; with
require_tlsa non-TLS client is refused (both engines). databases.countlimit 2: third create ->limit_exceeded; valkey instance limit likewise; connection limit 3: fourth concurrent session ->max_user_connectionserror.- readonly user cannot INSERT; readwrite can, cannot CREATE; password reset invalidates the old one; vault reveal.
- Export -> drop table -> import restores rows on both engines (gz and plain), ephemeral roles gone; a dump with
CREATE USERis refused;../paths refused. - Size quota flips a PostgreSQL database read-only (an empty PG database is ~7 MB).
- Reconcile recreated a database and user dropped behind the panel's back; remote off closes the listener again;
Valkey runs as the account in its slice,
CONFIGis denied; account deletion removed DBs/users/roles via the event.
External (bring-your-own) servers — w6-04
databases.server.createregisters a MySQL/MariaDB, PostgreSQL, MongoDB, Redis/Valkey or ClickHouse server: host, port,tls(off|on= encrypted, certificate not checked |verify= checked, optionally against a pasted CA) and the admin login. It is TESTED ON SAVE through the account's node (databases.server.testagent op: version, encryption, latency); a failing test saves nothing. The admin password goes to the vault (server/<id>) and is never returned, logged, audited or put in an event; it reaches the client only in its environment (MYSQL_PWD,PGPASSWORD, an HTTP header, RESPAUTH), never argv or SQL (unit-tested).- Plans: limits
databases.servers_sql/_clickhouse/_redis/_mongodb(count; 0 = that kind is not allowed). - Every existing action takes
server_id(db.create,user.create, lists); the engine is the server's kind. Names keep the<account>_prefix. Users on an external MySQL/MariaDB get host%unlessremote.setrestricts them. Per kind: MySQL/MariaDB (flavour auto-detected: MySQL 8 copies hashes asIDENTIFIED WITH plugin AS 0x..) and PostgreSQL (same<db>_own/_rw/_romodel; a newer server brings the matching PGDGpg_dump) support everything incl. export/import/size; ClickHouse (HTTP interface: databases, SQL users, GRANT ondb.*, sizes) has no export/import; Redis/Valkey: a "database" is the key namespace<name>:*, users are ACL users, readonly =%R~; MongoDB: register + connectivity test only (management is a follow-up). The agent refuses loopback / link-local / metadata hosts and names resolving to them (use the built-in engine for this node). - Deleting a server is refused while databases/users are registered on it; deleting an account forgets its servers (the servers themselves are never touched).
databases.db.snippets: PHP, Node, Python, Go, Ruby, Java and .NET for every family, with aPASSWORDplaceholder.
Built-in engines on demand (databases.engine.install)
mysql (Oracle 8.4 LTS apt repo; refused while MariaDB owns :3306), postgresql + version 14-18 (PGDG), clickhouse
(vendor repo; loopback; the passwordless default user is removed, rc_admin credentials in /etc/rc-clickhouse/admin.json
0600), qdrant (pinned release + SHA-256, loopback, API key in /etc/rc-qdrant/env), ferretdb (v1.24 pinned, over the
node's PostgreSQL: a FerretDB "database" is a PostgreSQL database + user, clients use the PostgreSQL login with
authMechanism=PLAIN). Repos and download URLs are fixed in databases_engines.go; keys are fetched over https.
Admin tool (Adminer) single sign-on
databases.admin.open (database or external server) -> agent op databases.admin.session: installs Adminer (Ubuntu/Debian
package, Apache-2.0/GPL-2) + php-cli on demand, runs it with php -S on 127.0.0.1:17977 as system user rc-adminer
(hardened unit), writes the login into a 0600 handoff file on tmpfs and returns a 60 s, single-use, HMAC-signed token
(/?rc_sso=<token>). The page consumes the handoff, keeps the credentials in its server-side session and signs Adminer in
through its own login POST with an EMPTY password field: the browser never sees a password. Built-in databases get an
ephemeral rc_sso_* login limited to that database, swept by the schedule databases.admin.sweep (5 min).
The panel has no reverse proxy yet: the action returns {node_id, port, path} for it (see handover).
Follow-ups closed
import takes clean (drops everything first: MariaDB/MySQL drop+create keeping charset, PostgreSQL recreates public);
size enforcement runs from the schedule databases.stats.run (15 min, one unreachable server does not stop the rest).
Next
UI (w6-08); panel reverse proxy for the admin tool; MongoDB management on external servers; Qdrant collections/keys per
account; ClickHouse/Redis dump; pin repo key fingerprints; grace period for deletes (AC-18), clone/rename, maintenance
ops, slow-query log; firewall rule via security.