toolzer Internal Docs
Platform

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 rule toolzerhub: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.json

Publishing 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.

On this page