#!/bin/sh # This script will backup /etc/pve content : # 1. Make an archive to a first directory (default: /etc/proxmox.pve/backup/). # 2. Hard link a fix archive name (pve.latest.tar.gz) to new archive # Easy to monitor (eg. this path can be expected). # 3. Limit permissions to backup directory (default: backup:adm). # 4. Clean backups older than retention time (default: 7). # 5. (optionnal) Copy backup to a second directory (nfs mountpoint, other hdd,…). # # This script can be call by a cronjob (eg. daily). # Vars {{{ readonly PROGNAME=$(basename "${0}") readonly PROGDIR=$(readlink -m $(dirname "${0}")) readonly ARGS="${*}" readonly NBARGS="${#}" [ -z "${DEBUG}" ] && DEBUG=1 readonly DEFAULT_FIRST_BKP_DIR="/etc/proxmox.pve/backup" readonly TODAY_VAR=$(date +%Y%m%d) readonly DEFAULT_RETENTION_TIME="7" readonly DEFAULT_USER="backup" readonly DEFAULT_GROUP="adm" ## Colors readonly PURPLE='\033[1;35m' readonly RED='\033[0;31m' readonly RESET='\033[0m' readonly COLOR_DEBUG="${PURPLE}" # }}} usage() { # {{{ cat <<- EOF usage: $PROGNAME [-d|-f|-g|-h|-r|-s|-u] Backup /etc/pve content. EXAMPLES : - Backup /etc/pve content to ${DEFAULT_FIRST_BKP_DIR} directory ${PROGNAME} - Backup /etc/pve content to /var/backups/pve directory ${PROGNAME} --first-directory /var/backups/pve - Backup to default path and keep backups for 14 days ${PROGNAME} --retention 14 - Duplicate backups to a second directory (/mnt/nfs/pve) ${PROGNAME} --second-directory /mnt/nfs/pve OPTIONS : -d,--debug Enable debug messages. -f,--first,--first-directory Path to a first directory to store backup And override default path ${DEFAULT_FIRST_BKP_DIR}. -g,--group Group of the backup files (default: ${DEFAULT_GROUP}). -h,--help Print this help message. -r,--retention,--retention-time Backups older than retention time (default: ${DEFAULT_RETENTION_TIME}) will be delete. -s,--second,--second-directory Path to a second directory to duplicate backups (default: not set). -u,--user Owner of the backup files (default: ${DEFAULT_USER}). EOF } # }}} debug_message() { # {{{ local_message="${1}" ## Print message if DEBUG is enable (=0) [ "${DEBUG}" -eq "0" ] && printf '\e[1;35m%-6b\e[m\n' "DEBUG − ${PROGNAME} : ${local_message}" return 0 } # }}} define_vars() { # {{{ ## If first_bkp_dir wasn't defined {{{ if [ -z "${first_bkp_dir}" ]; then ## Use default path to store backup first_bkp_dir="${DEFAULT_FIRST_BKP_DIR}" fi ## }}} ## If retention_time wasn't defined {{{ if [ -z "${retention_time}" ]; then ## Use default retention time to clean backups retention_time="${DEFAULT_RETENTION_TIME}" fi ## }}} } # }}} is_directory_absent() { # {{{ local_directory_absent="${1}" ## Directory exists by default return_is_directory_absent="1" ### Check if the directory exists # shellcheck disable=SC2086 if test -d "${local_directory_absent}"; then return_is_directory_absent="1" debug_message "is_directory_absent − \ The directory ${RED}${local_directory_absent}${COLOR_DEBUG} exists." else return_is_directory_absent="0" debug_message "is_directory_absent − \ The directory ${RED}${local_directory_absent}${COLOR_DEBUG} doesn't exist." fi return "${return_is_directory_absent}" } # }}} main() { # {{{ ## Define all vars define_vars ## Verify if /etc/pve directory is absent {{{ ### Display an explicit error message ### AND exit with error code 1 is_directory_absent /etc/pve \ && printf '%b\n' "${RED}/etc/pve directory doesn't seems available. Are you sure you run this script on a Proxmox host?${RESET}" \ && exit 1 ## }}} ## Verify if the first destination directory is absent {{{ ### AND create it is_directory_absent "${first_bkp_dir}" \ && mkdir -p -- "${first_bkp_dir}" ## }}} ## Create an archive of /etc/pve to $first_bkp_dir {{{ ### OR exit with error code 2 if it fails tar czf "${first_bkp_dir}/pve.${TODAY_VAR}.tar.gz" -C /etc/ pve/ \ || exit 2 ## }}} ## Create an hard link to pve.latest.tar.gz {{{ ### OR exit with error code 3 if it fails ln --force -- "${first_bkp_dir}/pve.${TODAY_VAR}.tar.gz" "${first_bkp_dir}/pve.latest.tar.gz" \ || exit 3 ## }}} ## Fix backups permissions {{{ ### Only readable by specified user:group (default: backup:adm) chown -R "${user_bkp_dir}:${group_bkp_dir}" -- "${first_bkp_dir}" \ && chmod 'u+rwX,g+rX,o-rwx' -R -- "${first_bkp_dir}" ## }}} ## Clean files older than $retention_time {{{ ### OR exit with error code 4 if it fails find "${first_bkp_dir}" -maxdepth 1 -type f -mtime +"${retention_time}" -iname "pve.*.tar.gz" -delete \ || exit 4 ## }}} ## If second directory is defined {{{ if [ -n "${second_bkp_dir}" ]; then ### Verify if the second destination directory is absent {{{ #### AND create it is_directory_absent "${second_bkp_dir}" \ && mkdir -p -- "${second_bkp_dir}" ### }}} ### Synchronize first directory to second {{{ #### OR exit with error code 12 if it fails rsync -a -- "${first_bkp_dir}/" "${second_bkp_dir}/" \ || exit 12 ### }}} fi ## }}} } # }}} # Manage arguments # {{{ # This code can't be in a function due to argument management if [ ! "${NBARGS}" -eq "0" ]; then manage_arg="0" ## If the first argument is not an option if ! printf -- '%s' "${1}" | grep -q -E -- "^-+"; then ## Print help message and exit printf '%b\n' "${RED}Invalid option: ${1}${RESET}" printf '%b\n' "---" usage exit 1 fi # Parse all options (start with a "-") one by one while printf -- '%s' "${1}" | grep -q -E -- "^-+"; do case "${1}" in -d|--debug ) ## debug DEBUG=0 ;; -f|--first|--first-directory ) ## first directory to store backup ## Move to the next argument shift ## Define first_bkp_dir first_bkp_dir="${1}" ;; -g|--group ) ## group of backup files ## Move to the next argument shift ## Define group_bkp_dir group_bkp_dir="${1}" ;; -h|--help ) ## help usage ## Exit after help informations exit 0 ;; -r|--retention,--retention-time ) ## clean backups older than retention time ## Move to the next argument shift ## Define retention_time retention_time="${1}" ;; -s|--second|--second-directory ) ## second directory to duplicate backup ## Move to the next argument shift ## Define second_bkp_dir second_bkp_dir="${1}" ;; -u|--user ) ## owner of backup files ## Move to the next argument shift ## Define user_bkp_dir user_bkp_dir="${1}" ;; * ) ## unknow option printf '%b\n' "${RED}Invalid option: ${1}${RESET}" printf '%b\n' "---" usage exit 1 ;; esac debug_message "Arguments management − \ ${RED}${1}${COLOR_DEBUG} option managed." ## Move to the next argument shift manage_arg=$((manage_arg+1)) done debug_message "Arguments management − \ ${RED}${manage_arg}${COLOR_DEBUG} argument(s) successfully managed." else debug_message "Arguments management − \ No arguments/options to manage." fi # }}} main exit 255