| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954 | 
							- #!/usr/bin/env bash
 
- # {{{ License and Copyright
 
- # https://github.com/k0lter/autopostgresqlbackup
 
- # Copyright (c) 2005 Aaron Axelsen <axelseaa@amadmax.com>
 
- #               2005 Friedrich Lobenstock <fl@fl.priv.at>
 
- #               2013-2023 Emmanuel Bouthenot <kolter@openics.org>
 
- #
 
- # This program is free software; you can redistribute it and/or modify
 
- # it under the terms of the GNU General Public License as published by
 
- # the Free Software Foundation; either version 2 of the License, or
 
- # (at your option) any later version.
 
- #
 
- # This program is distributed in the hope that it will be useful,
 
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
 
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
- # GNU General Public License for more details.
 
- #
 
- # You should have received a copy of the GNU General Public License
 
- # along with this program; if not, write to the Free Software
 
- # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
- # }}}
 
- # {{{ Constants
 
- PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/postgres/bin:/usr/local/pgsql/bin
 
- HOMEPAGE="https://github.com/k0lter/autopostgresqlbackup"
 
- NAME="AutoPostgreSQLBackup"         # Script name
 
- VERSION="2.2"                       # Version Number
 
- DATE="$(date '+%Y-%m-%d_%Hh%Mm')"   # Datestamp e.g 2002-09-21
 
- DNOW="$(date '+%u')"                # Day number of the week 1 to 7 where 1 represents Monday
 
- DNOM="$(date '+%d')"                # Date of the Month e.g. 27
 
- # }}}
 
- # {{{ Variables
 
- # Configuration file or directory
 
- CONFIG="/etc/autodbbackup.d"
 
- # Legacy configuration file path (for backward compatibility)
 
- CONFIG_COMPAT="/etc/default/autopostgresqlbackup"
 
- # Email Address to send errors to. If empty errors are displayed on stdout.
 
- MAILADDR="root"
 
- # Database engines supported: postgresql, mysql
 
- DBENGINE="postgresql"
 
- # Only while using PostgreSQL DB Engine
 
- SU_USERNAME=""
 
- # Username to access the Database server
 
- USERNAME=""
 
- # Password to access then Database server
 
- PASSWORD=""
 
- # Host name (or IP address) of the Database server.
 
- DBHOST="localhost"
 
- # Port of Database server.
 
- DBPORT=""
 
- # List of database(s) names(s) to backup.
 
- DBNAMES="all"
 
- # List of databases to exclude
 
- DBEXCLUDE=""
 
- # Virtual database name used to dump global objects (users, roles, tablespaces)
 
- GLOBALS_OBJECTS="postgres_globals"
 
- # Backup directory
 
- BACKUPDIR="/var/backups"
 
- # Include CREATE DATABASE statement
 
- CREATE_DATABASE="yes"
 
- # Which day do you want weekly backups?
 
- DOWEEKLY=7
 
- # Which day do you want monthly backups?
 
- DOMONTHLY=1
 
- # Backup retention count for daily backups.
 
- BRDAILY=14
 
- # Backup retention count for weekly backups.
 
- BRWEEKLY=5
 
- # Backup retention count for monthly backups.
 
- BRMONTHLY=12
 
- # Compression tool.
 
- COMP="gzip"
 
- # Compression tools options.
 
- COMP_OPTS=
 
- # pg_dump path (pg_dump will be used if empty)
 
- PGDUMP=
 
- # pg_dumpall path (pg_dumpall will be used if empty)
 
- PGDUMPALL=
 
- # Options string for use with all_dump (see pg_dump manual page).
 
- PGDUMP_OPTS=
 
- # Options string for use with pg_dumpall (see pg_dumpall manual page).
 
- PGDUMPALL_OPTS=
 
- # mysql path (mysql will be used if empty)
 
- MY=
 
- # mysqldump path (mysqldump will be used if empty)
 
- MYDUMP=
 
- # Options string for use with mysqldump (see myqldump manual page).
 
- MYDUMP_OPTS=
 
- # Backup files extension
 
- EXT="sql"
 
- # Backup files permission
 
- PERM=600
 
- # Minimum size (in bytes) for a dump/file (compressed or not).
 
- MIN_DUMP_SIZE=256
 
- # Enable encryption (asymmetric) with GnuPG.
 
- ENCRYPTION="no"
 
- # Encryption public key (path to the key)
 
- ENCRYPTION_PUBLIC_KEY=
 
- # Suffix for encyrpted files
 
- ENCRYPTION_SUFFIX=".enc"
 
- # Command or script to execute before backups
 
- PREBACKUP=
 
- # Command or script to execute after backups
 
- POSTBACKUP=
 
- # Debug mode
 
- DEBUG="no"
 
- # Encryption prerequisites
 
- GPG_HOMEDIR=
 
- # Database connection arguments
 
- CONN_ARGS=()
 
- # Hostname
 
- HOSTNAME="$(uname -n)"
 
- if [[ "${HOSTNAME}" != *.* ]]; then
 
-     HOSTNAME="$(hostname --fqdn)"
 
- fi
 
- # Return Code
 
- RC=0
 
- # }}}
 
- # {{{ log{,ger,_info,_debug,_warn,_error}()
 
- logger() {
 
-     local fd line severity reset color
 
-     fd="${1}"
 
-     severity="${2}"
 
-     reset=
 
-     color=
 
-     if [ -n "${TERM}" ]; then
 
-         reset="\e[0m"
 
-         case "${severity}" in
 
-             error)
 
-                 color="\e[0;91m"
 
-                 ;;
 
-             warn)
 
-                 color="\e[0;93m"
 
-                 ;;
 
-             debug)
 
-                 color="\e[0;96m"
 
-                 ;;
 
-             *)
 
-                 color="\e[0;94m"
 
-                 ;;
 
-         esac
 
-     fi
 
-     while IFS= read -r line ; do
 
-         printf "%s|%s|%s\n" "${fd}" "${severity}" "${line}" >> "${LOG_FILE}"
 
-         if [ "${DEBUG}" = "yes" ]; then
 
-             if [ "${fd}" = "out" ]; then
 
-                 printf "${color}%6s${reset}|%s\n" "${severity}" "${line}" >&6
 
-             elif [ "${fd}" = "err" ]; then
 
-                 printf "${color}%6s${reset}|%s\n" "${severity}" "${line}" >&7
 
-             fi
 
-         fi
 
-     done
 
- }
 
- log() {
 
-     echo "$@" | logger "out" ""
 
- }
 
- log_debug() {
 
-     echo "$@" | logger "out" "debug"
 
- }
 
- log_info() {
 
-     echo "$@" | logger "out" "info"
 
- }
 
- log_error() {
 
-     echo "$@" | logger "err" "error"
 
- }
 
- log_warn() {
 
-     echo "$@" | logger "err" "warn"
 
- }
 
- # }}}
 
- # {{{ arg_encode()
 
- arg_encode() {
 
-     while read -r arg ; do
 
-         echo "${arg}" | sed \
 
-         -e 's/%/%25/g' \
 
-         -e 's/ /%20/g' \
 
-         -e 's/\$/%24/g' \
 
-         -e 's/`/%60/g' \
 
-         -e 's/"/%22/g' \
 
-         -e "s/'/%27/g" \
 
-         -e "s/#/%23/g" \
 
-         -e 's/=/%3D/g' \
 
-         -e 's/\[/%5B/g' \
 
-         -e 's/\]/%5D/g' \
 
-         -e 's/!/%21/g' \
 
-         -e 's/>/%3E/g' \
 
-         -e 's/</%3C/g' \
 
-         -e 's/|/%7C/g' \
 
-         -e 's/;/%3B/g' \
 
-         -e 's/{/%7B/g' \
 
-         -e 's/}/%7D/g' \
 
-         -e 's/(/%28/g' \
 
-         -e 's/)/%29/g' \
 
-         -e 's/\*/%2A/g' \
 
-         -e 's/:/%3A/g' \
 
-         -e 's/\?/%3F/g' \
 
-         -e 's/&/%26/g' \
 
-         -e 's/\//%2F/g'
 
-     done
 
- }
 
- # }}}
 
- # {{{ arg_decode()
 
- arg_decode() {
 
-     while read -r arg ; do
 
-         echo -e "${arg//%/\\x}"
 
-     done
 
- }
 
- # }}}
 
- # {{{ gpg_setup()
 
- gpg_setup() {
 
-     GPG_HOMEDIR="$(mktemp --quiet --directory -t "${NAME}.XXXXXX")"
 
-     chmod 700 "${GPG_HOMEDIR}"
 
-     log_debug "With encryption enabled creating a temporary GnuPG home in ${GPG_HOMEDIR}"
 
-     gpg --quiet --homedir "${GPG_HOMEDIR}" --quick-gen-key --batch --passphrase-file /dev/null "root@${HOSTNAME}"
 
- }
 
- # }}}
 
- # {{{ encryption()
 
- encryption() {
 
-     log_debug "Encrypting using public key ${ENCRYPTION_PUBLIC_KEY}"
 
-     gpg --homedir "${GPG_HOMEDIR}" --encrypt --passphrase-file /dev/null --recipient-file "${ENCRYPTION_PUBLIC_KEY}" 2> >(logger "err" "error")
 
- }
 
- # }}}
 
- # {{{ compression()
 
- compression () {
 
-     if [ -n "${COMP_OPTS}" ]; then
 
-         IFS=" " read -r -a comp_args <<< "${COMP_OPTS}"
 
-         log_debug "Compressing using '${COMP} ${comp_args[*]}'"
 
-         "${COMP}" "${comp_args[@]}" 2> >(logger "err" "error")
 
-     else
 
-         log_debug "Compressing using '${COMP}'"
 
-         "${COMP}" 2> >(logger "err" "error")
 
-     fi
 
- }
 
- # }}}
 
- # {{{ postgresqldb_init()
 
- postgresqldb_init () {
 
-     if [ -z "${DBPORT}" ]; then
 
-         DBPORT="5432"
 
-     fi
 
-     CONN_ARGS=(--port "${DBPORT}")
 
-     if [ "${DBHOST}" != "localhost" ]; then
 
-         CONN_ARGS+=(--host "${DBHOST}")
 
-     fi
 
-     if [ -z "${USERNAME}" ]; then
 
-         USERNAME="postgres"
 
-     fi
 
-     CONN_ARGS+=(--username "${USERNAME}")
 
-     if [ -z "${PGDUMP}" ]; then
 
-         PGDUMP="pg_dump"
 
-     fi
 
-     if [ -z "${PGDUMPALL}" ]; then
 
-         PGDUMPALL="pg_dumpall"
 
-     fi
 
- }
 
- # }}}
 
- # {{{ postgresqldb_list()
 
- postgresqldb_list () {
 
-     local cmd_prog cmd_args raw_dblist dblist dbexcl databases
 
-     cmd_prog="psql"
 
-     cmd_args=(-t -l -A -F:)
 
-     if [ "${#CONN_ARGS[@]}" -gt 0 ]; then
 
-         cmd_args+=("${CONN_ARGS[@]}")
 
-     fi
 
-     log_debug "Running command: ${cmd_prog} ${cmd_args[*]}"
 
-     raw_dblist=$(
 
-         if [ -n "${SU_USERNAME}" ]; then
 
-             if ! su - "${SU_USERNAME}" -c "${cmd_prog} ${cmd_args[*]}" 2> >(logger "err" "error"); then
 
-                 log_error "Running (as user '${SU_USERNAME}' command '${cmd_prog} ${cmd_args[*]}' has failed"
 
-             fi
 
-         elif ! "${cmd_prog}" "${cmd_args[@]}" 2> >(logger "err" "error"); then
 
-             log_error "Running command '${cmd_prog} ${cmd_args[*]}' has failed"
 
-         fi
 
-     )
 
-     read -r -a dblist <<< "$(
 
-         printf "%s\n" "${raw_dblist}" | \
 
-             sed -E -n 's/^([^:]+):.+$/\1/p' | \
 
-             arg_encode | \
 
-             tr '\n' ' '
 
-     )"
 
-     log_debug "Automatically found databases: ${dblist[*]}"
 
-     if [ -n "${DBEXCLUDE}" ]; then
 
-         IFS=" " read -r -a dbexcl <<< "${DBEXCLUDE}"
 
-     else
 
-         dbexcl=()
 
-     fi
 
-     dbexcl+=(template0)
 
-     log_debug "Excluded databases: ${dbexcl[*]}"
 
-     mapfile -t databases < <(
 
-         comm -23 \
 
-             <(IFS=$'\n'; echo "${dblist[*]}" | sort) \
 
-             <(IFS=$'\n'; echo "${dbexcl[*]}" | sort) \
 
-         )
 
-     databases+=("${GLOBALS_OBJECTS}")
 
-     log_debug "Database(s) to be backuped: ${databases[*]}"
 
-     printf "%s " "${databases[@]}"
 
- }
 
- # }}}
 
- # {{{ postgresqldb_dump()
 
- postgresqldb_dump () {
 
-     local db_name cmd_prog cmd_args pg_args
 
-     db_name="${1}"
 
-     if [ -n "${PGDUMP_OPTS}" ]; then
 
-         IFS=" " read -r -a PGDUMP_ARGS <<< "${PGDUMP_OPTS}"
 
-     else
 
-         PGDUMP_ARGS=()
 
-     fi
 
-     # pg_dumpall options
 
-     if [ -n "${PGDUMPALL_OPTS}" ]; then
 
-         IFS=" " read -r -a PGDUMPALL_ARGS <<< "${PGDUMPALL_OPTS}"
 
-     else
 
-         PGDUMPALL_ARGS=()
 
-     fi
 
-     if [ "${db_name}" = "${GLOBALS_OBJECTS}" ]; then
 
-         cmd_prog="${PGDUMPALL}"
 
-         cmd_args=(--globals-only)
 
-         pg_args=("${PGDUMPALL_ARGS[@]}")
 
-     else
 
-         cmd_prog="${PGDUMP}"
 
-         if [ -n "${SU_USERNAME}" ]; then
 
-             cmd_args=("'${db_name}'")
 
-         else
 
-             cmd_args=("${db_name}")
 
-         fi
 
-         pg_args=("${PGDUMP_ARGS[@]}")
 
-         if [ "${CREATE_DATABASE}" = "yes" ]; then
 
-             pg_args+=(--create)
 
-         fi
 
-     fi
 
-     if [ "${#CONN_ARGS[@]}" -gt 0 ]; then
 
-         cmd_args+=("${CONN_ARGS[@]}")
 
-     fi
 
-     if [ "${#pg_args[@]}" -gt 0 ]; then
 
-         cmd_args+=("${pg_args[@]}")
 
-     fi
 
-     log_debug "Running command: ${cmd_prog} ${cmd_args[*]}"
 
-     if [ -n "${SU_USERNAME}" ]; then
 
-         if ! su - "${SU_USERNAME}" -c "${cmd_prog} ${cmd_args[*]}" 2> >(logger "err" "error"); then
 
-             log_error "Running (as user '${SU_USERNAME}' command '${cmd_prog} ${cmd_args[*]}' has failed"
 
-         fi
 
-     elif ! "${cmd_prog}" "${cmd_args[@]}" 2> >(logger "err" "error"); then
 
-         log_error "Running command '${cmd_prog} ${cmd_args[*]}' has failed"
 
-     fi
 
- }
 
- # }}}
 
- # {{{ mysqldb_init()
 
- mysqldb_init () {
 
-     CONN_ARGS=()
 
-     if [ -z "${DBPORT}" ]; then
 
-         DBPORT="3306"
 
-     fi
 
-     if [ "${DBHOST}" != "localhost" ]; then
 
-         CONN_ARGS+=(--host "${DBHOST}")
 
-     fi
 
-     if [ "${DBPORT}" != "3306" ]; then
 
-         CONN_ARGS+=(--port "${DBPORT}")
 
-     fi
 
-     if [ -z "${USERNAME}" ]; then
 
-         USERNAME="root"
 
-     fi
 
-     CONN_ARGS+=(--user "${USERNAME}")
 
-     if [ -n "${PASSWORD}" ]; then
 
-         CONN_ARGS+=(--password "${PASSWORD}")
 
-     fi
 
-     if [ -z "${MY}" ]; then
 
-         MY="mysql"
 
-     fi
 
-     if [ -z "${MYDUMP}" ]; then
 
-         MYDUMP="mysqldump"
 
-     fi
 
- }
 
- # }}}
 
- # {{{ mysqldb_list()
 
- mysqldb_list () {
 
-     local cmd_prog cmd_args raw_dblist dblist dbexcl databases
 
-     cmd_prog="${MY}"
 
-     cmd_args=(--batch --skip-column-names --execute 'SHOW DATABASES;')
 
-     if [ "${#CONN_ARGS[@]}" -gt 0 ]; then
 
-         cmd_args+=("${CONN_ARGS[@]}")
 
-     fi
 
-     log_debug "Running command: ${cmd_prog} ${cmd_args[*]}"
 
-     raw_dblist=$(
 
-         if ! "${cmd_prog}" "${cmd_args[@]}" 2> >(logger "err" "error"); then
 
-             log_error "Running command '${cmd_prog} ${cmd_args[*]}' has failed"
 
-         fi
 
-     )
 
-     read -r -a dblist <<< "$(
 
-         printf "%s\n" "${raw_dblist}" | \
 
-             arg_encode | \
 
-             tr '\n' ' '
 
-     )"
 
-     log_debug "Automatically found databases: ${dblist[*]}"
 
-     if [ -n "${DBEXCLUDE}" ]; then
 
-         IFS=" " read -r -a dbexcl <<< "${DBEXCLUDE}"
 
-     else
 
-         dbexcl=()
 
-     fi
 
-     dbexcl+=(information_schema performance_schema mysql)
 
-     log_debug "Excluded databases: ${dbexcl[*]}"
 
-     mapfile -t databases < <(
 
-         comm -23 \
 
-             <(IFS=$'\n'; echo "${dblist[*]}" | sort) \
 
-             <(IFS=$'\n'; echo "${dbexcl[*]}" | sort) \
 
-         )
 
-     log_debug "Database(s) to be backuped: ${databases[*]}"
 
-     printf "%s " "${databases[@]}"
 
- }
 
- # }}}
 
- # {{{ mysqldb_dump()
 
- mysqldb_dump () {
 
-     local db_name cmd_prog cmd_args my_args
 
-     db_name="${1}"
 
-     if [ -n "${MYDUMP_OPTS}" ]; then
 
-         IFS=" " read -r -a MYDUMP_ARGS <<< "${MYDUMP_OPTS}"
 
-     else
 
-         MYDUMP_ARGS=()
 
-     fi
 
-     cmd_prog="${MYDUMP}"
 
-     cmd_args=("${db_name}")
 
-     my_args=("${MYDUMP_ARGS[@]}")
 
-     my_args+=(--quote-names --events --routines)
 
-     if [ "${CREATE_DATABASE}" = "no" ]; then
 
-         my_args+=(--databases)
 
-     else
 
-         my_args+=(--no-create-db)
 
-     fi
 
-     if [ "${#CONN_ARGS[@]}" -gt 0 ]; then
 
-         cmd_args+=("${CONN_ARGS[@]}")
 
-     fi
 
-     if [ "${#my_args[@]}" -gt 0 ]; then
 
-         cmd_args+=("${my_args[@]}")
 
-     fi
 
-     log_debug "Running command: ${cmd_prog} ${cmd_args[*]}"
 
-     if ! "${cmd_prog}" "${cmd_args[@]}" 2> >(logger "err" "error"); then
 
-         log_error "Running command '${cmd_prog} ${cmd_args[*]}' has failed"
 
-     fi
 
- }
 
- # }}}
 
- # {{{ db_init()
 
- db_init () {
 
-     case ${DBENGINE} in
 
-         postgresql)
 
-             postgresqldb_init
 
-             ;;
 
-         mysql)
 
-             mysqldb_init
 
-             ;;
 
-         *)
 
-             log_error "Unsupported database engine ${DBENGINE}, check DBENGINE configuration parameter"
 
-             return 1
 
-             ;;
 
-     esac
 
- }
 
- # }}}
 
- # {{{ db_list()
 
- db_list () {
 
-     case ${DBENGINE} in
 
-         postgresql)
 
-             postgresqldb_list
 
-             ;;
 
-         mysql)
 
-             mysqldb_list
 
-             ;;
 
-         *)
 
-             log_error "Unsupported database engine ${DBENGINE}, check DBENGINE configuration parameter"
 
-             return 1
 
-             ;;
 
-     esac
 
- }
 
- # }}}
 
- # {{{ db_dump()
 
- db_dump () {
 
-     case ${DBENGINE} in
 
-         postgresql|mysql)
 
-             ${DBENGINE}db_dump "${1}"
 
-             ;;
 
-         *)
 
-             log_error "Unsupported database engine ${DBENGINE}, check DBENGINE configuration parameter"
 
-             return 1
 
-             ;;
 
-     esac
 
- }
 
- # }}}
 
- # {{{ db_purge()
 
- db_purge() {
 
-     local dumpdir db when count line
 
-     dumpdir="${1}"
 
-     db="${2}"
 
-     when="${3}"
 
-     count="${4}"
 
-     # Since version >= 2.0 the dump filename no longer contains the week number
 
-     # or the abbreviated month name so in order to be sure to remove the older
 
-     # dumps we need to sort the filename on the datetime part (YYYY-MM-DD_HHhMMm)
 
-     log_info "Rotating ${count} ${when} backups..."
 
-     log_debug "Looking for '${db}_*' in '${dumpdir}/${when}/${db}'"
 
-     find "${dumpdir}/${when}/${db}/" -name "${db}_*" | \
 
-         sed -E 's/(^.+([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}h[0-9]{2}m).*$)/\2 \1/' | \
 
-         sort -r | \
 
-         sed -E -n 's/\S+ //p' | \
 
-         tail -n "+${count}" | \
 
-         xargs -L1 rm -fv | \
 
-         while IFS= read -r line ; do
 
-             log_info "${line}"
 
-         done
 
- }
 
- # }}}
 
- # {{{ dump()
 
- dump() {
 
-     local db_name dump_file comp_ext
 
-     db_name="${1}"
 
-     dump_file="${2}"
 
-     if [ -n "${COMP}" ]; then
 
-         comp_ext=".comp"
 
-         case "${COMP}" in
 
-             gzip|pigz)
 
-                 comp_ext=".gz"
 
-                 ;;
 
-             bzip2)
 
-                 comp_ext=".bz2"
 
-                 ;;
 
-             xz)
 
-                 comp_ext=".xz"
 
-                 ;;
 
-             zstd)
 
-                 comp_ext=".zstd"
 
-                 ;;
 
-         esac
 
-         dump_file="${dump_file}${comp_ext}"
 
-     fi
 
-     if [ "${ENCRYPTION}" = "yes" ]; then
 
-         dump_file="${dump_file}${ENCRYPTION_SUFFIX}"
 
-     fi
 
-     if [ -n "${COMP}" ] && [ "${ENCRYPTION}" = "yes" ]; then
 
-         log_debug "Dumping (${db_name}) +compress +encrypt to '${dump_file}'"
 
-         db_dump "${db_name}" | compression | encryption > "${dump_file}"
 
-     elif [ -n "${COMP}" ]; then
 
-         log_debug "Dumping (${db_name}) +compress to '${dump_file}'"
 
-         db_dump "${db_name}" | compression > "${dump_file}"
 
-     elif [ "${ENCRYPTION}" = "yes" ]; then
 
-         log_debug "Dumping (${db_name}) +encrypt to '${dump_file}'"
 
-         db_dump "${db_name}" | encryption > "${dump_file}"
 
-     else
 
-         log_debug "Dumping (${db_name}) to '${dump_file}'"
 
-         db_dump "${db_name}" > "${dump_file}"
 
-     fi
 
-     if [ -f "${dump_file}" ]; then
 
-         log_debug "Fixing permissions (${PERM}) on '${dump_file}'"
 
-         chmod "${PERM}" "${dump_file}"
 
-         fsize=$(stat -c '%s' "${dump_file}")
 
-         if [ ! -s "${dump_file}" ]; then
 
-             log_error "Something went wrong '${dump_file}' is empty"
 
-         elif [ "${fsize}" -lt "${MIN_DUMP_SIZE}" ]; then
 
-             log_warn "'${dump_file}' (${fsize} bytes) is below the minimum required size (${MIN_DUMP_SIZE} bytes)"
 
-         fi
 
-     else
 
-         log_error "Something went wrong '${dump_file}' does not exists (error during dump?)"
 
-     fi
 
- }
 
- # }}}
 
- # {{{ setup()
 
- setup() {
 
-     # Using a shared memory filesystem (if available) to avoid
 
-     # issues when there is no left space on backup storage
 
-     if [ -w "/dev/shm" ]; then
 
-         LOG_DIR="/dev/shm"
 
-     fi
 
-     LOG_PREFIX="${LOG_DIR}/${NAME}_${DBHOST//\//_}-$(date '+%Y-%m-%d_%Hh%Mm')"
 
-     LOG_FILE="${LOG_PREFIX}.log"
 
-     LOG_REPORT="${LOG_PREFIX}.report"
 
-     # Create required directories
 
-     if [ ! -e "${BACKUPDIR}" ]; then         # Check Backup Directory exists.
 
-         mkdir -p "${BACKUPDIR}"
 
-     fi
 
-     if [ ! -e "${BACKUPDIR}/daily" ]; then   # Check Daily Directory exists.
 
-         mkdir -p "${BACKUPDIR}/daily"
 
-     fi
 
-     if [ ! -e "${BACKUPDIR}/weekly" ]; then  # Check Weekly Directory exists.
 
-         mkdir -p "${BACKUPDIR}/weekly"
 
-     fi
 
-     if [ ! -e "${BACKUPDIR}/monthly" ]; then # Check Monthly Directory exists.
 
-         mkdir -p "${BACKUPDIR}/monthly"
 
-     fi
 
-     HOST="${DBHOST}:${DBPORT}"
 
-     if [ "${DBHOST}" = "localhost" ]; then
 
-         HOST="${HOSTNAME}:${DBPORT} (socket)"
 
-     fi
 
- }
 
- # }}}
 
- #  {{{ cleanup()
 
- cleanup() {
 
-     # Cleanup GnuPG home dir
 
-     if [ -d "${GPG_HOMEDIR}" ]; then
 
-         rm -rf "${GPG_HOMEDIR}"
 
-     fi
 
-     # Clean up log files
 
-     rm -f "${LOG_FILE}" "${LOG_REPORT}"
 
- }
 
- # }}}
 
- # {{{ setup_io()
 
- setup_io() {
 
-     exec 6>&1           # Link file descriptor #6 with stdout.
 
-                         # Saves stdout.
 
-     exec 7>&2           # Link file descriptor #7 with stderr.
 
-                         # Saves stderr.
 
-     exec >  >( logger "out")
 
-     exec 2> >( logger "err")
 
- }
 
- # }}}
 
- #  {{{ cleanup_io()
 
- cleanup_io() {
 
-     exec 1>&6 6>&-      # Restore stdout and close file descriptor #6.
 
-     exec 2>&7 7>&-      # Restore stdout and close file descriptor #7.
 
- }
 
- # }}} 
 
- # {{{ usage()
 
- usage() {
 
- cat <<EOH
 
- USAGE: $(basename "$0") [OPTIONS]
 
- ${NAME} ${VERSION}
 
- A fully automated tool to make periodic backups of PostgreSQL databases.
 
- Options:
 
-     -h  Shows this help
 
-     -d  Run in debug mode (no mail sent)
 
-     -c  Configuration file or directory (default: ${CONFIG})
 
-         Note: if ${CONFIG} file or directory does not exists
 
-         but ${CONFIG_COMPAT} exists, it will be used
 
-         for backward compatibility.
 
- EOH
 
- }
 
- # }}}
 
- # {{{ Process command line arguments
 
- while getopts "hdc:" OPTION ; do
 
-     case "${OPTION}" in
 
-         h)
 
-             usage
 
-             exit 0
 
-             ;;
 
-         d)
 
-             DEBUG="yes"
 
-             ;;
 
-         c)
 
-             CONFIG="${OPTARG}"
 
-             CONFIG_COMPAT=
 
-             ;;
 
-         *)
 
-             printf "Try \`%s -h\` to check the command line arguments\n" "${NAME}" >&2
 
-             exit 1
 
-     esac
 
- done
 
- # }}}
 
- # {{{ I/O redirection(s) for logging
 
- setup_io
 
- # }}}
 
- # {{{ Setup runtime settings
 
- setup
 
- # }}}
 
- # {{{ Config file loading
 
- CONFIG_N=0
 
- if [ -d "${CONFIG}" ]; then
 
-     CONFIG_N=$(find "${CONFIG}" -type f -iname '*.conf' | wc -l)
 
- fi
 
- if [ -f "${CONFIG_COMPAT}" ]; then
 
-     log_debug "Loading config '${CONFIG}' (for backward compatibility)"
 
-     # shellcheck source=/dev/null
 
-     . "${CONFIG_COMPAT}"
 
-     setup
 
- elif [ "${CONFIG_N}" -gt 0 ]; then
 
-     CMD="$(readlink -f "${0}")"
 
-     CMD_ARGS=()
 
-     if [ "${DEBUG}" = "yes" ]; then
 
-         CMD_ARGS+=(-d)
 
-     fi
 
-     cleanup_io
 
-     cleanup
 
-     find "${CONFIG}" -type f -iname '*.conf' -print0 | \
 
-         xargs -0 -L1 "${CMD}" "${CMD_ARGS[@]}" -c
 
-     exit $?
 
- elif [ -f "${CONFIG}" ]; then
 
-     log_debug "Loading config '${CONFIG}'"
 
-     # shellcheck source=/dev/null
 
-     . "${CONFIG}"
 
-     setup
 
- else
 
-     log_error "${NAME}: config file or directory '${CONFIG}' does not exists or directory '${CONFIG}' does not contains any configuration files."
 
-     cleanup_io
 
-     cleanup
 
-     exit 1
 
- fi
 
- # }}}
 
- # {{{ PreBackup
 
- # Run command before we begin
 
- if [ -n "${PREBACKUP}" ]; then
 
-     log_info "Prebackup command output:"
 
-     ${PREBACKUP} | \
 
-         while IFS= read -r line ; do
 
-             log "  ${line}"
 
-         done
 
- fi
 
- # }}}
 
- # {{{ main()
 
- log_info "${NAME} version ${VERSION}"
 
- log_info "Homepage: ${HOMEPAGE}"
 
- log_info "Backup of Database Server (${DBENGINE}) - ${HOST}"
 
- if [ -n "${COMP}" ]; then
 
-     if ! command -v "${COMP}" >/dev/null ; then
 
-         log_warn "Disabling compression, '${COMP}' command not found"
 
-         unset COMP
 
-     fi
 
- fi
 
- if [ "${ENCRYPTION}" = "yes" ]; then
 
-     if [ ! -s "${ENCRYPTION_PUBLIC_KEY}" ]; then
 
-         log_warn "Disabling encryption, '${ENCRYPTION_PUBLIC_KEY}' is empty or does not exists"
 
-         ENCRYPTION="no"
 
-     elif ! command -v "gpg" >/dev/null ; then
 
-         log_warn "Disabling encryption, 'gpg' command not found"
 
-         ENCRYPTION="no"
 
-     else
 
-         gpg_setup
 
-         if ! keyinfo="$(gpg --quiet --homedir "${GPG_HOMEDIR}" "${ENCRYPTION_PUBLIC_KEY}" 2>/dev/null)"; then
 
-             log_warn "Disabling encryption, key in '${ENCRYPTION_PUBLIC_KEY}' does not seems to be a valid public key"
 
-             ENCRYPTION="no"
 
-             if command -v "openssl" >/dev/null && openssl x509 -noout -in "${ENCRYPTION_PUBLIC_KEY}" >/dev/null 2>&1; then
 
-                 log_warn "public key in '${ENCRYPTION_PUBLIC_KEY}' seems to be in PEM format"
 
-                 log_warn "Encryption using openssl is no longer supported: see ${HOMEPAGE}#openssl-encryption"
 
-             fi
 
-         else
 
-             keyfp="$(echo "${keyinfo}" | sed -E -n 's/^\s*([a-z0-9]+)\s*$/\1/pi')"
 
-             keyuid="$(echo "${keyinfo}" | sed -E -n 's/^\s*uid\s+(\S.*)$/\1/pi' | head -n1)"
 
-             log_info "Encryption public key is: 0x${keyfp} (${keyuid})"
 
-         fi
 
-     fi
 
- fi
 
- log_info "Backup Start: $(date)"
 
- if [ "${DNOM}" = "${DOMONTHLY}" ]; then
 
-     period="monthly"
 
-     rotate="${BRMONTHLY}"
 
- elif [ "${DNOW}" = "${DOWEEKLY}" ]; then
 
-     period="weekly"
 
-     rotate="${BRWEEKLY}"
 
- else
 
-     period="daily"
 
-     rotate="${BRDAILY}"
 
- fi
 
- db_init
 
- # If backing up all DBs on the server
 
- if [ "${DBNAMES}" = "all" ]; then
 
-     DBNAMES="$(db_list)"
 
- fi
 
- for db_enc in ${DBNAMES} ; do
 
-     db="$(echo "${db_enc}" | arg_decode)"
 
-     log_info "Backup of Database (${period}) '${db}'"
 
-     backupdbdir="${BACKUPDIR}/${period}/${db_enc}"
 
-     if [ ! -e "${backupdbdir}" ]; then
 
-         log_debug "Creating Backup DB directory '${backupdbdir}'"
 
-         mkdir -p "${backupdbdir}"
 
-     fi
 
-     db_purge "${BACKUPDIR}" "${db_enc}" "${period}" "${rotate}"
 
-     backupfile="${backupdbdir}/${db_enc}_${DATE}.${EXT}"
 
-     dump "${db}" "${backupfile}"
 
- done
 
- log_info "Backup End: $(date)"
 
- log_info "Total disk space used for ${BACKUPDIR}: $(du -hs "${BACKUPDIR}" | cut -f1)"
 
- # }}}
 
- # {{{ PostBackup
 
- # Run command when we're done
 
- if [ -n "${POSTBACKUP}" ]; then
 
-     log_info "Postbackup command output:"
 
-     ${POSTBACKUP} | \
 
-         while IFS= read -r line ; do
 
-             log "  ${line}"
 
-         done
 
- fi
 
- # }}}
 
- # {{{ cleanup I/O redirections
 
- cleanup_io
 
- # }}}
 
- # {{{ Reporting
 
- if grep -q '^err|' "${LOG_FILE}"; then
 
-     RC=1
 
- fi
 
- if [ "${DEBUG}" = "no" ] && [ ${RC} = 1 ]; then
 
-     (
 
-         printf "*Errors/Warnings* (below) reported during backup on *%s*:\n\n" "${HOST}"
 
-         grep '^err|' "${LOG_FILE}" | cut -d '|' -f 3- | \
 
-         while IFS= read -r line ; do
 
-             printf "  | %s\n" "${line}"
 
-         done
 
-         printf "\n\nFull backup log follows:\n\n"
 
-         grep -v '^...|debug|' "${LOG_FILE}" | \
 
-         while IFS="|" read -r fd level line ; do
 
-             if [ -n "${level}" ]; then
 
-                 printf "%8s| %s\n" "*${level}*" "${line}"
 
-             else
 
-                 printf "%8s| %s\n" "" "${line}"
 
-             fi
 
-         done
 
-         printf "\nFor more information, try to run %s in debug mode, see \`%s -h\`\n" "${NAME}" "$(basename "$0")"
 
-     ) > "${LOG_REPORT}"
 
-     if [ -n "${MAILADDR}" ]; then
 
-         mail -s "${NAME} issues on ${HOSTNAME}" "${MAILADDR}" < "${LOG_REPORT}"
 
-     else
 
-         cat "${LOG_REPORT}"
 
-     fi
 
- fi
 
- # }}}
 
- # {{{ Cleanup and exit()
 
- cleanup
 
- exit ${RC}
 
- # }}}
 
- # vim: foldmethod=marker foldlevel=0 foldenable
 
 
  |