thermograph/infra/ops/README.md
emi 864d38d772
All checks were successful
secrets-guard / encrypted (push) Successful in 16s
shell-lint / shellcheck (push) Successful in 17s
ops/dbq.sh: read-only Postgres queries across all environments (#18)
2026-07-24 19:28:58 +00:00

65 lines
2.6 KiB
Markdown

# ops/ — operator + agent query tooling
Read-only query access to Thermograph data, uniform across environments.
## `dbq.sh` — Postgres, any environment
```sh
infra/ops/dbq.sh dev "select count(*) from climate_history"
infra/ops/dbq.sh prod -tA -c "select max(date) from climate_history"
infra/ops/dbq.sh beta -c '\dt'
echo "select 1" | infra/ops/dbq.sh prod -f -
```
Environments: `dev` (LAN compose stack on the dev machine), `beta`
(75.119.132.91), `prod` (169.58.46.181). Extra args pass straight through to
`psql` (`-tA`, `-x`, `--csv`, `-f`, …); stdin is forwarded.
### Why it execs into the container instead of using a connection string
None of the databases are exposed over TCP — each listens only on its private
docker network. Prod's is a Swarm **overlay** (`10.0.2.0/24`) that the host
cannot route to, so `ssh -L` works for beta but is *impossible* for prod;
publishing 5432 would mean new ufw rules plus a Swarm endpoint change on
production. Running `psql` inside the db container works identically in all
three environments with no ports, no tunnels, and no infra changes.
The prod container name is a Swarm task name that changes on every redeploy, so
it is resolved at call time via `docker ps --filter name=…`, never hardcoded.
### Safety
Queries connect as **`thermograph_ro`** — a `NOSUPERUSER` role granted only
`pg_read_all_data`. Read-only is enforced by Postgres, not by convention:
```
$ infra/ops/dbq.sh prod -c "create table t(i int)"
ERROR: permission denied for schema public
```
The app's own `thermograph` role is a superuser and is deliberately **not** used
by this tool. If the role is ever missing (fresh database), recreate it with:
```sql
CREATE ROLE thermograph_ro LOGIN;
GRANT CONNECT ON DATABASE thermograph TO thermograph_ro;
GRANT pg_read_all_data TO thermograph_ro;
```
## Iceberg (planned)
**Implementation spec: [`ICEBERG-HANDOFF.md`](./ICEBERG-HANDOFF.md)** — the
contract, environment constraints, acceptance criteria and anti-goals for the
Iceberg worker.
Iceberg lands as a sibling in this directory following the same three
conventions, so the query surface stays uniform:
1. **Env-keyed dispatch**`iceberg.sh <dev|beta|prod> "<sql>"`, same argument
shape as `dbq.sh`.
2. **Run the engine where the data is reachable** — the catalog/warehouse
dictates it (a Trino/DuckDB/pyiceberg invocation, containerised the same way),
rather than exposing new network endpoints.
3. **A dedicated read-only identity** — the Iceberg equivalent of
`thermograph_ro`, so the same "read-only enforced by the system, not by
convention" property holds.