Skip to content

Instantly share code, notes, and snippets.

@patgmiller
Last active December 23, 2024 17:44
Show Gist options
  • Save patgmiller/492cb43bedcad2872fd670ed4f8921bd to your computer and use it in GitHub Desktop.
Save patgmiller/492cb43bedcad2872fd670ed4f8921bd to your computer and use it in GitHub Desktop.
postgresql dump and restore script
#!/usr/bin/env bash
set -euo pipefail
SCRIPT=$(basename "$0")
FILE=
DEBUG=0
DRYRUN=0
VERBOSE=0
declare {HOST,DST_HOST}=localhost
declare {DBASE,DST_DBASE}=
declare {USER,DST_USER}=
declare {PORT,DST_PORT}=5432
PSQLEXT=( ) # psql extensions array
BKPARGS=(-O -x --quote-all-identifiers) # pg_dump export arguments
DSTARGS=(-O -x) # pg_restore arguments
function usage() {
printf "Export / Dump a source postgres database and optionally import / restore the output into another destination database. Accepts positional arguments to limit which tables are included.
Usage %s -f FILE [-u user] [-u database] [-U dst-user] [-D dst-database] [table [tables...]]
Source Database
-h, --host HOST [default: localhost] Connection host for source database
-d, --database DB Source database to export from
-u, --user USER User to connect with for source database
-p, --port PORT [default: 5432] Connection port on source database
-w, --password Prompt for password when connecting to source database
Destination Database
-H, --dst-host HOST [default: localhost] Connection host for destination database
-D, --dst-database DB Destination database to import into
-U, --dst-user USER User to connect with for destination database
-P, --dst-port PORT [default: 5432] Connection port on destination database
-W, --dst-password Prompt for password when connecting to destination database
Backup Options
-f, --file FILE [REQUIRED] Output file or directory location for database backup
-j, --jobs JOBS Set number of parallel jobs to run in postgres for backup
Restore Options
-c, --clean Clean (drop) database objects before recreating
-J, --dst-jobs JOBS Set number of parallel jobs to run in postgres for restore
Extensions
-e, --extensions E1,E2 Comma separated list of postsgres extensions to create in new database
Global options
-v, --verbose Print commands that are to be executed
-r, --dry-run Only show actions that would be applied, DO NOT execute commands
-x, --debug Enable shell debugging
-?, --help display this help message
" "$SCRIPT"
}
function printUsageExit() {
printf '%s\n\n' "$1"
usage && exit 1
}
function cleanDropOnRestore() {
DSTARGS=( "${DSTARGS[@]}" "-c" )
}
function readPsqlExtensions() {
local ext="$1"
if ! IFS="," read -r -a PSQLEXT <<< "$ext"; then
printUsageExit 'Error while reading list of postgres sql extentions'
fi
}
function parallelJobs() {
# set # of parallel jobs for backup / restore
local target="$1" jobs="$2"
# only set if $OPTARG > 0
if [[ $jobs -gt 0 ]]; then
if [[ "$target" -eq 1 ]]; then
BKPARGS=( "-j${jobs}" "${BKPARGS[@]//-j[0-9]}" )
elif [[ "$target" -eq 2 ]]; then
DSTARGS=( "-j${jobs}" "${DSTARGS[@]//-j[0-9]}" )
fi
fi
}
function passwordPrompt() {
if [[ "$1" -eq 1 ]]; then
# prompt for pass during step 1
BKPARGS=( "${BKPARGS[@]}" "-W" )
elif [[ "$1" -eq 2 ]]; then
# prompt for pass during step 2
DSTARGS=( "${DSTARGS[@]}" "-W" )
fi
}
while getopts ":d:D:h:H:j:J:p:P:u:U:-:e:f:cxrvwW" opt
do
case $opt in
-)
case "${OPTARG}" in
clean) cleanDropOnRestore ;;
database)
DBASE="${!OPTIND}"
shift 1
;;
dst-database)
DST_DBASE="${!OPTIND}"
shift 1
;;
host)
HOST="${!OPTIND}"
shift 1
;;
dst-host)
DST_HOST="${!OPTIND}"
shift 1
;;
port)
PORT="${!OPTIND}"
shift 1
;;
dst-port)
DST_PORT="${!OPTIND}"
shift 1
;;
extensions)
readPsqlExtensions "${!OPTIND}"
shift 1
;;
file)
FILE="${!OPTIND}"
shift 1
;;
jobs)
parallelJobs 1 "${!OPTIND}"
shift 1
;;
dst-jobs)
parallelJobs 2 "${!OPTIND}"
shift 1
;;
password) passwordPrompt 1 ;;
dst-password) passwordPrompt 2 ;;
dry-run) DRYRUN=1 ;;
verbose) VERBOSE=1 ;;
debug) DEBUG=1 ;;
help) usage && exit 1 ;;
*)
if [ "$OPTERR" = 1 ] && [ "${OPTSPEC:0:1}" != ":" ]; then
echo "Unknown option --${OPTARG}" >&2
fi
;;
esac;;
c) cleanDropOnRestore ;;
d) DBASE="${OPTARG}" ;;
D) DST_DBASE="${OPTARG}" ;;
h) HOST="${OPTARG}" ;;
H) DST_HOST="${OPTARG}" ;;
j) parallelJobs 1 "$OPTARG" ;;
J) parallelJobs 2 "$OPTARG" ;;
p) PORT="${OPTARG}" ;;
P) DST_PORT="${OPTARG}" ;;
u) USER="${OPTARG}" ;;
U) DST_USER="${OPTARG}" ;;
w) passwordPrompt 1 ;;
W) passwordPrompt 2 ;;
f) FILE="${OPTARG}" ;;
e) readPsqlExtensions "${OPTARG}" ;;
r) DRYRUN=1 ;;
v) VERBOSE=1 ;;
x) DEBUG=1 ;;
?|*) usage && exit 1 ;; esac
done
shift $((OPTIND - 1))
[[ $DEBUG -eq 1 ]] && set -x
if [[ -z "${FILE}" ]]; then
printUsageExit 'Missing required backup location: -f FILE'
fi
# backup
if [ -n "${DBASE}" ] && [ -n "${USER}" ]; then
if [[ -d "${FILE}" ]]; then
rm -rf "${FILE}" # remove existing back up director
fi
mkdir -p "${FILE}" # make sure location exists
TABLES=( $@ )
# business end ;D
PSQL="pg_dump -h ${HOST} -p ${PORT} -U ${USER} ${DBASE} -Fd ${BKPARGS[*]} -f ${FILE}"
TBLCMD=""
for table in "${TABLES[@]}"; do
TBLCMD+="-t ${table} "
done
[ $VERBOSE -eq 1 ] && echo "${PSQL[*]} ${TBLCMD[*]}"
[ $DRYRUN -eq 0 ] && (eval "${PSQL[@]}" "${TBLCMD[@]}" || exit $?)
fi
# restore
if [ -n "${DST_DBASE}" ] && [ -n "${DST_USER}" ]; then
# create extensions
if [[ ${#PSQLEXT[@]} -gt 0 ]]; then
for ext in "${PSQLEXT[@]}"; do
PSQL="psql -h ${DST_HOST} -p ${DST_PORT} -U ${DST_USER} ${DST_DBASE} -c 'create extension if not exists \"$ext\";'"
[ $VERBOSE -eq 1 ] && echo "${PSQL[*]}"
[ $DRYRUN -eq 0 ] && (eval "${PSQL[@]}" || exit $?)
done
fi
# business end ;D
PSQL="pg_restore -h ${DST_HOST} -p ${DST_PORT} -U ${DST_USER} -d ${DST_DBASE} ${DSTARGS[*]} ${FILE}"
[ $VERBOSE -eq 1 ] && echo "${PSQL[*]}"
[ $DRYRUN -eq 0 ] && (eval "${PSQL[@]}" || exit $?)
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment