From d51de7700abe44df1b9ee2e84479353a679395c3 Mon Sep 17 00:00:00 2001 From: Chris Munns Date: Thu, 18 Jun 2026 18:21:21 -0400 Subject: [PATCH] Enforce read-only sessions on diagnostic scripts Export PGOPTIONS with default_transaction_read_only=on (plus statement and lock timeouts on the fast diagnostics) so status/preflight/compare checks cannot write and can't hang on a busy source. verify-migration gets read-only only, since it manages its own per-table COUNT(*) timeout. --- pgcopydb-helpers/AGENTS.md | 10 ++++++++-- pgcopydb-helpers/check-cdc-status.sh | 7 +++++++ pgcopydb-helpers/check-migration-status.sh | 7 +++++++ pgcopydb-helpers/compare-pg-params.sh | 7 +++++++ pgcopydb-helpers/preflight-check.sh | 7 +++++++ pgcopydb-helpers/verify-migration.sh | 8 ++++++++ 6 files changed, 44 insertions(+), 2 deletions(-) diff --git a/pgcopydb-helpers/AGENTS.md b/pgcopydb-helpers/AGENTS.md index 14d5f88..60ee6a4 100644 --- a/pgcopydb-helpers/AGENTS.md +++ b/pgcopydb-helpers/AGENTS.md @@ -34,6 +34,8 @@ Compares performance-relevant PostgreSQL parameters between source and target da **Requires:** `PGCOPYDB_SOURCE_PGURI`, `PGCOPYDB_TARGET_PGURI` +**Read-only** — connects with `default_transaction_read_only=on`, a statement timeout, and a lock timeout; makes no modifications. + --- #### `verify-migration.sh` @@ -75,7 +77,7 @@ Verifies that all data was copied correctly from source to target after a migrat **Requires:** `PGCOPYDB_SOURCE_PGURI`, `PGCOPYDB_TARGET_PGURI` -**Read-only** — makes no modifications to either database. +**Read-only** — connects with `default_transaction_read_only=on`; makes no modifications to either database. No global statement timeout: exact-count `COUNT(*)` queries set their own via `--exact-count-timeout`. --- @@ -98,7 +100,7 @@ Validates all migration prerequisites before starting `pgcopydb clone --follow`. **Exit code:** 0 if no FAILs, 1 if any FAILs. -**Read-only** — makes no modifications to either database. +**Read-only** — connects with `default_transaction_read_only=on`, a statement timeout, and a lock timeout; makes no modifications to either database. --- @@ -254,6 +256,8 @@ Displays a full migration progress dashboard: phase completion status, table/ind **Requires:** `PGCOPYDB_TARGET_PGURI` (for active operations query). Reads from the most recent `~/migration_*` directory. +**Read-only** — connects with `default_transaction_read_only=on`, a statement timeout, and a lock timeout; makes no modifications. + --- #### `check-cdc-status.sh` @@ -277,6 +281,8 @@ Displays CDC-specific replication progress: apply and streaming LSN positions, b **Requires:** `PGCOPYDB_SOURCE_PGURI`, `PGCOPYDB_TARGET_PGURI` +**Read-only** — connects with `default_transaction_read_only=on`, a statement timeout, and a lock timeout; makes no modifications. + --- ### Recovery diff --git a/pgcopydb-helpers/check-cdc-status.sh b/pgcopydb-helpers/check-cdc-status.sh index 9d9b184..1e2cb2b 100755 --- a/pgcopydb-helpers/check-cdc-status.sh +++ b/pgcopydb-helpers/check-cdc-status.sh @@ -15,6 +15,13 @@ source ~/.env set +a set -u +# --- Read-only safety belt --- +# This script only reads. default_transaction_read_only blocks any accidental +# write, and the statement/lock timeouts keep a check from hanging on a busy +# database — important when querying the live source. Exported so every psql call +# inherits it. +export PGOPTIONS='-c default_transaction_read_only=on -c statement_timeout=30000 -c lock_timeout=5000' + # --- Configuration --- MIGRATION_DIR="${MIGRATION_DIR:-$(ls -dt ~/migration_*/ 2>/dev/null | head -1 || true)}" diff --git a/pgcopydb-helpers/check-migration-status.sh b/pgcopydb-helpers/check-migration-status.sh index 7e6ed8a..8641c68 100644 --- a/pgcopydb-helpers/check-migration-status.sh +++ b/pgcopydb-helpers/check-migration-status.sh @@ -27,6 +27,13 @@ source ~/.env set +a set -u +# --- Read-only safety belt --- +# This script only reads. default_transaction_read_only blocks any accidental +# write, and the statement/lock timeouts keep a check from hanging on a busy +# database — important when querying the live source. Exported so every psql call +# inherits it. +export PGOPTIONS='-c default_transaction_read_only=on -c statement_timeout=30000 -c lock_timeout=5000' + MIGRATION_DIR="${MIGRATION_DIR:-$(ls -dt ~/migration_*/ 2>/dev/null | head -1 || true)}" if [ -z "$MIGRATION_DIR" ]; then echo -e "${RED}✗ No migration directory found${NC}" diff --git a/pgcopydb-helpers/compare-pg-params.sh b/pgcopydb-helpers/compare-pg-params.sh index eb3a99c..e1a4188 100644 --- a/pgcopydb-helpers/compare-pg-params.sh +++ b/pgcopydb-helpers/compare-pg-params.sh @@ -17,6 +17,13 @@ source ~/.env set +a set -u +# --- Read-only safety belt --- +# This script only reads. default_transaction_read_only blocks any accidental +# write, and the statement/lock timeouts keep a diagnostic from hanging on a busy +# database — important when querying the live source. Exported so every psql call +# inherits it. +export PGOPTIONS='-c default_transaction_read_only=on -c statement_timeout=30000 -c lock_timeout=5000' + if [ -z "${PGCOPYDB_SOURCE_PGURI:-}" ] || [ -z "${PGCOPYDB_TARGET_PGURI:-}" ]; then echo "ERROR: PGCOPYDB_SOURCE_PGURI and PGCOPYDB_TARGET_PGURI must be set in ~/.env" exit 1 diff --git a/pgcopydb-helpers/preflight-check.sh b/pgcopydb-helpers/preflight-check.sh index d4275f2..d21c645 100755 --- a/pgcopydb-helpers/preflight-check.sh +++ b/pgcopydb-helpers/preflight-check.sh @@ -20,6 +20,13 @@ source ~/.env set +a set -u +# --- Read-only safety belt --- +# This script only reads. default_transaction_read_only blocks any accidental +# write, and the statement/lock timeouts keep a check from hanging on a busy +# database — important when querying the live source. Exported so every psql call +# inherits it. +export PGOPTIONS='-c default_transaction_read_only=on -c statement_timeout=30000 -c lock_timeout=5000' + if [ -z "${PGCOPYDB_SOURCE_PGURI:-}" ] || [ -z "${PGCOPYDB_TARGET_PGURI:-}" ]; then echo "ERROR: PGCOPYDB_SOURCE_PGURI and PGCOPYDB_TARGET_PGURI must be set in ~/.env" exit 1 diff --git a/pgcopydb-helpers/verify-migration.sh b/pgcopydb-helpers/verify-migration.sh index 8532373..1ebc4c5 100755 --- a/pgcopydb-helpers/verify-migration.sh +++ b/pgcopydb-helpers/verify-migration.sh @@ -33,6 +33,14 @@ source ~/.env set +a set -u +# ── Read-only safety belt ───────────────────────────────────────────────────── +# verify only reads. default_transaction_read_only blocks any accidental write. +# No global statement_timeout here: exact-count COUNT(*) queries set their own +# (--exact-count-timeout), and a short lock_timeout could turn a transient lock on +# a busy source into a false verification failure. Exported so every psql call +# inherits it. +export PGOPTIONS='-c default_transaction_read_only=on' + if [[ -z "${PGCOPYDB_SOURCE_PGURI:-}" || -z "${PGCOPYDB_TARGET_PGURI:-}" ]]; then echo "ERROR: PGCOPYDB_SOURCE_PGURI and PGCOPYDB_TARGET_PGURI must be set in ~/.env" exit 1