Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / scripts/restore-tenant.sh |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | bdacd9d0b4d5998d25fc74bf4007f4c3ec538c7a |
| Branch | deploy-and-ops |
| Path or archive member | scripts/restore-tenant.sh |
| Original SHA-256 | e14f5841c587fe6f841a2dd6065fd961b2509234957c4777634e8b44341ed4b5 |
| Git blob SHA | 822a270c167e1456c55026c2fda2d0600a838a28 |
| Reading-copy SHA-256 | e14f5841c587fe6f841a2dd6065fd961b2509234957c4777634e8b44341ed4b5 |
Recovered source transcript
Download complete source text ↓94 lines · 5.3 KB
#!/usr/bin/env bash
# Restore a tenant backup into a FRESH database, mirroring cmd/provision-tenant's prep, then verify.
# DESTRUCTIVE to the target database. Before a SAME NAME restore, take the tenant offline first
# (directory.SetStatus offline, or UPDATE tenant_directory SET status='offline'), because the router does
# not filter by status and would route live requests into a database being recreated; mark it active again
# only after a green verify.
#
# A tenant database has TWO schemas (public and the segregated eeo) and the migrator owns every table, so:
# - the target is created from template0 with CREATE EXTENSION vector and the schema and connect grants
# in place (the superuser only steps), exactly as provision-tenant does, BEFORE the restore;
# - pg_restore runs as the MIGRATOR (never with --no-owner) so the tables come back migrator owned;
# - verify compares per table row counts (public and eeo) and a vector sample, and re asserts the
# isolation invariants.
# Pin the client to PostgreSQL 16 (set PGBIN). Do NOT combine -j with --single-transaction.
#
# Usage:
# scripts/restore-tenant.sh \
# -admin-dsn "host=/tmp user=postgres dbname=postgres sslmode=disable" \
# -migrator-dsn "host=H port=5432 user=missionmatch_migrator password=... dbname=postgres sslmode=disable" \
# -source tenant_acme -target tenant_acme_restored -dump FILE
set -euo pipefail
PGBIN="${PGBIN:-}"
admin=""; mig=""; source=""; target=""; dump=""
while [ $# -gt 0 ]; do
case "$1" in
-admin-dsn) admin="$2"; shift 2 ;;
-migrator-dsn) mig="$2"; shift 2 ;;
-source) source="$2"; shift 2 ;;
-target) target="$2"; shift 2 ;;
-dump) dump="$2"; shift 2 ;;
*) echo "restore-tenant: unknown argument $1" >&2; exit 2 ;;
esac
done
for v in admin mig source target dump; do
eval "val=\$$v"
[ -n "$val" ] || { echo "restore-tenant: -$v is required" >&2; exit 2; }
done
# CREATE DATABASE cannot be parameterized, so the name must be validated, never interpolated blindly.
echo "$target" | grep -Eq '^[a-z][a-z0-9_]{2,62}$' || { echo "restore-tenant: target name $target is not a safe identifier" >&2; exit 2; }
[ -f "$dump" ] || { echo "restore-tenant: dump file $dump not found" >&2; exit 2; }
psql="${PGBIN:+$PGBIN/}psql"
pgrestore="${PGBIN:+$PGBIN/}pg_restore"
# Swap the database name in a keyword DSN (dbname=...).
swapdb() { echo "$1" | sed -E "s/dbname=[a-zA-Z0-9_]+/dbname=$2/"; }
admTarget="$(swapdb "$admin" "$target")"
migTarget="$(swapdb "$mig" "$target")"
migSource="$(swapdb "$mig" "$source")"
echo "restore-tenant: 1/4 create the target database $target from template0 (superuser)"
"$psql" "$admin" -v ON_ERROR_STOP=1 -c "CREATE DATABASE $target TEMPLATE template0"
echo "restore-tenant: 2/4 prepare the target (extension, schema and connect grants), mirroring provision-tenant"
"$psql" "$admTarget" -v ON_ERROR_STOP=1 <<SQL
CREATE EXTENSION IF NOT EXISTS vector;
GRANT USAGE ON SCHEMA public TO missionmatch_app, missionmatch_compliance;
GRANT CREATE, USAGE ON SCHEMA public TO missionmatch_migrator;
GRANT CREATE ON DATABASE $target TO missionmatch_migrator;
REVOKE CONNECT ON DATABASE $target FROM PUBLIC;
GRANT CONNECT ON DATABASE $target TO missionmatch_app, missionmatch_compliance, missionmatch_migrator;
SQL
echo "restore-tenant: 3/4 restore as the superuser (the dump's ALTER OWNER statements still land every table"
echo " owned by the migrator, while the superuser can also restore the extension comment that"
echo " a migrator only restore cannot; parallel, hard fail)"
"$pgrestore" -d "$admTarget" --exit-on-error -j 4 "$dump"
echo "restore-tenant: 4/4 verify"
fail=0
# (a) per table row count equality across public and eeo (counted as the migrator, never the app role,
# which is denied on the eeo schema and would either error or read zero).
tables="$("$psql" "$migSource" -tAc "SELECT schemaname||'.'||tablename FROM pg_tables WHERE schemaname IN ('public','eeo') ORDER BY 1")"
for t in $tables; do
s="$("$psql" "$migSource" -tAc "SELECT count(*) FROM $t")"
d="$("$psql" "$migTarget" -tAc "SELECT count(*) FROM $t")"
if [ "$s" != "$d" ]; then echo " MISMATCH $t: source=$s target=$d"; fail=1; fi
done
matched="$(echo "$tables" | wc -l | tr -d ' ')"
[ "$fail" = 0 ] && echo " row counts match across $matched tables (public and eeo)"
# (b) a sample query that proves the pgvector data came back.
vec="$("$psql" "$migTarget" -tAc "SELECT count(*) FROM candidates WHERE resume_embedding IS NOT NULL")"
echo " candidates with a restored vector: $vec"
# (c) the isolation invariants: pgvector present, candidates RLS forced, eeo present, app denied on eeo.
inv="$("$psql" "$admTarget" -tAc "SELECT (SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname='vector')) AND (SELECT relforcerowsecurity FROM pg_class WHERE relname='candidates') AND (SELECT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname='eeo')) AND NOT has_table_privilege('missionmatch_app','eeo.candidate_demographics','SELECT')")"
[ "$inv" = "t" ] && echo " invariants hold: pgvector present, candidates RLS forced, eeo present, app denied on eeo" || { echo " INVARIANTS FAILED"; fail=1; }
if [ "$fail" = 0 ]; then
echo "restore-tenant: DONE and VERIFIED. Mark the tenant active again in the directory."
else
echo "restore-tenant: VERIFY FAILED, leave the tenant offline and investigate" >&2
exit 1
fi