#!/usr/bin/env bash # restore.sh [--db dci_restore] [--swap] # local path (copied to the data node) or a remote path under /srv/dci/backups/pg/ (or `latest`) # Restores into a SEPARATE database (default dci_restore) so production is never touched by accident. # --swap after a successful restore, stop api/scheduler, rename dci → dci_old_ and dci_restore → dci, # restart. (Reversible: rename back.) source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" dump="${1:-}"; [[ -n $dump ]] || die "usage: restore.sh [--db name] [--swap]" shift db="dci_restore"; swap=0 while [[ $# -gt 0 ]]; do case "$1" in --db) db="$2"; shift ;; --swap) swap=1 ;; *) die "unknown flag $1" ;; esac; shift done [[ $db =~ ^[a-z_][a-z0-9_]*$ ]] || die "bad db name" [[ $db != dci ]] || die "refusing to restore directly into 'dci' — restore into dci_restore and use --swap" if [[ $dump == latest ]]; then remote_dump="$(remote data "ls -1t $BACKUP_DIR/pg/*.dump | head -1")" elif [[ -f $dump ]]; then log "copy $dump → $(host_for data):$BACKUP_DIR/pg/" rsync -az --info=progress2 -e "ssh ${SSH_OPTS[*]}" "$dump" "$(host_for data):$BACKUP_DIR/pg/" remote_dump="$BACKUP_DIR/pg/$(basename "$dump")" else remote_dump="$dump" remote data "test -f '$remote_dump'" || die "no such file on data node: $remote_dump" fi in_container="/backups/$(basename "$remote_dump")" # /srv/dci/backups/pg is mounted at /backups in postgres log "restore $remote_dump → database $db" compose data "exec -T postgres psql -U dci -d postgres -v ON_ERROR_STOP=1 -c \"DROP DATABASE IF EXISTS $db WITH (FORCE)\" -c \"CREATE DATABASE $db OWNER dci TEMPLATE template0 ENCODING 'UTF8' LOCALE 'C.UTF-8'\"" compose data "exec -T postgres psql -U dci -d $db -v ON_ERROR_STOP=1 -c 'CREATE EXTENSION IF NOT EXISTS pg_trgm' -c 'CREATE EXTENSION IF NOT EXISTS unaccent'" compose data "exec -T postgres pg_restore -U dci -d $db --no-owner --role=dci --jobs=4 --exit-on-error $in_container" compose data "exec -T postgres psql -U dci -d $db -Atc \"select 'tables: '||count(*) from information_schema.tables where table_schema='public'\" -Atc \"select 'facilities: '||count(*) from facilities\" -Atc \"select 'migrations: '||count(*) from _dci_migrations\"" || true ok "restored into $db" if [[ $swap == 1 ]]; then stamp="$(date +%Y%m%d%H%M)" warn "swapping $db → dci (old data kept as dci_old_$stamp)" compose data "stop api scheduler worker-maint" || true compose crawl "stop worker worker-b" || warn "crawl workers not stopped (unreachable?) — stop them manually" compose data "exec -T postgres psql -U dci -d postgres -v ON_ERROR_STOP=1 \ -c \"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IN ('dci','$db') AND pid <> pg_backend_pid()\" \ -c \"ALTER DATABASE dci RENAME TO dci_old_$stamp\" -c \"ALTER DATABASE $db RENAME TO dci\"" compose data "up -d api scheduler" compose crawl "up -d" || true ok "swapped; previous database is dci_old_$stamp (drop it when satisfied: psql.sh -c 'DROP DATABASE dci_old_$stamp')" fi