Operations
One-time procedures and standing operational access. Routine deploys are in deployment.md; standing up a host from scratch is in infrastructure.md.
Renaming the databases to core (done)
Executed. Postgres is core_data / role core, ClickHouse is core_analytics
— confirmed live throughout the compose files and code. The rollback SQL (the
rename statements reversed) lives in git history: introduced as DEPLOY.md in
commit fa9ffb5c, later folded into this file by 7b7a85ba.
What deliberately did NOT get renamed
- The R2 bucket is still
toolzerhub-data(apps/api/src/storage/r2-store.ts). R2 has no rename operation — changing it means creating a new bucket and copying every object across, which is a data migration, not a rename. TOOLZERHUB_API_KEY, by compatibility. Only the variable's name is brand-coupled; the deprecated shared value is supplied through deployment secrets while published Apify Actors migrate to dedicated keys. See the Actor-migration follow-up.- Public hostnames and the
tzhub_live_key prefix — those are the product's identity to customers, not internal naming. - Grafana alert rule UIDs (
toolzerhub-api-high-5xx, ...) and the Prometheus recording ruletoolzerhub:api_data_5xx:ratio5m. Renaming a rule UID makes Grafana treat it as a new alert, orphaning its state and any existing silences; renaming a recording rule leaves the historical series behind under the old name. Both are cosmetic and worth their own change.
Looking at production data
Two ways in, both through one read-only role. Neither can write.
The core_reader role
Migration 0024_core_reader_role.sql creates it with SELECT on the
platform schema and ALTER DEFAULT PRIVILEGES so tables added by later
migrations are readable too. It ships NOLOGIN and without a password —
a password in a migration is a password in git forever, which is the mistake
scripts/promoteAdmin.ts exists to avoid.
One owner step activates it, run once after the migration has applied:
ALTER ROLE core_reader WITH LOGIN PASSWORD '<generate a long random one>';It has no grants on rankings, and no INSERT/UPDATE/DELETE/DDL
anywhere. The worst a leaked core_reader password can do is disclose data —
which is bad, so put it in a password manager, but it cannot destroy anything
or be escalated.
CloudBeaver (the UI)
Added to infrastructure/data-services/docker-compose.yml. It publishes no
ports, like everything else in that stack, and reaches postgres and
clickhouse by service name over dokploy-network. Add the connection once
in its UI using core_reader.
Put Cloudflare Access or an IP allowlist in front of whatever hostname Dokploy routes to it. This is a console onto customer identity, billing and usage. Without that, CloudBeaver's own login page is the only thing between the public internet and every customer record you have — and a database console is a target people actively scan for.
MCP (letting Claude query it)
Point a Postgres MCP server at core_reader. Do not publish the Postgres
port to do it — tunnel instead, so the database stays unreachable from the
internet:
ssh -N -L 15432:postgres:5432 <user>@<vps>then give the MCP server postgres://core_reader:<password>@localhost:15432/core_data.
Read-only is doing real work here beyond guarding against typos: an MCP server executes SQL that a model composed, and that model reads rows which may contain text a customer chose. Keeping the credential incapable of writing means a prompt-injection attempt in a workspace name is a curiosity rather than an incident.
Releasing @thenetaji/packages
Versioning is changesets-driven:
pnpm changeset # describe the change
pnpm version # apply it to packages/package.jsonPublishing happens on push to main when packages/** changes. If the version
is unchanged the workflow skips with a notice rather than failing — a source
change with no release is the normal case, and publishing over an existing
version is a 409.
apps/api and apps/worker are private deployed services, not published
packages — nobody installs them, the deploy IS the release, and the commit
SHA (baked into each image via the GIT_COMMIT build-arg) already identifies
what's live. They carry no version and no release tags. The internal worker
changelog uses conventional commits. The public API changelog uses
reviewed main release notes.
A breaking API change goes
through the URL (/v1 → /v2), not a version bump.