From eb5fbc6fb490d8b3bf9e409dfe0855a6a9aeb68f Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Tue, 21 Dec 2021 17:46:33 +0100 Subject: [PATCH] Adding scripts/backup-pdns.sh --- scripts/backup-pdns.sh | 485 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 485 insertions(+) create mode 100755 scripts/backup-pdns.sh diff --git a/scripts/backup-pdns.sh b/scripts/backup-pdns.sh new file mode 100755 index 0000000..1004243 --- /dev/null +++ b/scripts/backup-pdns.sh @@ -0,0 +1,485 @@ +#!/usr/bin/env bash + +set -e +set -u + +BASE_NAME="$( basename ${0} )" +MY_REAL_NAME=$( readlink -f $0 ) +BIN_DIR=$( dirname "${MY_REAL_NAME}" ) +BASE_DIR=$( dirname "${BIN_DIR}" ) +LIB_DIR="${BASE_DIR}/lib" +CONF_DIR="${BASE_DIR}/etc" + +if [[ -f "${LIB_DIR}/functions.rc" ]] ; then + . "${LIB_DIR}/functions.rc" +else + echo "Bash resource file '${LIB_DIR}/functions.rc' not found" >&2 + exit 5 +fi + +export LC_ALL="en_US.utf8" +export LANG="en_US.utf8" + +VERSION="1.3" + +declare -a ZONES=() +KEEP_LAST=3 +KEEP_DAYS=15 +KEEP_WEEKS=12 +KEEP_MONTHS=18 +KEEP_YEARS=4 +# Where to store backup copies. +BACKUP_ROOTDIR="/var/backup/pdns" + +BACKUP_TIMESTAMP="$( date +'%Y-%m-%d_%H-%M-%S' )" + +# Define, check, create directories. +BACKUP_DIR= + +BYTES_TOTAL=0 + +detect_color + +DESCRIPTION=$( 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: ${BASE_NAME} [Common Options] [-L LAST] [-D DAYS] [-W WEEKS] [-M MONTHS] [-Y YEARS] [--dir DIRECTORY] + ${BASE_NAME} [-h|--help] + ${BASE_NAME} [-V|--version] + + Options: + -L|--last LAST Keep the last ${KEEP_LAST} backup sets. + -D|--days DAYS Keep the backup files of the last DAYS. Default: ${KEEP_DAYS} days. + -W|--weeks WEEKS + Keep the backup files of the last WEEKS. Default: ${KEEP_WEEKS} weeks. + -M|--months MONTHS + Keep the backup files of the last MONTHS. Default: ${KEEP_MONTHS} months. + -Y|--years YEARS + Keep the backup files of the last YEARS. Default: ${KEEP_YEARS} years. + --dir DIRECTORY The directory, where to keep the backup files. Default: '${BACKUP_ROOTDIR}'." + EOF + + echo + echo " Common Options:" + echo "${STD_USAGE_MSG}" + +} + +#------------------------------------------------------------------------------ +get_options() { + + local tmp= + local base_dir= + + local short_options="L:D:W:M:Y:${STD_SHORT_OPTIONS}" + local long_options="last:,days:,weeks:,months:,years:,dir:,${STD_LONG_OPTIONS}" + + set +e + tmp=$( getopt -o "${short_options}" --long "${long_options}" -n "${BASE_NAME}" -- "$@" ) + if [[ $? != 0 ]] ; then + echo "" >&2 + usage >&2 + exit 1 + fi + set -e + + # Note the quotes around `$TEMP': they are essential! + eval set -- "${tmp}" + eval_common_options "$@" + if [[ "${DEBUG}" == 'y' ]] ; then + declare -p REMAINING_OPTS + declare -p REMAINING_ARGS + fi + + local len="${#REMAINING_OPTS[*]}" + local i="0" + local j= + local arg= + while [[ "$i" -lt "${len}" ]] ; do + + arg="${REMAINING_OPTS[$i]}" + + case "${arg}" in + -L|--last) + j=$(( $i + 1 )) + KEEP_LAST="${REMAINING_OPTS[$j]}" + i=$(( $i + 2 )) + ;; + -D|--days) + j=$(( $i + 1 )) + KEEP_DAYS="${REMAINING_OPTS[$j]}" + i=$(( $i + 2 )) + ;; + -W|--weeks) + j=$(( $i + 1 )) + KEEP_WEEKS="${REMAINING_OPTS[$j]}" + i=$(( $i + 2 )) + ;; + -M|--months) + j=$(( $i + 1 )) + KEEP_MONTHS="${REMAINING_OPTS[$j]}" + i=$(( $i + 2 )) + ;; + -Y|--years) + j=$(( $i + 1 )) + KEEP_YEARS="${REMAINING_OPTS[$j]}" + i=$(( $i + 2 )) + ;; + --dir) + j=$(( $i + 1 )) + BACKUP_ROOTDIR="${REMAINING_OPTS[$j]}" + i=$(( $i + 2 )) + ;; + *) echo -e "Internal error - option '${RED}${arg}${NORMAL} was wrong!" + exit 1 + ;; + esac + done + + if [[ "${#REMAINING_ARGS[@]}" != "0" ]] ; then + error "Invalid arguments given." + echo >&2 + usage >&2 + exit 1 + fi + + local int_val= + local wrong_keep_vals="n" + + int_val=$(( $KEEP_LAST + 0 )) + if [[ "${int_val}" -le "0" ]] ; then + error "Invalid number of keeping '${RED}${KEEP_LAST}${NORMAL}' backup sets." + wrong_keep_vals="y" + fi + + int_val=$(( $KEEP_DAYS + 0 )) + if [[ "${int_val}" -le "0" ]] ; then + error "Invalid number of days '${RED}${KEEP_DAYS}${NORMAL}' to keep backup sets." + wrong_keep_vals="y" + fi + + int_val=$(( $KEEP_WEEKS + 0 )) + if [[ "${int_val}" -le "0" ]] ; then + error "Invalid number of weeks '${RED}${KEEP_WEEKS}${NORMAL}' to keep backup sets." + wrong_keep_vals="y" + fi + + int_val=$(( $KEEP_MONTHS + 0 )) + if [[ "${int_val}" -le "0" ]] ; then + error "Invalid number of months '${RED}${KEEP_MONTHS}${NORMAL}' to keep backup sets." + wrong_keep_vals="y" + fi + + int_val=$(( $KEEP_YEARS + 0 )) + if [[ "${int_val}" -le "0" ]] ; then + error "Invalid number of years '${RED}${KEEP_YEARS}${NORMAL}' to keep backup sets." + wrong_keep_vals="y" + fi + + if [[ "${wrong_keep_vals}" == "y" ]] ; then + echo >&2 + description >&2 + echo + usage >&2 + exit 1 + fi + + if command -pv pdnsutil >/dev/null ; then + : + else + error "Command '${RED}pdnsutil${NORMAL}' not found." + echo >&2 + exit 3 + fi + + if command -pv idn >/dev/null ; then + : + else + error "Command '${RED}idn${NORMAL}' not found." + echo >&2 + exit 3 + fi + + if command -pv get-file-to-remove >/dev/null ; then + : + else + error "Command '${RED}get-file-to-remove${NORMAL}' not found." + echo >&2 + exit 3 + fi + + BACKUP_DIR="${BACKUP_ROOTDIR}/${BACKUP_TIMESTAMP}" + +} + +######################################### +# Some often used funktions + +#------------------------------------------------------------------------------ +MKDIR() { + local cmd="mkdir" + 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 +# return 0 +# fi + echo +} + +#------------------------------------------------------------------------------ +check_for_root() { + local my_id=$( id -u ) + if [[ "${my_id}" != "0" ]] ; then + error "You must be ${RED}root${NORMAL} to execute this script." + echo >&2 + exit 1 + fi +} + +################################################################################ + +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 ..." + + 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() { + + empty_line + info "Cleaning up old backup files and directories ..." + + local verbose_option="" + if [[ "${VERBOSE}" == "y" ]] ; then + verbose_option="--verbose" + fi + + local pattern="\"${BACKUP_ROOTDIR}\"/*" + local has_dirs='n' + local bdir= + local oifs="${IFS}" + IFS=" +" + + local -a dirs=() + + local cmd="get-file-to-remove --last ${KEEP_LAST}" + if [[ "${VERBOSE}" == "y" ]] ; then + cmd+=" --verbose" + else + cmd+=" --quiet" + fi + cmd+=" --days ${KEEP_DAYS}" + cmd+=" --weeks ${KEEP_WEEKS}" + cmd+=" --months ${KEEP_MONTHS}" + cmd+=" --years ${KEEP_YEARS}" + cmd+=" ${pattern}" + + debug "Executing: ${cmd}" + + for bdir in $( eval ${cmd} ) ; do + dirs+=( "${bdir}" ) + done + + IFS="${oifs}" + + if [[ "${#dirs[*]}" -gt "0" ]] ; then + for bdir in "${dirs[@]}" ; do + info "Removing directory '${CYAN}${bdir}${NORMAL}' ..." + RM -r "${bdir}" + done + else + debug "No old backup directories to remove." + fi + +} + +#------------------------------------------------------------------------------ +get_zones() { + + local zone= + for zone in $( pdnsutil list-all-zones | sort ) ; do + ZONES+=( "${zone}" ) + done + + if [[ "${VERBOSE}" == "y" ]] ; then + echo + echo "Zones to backup:" + for zone in "${ZONES[@]}" ; do + echo " * '${zone}'" + done + echo + fi + + +} + +#------------------------------------------------------------------------------ +backup_zones() { + + local db= + for zone in "${ZONES[@]}" ; do + backup_zone "${zone}" + done + + empty_line + info "Backed up ${#ZONES[*]} zones." + 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_zone() { + + local zone="$1" + local zone_utf8=$( idn --idna-to-unicode "${zone}" ) + + local zone_show="'${zone}'" + if [[ "${zone}" != "${zone_utf8}" ]] ; then + zone_show="'${zone}' (${zone_utf8})" + fi + + local verbose_option= + if [[ "${VERBOSE}" == "y" ]] ; then + verbose_option="--verbose" + fi + + empty_line + info "Backing up zone '${GREEN}${zone}${NORMAL}' ..." + + local output_txt="${zone_utf8}.txt" + local output_txt_compressed="${output_txt}.bz2" + local out_txt_tgt="${BACKUP_DIR}/${output_txt}" + local out_txt_tgt_compressed="${BACKUP_DIR}/${output_txt_compressed}" + + local title= + local title=$( printf "All information about zone %s:" ${zone_show} ) + local nr_chars=$( printf "${title}" | wc -c ) + local title_line=$( printf '%*s\n' "${nr_chars}" '' | tr ' ' '-' ) + echo -e "\n${title}\n${title_line}\n" > "${out_txt_tgt}" + pdnsutil show-zone "${zone}" >> "${out_txt_tgt}" 2>/dev/null + + title="All Resource Records:" + nr_chars=$( printf "${title}" | wc -c ) + title_line=$( printf '%*s\n' "${nr_chars}" '' | tr ' ' '-' ) + echo -e "\n${title}\n${title_line}\n" >> "${out_txt_tgt}" + pdnsutil list-zone "${zone}" >> "${out_txt_tgt}" 2>/dev/null + + local blocks=$(stat -c "%b" "${out_txt_tgt}") + local bs=$(stat -c "%B" "${out_txt_tgt}") + local bytes=$(stat -c "%s" "${out_txt_tgt}") + local b_bytes=$(( ${blocks} * ${bs} )) + local k_bytes=$(( ${b_bytes} / 1024 )) + local msg=$( printf "Original size of %-60s %10d Bytes => %7d KiB" \ + "'${output_txt}':" "${bytes}" "${k_bytes}" ) + info "${msg}" + + debug "Compressing '${out_txt_tgt}' ..." + bzip2 ${verbose_option} --best "${out_txt_tgt}" + + blocks=$(stat -c "%b" "${out_txt_tgt_compressed}") + bs=$(stat -c "%B" "${out_txt_tgt_compressed}") + bytes=$(stat -c "%s" "${out_txt_tgt_compressed}") + b_bytes=$(( ${blocks} * ${bs} )) + k_bytes=$(( ${b_bytes} / 1024 )) + + BYTES_TOTAL=$(( ${BYTES_TOTAL} + ${b_bytes} )) + + local msg=$( printf "Compressed size of %-60s %10d Bytes => %7d KiB" \ + "'${output_txt}':" "${bytes}" "${k_bytes}" ) + info "${msg}" + +} + + + +################################################################################ +## +## Main +## +################################################################################ + +#------------------------------------------------------------------------------ +main() { + + get_options "$@" + check_for_root + + set_locale 'en_US.utf8' + + prepare_dirs + info "Starting backup ..." + cleanup_old_backups + get_zones + backup_zones + + empty_line + info "Finished." + +} + +main "$@" + +exit 0 + +# vim: ts=4 et list -- 2.39.5