11 Commits

Author SHA1 Message Date
b979cdd51f Update with commands in array for calls, logging for single file restore
All commands are now run through a single array variable
All logs are in logs folder for the restore which is a sub folder to where the SQL file is located
On restore abort if the basic database creation failed or skip to the next database in block restore
2025-10-17 10:39:55 +09:00
0b938f31c8 Fix postgresql drop restore for a single db dump 2025-10-16 16:06:46 +09:00
9f454ac2bf Fix log path override with "." folder name
We add the / before so we do not create ".logfile"
2025-10-16 15:35:44 +09:00
3eb09635f9 Remove default backup dir and do not create backup folder automatically
Fails if the backup folder does not exist or is not writable.
2025-10-15 18:46:02 +09:00
bb5c65818b Some more clean up for code
Fix ident get from file, they where subtly not correct
- wrong lookup for <10 postgresql version
- if version has a . inside the host/port lookups have to shift
- dry run notice for restore runs
2025-04-15 09:42:39 +09:00
01b434fca0 Postgresql version check calls update formatting 2025-04-15 08:44:00 +09:00
806247996d Clean up all scripts to match bash linting rules
pg_db_dump_file log parameter never set the log folder, it was always overwritten

pg_drop_restore had a double ;; inside which stopped the script from working

All: remove -a flag for Amazon Linux OS. This was only for V1 type and we do no longer support this (or use it) and V2 AWS Linux is based more on a redhat type, so the -r flag will work

Various other formatting updates

Change all strange ls to find calls

Change all exec calls with params to params in array
2025-04-15 08:40:21 +09:00
7d2f75366d Clean up code for pg_db_dump_file
Fixes for wrong empty settings
Fix log path not set correct if given as parameter

Fix all shell linting problems
2025-04-14 15:58:41 +09:00
6adee6abc4 Do only set LOG_PATH if LOG_PATH is not set 2025-04-14 10:59:08 +09:00
fc1f5dcae7 Add shellcheckrc 2024-09-03 12:40:48 +09:00
fb092feabc Fix for "local" socket host name
if -h local is set, be sure to never set -h flag so we use the socket
and not host connection.

Needed for trust with socket flow (sudo -u)
2022-01-17 15:12:12 +09:00
4 changed files with 678 additions and 413 deletions

2
.shellcheckrc Normal file
View File

@@ -0,0 +1,2 @@
shell=bash
external-sources=true

View File

@@ -11,7 +11,7 @@ set -e -u -o pipefail
function usage () function usage ()
{ {
cat <<- EOT cat <<- EOT
Usage: ${0##/*/} [-t] [-s] [-g] [-c] [-r|-a] [-k <number to keep>] [-n] [-b <path>] [-i <postgreSQL version>] [-d <dump database name> [-d ...]] [-e <exclude dump> [-e ...]] [-u <db user>] [-h <db host>] [-p <db port>] [-l <db password>] [-L <log path>] Usage: ${0##/*/} [-t] [-s] [-g] [-c] [-r] [-k <number to keep>] [-n] [-b <path>] [-i <postgreSQL version>] [-d <dump database name> [-d ...]] [-e <exclude dump> [-e ...]] [-u <db user>] [-h <db host>] [-p <db port>] [-l <db password>] [-L <log path>]
-t: test usage, no real backup is done -t: test usage, no real backup is done
-s: turn ON ssl mode, default mode is off -s: turn ON ssl mode, default mode is off
@@ -28,7 +28,6 @@ function usage ()
-p <db port>: default port is '5432' -p <db port>: default port is '5432'
-l <db password>: default password is empty -l <db password>: default password is empty
-r: use redhat base paths instead of debian -r: use redhat base paths instead of debian
-a: use amazon base paths instead of debian
-L <log path>: where to put the dump log file, if not set tries to use PostgreSQL log folder -L <log path>: where to put the dump log file, if not set tries to use PostgreSQL log folder
EOT EOT
} }
@@ -56,10 +55,19 @@ OPTARG_REGEX="^-";
# log path # log path
LOG_PATH=''; LOG_PATH='';
# base path for PostgreSQL binary # base path for PostgreSQL binary
DBPATH_BASE=''; # DBPATH_BASE='';
# defaults # defaults
_BACKUPDIR='/mnt/backup/db_dumps_fc/'; _BACKUPDIR='';
_DB_VERSION=$(pgv=$(pg_dump --version| grep "pg_dump" | cut -d " " -f 3); if [[ $(echo "${pgv}" | cut -d "." -f 1) -ge 10 ]]; then echo "${pgv}" | cut -d "." -f 1; else echo "${pgv}" | cut -d "." -f 1,2; fi ); _DB_VERSION=$(
pgv=$(
pg_dump --version | grep "pg_dump" | cut -d " " -f 3
);
if [[ $(echo "${pgv}" | cut -d "." -f 1) -ge 10 ]]; then
echo "${pgv}" | cut -d "." -f 1;
else
echo "${pgv}" | cut -d "." -f 1,2;
fi
);
_DB_USER='postgres'; _DB_USER='postgres';
_DB_PASSWD=''; _DB_PASSWD='';
_DB_HOST=''; _DB_HOST='';
@@ -67,12 +75,11 @@ _DB_PORT=5432;
_EXCLUDE=''; # space separated list of database names _EXCLUDE=''; # space separated list of database names
_INCLUDE=''; # space seperated list of database names _INCLUDE=''; # space seperated list of database names
REDHAT=0; REDHAT=0;
AMAZON=0; # CONN_DB_HOST='';
CONN_DB_HOST='';
ERROR=0; ERROR=0;
# set options # set options
while getopts ":ctsgnk:b:i:d:e:u:h:p:l:L:ram" opt; do while getopts ":ctsgnk:b:i:d:e:u:h:p:l:L:rm" opt; do
# pre test for unfilled # pre test for unfilled
if [ "${opt}" = ":" ] || [[ "${OPTARG-}" =~ ${OPTARG_REGEX} ]]; then if [ "${opt}" = ":" ] || [[ "${OPTARG-}" =~ ${OPTARG_REGEX} ]]; then
if [ "${opt}" = ":" ]; then if [ "${opt}" = ":" ]; then
@@ -125,79 +132,93 @@ while getopts ":ctsgnk:b:i:d:e:u:h:p:l:L:ram" opt; do
fi; fi;
# set options # set options
case ${opt} in case ${opt} in
t|test) # t|test)
t)
TEST=1; TEST=1;
;; ;;
g|globals) # g|globals)
g)
GLOBALS=0; GLOBALS=0;
;; ;;
c|clean-up-before) # c|clean-up-before)
c)
PRE_RUN_CLEAN_UP=1; PRE_RUN_CLEAN_UP=1;
;; ;;
s|sslmode) # s|sslmode)
s)
SSLMODE=enable; SSLMODE=enable;
;; ;;
k|keep) # k|keep)
k)
KEEP=${OPTARG}; KEEP=${OPTARG};
;; ;;
n|number-keep) # n|number-keep)
n)
CLEAN_NUMBER=1; CLEAN_NUMBER=1;
;; ;;
b|backuppath) # b|backuppath)
b)
if [ -z "${BACKUPDIR}" ]; then if [ -z "${BACKUPDIR}" ]; then
BACKUPDIR="${OPTARG}"; BACKUPDIR="${OPTARG}";
fi; fi;
;; ;;
i|ident) # i|ident)
i)
if [ -z "${DB_VERSION}" ]; then if [ -z "${DB_VERSION}" ]; then
DB_VERSION=${OPTARG}; DB_VERSION=${OPTARG};
SET_IDENT=1; SET_IDENT=1;
fi; fi;
;; ;;
u|user) # u|user)
u)
if [ -z "${DB_USER}" ]; then if [ -z "${DB_USER}" ]; then
DB_USER=${OPTARG}; DB_USER=${OPTARG};
fi; fi;
;; ;;
h|hostname) # h|hostname)
h)
if [ -z "${DB_HOST}" ]; then if [ -z "${DB_HOST}" ]; then
DB_HOST=${OPTARG}; DB_HOST=${OPTARG};
fi; fi;
;; ;;
p|port) # p|port)
p)
if [ -z "${DB_PORT}" ]; then if [ -z "${DB_PORT}" ]; then
DB_PORT=${OPTARG}; DB_PORT=${OPTARG};
fi; fi;
;; ;;
l|login) # l|login)
l)
if [ -z "${DB_PASSWD}" ]; then if [ -z "${DB_PASSWD}" ]; then
DB_PASSWD=${OPTARG}; DB_PASSWD=${OPTARG};
fi; fi;
;; ;;
d|database) # d|database)
if [ ! -z "${INCLUDE}" ]; then d)
if [ -z "${INCLUDE}" ]; then
INCLUDE=${INCLUDE}" "; INCLUDE=${INCLUDE}" ";
fi; fi;
INCLUDE=${INCLUDE}${OPTARG}; INCLUDE=${INCLUDE}${OPTARG};
;; ;;
e|exclude) # e|exclude)
if [ ! -z "${EXCLUDE}" ]; then e)
if [ -z "${EXCLUDE}" ]; then
EXCLUDE=${EXCLUDE}" "; EXCLUDE=${EXCLUDE}" ";
fi; fi;
EXCLUDE=${EXCLUDE}${OPTARG}; EXCLUDE=${EXCLUDE}${OPTARG};
;; ;;
r|redhat) # r|redhat)
r)
REDHAT=1; REDHAT=1;
;; ;;
a|amazon) # L|logpath)
AMAZON=1; L)
;; if [ -z "${LOG_PATH}" ]; then
L|logpath)
if [ ! -z "{$LOG_PATH}" ]; then
LOG_PATH="${OPTARG}"; LOG_PATH="${OPTARG}";
fi; fi;
;; ;;
m|manual) # m|manual)
m)
usage; usage;
exit 0; exit 0;
;; ;;
@@ -216,13 +237,8 @@ if [ ${ERROR} -eq 1 ]; then
exit 0; exit 0;
fi; fi;
if [ "${REDHAT}" -eq 1 ] && [ "${AMAZON}" -eq 1 ]; then
echo "You cannot set the -a and -r flag at the same time";
exit 0;
fi;
# if we have numeric keep and keep number is set to 0 abort # if we have numeric keep and keep number is set to 0 abort
if [ ${CLEAN_NUMBER} -eq 1 ] && [ ${KEEP} -lt 1 ]; then if [ "${CLEAN_NUMBER}" -eq 1 ] && [ "${KEEP}" -lt 1 ]; then
echo "If keep in numbers is on, keep must be at least 1 or higher"; echo "If keep in numbers is on, keep must be at least 1 or higher";
exit 0; exit 0;
fi; fi;
@@ -233,7 +249,7 @@ for name in BACKUPDIR DB_VERSION DB_USER DB_PASSWD DB_HOST DB_PORT EXCLUDE INCLU
if [ -z "${!name}" ]; then if [ -z "${!name}" ]; then
# add the _ for the default name # add the _ for the default name
default="_"${name}; default="_"${name};
eval ${name}=\${!default}; declare $name=${!default}
fi; fi;
done; done;
@@ -243,20 +259,19 @@ if [ "${REDHAT}" -eq 1 ]; then
# This is also for Amazon NEWER (after 9.6) # This is also for Amazon NEWER (after 9.6)
PG_BASE_PATH="/usr/pgsql-"; PG_BASE_PATH="/usr/pgsql-";
# I assume that as default # I assume that as default
LOG_PATH="/var/lib/pgsql/${DB_VERSION}/data/log/"; if [ -z "${LOG_PATH}" ]; then
elif [ "${AMAZON}" -eq 1 ]; then LOG_PATH="/var/lib/pgsql/${DB_VERSION}/data/log";
# only older 9.6 or before fi;
PG_BASE_PATH="/usr/lib64/pgsql";
# LOG PATH, will be attached to DB VERSION
LOG_PATH="/var/lib/pgsql${DB_VERSION}/data/pg_log";
else else
# Debian base path # Debian base path
PG_BASE_PATH="/usr/lib/postgresql/"; PG_BASE_PATH="/usr/lib/postgresql/";
LOG_PATH="/var/log/postgresql/"; if [ -z "${LOG_PATH}" ]; then
LOG_PATH="/var/log/postgresql";
fi;
fi; fi;
# setup log before everything else # setup log before everything else
LOG="${LOG_PATH}pg_db_dump_file.log"; LOG="${LOG_PATH}/pg_db_dump_file.log";
# if we cannot write to the log file abort # if we cannot write to the log file abort
if [[ -f "${LOG}" && ! -w "${LOG}" ]] || [[ ! -f "${LOG}" && ! -w "${LOG_PATH}" ]]; then if [[ -f "${LOG}" && ! -w "${LOG}" ]] || [[ ! -f "${LOG}" && ! -w "${LOG_PATH}" ]]; then
echo "Cannot write to ${LOG} or create a new log file in ${LOG_PATH}"; echo "Cannot write to ${LOG} or create a new log file in ${LOG_PATH}";
@@ -267,7 +282,7 @@ exec &> >(tee -a "${LOG}");
# check DB port is valid number # check DB port is valid number
if ! [[ "${DB_PORT}" =~ ${PORT_REGEX} ]]; then if ! [[ "${DB_PORT}" =~ ${PORT_REGEX} ]]; then
echo "The port needs to be a valid number: ${_port}"; echo "The port needs to be a valid number: ${DB_PORT}";
exit 0; exit 0;
fi; fi;
@@ -278,13 +293,19 @@ else
BC_OK=0; BC_OK=0;
fi; fi;
# the default pg params
PG_PARAMS=("-U" "${DB_USER}" "-p" "${DB_PORT}")
# if DB_HOST is set, we need to add -h to the command line # if DB_HOST is set, we need to add -h to the command line
# if nothing is set, DB_HOST is set to local so we know this is a "port" connection for later automatic restore # if nothing is set, DB_HOST is set to local so we know this is a "port" connection for later automatic restore
if [ -z "${DB_HOST}" ]; then if [ -z "${DB_HOST}" ]; then
DB_HOST='local'; DB_HOST='local';
else else
CONN_DB_HOST='-h '${DB_HOST}; # CONN_DB_HOST='-h '${DB_HOST};
PG_PARAMS+=("-h" "${DB_HOST}");
fi; fi;
# copy to select
PG_PARAMS_SELECT=("${PG_PARAMS[@]}");
PG_PARAMS_SELECT+=("-d" "template1" "-t" "-A" "-F" "," "-X" "-q" "-c");
# set the binaries we need # set the binaries we need
PG_PATH=${PG_BASE_PATH}${DB_VERSION}'/bin/'; PG_PATH=${PG_BASE_PATH}${DB_VERSION}'/bin/';
@@ -295,7 +316,7 @@ DB_TYPE='pgsql';
db=''; db='';
# core abort if no core files found # core abort if no core files found
if [ ! -f ${PG_PSQL} ] || [ ! -f ${PG_DUMP} ] || [ ! -f ${PG_DUMPALL} ]; then if [ ! -f "${PG_PSQL}" ] || [ ! -f "${PG_DUMP}" ] || [ ! -f "${PG_DUMPALL}" ]; then
echo "One of the core binaries (psql, pg_dump, pg_dumpall) could not be found."; echo "One of the core binaries (psql, pg_dump, pg_dumpall) could not be found.";
echo "Search Path: ${PG_PATH}"; echo "Search Path: ${PG_PATH}";
echo "Perhaps manual ident set with -i is necessary"; echo "Perhaps manual ident set with -i is necessary";
@@ -303,26 +324,28 @@ if [ ! -f ${PG_PSQL} ] || [ ! -f ${PG_DUMP} ] || [ ! -f ${PG_DUMPALL} ]; then
exit 0; exit 0;
fi; fi;
if [ ! -d ${BACKUPDIR} ] ; then # not directory or length is zero
if ! mkdir ${BACKUPDIR} ; then if [ ! -d "${BACKUPDIR}" ] || [ -z "${BACKUPDIR}" ]; then
echo "Cannot create backup directory: ${BACKUPDIR}" echo "Backup directory does not exist: ${BACKUPDIR}";
exit 0; exit 0;
fi
fi fi
# check if we can write into that folder # check if we can write into that folder
touch ${BACKUPDIR}/tmpfile || echo "[!] touch failed"; touch "${BACKUPDIR}/tmpfile" || echo "[!] touch failed";
if [ ! -f ${BACKUPDIR}/tmpfile ]; then if [ ! -f "${BACKUPDIR}/tmpfile" ]; then
echo "Cannot write to ${BACKUPDIR}"; echo "Cannot write to ${BACKUPDIR}";
exit 0; exit 0;
else else
rm -f ${BACKUPDIR}/tmpfile; rm "${BACKUPDIR}/tmpfile";
fi; fi;
# if backupdir is "." rewrite to pwd # if backupdir is "." rewrite to pwd
if [ "${BACKUPDIR}" == '.' ]; then if [ "${BACKUPDIR}" == '.' ]; then
BACKUPDIR=$(pwd); BACKUPDIR=$(pwd);
fi; fi;
# check if we can connect to template1 table, if not we abort here # check if we can connect to template1 table, if not we abort here
connect=$(${PG_PSQL} -U "${DB_USER}" ${CONN_DB_HOST} -p ${DB_PORT} -d template1 -t -A -F "," -X -q -c "SELECT datname FROM pg_catalog.pg_database WHERE datname = 'template1';") || echo "[!] pgsql connect error"; _PG_PARAMS_SELECT=("${PG_PARAMS_SELECT[@]}");
_PG_PARAMS_SELECT+=("SELECT datname FROM pg_catalog.pg_database WHERE datname = 'template1';");
PG_COMMAND=("${PG_PSQL}" "${_PG_PARAMS_SELECT[@]}");
connect=$("${PG_COMMAND[@]}") || echo "[!] pgsql connect error";
if [ "${connect}" != "template1" ]; then if [ "${connect}" != "template1" ]; then
echo "Failed to connect to template1 with user '${DB_USER}' at host '${DB_HOST}' on port '${DB_PORT}'"; echo "Failed to connect to template1 with user '${DB_USER}' at host '${DB_HOST}' on port '${DB_PORT}'";
exit 0; exit 0;
@@ -330,7 +353,16 @@ fi;
# if we have an ident override set, set a different DUMP VERSION here than the automatic one # if we have an ident override set, set a different DUMP VERSION here than the automatic one
if [ "${SET_IDENT}" -eq 1 ]; then if [ "${SET_IDENT}" -eq 1 ]; then
DUMP_DB_VERSION=$(pgv=$(${PG_PATH}/pg_dump --version| grep "pg_dump" | cut -d " " -f 3); if [[ $(echo "${pgv}" | cut -d "." -f 1) -ge 10 ]]; then echo "${pgv}" | cut -d "." -f 1; else echo "${pgv}" | cut -d "." -f 1,2; fi ); DUMP_DB_VERSION=$(
pgv=$(
"${PG_PATH}/pg_dump" --version | grep "pg_dump" | cut -d " " -f 3
);
if [[ $(echo "${pgv}" | cut -d "." -f 1) -ge 10 ]]; then
echo "${pgv}" | cut -d "." -f 1;
else
echo "${pgv}" | cut -d "." -f 1,2;
fi
);
else else
DUMP_DB_VERSION=${DB_VERSION}; DUMP_DB_VERSION=${DB_VERSION};
fi; fi;
@@ -349,15 +381,15 @@ function convert_time
{ {
timestamp=${1}; timestamp=${1};
# round to four digits for ms # round to four digits for ms
timestamp=$(printf "%1.4f" $timestamp); timestamp=$(printf "%1.4f" "$timestamp");
# get the ms part and remove any leading 0 # get the ms part and remove any leading 0
ms=$(echo ${timestamp} | cut -d "." -f 2 | sed -e 's/^0*//'); ms=$(echo "${timestamp}" | cut -d "." -f 2 | sed -e 's/^0*//');
timestamp=$(echo ${timestamp} | cut -d "." -f 1); timestamp=$(echo "${timestamp}" | cut -d "." -f 1);
timegroups=(86400 3600 60 1); # day, hour, min, sec timegroups=(86400 3600 60 1); # day, hour, min, sec
timenames=("d" "h" "m" "s"); # day, hour, min, sec timenames=("d" "h" "m" "s"); # day, hour, min, sec
output=( ); output=( );
time_string=''; time_string='';
for timeslice in ${timegroups[@]}; do for timeslice in "${timegroups[@]}"; do
# floor for the division, push to output # floor for the division, push to output
if [ ${BC_OK} -eq 1 ]; then if [ ${BC_OK} -eq 1 ]; then
output[${#output[*]}]=$(echo "${timestamp}/${timeslice}" | bc); output[${#output[*]}]=$(echo "${timestamp}/${timeslice}" | bc);
@@ -369,8 +401,8 @@ function convert_time
done; done;
for ((i=0; i<${#output[@]}; i++)); do for ((i=0; i<${#output[@]}; i++)); do
if [ ${output[$i]} -gt 0 ] || [ ! -z "$time_string" ]; then if [ "${output[$i]}" -gt 0 ] || [ -n "$time_string" ]; then
if [ ! -z "${time_string}" ]; then if [ -n "${time_string}" ]; then
time_string=${time_string}" "; time_string=${time_string}" ";
fi; fi;
time_string=${time_string}${output[$i]}${timenames[$i]}; time_string=${time_string}${output[$i]}${timenames[$i]};
@@ -378,9 +410,9 @@ function convert_time
done; done;
# milliseconds must be filled, but we also check that they are non "nan" string # milliseconds must be filled, but we also check that they are non "nan" string
# that can appear in the original value # that can appear in the original value
if [ ! -z ${ms} ] && [ "${ms}" != "nan" ]; then if [ -n "${ms}" ] && [ "${ms}" != "nan" ]; then
if [ ${ms} -gt 0 ]; then if [ "${ms}" -gt 0 ]; then
time_string=${time_string}" "${ms}"ms"; time_string="${time_string} ${ms}ms";
fi; fi;
fi; fi;
# just in case the time is 0 # just in case the time is 0
@@ -399,15 +431,15 @@ function convert_bytes
{ {
bytes=${1}; bytes=${1};
# use awk to calc it # use awk to calc it
echo -n $(echo ${bytes} | awk 'function human(x) { echo -n "$(echo "${bytes}" | awk 'function human(x) {
s=" B KB MB GB TB EB PB YB ZB" s=" B KB MB GB TB EB PB YB ZB"
while (x>=1024 && length(s)>1) while (x>=1024 && length(s)>1)
{x/=1024; s=substr(s,4)} {x/=1024; s=substr(s,4)}
s=substr(s,1,4) s=substr(s,1,4)
xf=(s==" B ")?"%d ":"%.2f" xf=(s==" B ")?"%d ":"%.2f"
return sprintf( xf"%s\n", x, s) return sprintf( xf"%s\n", x, s)
} }
{gsub(/^[0-9]+/, human($1)); print}'); {gsub(/^[0-9]+/, human($1)); print}')";
} }
# METHOD: get_dump_file_name # METHOD: get_dump_file_name
@@ -418,22 +450,20 @@ function convert_bytes
function get_dump_file_name function get_dump_file_name
{ {
# set base search for the files # set base search for the files
sequence=*; if [ "${db}" ]; then
if [ ${db} ]; then db_name="${db}.${owner}.${encoding}.";
db_name=${db}"."${owner}"."${encoding}".";
else else
db_name="pg_globals."${DB_USER}".NONE."; db_name="pg_globals.${DB_USER}.NONE.";
fi; fi;
file=${BACKUPDIR}"/"${db_name}${DB_TYPE}"-"${DUMP_DB_VERSION}"_"${DB_HOST}"_"${DB_PORT}"_"$(date +%Y%m%d)"_"$(date +%H%M)"_"${sequence}".c.sql";
seq=''; seq='';
# we need to find the next sequence number # we need to find the next sequence number
for i in $(ls -1 ${file} 2>/dev/null); do for i in "${BACKUPDIR}/${db_name}${DB_TYPE}-${DUMP_DB_VERSION}_${DB_HOST}_${DB_PORT}_$(date +%Y%m%d)_$(date +%H%M)"*.c.sql; do
# get the last sequence and cut any leading 0 so we can run +1 on it # get the last sequence and cut any leading 0 so we can run +1 on it
seq=$(echo $i | cut -d "." -f 3 | cut -d "_" -f 4 | sed -e "s/^0//g"); seq=$(echo "$i" | cut -d "." -f 4 | cut -d "_" -f 6 | sed -e "s/^0//g");
done; done;
if [ ! -z ${seq} ]; then if [ -n "${seq}" ]; then
# add +1 and if < 10 prefix with 0 # add +1 and if < 10 prefix with 0
let seq=${seq}+1; seq=$((seq+1));
if [ ${seq} -lt 10 ]; then if [ ${seq} -lt 10 ]; then
sequence="0"${seq}; sequence="0"${seq};
else else
@@ -443,7 +473,7 @@ function get_dump_file_name
sequence="01"; sequence="01";
fi; fi;
# now build correct file name # now build correct file name
filename=${BACKUPDIR}"/"${db_name}${DB_TYPE}"-"${DUMP_DB_VERSION}"_"${DB_HOST}"_"${DB_PORT}"_"$(date +%Y%m%d)"_"$(date +%H%M)"_"${sequence}".c.sql"; filename="${BACKUPDIR}/${db_name}${DB_TYPE}-${DUMP_DB_VERSION}_${DB_HOST}_${DB_PORT}_$(date +%Y%m%d)_$(date +%H%M)_${sequence}.c.sql";
echo "${filename}"; echo "${filename}";
} }
@@ -460,8 +490,11 @@ function get_dump_databases
if [ ${GLOBALS} -eq 1 ]; then if [ ${GLOBALS} -eq 1 ]; then
search_names+=("pg_globals.*"); search_names+=("pg_globals.*");
fi; fi;
for owner_db in $(${PG_PSQL} -U ${DB_USER} ${CONN_DB_HOST} -p ${DB_PORT} -d template1 -t -A -F "," -X -q -c "SELECT pg_catalog.pg_get_userbyid(datdba) AS owner, datname, pg_catalog.pg_encoding_to_char(encoding) FROM pg_catalog.pg_database WHERE datname "\!"~ 'template(0|1)';"); do _PG_PARAMS_SELECT=("${PG_PARAMS_SELECT[@]}");
db=$(echo ${owner_db} | cut -d "," -f 2); _PG_PARAMS_SELECT+=("SELECT pg_catalog.pg_get_userbyid(datdba) AS owner, datname, pg_catalog.pg_encoding_to_char(encoding) FROM pg_catalog.pg_database WHERE datname !~ 'template(0|1)';");
PG_COMMAND=("${PG_PSQL}" "${_PG_PARAMS_SELECT[@]}");
for owner_db in $("${PG_COMMAND[@]}"); do
db=$(echo "${owner_db}" | cut -d "," -f 2);
# check if we exclude this db # check if we exclude this db
exclude=0; exclude=0;
include=0; include=0;
@@ -471,7 +504,7 @@ function get_dump_databases
break; break;
fi; fi;
done; done;
if [ ! -z "${INCLUDE}" ]; then if [ -n "${INCLUDE}" ]; then
for incl_db in ${INCLUDE}; do for incl_db in ${INCLUDE}; do
if [ "${db}" = "${incl_db}" ]; then if [ "${db}" = "${incl_db}" ]; then
include=1; include=1;
@@ -494,7 +527,7 @@ function get_dump_databases
# DESC : checks for older files than given keep time/amount and removes them # DESC : checks for older files than given keep time/amount and removes them
function clean_up function clean_up
{ {
if [ -d ${BACKUPDIR} ]; then if [ -d "${BACKUPDIR}" ]; then
if [ ${CLEAN_NUMBER} -eq 0 ]; then if [ ${CLEAN_NUMBER} -eq 0 ]; then
echo "Cleanup older than ${KEEP} days backup in ${BACKUPDIR}"; echo "Cleanup older than ${KEEP} days backup in ${BACKUPDIR}";
else else
@@ -502,41 +535,46 @@ function clean_up
# if we run clean before backup, we need to clean up +1 # if we run clean before backup, we need to clean up +1
# so if we keep one, we must remove all data before running new # so if we keep one, we must remove all data before running new
if [ ${PRE_RUN_CLEAN_UP} -eq 1 ]; then if [ ${PRE_RUN_CLEAN_UP} -eq 1 ]; then
let KEEP=${KEEP}-1; # let KEEP=${KEEP}-1;
KEEP=$((KEEP-1));
fi; fi;
fi; fi;
# build the find string based on the search names patter # build the find string based on the search names patter
find_string=''; find_params=("${BACKUPDIR}");
for name in "${search_names[@]}"; do for name in "${search_names[@]}"; do
# for not number based, we build the find string here # for not number based, we build the find string here
# else we do the delete here already # else we do the delete here already
if [ ${CLEAN_NUMBER} -eq 0 ]; then if [ ${CLEAN_NUMBER} -eq 0 ]; then
if [ ! -z "${find_string}" ]; then if [ ${#find_params[@]} -gt 1 ]; then
find_string=${find_string}' -o '; find_params+=("-o");
fi; fi;
find_string=${find_string}"-mtime +${KEEP} -name "${name}${DB_TYPE}*.sql" -type f -delete -print"; find_params+=("-mtime" "+${KEEP}" "-name" "${name}${DB_TYPE}*.sql" "-type" "f" "-delete" "-print");
echo "- Remove old backups for '${name}'"; echo "- Remove old backups for '${name}'";
else else
# if we do number based delete of old data, but only if the number of # if we do number based delete of old data, but only if the number of
# files is bigger than the keep number or equal if we do PRE_RUN_CLEAN_UP # files is bigger than the keep number or equal if we do PRE_RUN_CLEAN_UP
# this can be error, but we allow it -> script should not abort here # this can be error, but we allow it -> script should not abort here
# note we have a wildcard in the name, so we can't put that into "" # note we have a wildcard in the name, so we can't put that into ""
count=$(ls "${BACKUPDIR}/"${name}${DB_TYPE}*.sql 2>/dev/null | wc -l) || true; count=$(find "${BACKUPDIR}" -name "${name}${DB_TYPE}*.sql" -type f -print | wc -l);
if [ ${PRE_RUN_CLEAN_UP} -eq 1 ]; then if [ ${PRE_RUN_CLEAN_UP} -eq 1 ]; then
let count=${count}+1; # let count=${count}+1;
count=$((count+1));
fi; fi;
if [ ${count} -gt ${KEEP} ]; then if [ "${count}" -gt "${KEEP}" ]; then
# calculate the amount to delete # calculate the amount to delete
# eg if we want to keep 1, and we have 3 files then we need to delete 2 # eg if we want to keep 1, and we have 3 files then we need to delete 2
# keep is always +1 (include the to backup count). # keep is always +1 (include the to backup count).
# count is +1 if we do a pre-run cleanup # count is +1 if we do a pre-run cleanup
# grouped by db name, db type # grouped by db name, db type
let TO_DELETE=${count}-${KEEP}; TO_DELETE=$((count-KEEP));
echo "- Remove old backups for '${name}', found ${count}, will delete ${TO_DELETE}"; echo "- Remove old backups for '${name}', found ${count}, will delete ${TO_DELETE}";
if [ ${TEST} -eq 0 ]; then if [ ${TEST} -eq 0 ]; then
ls -tr "${BACKUPDIR}/"${name}${DB_TYPE}*.sql 2>/dev/null | head -n ${TO_DELETE} | xargs rm; find "${BACKUPDIR}" -name "${name}${DB_TYPE}*.sql" -type f -printf "%Ts\t%p\n" | \
sort -n | \
head -n ${TO_DELETE} | \
xargs rm;
else else
echo "ls -tr ${BACKUPDIR}/${name}${DB_TYPE}*.sql 2>/dev/null | head -n ${TO_DELETE} | xargs rm"; print "find \"${BACKUPDIR}\" -name \"${name}${DB_TYPE}*.sql\" -type f -printf \"%Ts\\t%p\\n\" | sort -nr | head -n ${TO_DELETE} | xargs rm";
fi; fi;
fi; fi;
fi; fi;
@@ -544,15 +582,15 @@ function clean_up
# if we do find (day based) delete of old data # if we do find (day based) delete of old data
if [ ${CLEAN_NUMBER} -eq 0 ]; then if [ ${CLEAN_NUMBER} -eq 0 ]; then
if [ ${TEST} -eq 0 ]; then if [ ${TEST} -eq 0 ]; then
find ${BACKUPDIR} ${find_string}; find "${find_params[@]}";
else else
echo "find ${BACKUPDIR} ${find_string}"; echo "find ${find_params[*]}";
fi; fi;
fi; fi;
fi fi
} }
if [ ! -z "${DB_PASSWD}" ]; then if [ -n "${DB_PASSWD}" ]; then
export PGPASSWORD=${DB_PASSWD}; export PGPASSWORD=${DB_PASSWD};
fi; fi;
START=$(date "+%s"); START=$(date "+%s");
@@ -574,11 +612,16 @@ if [ ${GLOBALS} -eq 1 ]; then
# reset any previous set db name from deletes so the correct global file name is set # reset any previous set db name from deletes so the correct global file name is set
db=''; db='';
filename=$(get_dump_file_name); filename=$(get_dump_file_name);
search_names+=("pg_globals.*"); # this is used for the find/delete part # this is used for the find/delete part
search_names+=("pg_globals.*");
# build dump parms
_PG_PARAMS_DUMP=("${PG_PARAMS[@]}");
_PG_PARAMS_DUMP+=("--globals-only")
PG_COMMAND=("${PG_DUMPALL}" "${_PG_PARAMS_DUMP[@]}");
if [ ${TEST} -eq 0 ]; then if [ ${TEST} -eq 0 ]; then
${PG_DUMPALL} -U ${DB_USER} ${CONN_DB_HOST} -p ${DB_PORT} --globals-only > "${filename}"; "${PG_COMMAND[@]}" > "${filename}";
else else
echo "${PG_DUMPALL} -U ${DB_USER} ${CONN_DB_HOST} -p ${DB_PORT} --globals-only > ${filename}"; echo "${PG_COMMAND[*]} > ${filename}";
fi; fi;
echo "done"; echo "done";
else else
@@ -589,21 +632,24 @@ echo -n "(+) Dump databases: ";
if [ -z "${INCLUDE}" ]; then if [ -z "${INCLUDE}" ]; then
echo "All"; echo "All";
else else
echo ${INCLUDE}; echo "${INCLUDE}";
fi; fi;
echo -n "(-) Exclude databases: "; echo -n "(-) Exclude databases: ";
if [ -z "${EXCLUDE}" ]; then if [ -z "${EXCLUDE}" ]; then
echo "None"; echo "None";
else else
echo ${EXCLUDE}; echo "${EXCLUDE}";
fi; fi;
filesize_sum=0; filesize_sum=0;
for owner_db in $(${PG_PSQL} -U ${DB_USER} ${CONN_DB_HOST} -p ${DB_PORT} -d template1 -t -A -F "," -X -q -c "SELECT pg_catalog.pg_get_userbyid(datdba) AS owner, datname, pg_catalog.pg_encoding_to_char(encoding) AS encoding FROM pg_catalog.pg_database WHERE datname "\!"~ 'template(0|1)' ORDER BY datname;"); do _PG_PARAMS_SELECT=("${PG_PARAMS_SELECT[@]}");
_PG_PARAMS_SELECT+=("SELECT pg_catalog.pg_get_userbyid(datdba) AS owner, datname, pg_catalog.pg_encoding_to_char(encoding) AS encoding FROM pg_catalog.pg_database WHERE datname !~ 'template(0|1)' ORDER BY datname;");
PG_COMMAND=("${PG_PSQL}" "${_PG_PARAMS_SELECT[@]}");
for owner_db in $("${PG_COMMAND[@]}"); do
# get the user who owns the DB too # get the user who owns the DB too
owner=$(echo ${owner_db} | cut -d "," -f 1); owner=$(echo "${owner_db}" | cut -d "," -f 1);
db=$(echo ${owner_db} | cut -d "," -f 2); db=$(echo "${owner_db}" | cut -d "," -f 2);
encoding=$(echo ${owner_db} | cut -d "," -f 3); encoding=$(echo "${owner_db}" | cut -d "," -f 3);
# check if we exclude this db # check if we exclude this db
exclude=0; exclude=0;
include=0; include=0;
@@ -613,7 +659,7 @@ for owner_db in $(${PG_PSQL} -U ${DB_USER} ${CONN_DB_HOST} -p ${DB_PORT} -d temp
break; break;
fi; fi;
done; done;
if [ ! -z "${INCLUDE}" ]; then if [ -n "${INCLUDE}" ]; then
for incl_db in ${INCLUDE}; do for incl_db in ${INCLUDE}; do
if [ "${db}" = "${incl_db}" ]; then if [ "${db}" = "${incl_db}" ]; then
include=1; include=1;
@@ -628,25 +674,29 @@ for owner_db in $(${PG_PSQL} -U ${DB_USER} ${CONN_DB_HOST} -p ${DB_PORT} -d temp
filename=$(get_dump_file_name); filename=$(get_dump_file_name);
search_names+=("${db}.*"); search_names+=("${db}.*");
SUBSTART=$(date "+%s"); SUBSTART=$(date "+%s");
# build dump parms
_PG_PARAMS_DUMP=("${PG_PARAMS[@]}");
_PG_PARAMS_DUMP+=("-c" "--format=c" "${db}")
PG_COMMAND=("${PG_DUMP}" "${_PG_PARAMS_DUMP[@]}");
if [ ${TEST} -eq 0 ]; then if [ ${TEST} -eq 0 ]; then
${PG_DUMP} -U ${DB_USER} ${CONN_DB_HOST} -p ${DB_PORT} -c --format=c ${db} > "${filename}"; "${PG_COMMAND[@]}" > "${filename}";
else else
echo "${PG_DUMP} -U ${DB_USER} ${CONN_DB_HOST} -p ${DB_PORT} -c --format=c ${db} > ${filename}"; echo "${PG_COMMAND[*]} > ${filename}";
fi; fi;
# get the file size for the dumped file and convert it to a human readable format # get the file size for the dumped file and convert it to a human readable format
filesize=0; filesize=0;
if [ -f "${filename}" ]; then if [ -f "${filename}" ]; then
filesize=$(wc -c "${filename}" | cut -f 1 -d ' '); filesize=$(wc -c "${filename}" | cut -f 1 -d ' ');
filesize_sum=$[$filesize+$filesize_sum]; filesize_sum=$((filesize+filesize_sum));
fi; fi;
DURATION=$[$(date "+%s")-${SUBSTART}]; DURATION=$(($(date "+%s")-SUBSTART));
printf "done (%s and %s)\n" "$(convert_time ${DURATION})" "$(convert_bytes ${filesize})"; printf "done (%s and %s)\n" "$(convert_time ${DURATION})" "$(convert_bytes "${filesize}")";
else else
printf -- "- Exclude database: %35s\n" "${db}"; printf -- "- Exclude database: %35s\n" "${db}";
fi; fi;
done done
printf "Backup ended at %s\n" "$(date '+%Y-%m-%d %H:%M:%S')"; printf "Backup ended at %s\n" "$(date '+%Y-%m-%d %H:%M:%S')";
if [ ! -z "${DB_PASSWD}" ]; then if [ -n "${DB_PASSWD}" ]; then
unset DB_PASSWD; unset DB_PASSWD;
fi; fi;
@@ -654,7 +704,7 @@ if [ ${PRE_RUN_CLEAN_UP} -eq 0 ]; then
clean_up; clean_up;
fi; fi;
DURATION=$[$(date "+%s")-${START}]; DURATION=$(($(date "+%s")-START));
printf "Cleanup ended at %s\n" "$(date '+%Y-%m-%d %H:%M:%S')"; printf "Cleanup ended at %s\n" "$(date '+%Y-%m-%d %H:%M:%S')";
printf "Finished backup in %s with %s\n" "$(convert_time ${DURATION})" "$(convert_bytes ${filesize_sum})"; printf "Finished backup in %s with %s\n" "$(convert_time ${DURATION})" "$(convert_bytes ${filesize_sum})";

View File

@@ -2,14 +2,14 @@
# Author: Clemens Schwaighofer # Author: Clemens Schwaighofer
# Description: # Description:
# Drop and restore one database # Drop and restore one database from a dump created by created by pg_db_dump_file.sh
function usage () function usage ()
{ {
cat <<- EOT cat <<- EOT
Restores a single database dump to a database Restores a single database dump to a database
Usage: ${0##/*/} -o <DB OWNER> -d <DB NAME> -f <FILE NAME> [-h <DB HOST>] [-p <DB PORT>] [-e <ENCODING>] [-i <POSTGRES VERSION>] [-j <JOBS>] [-s] [-r|-a] [-n] Usage: ${0##/*/} -o <DB OWNER> -d <DB NAME> -f <FILE NAME> [-h <DB HOST>] [-p <DB PORT>] [-e <ENCODING>] [-i <POSTGRES VERSION>] [-j <JOBS>] [-s] [-r] [-n]
-o <DB OWNER>: The user who will be owner of the database to be restored -o <DB OWNER>: The user who will be owner of the database to be restored
-d <DB NAME>: The database to restore the file to -d <DB NAME>: The database to restore the file to
@@ -21,7 +21,6 @@ function usage ()
-j <JOBS>: Run how many jobs Parallel. If not set, 2 jobs are run parallel -j <JOBS>: Run how many jobs Parallel. If not set, 2 jobs are run parallel
-s: Restore only schema, no data -s: Restore only schema, no data
-r: use redhat base paths instead of debian -r: use redhat base paths instead of debian
-a: use amazon base paths instead of debian
-n: dry run, do not do anything, just test flow -n: dry run, do not do anything, just test flow
EOT EOT
} }
@@ -29,20 +28,24 @@ function usage ()
_port=5432 _port=5432
_host='local'; _host='local';
_encoding='UTF8'; _encoding='UTF8';
role=''; # role='';
schema=''; schema='';
NO_ASK=0; NO_ASK=0;
TEMPLATEDB='template0'; TEMPLATEDB='template0';
SCHEMA_ONLY=0; SCHEMA_ONLY=0;
ERROR=0;
REDHAT=0; REDHAT=0;
AMAZON=0;
DRY_RUN=0; DRY_RUN=0;
BC='/usr/bin/bc'; BC='/usr/bin/bc';
PORT_REGEX="^[0-9]{4,5}$"; PORT_REGEX="^[0-9]{4,5}$";
OPTARG_REGEX="^-"; OPTARG_REGEX="^-";
# log path
LOG_PATH='';
MAX_JOBS=''; MAX_JOBS='';
PG_PARAMS=();
PG_PARAM_ROLE=();
# if we have options, set them and then ignore anything below # if we have options, set them and then ignore anything below
while getopts ":o:d:h:f:p:e:i:j:raqnms" opt; do while getopts ":o:d:h:f:p:e:i:j:rqnms" opt; do
# pre test for unfilled # pre test for unfilled
if [ "${opt}" = ":" ] || [[ "${OPTARG-}" =~ ${OPTARG_REGEX} ]]; then if [ "${opt}" = ":" ] || [[ "${OPTARG-}" =~ ${OPTARG_REGEX} ]]; then
if [ "${opt}" = ":" ]; then if [ "${opt}" = ":" ]; then
@@ -86,71 +89,80 @@ while getopts ":o:d:h:f:p:e:i:j:raqnms" opt; do
esac esac
fi; fi;
case $opt in case $opt in
o|owner) # o|owner)
o)
if [ -z "$owner" ]; then if [ -z "$owner" ]; then
owner=$OPTARG; owner=$OPTARG;
# if not standard user we need to set restore role # if not standard user we need to set restore role
# so tables/etc get set to new user # so tables/etc get set to new user
role="--no-owner --role $owner"; # role="--no-owner --role $owner";
PG_PARAM_ROLE=("--no-owner" "--role" "$owner");
fi; fi;
;; ;;
d|database) # d|database)
d)
if [ -z "$database" ]; then if [ -z "$database" ]; then
database=$OPTARG; database=$OPTARG;
fi; fi;
;; ;;
e|encoding) # e|encoding)
e)
if [ -z "$encoding" ]; then if [ -z "$encoding" ]; then
encoding=$OPTARG; encoding=$OPTARG;
fi; fi;
;; ;;
f|file) # f|file)
f)
if [ -z "$file" ]; then if [ -z "$file" ]; then
file=$OPTARG; file=$OPTARG;
fi; fi;
;; ;;
h|hostname) # h|hostname)
if [ -z "$host" ]; then h)
if [ -z "$_host" ]; then
# if local it is socket # if local it is socket
if [ "$OPTARG" != "local" ]; then if [ "$OPTARG" != "local" ]; then
host='-h '$OPTARG; PG_PARAMS+=("-h" "${OPTARG}");
else
host='';
fi; fi;
_host=$OPTARG; _host=$OPTARG;
fi; fi;
;; ;;
p|port) # p|port)
p)
if [ -z "$port" ]; then if [ -z "$port" ]; then
port='-p '$OPTARG; PG_PARAMS+=("-p" "${OPTARG}");
_port=$OPTARG; _port=$OPTARG;
fi; fi;
;; ;;
i|ident) # i|ident)
i)
if [ -z "$ident" ]; then if [ -z "$ident" ]; then
ident=$OPTARG; ident=$OPTARG;
fi; fi;
;; ;;
j|jobs) # j|jobs)
j)
MAX_JOBS=${OPTARG}; MAX_JOBS=${OPTARG};
;; ;;
q|quiet) # q|quiet)
q)
NO_ASK=1; NO_ASK=1;
;; ;;
r|redhat) # r|redhat)
r)
REDHAT=1; REDHAT=1;
;; ;;
a|amazon) # n|dry-run)
AMAZON=1; n)
;;
n|dry-run)
DRY_RUN=1; DRY_RUN=1;
;; ;;
s|schema-only) # s|schema-only)
s)
SCHEMA_ONLY=1 SCHEMA_ONLY=1
schema='-s'; schema='-s';
;; ;;
m|help) # m|help)
m)
usage; usage;
exit 0; exit 0;
;; ;;
@@ -162,10 +174,10 @@ while getopts ":o:d:h:f:p:e:i:j:raqnms" opt; do
esac; esac;
done; done;
if [ "$REDHAT" -eq 1 ] && [ "$AMAZON" -eq 1 ]; then if [ "${ERROR}" -eq 1 ]; then
echo "You cannot set the -a and -r flag at the same time"; exit 0;
exit 1;
fi; fi;
# check that the port is a valid number # check that the port is a valid number
if ! [[ "$_port" =~ $PORT_REGEX ]]; then if ! [[ "$_port" =~ $PORT_REGEX ]]; then
echo "The port needs to be a valid number: $_port"; echo "The port needs to be a valid number: $_port";
@@ -174,16 +186,15 @@ fi;
NUMBER_REGEX="^[0-9]{1,}$"; NUMBER_REGEX="^[0-9]{1,}$";
# find the max allowed jobs based on the cpu count # find the max allowed jobs based on the cpu count
# because setting more than this is not recommended # because setting more than this is not recommended
cpu=$(cat /proc/cpuinfo | grep processor | tail -n 1); _max_jobs=$(nproc --all);
_max_jobs=$[ ${cpu##*: }+1 ] # +1 because cpu count starts with 0
# if the MAX_JOBS is not number or smaller 1 or greate _max_jobs # if the MAX_JOBS is not number or smaller 1 or greate _max_jobs
if [ ! -z "${MAX_JOBS}" ]; then if [ -n "${MAX_JOBS}" ]; then
# check that it is a valid number # check that it is a valid number
if ! [[ "$MAX_JOBS" =~ $NUMBER_REGEX ]]; then if ! [[ "$MAX_JOBS" =~ $NUMBER_REGEX ]]; then
echo "Please enter a number for the -j option"; echo "Please enter a number for the -j option";
exit 1; exit 1;
fi; fi;
if [ "${MAX_JOBS}" -lt 1 ] || [ "${MAX_JOBS}" -gt ${_max_jobs} ]; then if [ "${MAX_JOBS}" -lt 1 ] || [ "${MAX_JOBS}" -gt "${_max_jobs}" ]; then
echo "The value for the jobs option -j cannot be smaller than 1 or bigger than ${_max_jobs}"; echo "The value for the jobs option -j cannot be smaller than 1 or bigger than ${_max_jobs}";
exit 1; exit 1;
fi; fi;
@@ -208,22 +219,21 @@ fi;
# PARAMS: timestamp in seconds or with milliseconds (nnnn.nnnn) # PARAMS: timestamp in seconds or with milliseconds (nnnn.nnnn)
# RETURN: formated string with human readable time (d/h/m/s) # RETURN: formated string with human readable time (d/h/m/s)
# CALL : var=$(convert_time $timestamp); # CALL : var=$(convert_time $timestamp);
# DESC : converts a timestamp or a timestamp with float milliseconds # DESC : converts a timestamp or a timestamp with float milliseconds to a human readable format
# to a human readable format
# output is in days/hours/minutes/seconds # output is in days/hours/minutes/seconds
function convert_time function convert_time
{ {
timestamp=${1}; timestamp=${1};
# round to four digits for ms # round to four digits for ms
timestamp=$(printf "%1.4f" $timestamp); timestamp=$(printf "%1.4f" "$timestamp");
# get the ms part and remove any leading 0 # get the ms part and remove any leading 0
ms=$(echo ${timestamp} | cut -d "." -f 2 | sed -e 's/^0*//'); ms=$(echo "${timestamp}" | cut -d "." -f 2 | sed -e 's/^0*//');
timestamp=$(echo ${timestamp} | cut -d "." -f 1); timestamp=$(echo "${timestamp}" | cut -d "." -f 1);
timegroups=(86400 3600 60 1); # day, hour, min, sec timegroups=(86400 3600 60 1); # day, hour, min, sec
timenames=("d" "h" "m" "s"); # day, hour, min, sec timenames=("d" "h" "m" "s"); # day, hour, min, sec
output=( ); output=( );
time_string=; time_string='';
for timeslice in ${timegroups[@]}; do for timeslice in "${timegroups[@]}"; do
# floor for the division, push to output # floor for the division, push to output
if [ ${BC_OK} -eq 1 ]; then if [ ${BC_OK} -eq 1 ]; then
output[${#output[*]}]=$(echo "${timestamp}/${timeslice}" | bc); output[${#output[*]}]=$(echo "${timestamp}/${timeslice}" | bc);
@@ -235,15 +245,19 @@ function convert_time
done; done;
for ((i=0; i<${#output[@]}; i++)); do for ((i=0; i<${#output[@]}; i++)); do
if [ ${output[$i]} -gt 0 ] || [ ! -z "$time_string" ]; then if [ "${output[$i]}" -gt 0 ] || [ -n "$time_string" ]; then
if [ ! -z "${time_string}" ]; then if [ -n "${time_string}" ]; then
time_string=${time_string}" "; time_string=${time_string}" ";
fi; fi;
time_string=${time_string}${output[$i]}${timenames[$i]}; time_string=${time_string}${output[$i]}${timenames[$i]};
fi; fi;
done; done;
if [ ! -z ${ms} ] && [ ${ms} -gt 0 ];; then # milliseconds must be filled, but we also check that they are non "nan" string
time_string=${time_string}" "${ms}"ms"; # that can appear in the original value
if [ -n "${ms}" ] && [ "${ms}" != "nan" ]; then
if [ "${ms}" -gt 0 ]; then
time_string="${time_string} ${ms}ms";
fi;
fi; fi;
# just in case the time is 0 # just in case the time is 0
if [ -z "${time_string}" ]; then if [ -z "${time_string}" ]; then
@@ -253,20 +267,28 @@ function convert_time
} }
# for the auto find, we need to get only the filename, and therefore remove all path info # for the auto find, we need to get only the filename, and therefore remove all path info
db_file=`basename $file`; db_file=$(basename "$file");
# if file is set and exist, but no owner or database are given, use the file name data to get user & database # if file is set and exist, but no owner or database are given, use the file name data to get user & database
if [ -r "$file" ] && ( [ ! "$owner" ] || [ ! "$database" ] || [ ! "$encoding" ] ); then if [ -r "$file" ] && { [ ! "$owner" ] || [ ! "$database" ] || [ ! "$encoding" ]; }; then
# file name format is # file name format is
# <database>.<owner>.<encoding>.<db type>-<version>_<host>_<port>_<date>_<time>_<sequence> # <database>.<owner>.<encoding>.<db type>-<version>_<host>_<port>_<date>_<time>_<sequence>
# we only are interested in the first two # we only are interested in the first two
_database=`echo $db_file | cut -d "." -f 1`; _database=$(echo "${db_file}" | cut -d "." -f 1);
_owner=`echo $db_file | cut -d "." -f 2`; _owner=$(echo "${db_file}" | cut -d "." -f 2);
__encoding=`echo $db_file | cut -d "." -f 3`; __encoding=$(echo "${db_file}" | cut -d "." -f 3);
# set the others as optional # set the others as optional
_ident=`echo $db_file | cut -d "." -f 4 | cut -d "-" -f 2`; # db version first part # the last _ is for version 10 or higher
_ident=$_ident'.'`echo $db_file | cut -d "." -f 5 | cut -d "_" -f 1`; # db version, second part (after .) # db version, without prefix of DB type
__host=`echo $db_file | cut -d "." -f 4 | cut -d "_" -f 2`; _ident=$(echo "${db_file}" | cut -d "." -f 4 | cut -d "-" -f 2 | cut -d "_" -f 1);
__port=`echo $db_file | cut -d "." -f 4 | cut -d "_" -f 3`; cut_pos=4;
# if this is < 10 then we need the second part too
if [ "${_ident}" -lt 10 ]; then
# db version, second part (after .)
_ident=$_ident'.'$(echo "$db_file" | cut -d "." -f 5 | cut -d "_" -f 1);
cut_pos=5;
fi;
__host=$(echo "${db_file}" | cut -d "." -f ${cut_pos} | cut -d "_" -f 2);
__port=$(echo "${db_file}" | cut -d "." -f ${cut_pos} | cut -d "_" -f 3);
# if any of those are not set, override by the file name settings # if any of those are not set, override by the file name settings
if [ ! "$owner" ]; then if [ ! "$owner" ]; then
owner=$_owner; owner=$_owner;
@@ -285,7 +307,7 @@ if [ -r "$file" ] && ( [ ! "$owner" ] || [ ! "$database" ] || [ ! "$encoding" ]
_host=$__host; _host=$__host;
fi; fi;
if [ ! "$encoding" ]; then if [ ! "$encoding" ]; then
if [ ! -z "$__encoding" ]; then if [ -n "$__encoding" ]; then
encoding=$__encoding; encoding=$__encoding;
else else
encoding=$_encoding; encoding=$_encoding;
@@ -308,43 +330,59 @@ fi;
if [ "$REDHAT" -eq 1 ]; then if [ "$REDHAT" -eq 1 ]; then
# Debian base path # Debian base path
PG_BASE_PATH='/usr/pgsql-'; PG_BASE_PATH="/usr/pgsql-";
elif [ "$AMAZON" -eq 1 ]; then
PG_BASE_PATH='/usr/lib64/pgsql';
else else
# Redhat base path (for non official ones would be '/usr/pgsql-' # Redhat base path (for non official ones would be '/usr/pgsql-'
PG_BASE_PATH='/usr/lib/postgresql/'; PG_BASE_PATH="/usr/lib/postgresql/";
fi; fi;
# if no ident is given, try to find the default one, if not fall back to pre set one # if no ident is given, try to find the default one, if not fall back to pre set one
if [ ! -z "$ident" ]; then if [ -n "$ident" ]; then
PG_PATH=$PG_BASE_PATH$ident'/bin/'; PG_PATH="${PG_BASE_PATH}${ident}/bin/";
if [ ! -d "$PG_PATH" ]; then if [ ! -d "$PG_PATH" ]; then
ident=''; ident='';
fi; fi;
fi; else
if [ -z "$ident" ]; then
# try to run psql from default path and get the version number # try to run psql from default path and get the version number
ident=$(pgv=$(pg_dump --version| grep "pg_dump" | cut -d " " -f 3); if [[ $(echo "${pgv}" | cut -d "." -f 1) -ge 10 ]]; then echo "${pgv}" | cut -d "." -f 1; else echo "${pgv}" | cut -d "." -f 1,2; fi ); ident=$(
if [ ! -z "$ident" ]; then pgv=$(
PG_PATH=$PG_BASE_PATH$ident'/bin/'; "pg_dump" --version | grep "pg_dump" | cut -d " " -f 3
else );
if [[ $(echo "${pgv}" | cut -d "." -f 1) -ge 10 ]]; then
echo "${pgv}" | cut -d "." -f 1;
else
echo "${pgv}" | cut -d "." -f 1,2;
fi
);
if [ -z "$ident" ]; then
# hard setting # hard setting
ident='9.6'; ident='15';
PG_PATH=$PG_BASE_PATH'9.6/bin/'; fi;
PG_PATH="${PG_BASE_PATH}${ident}/bin/'";
fi;
# set log path, this is the base path of the file + logs
LOG_PATH=$(dirname "${file}")"/logs/";
# create logs folder if missing
if [ ! -d "$LOG_PATH" ]; then
echo "+ Creating '$LOG_PATH' folder";
mkdir -p "$LOG_PATH";
if [ ! -d "$LOG_PATH" ]; then
echo "[!] Creation of '$LOG_PATH' folder failed";
exit 1;
fi; fi;
fi; fi;
PG_DROPDB=$PG_PATH"dropdb"; PG_DROPDB="${PG_PATH}dropdb";
PG_CREATEDB=$PG_PATH"createdb"; PG_CREATEDB="${PG_PATH}createdb";
PG_CREATELANG=$PG_PATH"createlang"; PG_CREATELANG="${PG_PATH}createlang";
PG_RESTORE=$PG_PATH"pg_restore"; PG_RESTORE="${PG_PATH}pg_restore";
PG_PSQL=$PG_PATH"psql"; PG_PSQL="${PG_PATH}psql";
TEMP_FILE="temp"; TEMP_FILE="temp";
LOG_FILE_EXT=$database.`date +"%Y%m%d_%H%M%S"`".log"; LOG_FILE_EXT="${database}.$(date +"%Y%m%d_%H%M%S").log";
# core abort if no core files found # core abort if no core files found
if [ ! -f $PG_PSQL ] || [ ! -f $PG_DROPDB ] || [ ! -f $PG_CREATEDB ] || [ ! -f $PG_RESTORE ]; then if [ ! -f "$PG_PSQL" ] || [ ! -f "$PG_DROPDB" ] || [ ! -f "$PG_CREATEDB" ] || [ ! -f "$PG_RESTORE" ]; then
echo "One of the core binaries (psql, pg_dump, createdb, pg_restore) could not be found."; echo "One of the core binaries (psql, pg_dump, createdb, pg_restore) could not be found.";
echo "Search Path: ${PG_PATH}"; echo "Search Path: ${PG_PATH}";
echo "Perhaps manual ident set with -i is necessary"; echo "Perhaps manual ident set with -i is necessary";
@@ -354,82 +392,163 @@ fi;
# check if port / host settings are OK # check if port / host settings are OK
# if I cannot connect with user postgres to template1, the restore won't work # if I cannot connect with user postgres to template1, the restore won't work
output=`echo "SELECT version();" | $PG_PSQL -U postgres $host $port template1 -q -t -X -A -F "," 2>&1`; _PG_PARAMS=("${PG_PARAMS[@]}");
found=`echo "$output" | grep "PostgreSQL"`; _PG_PARAMS+=("-U" "postgres" "template1" "-q" "-t" "-X" "-A" "-F" "," "-c" "SELECT version();");
_output=$("${PG_PSQL}" "${_PG_PARAMS[@]}" 2>&1);
found=$(echo "$_output" | grep "PostgreSQL");
# if the output does not have the PG version string, we have an error and abort # if the output does not have the PG version string, we have an error and abort
if [ -z "$found" ]; then if [ -z "$found" ]; then
echo "Cannot connect to the database: $output"; echo "Cannot connect to the database: $_output";
exit 1; exit 1;
fi; fi;
if [ $DRY_RUN -eq 1 ]; then
echo "Will drop database '$database' on host '$_host:$_port' and load file '$file' with user '$owner', set encoding '$encoding' and use database version '$ident'"; echo "**** [DRY RUN] ****";
fi;
echo "[.] Will drop database '$database' on host '$_host:$_port' and load file '$file' with user '$owner', set encoding '$encoding' and use database version '$ident'";
if [ $SCHEMA_ONLY -eq 1 ]; then if [ $SCHEMA_ONLY -eq 1 ]; then
echo "!!!!!!! WILL ONLY RESTORE SCHEMA, NO DATA !!!!!!!"; echo "!!!!!!! WILL ONLY RESTORE SCHEMA, NO DATA !!!!!!!";
fi; fi;
if [ $NO_ASK -eq 1 ]; then if [ $NO_ASK -eq 1 ] || [ $DRY_RUN -eq 1 ]; then
go='yes'; go='yes';
else else
echo "Continue? type 'yes'"; echo "Continue? type 'yes'";
read go; read -r go;
fi; fi;
if [ "$go" != 'yes' ]; then if [ "$go" != 'yes' ]; then
echo "Aborted"; echo "Aborted";
exit; exit;
else else
start_time=`date +"%F %T"`; start_time=$(date +"%F %T");
START=`date +'%s'`; START=$(date +'%s');
echo "Drop DB $database [$_host:$_port] @ $start_time"; echo "[1] - Drop DB $database [$_host:$_port] @ $start_time";
# DROP DATABASE # DROP DATABASE
_PG_PARAMS=("${PG_PARAMS[@]}");
_PG_PARAMS+=("-U" "postgres" "${database}");
PG_COMMAND=("${PG_DROPDB}" "${_PG_PARAMS[@]}");
if [ $DRY_RUN -eq 0 ]; then if [ $DRY_RUN -eq 0 ]; then
$PG_DROPDB -U postgres $host $port $database; "${PG_COMMAND[@]}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!:${RETURN_CODE}] Could not drop database $database, aborting";
exit 1;
fi;
else else
echo $PG_DROPDB -U postgres $host $port $database; echo "${PG_COMMAND[*]}";
fi; fi;
# CREATE DATABASE # CREATE DATABASE
echo "Create DB $database with $owner and encoding $encoding on [$_host:$_port] @ `date +"%F %T"`"; echo "[2] + Create DB $database with $owner and encoding $encoding on [$_host:$_port] @ $(date +"%F %T")";
_PG_PARAMS=("${PG_PARAMS[@]}");
_PG_PARAMS+=("-U" "postgres" "-O" "${owner}" "-E" "${encoding}" "-T" "${TEMPLATEDB}" "${database}");
PG_COMMAND=("${PG_CREATEDB}" "${_PG_PARAMS[@]}");
if [ $DRY_RUN -eq 0 ]; then if [ $DRY_RUN -eq 0 ]; then
$PG_CREATEDB -U postgres -O $owner -E $encoding -T $TEMPLATEDB $host $port $database; "${PG_COMMAND[@]}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!:${RETURN_CODE}] Could not create database $database, aborting";
exit 1;
fi;
else else
echo $PG_CREATEDB -U postgres -O $owner -E $encoding -T $TEMPLATEDB $host $port $database; echo "${PG_COMMAND[*]}";
fi; fi;
# CREATE plpgsql LANG # CREATE plpgsql LANG
if [ -f $PG_CREATELANG ]; then if [ -f "$PG_CREATELANG" ]; then
echo "Create plpgsql lang in DB $database on [$_host:$_port] @ `date +"%F %T"`"; _PG_PARAMS=("${PG_PARAMS[@]}");
_PG_PARAMS+=("-U" "postgres" "plpgsql" "${database}");
PG_COMMAND=("${PG_CREATELANG}" "${_PG_PARAMS[@]}");
echo "[3] + Create plpgsql lang in DB $database on [$_host:$_port] @ $(date +"%F %T")";
if [ $DRY_RUN -eq 0 ]; then if [ $DRY_RUN -eq 0 ]; then
$PG_CREATELANG -U postgres plpgsql $host $port $database; "${PG_COMMAND[@]}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!:${RETURN_CODE}] Could not create plgpgsql language in $database, aborting";
exit 1;
fi;
else else
echo $PG_CREATELANG -U postgres plpgsql $host $port $database; echo "${PG_COMMAND[*]}";
fi; fi;
fi; fi;
# RESTORE DATA # RESTORE DATA
echo "Restore data from $file to DB $database on [$_host:$_port] with Jobs $MAX_JOBS @ `date +"%F %T"`"; echo "[4] % Restore data from $file to DB $database on [$_host:$_port] with Jobs $MAX_JOBS @ $(date +"%F %T")";
_PG_PARAMS=("${PG_PARAMS[@]}");
_PG_PARAMS+=("${PG_PARAM_ROLE[@]}")
if [ -n "$schema" ]; then
_PG_PARAMS+=("${schema}");
fi;
_PG_PARAMS+=("-U" "postgres" "-d" "${database}" "-F" "c" "-v" "-c" "-j" "${MAX_JOBS}" "${file}");
PG_COMMAND=("${PG_RESTORE}" "${_PG_PARAMS[@]}");
LOG_ERROR_FILE="${LOG_PATH}/restore_errors.${LOG_FILE_EXT}";
LOG_OUTPUT_FILE="${LOG_PATH}/restore_output.${LOG_FILE_EXT}";
if [ $DRY_RUN -eq 0 ]; then if [ $DRY_RUN -eq 0 ]; then
$PG_RESTORE -U postgres -d $database -F c -v -c $schema -j $MAX_JOBS $host $port $role $file 2>restore_errors.$LOG_FILE_EXT; "${PG_COMMAND[@]}" 1>"${LOG_OUTPUT_FILE}" 2>"${LOG_ERROR_FILE}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!:${RETURN_CODE}] Could not restore the database $database successfully, check ${LOG_ERROR_FILE} for details";
fi;
else else
echo $PG_RESTORE -U postgres -d $database -F c -v -c $schema -j $MAX_JOBS $host $port $role $file 2>restore_errors.$LOG_FILE_EXT; echo "${PG_COMMAND[*]} 1>${LOG_OUTPUT_FILE} 2>${LOG_ERROR_FILE}";
fi; fi;
# BUG FIX FOR POSTGRESQL 9.6.2 db_dump # BUG FIX FOR POSTGRESQL 9.6.2 db_dump
# it does not dump the default public ACL so the owner of the DB cannot access the data, check if the ACL dump is missing and do a basic restore # it does not dump the default public ACL so the owner of the DB cannot access the data, check if the ACL dump is missing and do a basic restore
if [ -z $($PG_RESTORE -l $file | grep -- "ACL - public postgres") ]; then if ! "${PG_RESTORE}" -l "$file" | grep -q -- "ACL - public postgres"; then
echo "Fixing missing basic public schema ACLs from DB $database [$_host:$_port] @ `date +"%F %T"`"; echo "[5] ? Fixing missing basic public schema ACLs from DB $database [$_host:$_port] @ $(date +"%F %T")";
# grant usage on schema public to public; # grant usage on schema public to public;
_PG_PARAMS=("${PG_PARAMS[@]}");
_PG_PARAMS+=("-U" "postgres" "-AtqX" "-c" "GRANT USAGE ON SCHEMA public TO public;" "${database}");
PG_COMMAND=("${PG_PSQL}" "${_PG_PARAMS[@]}");
if [ $DRY_RUN -eq 0 ]; then
"${PG_COMMAND[@]}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!:${RETURN_CODE}] Could not fix usage access to basic public schema ACLs in $database";
fi;
else
echo "${PG_COMMAND[*]}";
fi;
# grant create on schema public to public; # grant create on schema public to public;
echo "GRANT USAGE ON SCHEMA public TO public;" | $PG_PSQL -U postgres -Atq $host $port $database; _PG_PARAMS=("${PG_PARAMS[@]}");
echo "GRANT CREATE ON SCHEMA public TO public;" | $PG_PSQL -U postgres -Atq $host $port $database; _PG_PARAMS+=("-U" "postgres" "-AtqX" "-c" "GRANT CREATE ON SCHEMA public TO public;" "${database}");
PG_COMMAND=("${PG_PSQL}" "${_PG_PARAMS[@]}");
if [ $DRY_RUN -eq 0 ]; then
"${PG_COMMAND[@]}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!:${RETURN_CODE}] Could not fix create access to basic public schema ACLs in $database";
fi;
else
echo "${PG_COMMAND[*]}";
fi;
fi; fi;
# SEQUENCE RESET DATA COLLECTION # SEQUENCE RESET DATA COLLECTION
echo "Resetting all sequences from DB $database [$_host:$_port] @ `date +"%F %T"`"; echo "[6] ? Resetting all sequences from DB $database [$_host:$_port] @ $(date +"%F %T")";
reset_query="SELECT 'SELECT SETVAL(' ||quote_literal(S.relname)|| ', MAX(' ||quote_ident(C.attname)|| ') ) FROM ' ||quote_ident(T.relname)|| ';' FROM pg_class AS S, pg_depend AS D, pg_class AS T, pg_attribute AS C WHERE S.relkind = 'S' AND S.oid = D.objid AND D.refobjid = T.oid AND D.refobjid = C.attrelid AND D.refobjsubid = C.attnum ORDER BY S.relname;"; reset_query="SELECT 'SELECT SETVAL(' ||quote_literal(S.relname)|| ', MAX(' ||quote_ident(C.attname)|| ') ) FROM ' ||quote_ident(T.relname)|| ';' FROM pg_class AS S, pg_depend AS D, pg_class AS T, pg_attribute AS C WHERE S.relkind = 'S' AND S.oid = D.objid AND D.refobjid = T.oid AND D.refobjid = C.attrelid AND D.refobjsubid = C.attnum ORDER BY S.relname;";
_PG_PARAMS=("${PG_PARAMS[@]}");
_PG_PARAMS+=("-U" "postgres" "-AtqX" "-o" "${TEMP_FILE}" "-c" "${reset_query}" "${database}");
PG_COMMAND=("${PG_PSQL}" "${_PG_PARAMS[@]}");
_PG_PARAMS_OUT=("${PG_PARAMS[@]}");
_PG_PARAMS_OUT+=("-U" "postgres" "-X" "-e" "-f" "${TEMP_FILE}" "${database}");
PG_COMMAND_OUT=("${PG_PSQL}" "${_PG_PARAMS_OUT[@]}");
LOG_OUTPUT_FILE="${LOG_PATH}/output_sequence.${LOG_FILE_EXT}";
LOG_ERROR_FILE="${LOG_PATH}/errors_sequence.${database}.${LOG_FILE_EXT}";
if [ $DRY_RUN -eq 0 ]; then if [ $DRY_RUN -eq 0 ]; then
echo "${reset_query}" | $PG_PSQL -U postgres -Atq $host $port -o $TEMP_FILE $database "${PG_COMMAND[@]}";
$PG_PSQL -U postgres $host $port -e -f $TEMP_FILE $database 1>output_sequence.$LOG_FILE_EXT 2>errors_sequence.$database.$LOG_FILE_EXT; RETURN_CODE=$?;
rm $TEMP_FILE; if [ $RETURN_CODE -ne 0 ]; then
echo "[!:${RETURN_CODE}] Could not create sequence reset query for database $database";
fi;
if [ -f "${TEMP_FILE}" ]; then
"${PG_COMMAND_OUT[@]}" 1>"${LOG_OUTPUT_FILE}" 2>"${LOG_ERROR_FILE}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!:${RETURN_CODE}] Could not reset sequences for database $database";
fi;
rm "${TEMP_FILE}";
fi;
else else
echo "${reset_query}"; echo "${PG_COMMAND[*]}";
echo $PG_PSQL -U postgres $host $port -e -f $TEMP_FILE $database 1>output_sequence.$LOG_FILE_EXT 2>errors_sequence.$database.$LOG_FILE_EXT; echo "${PG_COMMAND_OUT[*]} 1>${LOG_OUTPUT_FILE} 2>${LOG_ERROR_FILE}";
fi; fi;
echo "Restore of data $file for DB $database [$_host:$_port] finished"; echo "[.] $ Restore of data $file for DB $database [$_host:$_port] finished";
DURATION=$[ `date +'%s'`-$START ]; DURATION=$(($(date "+%s")-START));
echo "Start at $start_time and end at `date +"%F %T"` and ran for $(convert_time ${DURATION})"; echo "[.] * Start at $start_time and end at $(date +"%F %T") and ran for $(convert_time ${DURATION})";
echo "=== END RESTORE" >>restore_errors.$LOG_FILE_EXT; echo "=== END RESTORE" >>"${LOG_PATH}/restore_errors.${LOG_FILE_EXT}";
fi; fi;

View File

@@ -1,11 +1,15 @@
#!/bin/bash #!/bin/bash
# Author: Clemens Schwaighofer
# Description:
# Drop and restore databases from a folder for dump files created by pg_db_dump_file.sh
function usage () function usage ()
{ {
cat <<- EOT cat <<- EOT
Restores a list of database dumps from a folder to a database server Restores a list of database dumps from a folder to a database server
Usage: ${0##/*/} -f <dump folder> [-j <JOBS>] [-e <ENCODING>] [-h <HOST>] [-r|-a] [-g] [-n] Usage: ${0##/*/} -f <dump folder> [-j <JOBS>] [-e <ENCODING>] [-h <HOST>] [-r] [-g] [-n]
-e <ENCODING>: override global encoding, will be overruled by per file encoding -e <ENCODING>: override global encoding, will be overruled by per file encoding
-p <PORT>: override default port from file. -p <PORT>: override default port from file.
@@ -13,7 +17,6 @@ function usage ()
-f: dump folder source. Where the database dump files are located. This is a must set option -f: dump folder source. Where the database dump files are located. This is a must set option
-j <JOBS>: Run how many jobs Parallel. If not set, 2 jobs are run parallel -j <JOBS>: Run how many jobs Parallel. If not set, 2 jobs are run parallel
-r: use redhat base paths instead of debian -r: use redhat base paths instead of debian
-a: use amazon base paths instead of debian
-g: do not import globals file -g: do not import globals file
-n: dry run, do not import or change anything -n: dry run, do not import or change anything
EOT EOT
@@ -25,8 +28,8 @@ _host='local';
HOST=''; HOST='';
_encoding='UTF8'; _encoding='UTF8';
set_encoding=''; set_encoding='';
ERROR=0;
REDHAT=0; REDHAT=0;
AMAZON=0;
IMPORT_GLOBALS=1; IMPORT_GLOBALS=1;
TEMPLATEDB='template0'; # truly empty for restore TEMPLATEDB='template0'; # truly empty for restore
DUMP_FOLDER=''; DUMP_FOLDER='';
@@ -35,8 +38,10 @@ BC='/usr/bin/bc';
PORT_REGEX="^[0-9]{4,5}$"; PORT_REGEX="^[0-9]{4,5}$";
OPTARG_REGEX="^-"; OPTARG_REGEX="^-";
DRY_RUN=0; DRY_RUN=0;
PG_PARAM_HOST=();
PG_PARAM_PORT=();
# options check # options check
while getopts ":f:j:h:p:e:granm" opt; do while getopts ":f:j:h:p:e:grnm" opt; do
# pre test for unfilled # pre test for unfilled
if [ "${opt}" = ":" ] || [[ "${OPTARG-}" =~ ${OPTARG_REGEX} ]]; then if [ "${opt}" = ":" ] || [[ "${OPTARG-}" =~ ${OPTARG_REGEX} ]]; then
if [ "${opt}" = ":" ]; then if [ "${opt}" = ":" ]; then
@@ -68,44 +73,53 @@ while getopts ":f:j:h:p:e:granm" opt; do
esac esac
fi; fi;
case $opt in case $opt in
f|file) # f|file)
f)
DUMP_FOLDER=$OPTARG; DUMP_FOLDER=$OPTARG;
;; ;;
j|jobs) # j|jobs)
j)
MAX_JOBS=${OPTARG}; MAX_JOBS=${OPTARG};
;; ;;
e|encoding) # e|encoding)
e)
if [ -z "$encoding" ]; then if [ -z "$encoding" ]; then
encoding=$OPTARG; encoding=$OPTARG;
fi; fi;
;; ;;
h|hostname) # h|hostname)
h)
if [ -z "$host" ]; then if [ -z "$host" ]; then
host='-h '$OPTARG; # do not set if local name (uses socket)
if [ "$OPTARG" != "local" ]; then
PG_PARAM_HOST=("-h" "${OPTARG}");
fi;
_host=$OPTARG; _host=$OPTARG;
HOST=$OPRTARG; HOST=$OPRTARG;
fi; fi;
;; ;;
p|port) # p|port)
p)
if [ -z "$port" ]; then if [ -z "$port" ]; then
port='-p '$OPTARG; PG_PARAM_PORT=("-p" "${OPTARG}");
_port=$OPTARG; _port=$OPTARG;
PORT=$OPTARG; PORT=$OPTARG;
fi; fi;
;; ;;
g|globals) # g|globals)
g)
IMPORT_GLOBALS=0; IMPORT_GLOBALS=0;
;; ;;
r|redhat) # r|redhat)
r)
REDHAT=1; REDHAT=1;
;; ;;
a|amazon) # n|dry-run)
AMAZON=1; n)
;;
n|dry-run)
DRY_RUN=1; DRY_RUN=1;
;; ;;
m|help) # m|help)
m)
usage; usage;
exit 0; exit 0;
;; ;;
@@ -117,19 +131,16 @@ while getopts ":f:j:h:p:e:granm" opt; do
esac; esac;
done; done;
if [ "$REDHAT" -eq 1 ] && [ "$AMAZON" -eq 1 ]; then if [ "${ERROR}" -eq 1 ]; then
echo "You cannot set the -a and -r flag at the same time"; exit 0;
fi; fi;
if [ "$REDHAT" -eq 1 ]; then if [ "$REDHAT" -eq 1 ]; then
# Redhat base path (for non official ones would be '/usr/pgsql-' # Redhat base path (for non official ones would be '/usr/pgsql-'
DBPATH_BASE='/usr/pgsql-' PG_BASE_PATH='/usr/pgsql-'
elif [ "$AMAZON" -eq 1 ]; then
# Amazon paths (lib64 default amazon package)
DBPATH_BASE='/usr/lib64/pgsql';
else else
# Debian base path # Debian base path
DBPATH_BASE='/usr/lib/postgresql/'; PG_BASE_PATH='/usr/lib/postgresql/';
fi; fi;
# check that the port is a valid number # check that the port is a valid number
@@ -142,16 +153,15 @@ NUMBER_REGEX="^[0-9]{1,}$";
# find the max allowed jobs based on the cpu count # find the max allowed jobs based on the cpu count
# because setting more than this is not recommended # because setting more than this is not recommended
# so this fails in vmware hosts were we have random cpus assigned # so this fails in vmware hosts were we have random cpus assigned
cpu=$(cat /proc/cpuinfo | grep "processor" | wc -l); _max_jobs=$(nproc --all);
_max_jobs=${cpu##*: };
# if the MAX_JOBS is not number or smaller 1 or greate _max_jobs # if the MAX_JOBS is not number or smaller 1 or greate _max_jobs
if [ ! -z "${MAX_JOBS}" ]; then if [ -n "${MAX_JOBS}" ]; then
# check that it is a valid number # check that it is a valid number
if [[ ! ${MAX_JOBS} =~ ${NUMBER_REGEX} ]]; then if [[ ! ${MAX_JOBS} =~ ${NUMBER_REGEX} ]]; then
echo "Please enter a number for the -j option"; echo "Please enter a number for the -j option";
exit 1; exit 1;
fi; fi;
if [ ${MAX_JOBS} -lt 1 ] || [ ${MAX_JOBS} -gt ${_max_jobs} ]; then if [ "${MAX_JOBS}" -lt 1 ] || [ "${MAX_JOBS}" -gt "${_max_jobs}" ]; then
echo "The value for the jobs option -j cannot be smaller than 1 or bigger than ${_max_jobs}"; echo "The value for the jobs option -j cannot be smaller than 1 or bigger than ${_max_jobs}";
exit 1; exit 1;
fi; fi;
@@ -171,14 +181,14 @@ if [ ! -d "$DUMP_FOLDER" ]; then
exit; exit;
fi; fi;
LOGS=$DUMP_FOLDER'/logs/'; LOG_PATH=$DUMP_FOLDER'/logs/';
# create logs folder if missing # create logs folder if missing
if [ ! -d "$LOGS" ]; then if [ ! -d "$LOG_PATH" ]; then
echo "Creating '$LOGS' folder"; echo "+ Creating '$LOG_PATH' folder";
mkdir -p "$LOGS"; mkdir -p "$LOG_PATH";
if [ ! -d "$LOGS" ]; then if [ ! -d "$LOG_PATH" ]; then
echo "Creation of '$LOGS' folder failed"; echo "[!] Creation of '$LOG_PATH' folder failed";
exit; exit 1;
fi; fi;
fi; fi;
@@ -199,15 +209,15 @@ function convert_time
{ {
timestamp=${1}; timestamp=${1};
# round to four digits for ms # round to four digits for ms
timestamp=$(printf "%1.4f" $timestamp); timestamp=$(printf "%1.4f" "$timestamp");
# get the ms part and remove any leading 0 # get the ms part and remove any leading 0
ms=$(echo ${timestamp} | cut -d "." -f 2 | sed -e 's/^0*//'); ms=$(echo "${timestamp}" | cut -d "." -f 2 | sed -e 's/^0*//');
timestamp=$(echo ${timestamp} | cut -d "." -f 1); timestamp=$(echo "${timestamp}" | cut -d "." -f 1);
timegroups=(86400 3600 60 1); # day, hour, min, sec timegroups=(86400 3600 60 1); # day, hour, min, sec
timenames=("d" "h" "m" "s"); # day, hour, min, sec timenames=("d" "h" "m" "s"); # day, hour, min, sec
output=( ); output=( );
time_string=''; time_string='';
for timeslice in ${timegroups[@]}; do for timeslice in "${timegroups[@]}"; do
# floor for the division, push to output # floor for the division, push to output
if [ ${BC_OK} -eq 1 ]; then if [ ${BC_OK} -eq 1 ]; then
output[${#output[*]}]=$(echo "${timestamp}/${timeslice}" | bc); output[${#output[*]}]=$(echo "${timestamp}/${timeslice}" | bc);
@@ -219,16 +229,18 @@ function convert_time
done; done;
for ((i=0; i<${#output[@]}; i++)); do for ((i=0; i<${#output[@]}; i++)); do
if [ ${output[$i]} -gt 0 ] || [ ! -z "$time_string" ]; then if [ "${output[$i]}" -gt 0 ] || [ -n "$time_string" ]; then
if [ ! -z "${time_string}" ]; then if [ -n "${time_string}" ]; then
time_string=${time_string}" "; time_string=${time_string}" ";
fi; fi;
time_string=${time_string}${output[$i]}${timenames[$i]}; time_string=${time_string}${output[$i]}${timenames[$i]};
fi; fi;
done; done;
if [ ! -z ${ms} ]; then # milliseconds must be filled, but we also check that they are non "nan" string
if [ ${ms} -gt 0 ]; then # that can appear in the original value
time_string=${time_string}" "${ms}"ms"; if [ -n "${ms}" ] && [ "${ms}" != "nan" ]; then
if [ "${ms}" -gt 0 ]; then
time_string="${time_string} ${ms}ms";
fi; fi;
fi; fi;
# just in case the time is 0 # just in case the time is 0
@@ -239,25 +251,22 @@ function convert_time
} }
# default version (for folder) # default version (for folder)
DBPATH_VERSION='9.6/'; PG_PATH_VERSION='15/';
# if amazon remove "." from version PG_PATH_BIN='bin/';
if [ "${AMAZON}" -eq 1 ]; then
DBPATH_VERSION=$(echo "${DBPATH_VERSION}" | sed -e 's/\.//');
fi;
DBPATH_BIN='bin/';
# postgresql binaries # postgresql binaries
DROPDB="dropdb"; PG_DROPDB="dropdb";
CREATEDB="createdb"; PG_CREATEDB="createdb";
CREATELANG="createlang"; PG_CREATELANG="createlang";
PGRESTORE="pg_restore"; PG_RESTORE="pg_restore";
CREATEUSER="createuser"; PG_CREATEUSER="createuser";
PSQL="psql"; PG_PSQL="psql";
# default port and host # default port and host
EXCLUDE_LIST="pg_globals"; # space separated EXCLUDE_LIST="pg_globals"; # space separated
LOGFILE="tee -a $LOGS/PG_RESTORE_DB_FILE.`date +"%Y%m%d_%H%M%S"`.log"; LOG_FILE="tee -a "$LOG_PATH/PG_RESTORE_DB_FILE.$(date +"%Y%m%d_%H%M%S").log"";
# get the count for DBs to import # get the count for DBs to import
db_count=`find $DUMP_FOLDER -name "*.sql" -print | wc -l`; db_count=$(find "${DUMP_FOLDER}" -name "*.sql" -print | wc -l);
# start info # start info
if [ "${DUMP_FOLDER}" = "." ]; then if [ "${DUMP_FOLDER}" = "." ]; then
_DUMP_FOLDER="[current folder]"; _DUMP_FOLDER="[current folder]";
@@ -274,95 +283,122 @@ if [ -z "${PORT}" ]; then
else else
_PORT=${PORT}; _PORT=${PORT};
fi; fi;
echo "= Will import $db_count databases from $_DUMP_FOLDER" | $LOGFILE; if [ ${DRY_RUN} ]; then
echo "= into the DB server $_HOST:$_PORT" | $LOGFILE; echo "**** [DRY RUN] ****";
echo "= running $MAX_JOBS jobs" | $LOGFILE; fi;
echo "= import logs: $LOGS" | $LOGFILE; echo "= Will import $db_count databases from $_DUMP_FOLDER" | $LOG_FILE;
echo "" | $LOGFILE; echo "= into the DB server $_HOST:$_PORT" | $LOG_FILE;
echo "= running $MAX_JOBS jobs" | $LOG_FILE;
echo "= import logs: $LOG_PATH" | $LOG_FILE;
echo "" | $LOG_FILE;
pos=1; pos=1;
# go through all the files an import them into the database # go through all the files an import them into the database
MASTERSTART=`date +'%s'`; MASTERSTART=$(date +"%s");
master_start_time=`date +"%F %T"`; master_start_time=$(date +"%F %T");
# first import the pg_globals file if this is requested, default is yes # first import the pg_globals file if this is requested, default is yes
if [ "$IMPORT_GLOBALS" -eq 1 ]; then if [ "$IMPORT_GLOBALS" -eq 1 ]; then
start_time=`date +"%F %T"`; start_time=$(date +"%F %T");
START=`date +'%s'`; START=$(date +"%s");
# get the pg_globals file # get the pg_globals file
echo "=[Globals Restore]=START=[$start_time]==================================================>" | $LOGFILE; echo "=[Globals Restore]=START=[$start_time]==================================================>" | $LOG_FILE;
# get newest and only the first one # get newest and only the first one
file=`ls -1t $DUMP_FOLDER/pg_global* | head -1`; file=$(find "$DUMP_FOLDER" -name "pg_global*" -type f -printf "%Ts\t%p\n" | sort -nr | head -1);
filename=`basename $file`; filename=$(basename "$file");
# the last _ is for version 10 or higher # the last _ is for version 10 or higher
version=`echo $filename | cut -d "." -f 4 | cut -d "-" -f 2 | cut -d "_" -f 1`; # db version, without prefix of DB type # db version, without prefix of DB type
version=$(echo "$filename" | cut -d "." -f 4 | cut -d "-" -f 2 | cut -d "_" -f 1);
cut_pos=4;
# if this is < 10 then we need the second part too # if this is < 10 then we need the second part too
if [ ${version} -lt 10 ]; then if [ "${version}" -lt 10 ]; then
version=$version'.'`echo $filename | cut -d "." -f 5 | cut -d "_" -f 1`; # db version, second part (after .) # db version, second part (after .)
version=$version'.'$(echo "$filename" | cut -d "." -f 5 | cut -d "_" -f 1);
cut_pos=5;
fi; fi;
# if amazon remove "." from version # hostname of original DB, can be used as target host too
if [ "${AMAZON}" -eq 1 ]; then __host=$(echo "$filename" | cut -d "." -f ${cut_pos} | cut -d "_" -f 2);
version=$(echo "${version}" | sed -e 's/\.//'); # port of original DB, can be used as target port too
fi; __port=$(echo "$filename" | cut -d "." -f ${cut_pos} | cut -d "_" -f 3);
__host=`echo $filename | cut -d "." -f 5 | cut -d "_" -f 2`; # hostname of original DB, can be used as target host too
__port=`echo $filename | cut -d "." -f 5 | cut -d "_" -f 3`; # port of original DB, can be used as target port too
# override file port over given port if it differs and is valid # override file port over given port if it differs and is valid
if [ -z $_port ] && [ "$__port" != $_port ] && [[ $__port =~ $PORT_REGEX ]] ; then if [ -z "$_port" ] && [ "$__port" != "$_port" ] && [[ "$__port" =~ $PORT_REGEX ]] ; then
_port=$__port; _port=$__port;
port='-p '$_port; PG_PARAM_PORT=("-p" "$_port");
fi; fi;
if [ -z "$_host" ] && [ "$__host" != "local" ]; then if [ -z "$_host" ] && [ "$__host" != "local" ]; then
_host=$__host; _host=$__host;
host='-h '$_host; PG_PARAM_HOST=("-h" "${_host}");
fi; fi;
# create the path to the DB from the DB version in the backup file # create the path to the DB from the DB version in the backup file
if [ ! -z "$version" ]; then if [ -n "$version" ]; then
DBPATH_VERSION_LOCAL=$version'/'; PG_PATH_VERSION_LOCAL="${version}/";
else else
DBPATH_VERSION_LOCAL=$DBPATH_VERSION; PG_PATH_VERSION_LOCAL="${PG_PATH_VERSION}";
fi; fi;
DBPATH=$DBPATH_BASE$DBPATH_VERSION_LOCAL$DBPATH_BIN; PG_PATH="${PG_BASE_PATH}${PG_PATH_VERSION_LOCAL}${PG_PATH_BIN}";
echo "+ Restore globals file: $filename to [$_host:$_port] @ `date +"%F %T"`" | $LOGFILE; echo "+ Restore globals file: $filename to [$_host:$_port] @ $(date +"%F %T")" | $LOG_FILE;
_PG_PARAMS=("-U" "postgres");
_PG_PARAMS+=("${PG_PARAM_HOST[@]}");
_PG_PARAMS+=("${PG_PARAM_PORT[@]}");
_PG_PARAMS+=("-f" "$file" "-e" "-q" "-X" "template1");
PG_COMMAND=("${PG_PATH}${PG_PSQL}" "${_PG_PARAMS[@]}");
if [ ${DRY_RUN} -eq 0 ]; then if [ ${DRY_RUN} -eq 0 ]; then
$DBPATH$PSQL -U postgres $host $port -f $file -e -q -X template1 | $LOGFILE; "${PG_COMMAND[@]}" | $LOG_FILE;
else else
echo "$DBPATH$PSQL -U postgres $host $port -f $file -e -q -X template1" | $LOGFILE; echo "${PG_COMMAND[*]}" | $LOG_FILE;
fi; fi;
DURATION=$[ `date +'%s'`-$START ]; DURATION=$(($(date +"%s")-START));
printf "=[Globals Restore]=END===[%s]========================================================>\n" "$(convert_time ${DURATION})" | $LOGFILE; printf "=[Globals Restore]=END===[%s]========================================================>\n" "$(convert_time ${DURATION})" | $LOG_FILE;
fi; fi;
for file in $DUMP_FOLDER/*.sql; do for file in "$DUMP_FOLDER/"*.sql; do
start_time=`date +"%F %T"`; start_time=$(date +"%F %T");
START=`date +'%s'`; START=$(date +"%s");
echo "=[$pos/$db_count]=START=[$start_time]==================================================>" | $LOGFILE; echo "=[$pos/$db_count]=START=[$start_time]==================================================>" | $LOG_FILE;
# the encoding # the encoding
set_encoding=''; set_encoding='';
# get the filename # get the filename
filename=`basename $file`; filename=$(basename "$file");
# get the databse, user # get the databse, user
# default file name is <database>.<owner>.<encoding>.<type>-<version>_<host>_<port>_<date>_<time>_<sequence> # default file name is <database>.<owner>.<encoding>.<type>-<version>_<host>_<port>_<date>_<time>_<sequence>
database=`echo $filename | cut -d "." -f 1`; database=$(echo "$filename" | cut -d "." -f 1);
owner=`echo $filename | cut -d "." -f 2`; # check this is skip or not
__encoding=`echo $filename | cut -d "." -f 3`; exclude=0;
for exclude_db in $EXCLUDE_LIST; do
if [ "$exclude_db" = "$database" ]; then
exclude=1;
break;
fi;
done;
if [ $exclude -eq 1 ]; then
DURATION=0;
echo "# Skipped DB '$database'" | $LOG_FILE;
printf "=[$pos/$db_count]=END===[%s]========================================================>\n" "$(convert_time ${DURATION})" | $LOG_FILE;
pos=$((pos+1));
continue;
fi;
# restore DB
owner=$(echo "$filename" | cut -d "." -f 2);
__encoding=$(echo "$filename" | cut -d "." -f 3);
# the last _ part if for version 10 # the last _ part if for version 10
version=`echo $filename | cut -d "." -f 4 | cut -d "-" -f 2 | cut -d "_" -f 1`; # db version, without prefix of DB type # db version, without prefix of DB type
version=$(echo "$filename" | cut -d "." -f 4 | cut -d "-" -f 2 | cut -d "_" -f 1);
# if this is < 10 then we need the second part too # if this is < 10 then we need the second part too
if [ ${version} -lt 10 ]; then if [ "${version}" -lt 10 ]; then
version=$version'.'`echo $filename | cut -d "." -f 5 | cut -d "_" -f 1`; # db version, second part (after .) # db version, second part (after .)
version=$version'.'$(echo "$filename" | cut -d "." -f 5 | cut -d "_" -f 1);
fi; fi;
# if amazon remove "." from version # hostname of original DB, can be used as target host too
if [ "${AMAZON}" -eq 1 ]; then __host=$(echo "$filename" | cut -d "." -f 5 | cut -d "_" -f 2);
version=$(echo "${version}" | sed -e 's/\.//'); # port of original DB, can be used as target port too
fi; __port=$(echo "$filename" | cut -d "." -f 5 | cut -d "_" -f 3);
__host=`echo $filename | cut -d "." -f 5 | cut -d "_" -f 2`; # hostname of original DB, can be used as target host too # backup date and time, plus sequence
__port=`echo $filename | cut -d "." -f 5 | cut -d "_" -f 3`; # port of original DB, can be used as target port too # other=$(echo "$filename" | cut -d "." -f 5 | cut -d "_" -f 2-);
other=`echo $filename | cut -d "." -f 5 | cut -d "_" -f 2-`; # backup date and time, plus sequence
# override file port over given port if it differs and is valid # override file port over given port if it differs and is valid
if [ -z $_port ] && [ "$__port" != $_port ] && [[ $__port =~ $PORT_REGEX ]] ; then if [ -z "$_port" ] && [ "$__port" != "$_port" ] && [[ "$__port" =~ $PORT_REGEX ]] ; then
_port=$__port; _port=$__port;
port='-p '$_port; PG_PARAM_PORT=("-p" "$_port");
fi; fi;
if [ -z "$_host" ] && [ "$__host" != "local" ]; then if [ -z "$_host" ] && [ "$__host" != "local" ]; then
_host=$__host; _host=$__host;
host='-h '$_host; PG_PARAM_HOST=("-h" "${_host}");
fi; fi;
# override encoding (dangerous) # override encoding (dangerous)
# check if we have a master override # check if we have a master override
@@ -371,84 +407,142 @@ for file in $DUMP_FOLDER/*.sql; do
fi; fi;
# if no override encoding set first from file, then from global # if no override encoding set first from file, then from global
if [ ! "$set_encoding" ]; then if [ ! "$set_encoding" ]; then
if [ ! -z "$__encoding" ]; then if [ -n "$__encoding" ]; then
set_encoding=$__encoding; set_encoding=$__encoding;
else else
set_encoding=$_encoding; set_encoding=$_encoding;
fi; fi;
fi; fi;
# create the path to the DB from the DB version in the backup file # create the path to the DB from the DB version in the backup file
if [ ! -z "$version" ]; then if [ -n "$version" ]; then
DBPATH_VERSION_LOCAL=$version'/'; PG_PATH_VERSION_LOCAL="${version}/";
else else
DBPATH_VERSION_LOCAL=$DBPATH_VERSION; PG_PATH_VERSION_LOCAL="${PG_PATH_VERSION}";
fi; fi;
DBPATH=$DBPATH_BASE$DBPATH_VERSION_LOCAL$DBPATH_BIN; PG_PATH="${PG_BASE_PATH}${PG_PATH_VERSION_LOCAL}${PG_PATH_BIN}";
# check this is skip or not # create user if not exist yet
exclude=0; # check query for user
for exclude_db in $EXCLUDE_LIST; do # for all calls
if [ "$exclude_db" = "$database" ]; then _PG_PARAMS_ALL=("-U" "postgres");
exclude=1; _PG_PARAMS_ALL+=("${PG_PARAM_HOST[@]}");
fi; _PG_PARAMS_ALL+=("${PG_PARAM_PORT[@]}");
done; # for the call
if [ $exclude -eq 0 ]; then _PG_PARAMS=("${_PG_PARAMS_ALL[@]}");
# create user if not exist yet _PG_PARAMS+=("-A" "-F" "," "-t" "-q" "-X" "-c" "SELECT oid FROM pg_roles WHERE rolname = '$owner';" "template1");
# check query for user user_oid=$("$PG_PSQL" "${_PG_PARAMS[@]}");
user_oid=`echo "SELECT oid FROM pg_roles WHERE rolname = '$owner';" | $PSQL -U postgres $host $port -A -F "," -t -q -X template1`; if [ -z "$user_oid" ]; then
if [ -z $user_oid ]; then echo "+ Create USER '$owner' for DB '$database' [$_host:$_port] @ $(date +"%F %T")" | $LOG_FILE;
echo "+ Create USER '$owner' for DB '$database' [$_host:$_port] @ `date +"%F %T"`" | $LOGFILE; _PG_PARAMS=("${_PG_PARAMS_ALL[@]}");
if [ ${DRY_RUN} -eq 0 ]; then _PG_PARAMS+=("-D" "-R" "-S" "$owner");
$CREATEUSER -U postgres -D -R -S $host $port $owner; PG_COMMAND=("${PG_PATH}${PG_CREATEUSER}" "${_PG_PARAMS[@]}");
else if [ ${DRY_RUN} -eq 0 ]; then
echo "$CREATEUSER -U postgres -D -R -S $host $port $owner"; "${PG_COMMAND[@]}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!] Creation of user '$owner' failed, skipping database '$database'";
printf "=[$pos/$db_count]=END===[%s]========================================================>\n" "$(convert_time 0)" | $LOG_FILE;
pos=$((pos+1));
continue;
fi; fi;
fi;
# before importing the data, drop this database
echo "- Drop DB '$database' [$_host:$_port] @ `date +"%F %T"`" | $LOGFILE;
if [ ${DRY_RUN} -eq 0 ]; then
$DBPATH$DROPDB -U postgres $host $port $database;
else else
echo "$DBPATH$DROPDB -U postgres $host $port $database"; echo "${PG_COMMAND[*]}";
fi; fi;
echo "+ Create DB '$database' with '$owner' [$_host:$_port] @ `date +"%F %T"`" | $LOGFILE; fi;
if [ ${DRY_RUN} -eq 0 ]; then # before importing the data, drop this database
$DBPATH$CREATEDB -U postgres -O $owner -E $set_encoding -T $TEMPLATEDB $host $port $database; echo "- Drop DB '$database' [$_host:$_port] @ $(date +"%F %T")" | $LOG_FILE;
else _PG_PARAMS=("${_PG_PARAMS_ALL[@]}");
echo "$DBPATH$CREATEDB -U postgres -O $owner -E $set_encoding -T $TEMPLATEDB $host $port $database"; _PG_PARAMS+=("$database");
PG_COMMAND=("${PG_PATH}${PG_DROPDB}" "${_PG_PARAMS[@]}");
if [ ${DRY_RUN} -eq 0 ]; then
"${PG_COMMAND[@]}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!] Could not drop database, skipping database '$database'";
printf "=[$pos/$db_count]=END===[%s]========================================================>\n" "$(convert_time 0)" | $LOG_FILE;
pos=$((pos+1));
continue;
fi; fi;
if [ -f $DBPATH$CREATELANG ]; then
echo "+ Create plpgsql lang in DB '$database' [$_host:$_port] @ `date +"%F %T"`" | $LOGFILE;
if [ ${DRY_RUN} -eq 0 ]; then
$DBPATH$CREATELANG -U postgres plpgsql $host $port $database;
else
echo "$DBPATH$CREATELANG -U postgres plpgsql $host $port $database";
fi;
fi;
echo "% Restore data from '$filename' to DB '$database' using $MAX_JOBS jobs [$_host:$_port] @ `date +"%F %T"`" | $LOGFILE;
if [ ${DRY_RUN} -eq 0 ]; then
$DBPATH$PGRESTORE -U postgres -d $database -F c -v -c -j $MAX_JOBS $host $port $file 2>$LOGS'/errors.'$database'.'$(date +"%Y%m%d_%H%M%S".log);
else
echo "$DBPATH$PGRESTORE -U postgres -d $database -F c -v -c -j $MAX_JOBS $host $port $file 2>$LOGS'/errors.'$database'.'$(date +"%Y%m%d_%H%M%S".log)";
fi;
# BUG FIX FOR POSTGRESQL 9.6.2 db_dump
# it does not dump the default public ACL so the owner of the DB cannot access the data, check if the ACL dump is missing and do a basic restore
if [ -z "$($DBPATH$PGRESTORE -l $file | grep -- "ACL - public postgres")" ]; then
echo "? Fixing missing basic public schema ACLs from DB $database [$_host:$_port] @ `date +"%F %T"`";
# grant usage on schema public to public;
# grant create on schema public to public;
echo "GRANT USAGE ON SCHEMA public TO public;" | $DBPATH$PSQL -U postgres -Atq $host $port $database;
echo "GRANT CREATE ON SCHEMA public TO public;" | $DBPATH$PSQL -U postgres -Atq $host $port $database;
fi;
echo "$ Restore of data '$filename' for DB '$database' [$_host:$_port] finished" | $LOGFILE;
DURATION=$[ `date +'%s'`-$START ];
echo "* Start at $start_time and end at `date +"%F %T"` and ran for $(convert_time ${DURATION}) seconds" | $LOGFILE;
else else
DURATION=0; echo "${PG_COMMAND[*]}";
echo "# Skipped DB '$database'" | $LOGFILE;
fi; fi;
printf "=[$pos/$db_count]=END===[%s]========================================================>\n" "$(convert_time ${DURATION})" | $LOGFILE; echo "+ Create DB '$database' with '$owner' [$_host:$_port] @ $(date +"%F %T")" | $LOG_FILE;
pos=$[ $pos+1 ]; _PG_PARAMS=("${_PG_PARAMS_ALL[@]}");
_PG_PARAMS+=("-O" "$owner" "-E" "$set_encoding" "-T" "$TEMPLATEDB" "$database");
PG_COMMAND=("${PG_PATH}${PG_CREATEDB}" "${_PG_PARAMS[@]}");
if [ ${DRY_RUN} -eq 0 ]; then
"${PG_COMMAND[@]}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!] Could not create database, skipping database '$database'";
printf "=[$pos/$db_count]=END===[%s]========================================================>\n" "$(convert_time 0)" | $LOG_FILE;
pos=$((pos+1));
continue;
fi;
else
echo "${PG_COMMAND[*]}";
fi;
if [ -f "${PG_PATH}${PG_CREATELANG}" ]; then
echo "+ Create plpgsql lang in DB '$database' [$_host:$_port] @ $(date +"%F %T")" | $LOG_FILE;
_PG_PARAMS=("${_PG_PARAMS_ALL[@]}");
_PG_PARAMS+=("plpgsql" "$database");
PG_COMMAND=("${PG_PATH}${PG_CREATELANG}" "${_PG_PARAMS[@]}");
if [ ${DRY_RUN} -eq 0 ]; then
"${PG_COMMAND[@]}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!] Could not create plpgsql language, skipping database '$database'";
printf "=[$pos/$db_count]=END===[%s]========================================================>\n" "$(convert_time 0)" | $LOG_FILE;
pos=$((pos+1));
continue;
fi;
else
echo "${PG_COMMAND[*]}";
fi;
fi;
echo "% Restore data from '$filename' to DB '$database' using $MAX_JOBS jobs [$_host:$_port] @ $(date +"%F %T")" | $LOG_FILE;
_PG_PARAMS=("${_PG_PARAMS_ALL[@]}");
_PG_PARAMS+=("-d" "$database" "-F" "c" "-v" "-c" "-j" "$MAX_JOBS" "$file");
PG_COMMAND=("${PG_PATH}${PG_RESTORE}" "${_PG_PARAMS[@]}");
LOG_ERROR_FILE="$LOG_PATH/errors.${database}.$(date +"%Y%m%d_%H%M%S").log";
if [ ${DRY_RUN} -eq 0 ]; then
"${PG_COMMAND[@]}" 2>"${LOG_ERROR_FILE}";
RETURN_CODE=$?;
if [ $RETURN_CODE -ne 0 ]; then
echo "[!] Restore of database '$database' failed, see ${LOG_ERROR_FILE} for details";
fi;
else
echo "${PG_COMMAND[*]} 2>${LOG_ERROR_FILE}";
fi;
# BUG FIX FOR POSTGRESQL 9.6.2 db_dump
# it does not dump the default public ACL so the owner of the DB cannot access the data,
# check if the ACL dump is missing and do a basic restore
if ! "${PG_PATH}${PG_RESTORE}" -l "$file" | grep -q -- "ACL - public postgres"; then
echo "? Fixing missing basic public schema ACLs from DB $database [$_host:$_port] @ $(date +"%F %T")";
# grant usage on schema public to public;
_PG_PARAMS=("${_PG_PARAMS_ALL[@]}");
_PG_PARAMS+=("-AtqX" "-c" "GRANT USAGE ON SCHEMA public TO public;" "${database}");
PG_COMMAND=("${PG_PSQL}" "${_PG_PARAMS[@]}");
if [ ${DRY_RUN} -eq 0 ]; then
"${PG_COMMAND[@]}";
else
echo "${PG_COMMAND[*]}";
fi;
# grant create on schema public to public;
_PG_PARAMS=("${_PG_PARAMS_ALL[@]}");
_PG_PARAMS+=("-AtqX" "-c" "GRANT CREATE ON SCHEMA public TO public;" "${database}");
PG_COMMAND=("${PG_PSQL}" "${_PG_PARAMS[@]}");
if [ ${DRY_RUN} -eq 0 ]; then
"${PG_COMMAND[@]}";
else
echo "${PG_COMMAND[*]}";
fi;
fi;
echo "$ Restore of data '$filename' for DB '$database' [$_host:$_port] finished" | $LOG_FILE;
DURATION=$(($(date "+%s")-START));
echo "* Start at $start_time and end at $(date +"%F %T") and ran for $(convert_time ${DURATION}) seconds" | $LOG_FILE;
printf "=[$pos/$db_count]=END===[%s]========================================================>\n" "$(convert_time ${DURATION})" | $LOG_FILE;
pos=$((pos+1));
done; done;
DURATION=$[ `date +'%s'`-$MASTERSTART ]; DURATION=$(($(date "+%s")-MASTERSTART));
echo "" | $LOGFILE; echo "" | $LOG_FILE;
echo "= Start at $master_start_time and end at `date +"%F %T"` and ran for $(convert_time ${DURATION}) seconds. Imported $db_count databases." | $LOGFILE; echo "= Start at $master_start_time and end at $(date +"%F %T") and ran for $(convert_time ${DURATION}) seconds. Imported $db_count databases." | $LOG_FILE;