--- /dev/null
+#!/usr/bin/env bash
+
+set -e
+set -u
+
+export LC_ALL=C
+export LANG=C
+
+VERBOSE="n"
+DEBUG="n"
+QUIET='n'
+
+VERSION="1.0"
+
+# console colors:
+RED=""
+YELLOW=""
+GREEN=""
+BLUE=""
+NORMAL=""
+
+HAS_TTY='y'
+
+BASENAME="$(basename ${0})"
+BASE_DIR="$(dirname ${0})"
+
+declare -a DATABASES=()
+
+#########################################################
+# Modify below variables to fit your need ----
+#########################################################
+
+# Where to store backup copies.
+BACKUP_ROOTDIR="/var/backup/pdns"
+
+KEEP_DAYS=30
+
+# Date.
+YEAR="$( date +%Y)"
+MONTH="$( date +%m)"
+DAY="$( date +%d)"
+TIME="$( date +%H:%M:%S)"
+TIMESTAMP="${YEAR}-${MONTH}-${DAY}-${TIME}"
+
+# Define, check, create directories.
+BACKUP_DIR="${BACKUP_ROOTDIR}/${YEAR}/${MONTH}/${DAY}"
+
+#-------------------------------------------------------------------
+detect_color() {
+
+ local safe_term="${TERM//[^[:alnum:]]/?}"
+ local match_lhs=""
+ local use_color="false"
+ [[ -f ~/.dir_colors ]] && match_lhs="${match_lhs}$(<~/.dir_colors)"
+ [[ -f /etc/DIR_COLORS ]] && match_lhs="${match_lhs}$(</etc/DIR_COLORS)"
+ [[ -z ${match_lhs} ]] \
+ && type -P dircolors >/dev/null \
+ && match_lhs=$(dircolors --print-database)
+ [[ $'\n'${match_lhs} == *$'\n'"TERM "${safe_term}* ]] && use_color="true"
+
+ # console colors:
+ if [ "${use_color}" = "true" ] ; then
+ RED="\033[38;5;196m"
+ YELLOW="\033[38;5;226m"
+ GREEN="\033[38;5;46m"
+ BLUE="\033[38;5;27m"
+ NORMAL="\033[39m"
+ else
+ RED=""
+ YELLOW=""
+ GREEN=""
+ BLUE=""
+ NORMAL=""
+ fi
+
+ local my_tty=$(tty)
+ if [[ "${my_tty}" =~ 'not a tty' ]] ; then
+ my_tty='-'
+ fi
+
+ if [[ "${my_tty}" = '-' || "${safe_term}" = "dump" ]] ; then
+ HAS_TTY='n'
+ fi
+
+}
+detect_color
+
+#------------------------------------------------------------------------------
+description() {
+ echo -e $( cat <<-EOF
+ Creates a backup of all zones of the global instance of PowerDNS
+ on the current host.
+
+ Only the user '${GREEN}root${NORMAL}' may execute this script.
+
+ EOF
+ )
+}
+
+#------------------------------------------------------------------------------
+usage() {
+ cat <<-EOF
+ Usage: ${BASENAME} [-K DAYS|--keep=DAYS] [-d|--debug] [[-v|--verbose] | [-q|--quiet]]] [--nocolor]
+ ${BASENAME} [-h|--help]
+ ${BASENAME} [-V|--version]
+
+ Options:
+ -K|--keep DAYS Keep the backup files of the last DAYS. Default: ${KEEP_DAYS} days.
+ -d|--debug Debug output (bash -x).
+ -v|--verbose Set verbosity on. Mutually exclusive to '--quiet'.
+ -q|--quiet Quiet execution, only errors and warnings are shown.
+ --nocolor Don't use colors on display.
+ -h|--help Show this output and exit.
+ -V|--version prints out version number of the script and exit
+ EOF
+}
+
+
+#------------------------------------------------------------------------------
+get_options() {
+
+ local tmp=
+ local base_dir=
+
+ set +e
+ tmp=$( getopt -o K:dvqhV \
+ --long keep:,debug,verbose,quiet,nocolor,help,version \
+ -n "${BASENAME}" -- "$@" )
+ if [[ $? != 0 ]] ; then
+ echo "" >&2
+ usage >&2
+ exit 1
+ fi
+ set -e
+
+ # Note the quotes around `$TEMP': they are essential!
+ eval set -- "${tmp}"
+
+ local p=
+
+ while true ; do
+ case "$1" in
+ -K|--keep)
+ KEEP_DAYS="$2"
+ shift
+ shift
+ ;;
+ -d|--debug)
+ DEBUG="y"
+ shift
+ ;;
+ -v|--verbose)
+ VERBOSE="y"
+ shift
+ ;;
+ -q|--quiet)
+ QUIET="y"
+ RED=""
+ YELLOW=""
+ GREEN=""
+ BLUE=""
+ NORMAL=""
+ shift
+ ;;
+ --nocolor)
+ RED=""
+ YELLOW=""
+ GREEN=""
+ BLUE=""
+ NORMAL=""
+ shift
+ ;;
+ -h|--help)
+ description
+ echo
+ usage
+ exit 0
+ ;;
+ -V|--version)
+ echo "${BASENAME} version: ${VERSION}"
+ exit 0
+ ;;
+ --) shift
+ break
+ ;;
+ *) echo "Internal error!"
+ exit 1
+ ;;
+ esac
+ done
+
+ if [[ "${DEBUG}" = "y" ]] ; then
+ set -x
+ fi
+ if [[ "${VERBOSE}" == "y" && "${QUIET}" == "y" ]] ; then
+ error "The parameters '${RED}${VERBOSE}${NORMAL}' and '${RED}${VERBOSE}${NORMAL}' are mutually exclusive."
+ usage >&2
+ exit 1
+ fi
+
+ local keep_int=$(( $KEEP_DAYS + 0 ))
+ if [[ "${keep_int}" -le "0" ]] ; then
+ error "Invalid number of days '${RED}${KEEP_DAYS}${NORMAL}' to keep backup files."
+ echo >&2
+ description >&2
+ echo
+ usage >&2
+ exit 1
+ fi
+ debug "Keeping backupfiles, which are not older than ${keep_int} days."
+ KEEP_DAYS="${keep_int}"
+
+ local cur_user=$( id -u -n )
+ local cur_uid=$( id -u )
+ if [[ "${cur_uid}" != "0" ]] ; then
+ error "Wrong user '${RED}${cur_user}${NORMAL}'."
+ echo >&2
+ description >&2
+ echo
+ usage >&2
+ exit 1
+ fi
+
+}
+
+#########################################
+# Some often used funktions
+
+#------------------------------------------------------------------------------
+my_date() {
+ date +'%F %T.%N %:::z'
+}
+
+#------------------------------------------------------------------------------
+debug() {
+ if [[ "${VERBOSE}" != "y" ]] ; then
+ return 0
+ fi
+ echo -e " * [$(my_date)] [${BASENAME}:DEBUG]: $@"
+}
+
+#------------------------------------------------------------------------------
+info() {
+ if [[ "${QUIET}" == "y" ]] ; then
+ return 0
+ fi
+ echo -e " ${GREEN}*${NORMAL} [$(my_date)] [${BASENAME}:${GREEN}INFO${NORMAL}] : $@"
+}
+
+#------------------------------------------------------------------------------
+warn() {
+ echo -e " ${YELLOW}*${NORMAL} [$(my_date)] [${BASENAME}:${YELLOW}WARN${NORMAL}] : $@" >&2
+}
+
+#------------------------------------------------------------------------------
+error() {
+ echo -e " ${RED}*${NORMAL} [$(my_date)] [${BASENAME}:${RED}ERROR${NORMAL}]: $@" >&2
+}
+
+#------------------------------------------------------------------------------
+MKDIR() {
+ local cmd="mkdir"
+ if [[ "${VERBOSE}" == "y" ]] ; then
+ cmd+=" --verbose"
+ fi
+ eval ${cmd} "$@"
+}
+
+#------------------------------------------------------------------------------
+RM() {
+ local cmd="rm"
+ if [[ "${VERBOSE}" == "y" ]] ; then
+ cmd+=" --verbose"
+ fi
+ eval ${cmd} "$@"
+}
+
+#------------------------------------------------------------------------------
+MV() {
+ local cmd="mv"
+ if [[ "${VERBOSE}" == "y" ]] ; then
+ cmd+=" --verbose"
+ fi
+ eval ${cmd} "$@"
+}
+
+#------------------------------------------------------------------------------
+RMDIR() {
+ local cmd="rmdir"
+ if [[ "${VERBOSE}" == "y" ]] ; then
+ cmd+=" --verbose"
+ fi
+ eval ${cmd} "$@"
+}
+
+#------------------------------------------------------------------------------
+empty_line() {
+ if [[ "${QUIET}" == "y" ]] ; then
+ echo >> "${LOGFILE}"
+ return 0
+ fi
+ echo
+}
+
+################################################################################
+
+prepare_dirs() {
+
+ debug "Changing to '${BASE_DIR}' ..."
+ cd "${BASE_DIR}"
+
+ if [[ ! -d "${BACKUP_ROOTDIR}" ]] ; then
+ error "Directory '${RED}${BACKUP_ROOTDIR}${NORMAL}' does not exists or is not a directory."
+ exit 5
+ fi
+ if [[ ! -w "${BACKUP_ROOTDIR}" ]] ; then
+ error "No write access to '${RED}${BACKUP_ROOTDIR}${NORMAL}'."
+ exit 6
+ fi
+
+ info "Creating all necessary directories ..."
+ MKDIR -p "${BACKUP_DIR}"
+
+ local i=0
+ local new_backup_dir="${BACKUP_DIR}/"$( printf "%03d" "$i" )
+ while [[ -d "${new_backup_dir}" ]] ; do
+ i=$(( $i + 1 ))
+ new_backup_dir="${BACKUP_DIR}/"$( printf "%03d" "$i" )
+ done
+ BACKUP_DIR="${new_backup_dir}"
+ MKDIR -p "${BACKUP_DIR}"
+
+}
+
+#------------------------------------------------------------------------------
+cleanup_old_backups() {
+
+ info "Cleaning up old backup files and directories ..."
+
+ local verbose_option=""
+ if [[ "${VERBOSE}" == "y" ]] ; then
+ verbose_option="--verbose"
+ fi
+
+ find "${BACKUP_ROOTDIR}" -type f -mtime +${KEEP_DAYS} -print0 | \
+ xargs --null --no-run-if-empty rm ${verbose_option}
+
+ local year=
+ local month=
+ local day=
+ local i=
+
+ for year in $( ls -1 "${BACKUP_ROOTDIR}" ); do
+ local y_dir="${BACKUP_ROOTDIR}/${year}"
+ if [[ -d "${y_dir}" ]] ; then
+ for month in $( ls -1 "${y_dir}" ); do
+ local m_dir="${y_dir}/${month}"
+ if [[ -d "${m_dir}" ]] ; then
+ for day in $( ls -1 "${m_dir}" ); do
+ local d_dir="${m_dir}/${day}"
+ if [[ -d "${d_dir}" ]] ; then
+ for i in $( ls -1 "${d_dir}" ); do
+ local i_dir="${d_dir}/${i}"
+ if [[ -d "${i_dir}" && "${i_dir}" != "${BACKUP_DIR}" ]] ; then
+ rmdir --ignore-fail-on-non-empty "${i_dir}"
+ if [[ ! -d "${i_dir}" ]] ; then
+ debug "Removed directory '${i_dir}'."
+ fi
+ fi
+ done
+ rmdir --ignore-fail-on-non-empty "${d_dir}"
+ if [[ ! -d "${d_dir}" ]] ; then
+ debug "Removed directory '${d_dir}'."
+ fi
+ fi
+ done
+ rmdir --ignore-fail-on-non-empty "${m_dir}"
+ if [[ ! -d "${m_dir}" ]] ; then
+ debug "Removed directory '${m_dir}'."
+ fi
+ fi
+ done
+ rmdir --ignore-fail-on-non-empty "${y_dir}"
+ if [[ ! -d "${y_dir}" ]] ; then
+ debug "Removed directory '${y_dir}'."
+ fi
+ fi
+ done
+
+}
+
+#------------------------------------------------------------------------------
+backup_databases() {
+
+ local db=
+ for db in "${DATABASES[@]}" ; do
+ backup_database "${db}"
+ done
+
+ empty_line
+ local k_bytes=$(( ${BYTES_TOTAL} / 1024 ))
+ local m_bytes=$(( ${k_bytes} / 1024 ))
+ local msg=$( printf "Total compressed size: %10d Bytes => %7d KiB => %4d MiB" \
+ "${BYTES_TOTAL}" "${k_bytes}" "${m_bytes}" )
+ info "${msg}"
+}
+
+#------------------------------------------------------------------------------
+backup_database() {
+
+ local db="$1"
+
+ empty_line
+ info "Backing up database '${GREEN}${db}${NORMAL}' ..."
+
+ local output_sql="${db}-${TIMESTAMP}.sql"
+ local output_sql_compressed="${output_sql}.bz2"
+ local out_sql_tmp="${TMP_DIR}/${output_sql}"
+ local out_sql_tmp_compressed="${TMP_DIR}/${output_sql_compressed}"
+ local out_sql_tgt="${BACKUP_DIR}/${output_sql}"
+ local out_sql_tgt_compressed="${BACKUP_DIR}/${output_sql_compressed}"
+
+ local verbose_option=""
+ if [[ "${VERBOSE}" == "y" ]] ; then
+ verbose_option="--verbose"
+ fi
+
+ pg_dump ${verbose_option} --blobs --clean \
+ --create --if-exists --serializable-deferrable \
+ "${db}" 2>&1 >"${out_sql_tmp}" | tee -a "${LOGFILE}"
+
+ local blocks=$(stat -c "%b" "${out_sql_tmp}")
+ local bs=$(stat -c "%B" "${out_sql_tmp}")
+ local bytes=$(stat -c "%s" "${out_sql_tmp}")
+ local b_bytes=$(( ${blocks} * ${bs} ))
+ local k_bytes=$(( ${b_bytes} / 1024 ))
+ local m_bytes=$(( ${k_bytes} / 1024 ))
+ local msg=$( printf "Original size of %-50s %10d Bytes => %7d KiB => %4d MiB" \
+ "'${output_sql}':" "${bytes}" "${k_bytes}" "${m_bytes}" )
+ info "${msg}"
+
+ debug "Compressing '${out_sql_tmp}' ..."
+ bzip2 ${verbose_option} --best "${out_sql_tmp}" 2>&1 | tee -a "${LOGFILE}"
+
+ blocks=$(stat -c "%b" "${out_sql_tmp_compressed}")
+ bs=$(stat -c "%B" "${out_sql_tmp_compressed}")
+ bytes=$(stat -c "%s" "${out_sql_tmp_compressed}")
+ b_bytes=$(( ${blocks} * ${bs} ))
+ k_bytes=$(( ${b_bytes} / 1024 ))
+ m_bytes=$(( ${k_bytes} / 1024 ))
+
+ BYTES_TOTAL=$(( ${BYTES_TOTAL} + ${b_bytes} ))
+
+ local msg=$( printf "Compressed size of %-50s %10d Bytes => %7d KiB => %4d MiB" \
+ "'${output_sql}':" "${bytes}" "${k_bytes}" "${m_bytes}" )
+ info "${msg}"
+
+ debug "Moving '${out_sql_tmp_compressed}' => '${BACKUP_DIR}' ..."
+ MV -i "${out_sql_tmp_compressed}" "${BACKUP_DIR}"
+
+}
+
+
+
+################################################################################
+##
+## Main
+##
+################################################################################
+
+#------------------------------------------------------------------------------
+main() {
+
+ get_options "$@"
+
+ prepare_dirs
+ info "Starting backup ..."
+ cleanup_old_backups
+ #get_databases
+ #backup_databases
+
+ empty_line
+ info "Finished."
+
+}
+
+main "$@"
+
+exit 0
+
+# vim: ts=4 et list