#!/usr/bin/env bash # dbq -- run READ-ONLY SQL against any Thermograph Postgres (LAN dev / beta / prod). # # infra/ops/dbq.sh dev "select count(*) from climate_history" # infra/ops/dbq.sh prod -tA "select max(date) from climate_history" # infra/ops/dbq.sh beta -c '\dt' # echo "select 1" | infra/ops/dbq.sh prod -f - # # Why exec-into-the-container instead of a connection string: # none of the databases are exposed over TCP. Each listens only on its private # docker network -- and prod's is a Swarm *overlay* (10.0.2.0/24) that the host # itself cannot route to, so `ssh -L` works for beta but is impossible for prod. # Publishing 5432 would mean new firewall + Swarm endpoint changes on production. # Running psql *inside* the db container works identically in all three # environments with no ports, no tunnels and no infra changes -- locally for LAN # dev, over SSH for beta/prod. # # Safety: always connects as `thermograph_ro`, a NOSUPERUSER role granted only # pg_read_all_data. Read-only is enforced by Postgres itself, not by convention, # so a stray INSERT/DDL fails with "permission denied" even against prod. (The # app's own `thermograph` role is a superuser -- deliberately not used here.) # # Any extra arguments are passed straight through to psql, so -tA, -c, -f, -x, # --csv etc. all work. Stdin is forwarded, so `-f -` reads piped SQL. set -euo pipefail KEYFILE="${THERMOGRAPH_AGENT_KEY:-$HOME/.ssh/thermograph_agent_ed25519}" DB_USER="${THERMOGRAPH_DB_QUERY_USER:-thermograph_ro}" DB_NAME="${THERMOGRAPH_DB_NAME:-thermograph}" usage() { sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//' >&2 echo "environments: dev | beta | prod" >&2 exit 2 } env_name="${1:-}" [ -n "$env_name" ] || usage shift case "$env_name" in dev) filter=thermograph-dev-db ; ssh_target= ;; beta) filter=thermograph-db-1 ; ssh_target=agent@75.119.132.91 ;; prod) filter=thermograph_db ; ssh_target=agent@169.58.46.181 ;; -h|--help) usage ;; *) echo "dbq: unknown environment '$env_name' (want: dev|beta|prod)" >&2; exit 2 ;; esac [ $# -gt 0 ] || usage # Quote the psql arguments so they survive the remote shell intact. psql_args=$(printf '%q ' "$@") # The container name is resolved on the target host at call time: prod's Swarm # task name changes on every redeploy, so it can never be hardcoded. remote=$(cat <&2; exit 1; } exec docker exec -i "\$cid" psql -U ${DB_USER} -d ${DB_NAME} -v ON_ERROR_STOP=1 ${psql_args} REMOTE ) if [ -z "$ssh_target" ]; then exec bash -c "$remote" else exec ssh -i "$KEYFILE" -o StrictHostKeyChecking=no -o ConnectTimeout=15 \ "$ssh_target" "$remote" fi