From: Samir Benmendil Date: Sun, 13 Apr 2014 23:39:03 +0000 (+0100) Subject: bin: add rsync-snapshot for incremental backups X-Git-Url: https://git.rmz.io/dotfiles.git/commitdiff_plain/75e5badbd26282852df7098038885625371a2178 bin: add rsync-snapshot for incremental backups --- diff --git a/bin/rsync-snapshot b/bin/rsync-snapshot new file mode 100644 index 0000000..61b3940 --- /dev/null +++ b/bin/rsync-snapshot @@ -0,0 +1,456 @@ +#!/bin/bash +# ---------------------------------------------------------------------- +# created by francois scheurer on 20070323 +# derivate from mikes handy rotating-filesystem-snapshot utility +# see http://www.mikerubel.org/computers/rsync_snapshots +# ---------------------------------------------------------------------- +#rsync note: +# 1) rsync -avz /src/foo /dest => ok, creates /dest/foo, like cp -a /src/foo /dest +# 2) rsync -avz /src/foo/ /dest/foo => ok, creates /dest/foo, like cp -a /src/foo/. /dest/foo (or like cp -a /src/foo /dest) +# 3) rsync -avz /src/foo/ /dest/foo/ => ok, same as 2) +# 4) rsync -avz /src/foo/ /dest => dangerous!!! overwrite dest content, like cp -a /src/foo/. /dest +# solution: remove trailing / at /src/foo/ => 1) +# minor problem: rsync -avz /src/foo /dest/foo => creates /dest/foo/foo, like mkdir /dest/foo && cp -a /src/foo /dest/foo +# main options: +# -H --hard-links +# -a equals -rlptgoD (no -H,-A,-X) +# -r --recursive +# -l --links +# -p --perms +# -t --times +# -g --group +# -o --owner +# -D --devices --specials +# -x --one-file-system +# -S --sparse +# --numeric-ids +# useful options: +# -n --dry-run +# -z --compress +# -y --fuzzy +# --bwlimit=X limit disk IO to X kB/s +# -c --checksum +# -I --ignore-times +# --size-only +# other options: +# -v --verbose +# -P equals --progress --partial +# -h --human-readable +# --stats +# -e'ssh -o ServerAliveInterval=60' +# --delete +# --delete-delay +# --delete-excluded +# --ignore-existing +# -i --itemize-changes +# --stop-at +# --time-limit +# --rsh=\"ssh -p ${HOST_PORT} -i /root/.ssh/rsync_rsa -l root\" +# --rsync-path=\"/usr/bin/rsync\"" +# quickcheck options: +# the default behavior is to skip files with same size & mtime on destination +# mtime = last data write access +# atime = last data read access (can be ignored with noatime mount option or with chattr +A) +# ctime = last inode change (write access, change of permission or ownership) +# note that a checksum is always done after a file synchronization/transfer +# --modify-window=X ignore mtime differences less or equal to X sec +# --size-only skip files with same size on destination (ignore mtime) +# -c --checksum skip files with same MD5 checksum on destination (ignore size & mtime, all files are read once, then the list of files to be resynchronized is read a second time, there is a lot of disk IO but network trafic is minimal if many files are identical; log includes only different files) +# -I --ignore-times never skip files (all files are resynchronized, all files are read once, there is more network trafic than with --checksum but less disk IO and hence is faster than --checksum if net is fast or if most files are different; log includes all files) +# --link-dest does the quickcheck on another reference-directory and makes hardlinks if quickcheck succeeds +# (however, if mtime is different and --perms is used, the reference file is copied in a new inode) +# see also this link for a rsync tutorial: http://www.thegeekstuff.com/2010/09/rsync-command-examples/ +#todo: +# 'du' slow on many snapshot.X..done +# autokill after n minutes. +# if disk full, its better to replace the snapshot.001 than to cancel and have a very old backup (even if it may fail to create the snapshot and ends with 0 backups)..done +# rsync-snapshot for oracle redo logs..old +# 'find'-list with md5 signatures -> .gz file stored aside rsync.log.gz inside the snapshot.X folder; this file will be move to parent dir /backup/snapshot/localhost/ before deletion of a snapshot; this file will also be used to extract an incremental backup with tape-arch.sh..done (md5sum calculation with rsync-list.sh for acm14=18m58 and only 5m27 with a reference file. speedup is ~250-300%) +# realtime freedisk display with echo $(($(stat -f -c "%f" /backup/snapshot/) * 4096 / 1024)) +# use authorized_keys with restriction of bash (command=) and set sshd_config with PermitRootLogin=forced-commands-only, see http://troy.jdmz.net/rsync/index.html http://www.snailbook.com/faq/restricted-scp.auto.html +# note: rsync lists all files in snapshot.X disregarding inclusion patterns, this is slow. + + + + +# ------------- the help page ------------------------------------------ +if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then + cat << "EOF" +Version 2.01 2013-01-16 + +USAGE: rsync-snapshot.sh HOST [--recheck] + +PURPOSE: create a snapshot backup of the whole filesystem into the folder + '/backup/snapshot/HOST/snapshot.001'. + If HOST is 'localhost' it is replaced with the local hostname. + If HOST is a remote host then rsync over ssh is used to transfer the files + with a delta-transfer algorithm to transfer only minimal parts of the files + and improve speed; rsync uses for this the previous backup as reference. + This reference is also used to create hard links instead of files when + possible and thus save disk space. If original and reference file have + identical content but different timestamps or permissions then no hard link + is created. + A rotation of all backups renames snapshot.X into snapshot.X+1 and removes + backups with X>512. About 10 backups with non-linear distribution are kept + in rotation; for example with X=1,2,3,4,8,16,32,64,128,256,512. + The snapshots folders are protected read-only against all users including + root using 'chattr'. + The --recheck option forces a sync of all files even if they have same mtime + & size; it is can verify a backup and fix corrupted files; + --recheck recalculates also the MD5 integrity signatures without using the + last signature-file as precalculation. + Some features like filter rules, MD5, chattr, bwlimit and per server retention + policy can be configured by modifying the scripts directly. + +FILES: + /backup/snapshot/rsync/rsync-snapshot.sh the backup script + /backup/snapshot/rsync/rsync-list.sh the md5 signature script + /backup/snapshot/rsync/rsync-include.txt the filter rules + +Examples: + (nice -5 ./rsync-snapshot.sh >log &) ; tail -f log + cd /backup/snapshot; for i in $(ls -A); do nice -10 /backup/snapshot/rsync/rsync-snapshot.sh $i; done +EOF + exit 1 +fi + + + + +# ------------- tuning options, file locations and constants ----------- +SRC="$1" #name of backup source, may be a remote or local hostname +OPT="$2" #options (--recheck) +HOST_PORT=22 #port of source of backup +SCRIPT_PATH="/backup/snapshot/rsync" +SNAPSHOT_DST="/backup/snapshot" #destination folder +NAME="snapshot" #backup name +LOG="rsync.log" +MIN_MIBSIZE=5000 # older snapshots (except snapshot.001) are removed if free disk <= MIN_MIBSIZE. the script may exit without performing a backup if free disk is still short. +OVERWRITE_LAST=0 # if free disk space is too small, then this option let us remove snapshot.001 as well and retry once +MAX_MIBSIZE=80000 # older snapshots (except snapshot.001) are removed if their size >= MAX_MIBSIZE. the script performs a backup even if their size is too big. +#old: SPEED=5 # 1 is slow, 100 is fast, 100000 faster and 0 does not use slow-down. this allows to avoid rsync consuming too much system performance +BWLIMIT=100000 # bandwidth limit in KiB/s. 0 does not use slow-down. this allows to avoid rsync consuming too much system performance +BACKUPSERVER="rembk" # this server connects to all other to download filesystems and create remote snapshot backups +MD5LIST=0 #to compute a list of md5 integrity signatures of all backuped files, need 'rsync-list.sh' +CHATTR=1 # to use 'chattr' command and protect the backups again modification and deletion +DU=1 # to use 'du' command and calculate the size of existing backups, disable it if you have many backups and it is getting too slow (for example on BACKUPSERVER) +SOURCE="/" #source folder to backup + +HOST_LOCAL="$(hostname -s)" #local hostname +#HOST_SRC="${SRC:-${HOST_LOCAL}}" #explicit source hostname, default is local hostname +if [ -z "${SRC}" ] || [ "${SRC}" == "localhost" ]; then + HOST_SRC="${HOST_LOCAL}" #explicit source hostname, default is local hostname +else + HOST_SRC="${SRC}" #explicit source hostname +fi + +if [ "${HOST_LOCAL}" == "${BACKUPSERVER}" ]; then #if we are on BACKUPSERVER then do some fine tuning + MD5LIST=1 + MIN_MIBSIZE=35000 #needed free space for chunk-file tape-arch.sh + MAX_MIBSIZE=12000 + DU=0 # NB: 'du' is currently disabled on BACKUPSERVER for performance reasons +elif [ "${HOST_LOCAL}" == "${HOST_SRC}" ]; then #else if we are on a generic server then do other some fine tuning + if [ "${HOST_SRC}" == "ZRHSV-TST01" ]; then + MIN_MIBSIZE=500; CHATTR=0; DU=0; MD5LIST=0 + fi +fi + + + + +# ------------- initialization ----------------------------------------- +shopt -s extglob #enable extended pattern matching operators + +OPTION="--stats \ + --recursive \ + --links \ + --perms \ + --times \ + --group \ + --owner \ + --devices \ + --hard-links \ + --numeric-ids \ + --delete \ + --delete-excluded \ + --bwlimit=${BWLIMIT}" +# --progress +# --size-only +# --stop-at +# --time-limit +# --sparse + +if [ "${HOST_SRC}" != "${HOST_LOCAL}" ]; then #option for a remote server + SOURCE="${HOST_SRC}:${SOURCE}" + OPTION="${OPTION} \ + --compress \ + --rsh=\"ssh -p ${HOST_PORT} -i /root/.ssh/rsync_rsa -l root\" \ + --rsync-path=\"/usr/bin/rsync\"" +fi +if [ "${OPT}" == "--recheck" ]; then + OPTION="${OPTION} \ + --ignore-times" +elif [ -n "${OPT}" ]; then + echo "Try rsync-snapshot.sh --help ." + exit 2 +fi + + + + +# ------------- check conditions --------------------------------------- +echo "$(date +%Y-%m-%d_%H:%M:%S) ${HOST_SRC}: === Snapshot backup is created into ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.001 ===" +STARTDATE=$(date +%s) + +# make sure we're running as root +if (($(id -u) != 0)); then + echo "Sorry, must be root. Exiting..." + echo "$(date +%Y-%m-%d_%H:%M:%S) ${HOST_SRC}: === Snapshot failed. ===" + exit 2 +fi + +# make sure we have a correct snapshot folder +if [ ! -d "${SNAPSHOT_DST}/${HOST_SRC}" ]; then + echo "Sorry, folder ${SNAPSHOT_DST}/${HOST_SRC} is missing. Exiting..." + echo "$(date +%Y-%m-%d_%H:%M:%S) ${HOST_SRC}: === Snapshot failed. ===" + exit 2 +fi + +# make sure we do not have started already rsync-snapshot.sh or rsync process (started by rsync-cp.sh or by a remote rsync-snapshot.sh) in the background. +if [ "${HOST_LOCAL}" != "${BACKUPSERVER}" ]; then #because BACKUPSERVER need sometimes to perform an rsync-cp.sh it must disable the check of "already started". + #RSYNCPID=$(pgrep -f "/bin/bash .*rsync-snapshot.sh") + #if ([ -n "${RSYNCPID}" ] && [ "${RSYNCPID}" != "$$" ]) #|| pgrep -x "rsync" + if pgrep -f "/bin/\w*sh \w*rsync-snapshot\.sh" | grep -qv "$$"; then + echo "Sorry, rsync is already running in the background. Exiting..." + echo "$(date +%Y-%m-%d_%H:%M:%S) ${HOST_SRC}: === Snapshot failed. ===" + exit 2 + fi +fi + + + + +# ------------- remove some old backups -------------------------------- +# remove certain snapshots to achieve an exponential distribution in time of the backups (1,2,4,8,...) +for b in 512 256 128 64 32 16 8 4; do + let a=b/2+1 + let f=0 #this flag is set to 1 when we find the 1st snapshot in the range b..a + for i in $(seq -f'%03g' "${b}" -1 "${a}"); do + if [ -d "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i}" ]; then + if [ "${f}" -eq 0 ]; then + let f=1 + else + echo "$(date +%Y-%m-%d_%H:%M:%S) Removing ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i} ..." + [ "${CHATTR}" -eq 1 ] && chattr -R -i "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i}" &>/dev/null + rm -rf "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i}" + fi + fi + done +done + +# remove additional backups if free disk space is short +remove_snapshot() { + local MIN_MIBSIZE2=$1 + local MAX_MIBSIZE2=$2 + for i in $(seq -f'%03g' 512 -1 001); do + if [ -d "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i}" ] || [ ${i} -eq 1 ]; then + [ ! -h "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" ] && [ -d "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i}" ] && ln -s "${NAME}.${i}" "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" + let d=0 #disk space used by snapshots and free disk space are ok + echo -n "$(date +%Y-%m-%d_%H:%M:%S) Checking free disk space... " + FREEDISK=$(df -m ${SNAPSHOT_DST} | tail -1 | sed -e 's/ */ /g' | cut -d" " -f4 | sed -e 's/M*//g') + echo -n "${FREEDISK} MiB free. " + if [ ${FREEDISK} -ge ${MIN_MIBSIZE2} ]; then + echo "Ok, bigger than ${MIN_MIBSIZE2} MiB." + if [ "${DU}" -eq 0 ]; then #avoid slow 'du' + break + else + echo -n "$(date +%Y-%m-%d_%H:%M:%S) Checking disk space used by ${SNAPSHOT_DST}/${HOST_SRC} ... " + USEDDISK=$(du -ms "${SNAPSHOT_DST}/${HOST_SRC}/" | cut -f1) + echo -n "${USEDDISK} MiB used. " + if [ ${USEDDISK} -le ${MAX_MIBSIZE2} ]; then + echo "Ok, smaller than ${MAX_MIBSIZE2} MiB." + break + else + let d=2 #disk space used by snapshots is too big + fi + fi + else + let d=1 #free disk space is too small + fi + if [ ${d} -ne 0 ]; then #we need to remove snapshots + if [ ${i} -ne 1 ]; then + echo "Removing ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i} ..." + [ "${CHATTR}" -eq 1 ] && chattr -R -i "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i}" &>/dev/null + rm -rf "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i}" + [ -h "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" ] && rm -f "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" + else #all snapshots except snapshot.001 are removed + if [ ${d} -eq 1 ]; then #snapshot.001 causes that free space is too small + if [ "${OVERWRITE_LAST}" -eq 1 ]; then #last chance: remove snapshot.001 and retry once + OVERWRITE_LAST=0 + echo "Warning, free disk space will be smaller than ${MIN_MIBSIZE} MiB." + echo "$(date +%Y-%m-%d_%H:%M:%S) OVERWRITE_LAST enabled. Removing ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.001 ..." + rm -rf "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.001" + [ -h "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" ] && rm -f "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" + else + for j in ${LNKDST//--link-dest=/}; do + if [ -d "${j}" ] && [ "${CHATTR}" -eq 1 ] && [ $(lsattr -d "${j}" | cut -b5) != "i" ]; then + chattr -R +i "${j}" &>/dev/null #undo unprotection that was needed to use hardlinks + fi + done + [ ! -h "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" ] && ln -s "${NAME}.${j}" "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" + echo "Sorry, free disk space will be smaller than ${MIN_MIBSIZE} MiB. Exiting..." + echo "$(date +%Y-%m-%d_%H:%M:%S) ${HOST_SRC}: === Snapshot failed. ===" + exit 2 + fi + elif [ ${d} -eq 2 ]; then #snapshot.001 causes that disk space used by snapshots is too big + echo "Warning, disk space used by ${SNAPSHOT_DST}/${HOST_SRC} will be bigger than ${MAX_MIBSIZE} MiB. Continuing anyway..." + fi + fi + fi + fi + done +} + +# perform an estimation of required disk space for the new backup +while :; do #this loop is executed a 2nd time if OVERWRITE_LAST was ==1 and snapshot.001 got removed + OOVERWRITE_LAST="${OVERWRITE_LAST}" + echo -n "$(date +%Y-%m-%d_%H:%M:%S) Testing needed free disk space ..." + mkdir -p "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.test-free-disk-space" + chmod -R 775 "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.test-free-disk-space" + cat /dev/null >"${SNAPSHOT_DST}/${HOST_SRC}/${LOG}" + LNKDST=$(find "${SNAPSHOT_DST}/" -maxdepth 2 -type d -name "${NAME}.001" -printf " --link-dest=%p") + for i in ${LNKDST//--link-dest=/}; do + if [ -d "${i}" ] && [ "${CHATTR}" -eq 1 ] && [ $(lsattr -d "${i}" | cut -b5) == "i" ]; then + chattr -R -i "${i}" &>/dev/null #unprotect last snapshots to use hardlinks + fi + done + eval rsync \ + --dry-run \ + ${OPTION} \ + --include-from="${SCRIPT_PATH}/rsync-include.txt" \ + ${LNKDST} \ + "${SOURCE}" "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.test-free-disk-space" >>"${SNAPSHOT_DST}/${HOST_SRC}/${LOG}" + RES=$? + if [ "${RES}" -ne 0 ] && [ "${RES}" -ne 23 ] && [ "${RES}" -ne 24 ]; then + echo "Sorry, error in rsync execution (value ${RES}). Exiting..." + echo "$(date +%Y-%m-%d_%H:%M:%S) ${HOST_SRC}: === Snapshot failed. ===" + exit 2 + fi + let i=$(tail -100 "${SNAPSHOT_DST}/${HOST_SRC}/${LOG}" | grep 'Total transferred file size:' | cut -d " " -f5)/1048576 + echo " ${i} MiB needed." + rm -rf "${SNAPSHOT_DST}/${HOST_SRC}/${LOG}" "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.test-free-disk-space" + remove_snapshot $((${MIN_MIBSIZE} + ${i})) $((${MAX_MIBSIZE} - ${i})) + if [ "${OOVERWRITE_LAST}" == "${OVERWRITE_LAST}" ]; then #no need to retry + break + fi +done + + + + +# ------------- create the snapshot backup ----------------------------- +# perform the filesystem backup using rsync and hard-links to the latest snapshot +# Note: +# -rsync behaves like cp --remove-destination by default, so the destination +# is unlinked first. If it were not so, this would copy over the other +# snapshot(s) too! +# -use --link-dest to hard-link when possible with previous snapshot, +# timestamps, permissions and ownerships are preserved +echo "$(date +%Y-%m-%d_%H:%M:%S) Creating folder ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000 ..." +mkdir -p "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000" +chmod 775 "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000" +cat /dev/null >"${SNAPSHOT_DST}/${HOST_SRC}/${LOG}" +echo -n "$(date +%Y-%m-%d_%H:%M:%S) Creating backup of ${HOST_SRC} into ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000" +if [ -n "${LNKDST}" ]; then + echo " hardlinked with${LNKDST//--link-dest=/} ..." +else + echo " not hardlinked ..." +fi +eval rsync \ + -vv \ + ${OPTION} \ + --include-from="${SCRIPT_PATH}/rsync-include.txt" \ + ${LNKDST} \ + "${SOURCE}" "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000" >>"${SNAPSHOT_DST}/${HOST_SRC}/${LOG}" +RES=$? +if [ "${RES}" -ne 0 ] && [ "${RES}" -ne 23 ] && [ "${RES}" -ne 24 ]; then + echo "Sorry, error in rsync execution (value ${RES}). Exiting..." + echo "$(date +%Y-%m-%d_%H:%M:%S) ${HOST_SRC}: === Snapshot failed. ===" + exit 2 +fi +for i in ${LNKDST//--link-dest=/}; do + if [ -d "${i}" ] && [ "${CHATTR}" -eq 1 ] && [ $(lsattr -d "${i}" | cut -b5) != "i" ]; then + chattr -R +i "${i}" &>/dev/null #undo unprotection that was needed to use hardlinks + fi +done +mv "${SNAPSHOT_DST}/${HOST_SRC}/${LOG}" "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000/${LOG}" +gzip -f "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000/${LOG}" + + + + +# ------------- create the MD5 integrity signature --------------------- +# create a gziped 'find'-list of all snapshot files (including md5 signatures) +if [ "${MD5LIST}" -eq 1 ]; then + echo "$(date +%Y-%m-%d_%H:%M:%S) Computing filelist with md5 signatures of ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000 ..." + OWD="$(pwd)" + cd "${SNAPSHOT_DST}" +# NOW=$(date "+%s") +# MYTZ=$(date "+%z") +# let NOW${MYTZ:0:1}=3600*${MYTZ:1:2}+60*${MYTZ:3:2} # convert localtime to UTC +# DATESTR=$(date -d "1970-01-01 $((${NOW} - 1)) sec" "+%Y-%m-%d_%H:%M:%S") # 'now - 1s' to avoid missing files + DATESTR=$(date -d "1970-01-01 UTC $(($(date +%s) - 1)) seconds" "+%Y-%m-%d_%H:%M:%S") # 'now - 1s' to avoid missing files + REF_LIST="$(find ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.001/ -maxdepth 1 -type f -name 'snapshot.*.list.gz' 2>/dev/null)" + if [ -n "${REF_LIST}" ] && [ "${OPT}" != "--recheck" ]; then + REF_LIST2="/tmp/rsync-reflist.tmp" + gzip -dc "${REF_LIST}" >"${REF_LIST2}" + touch -r "${REF_LIST}" "${REF_LIST2}" + ${SCRIPT_PATH}/rsync-list.sh "${HOST_SRC}/${NAME}.000" 0 "${REF_LIST2}" | sort -u | gzip -c >"${HOST_SRC}/${NAME}.${DATESTR}.list.gz" + rm -f "${REF_LIST2}" + else + ${SCRIPT_PATH}/rsync-list.sh "${HOST_SRC}/${NAME}.000" 0 | sort -u | gzip -c >"${HOST_SRC}/${NAME}.${DATESTR}.list.gz" + fi + touch -d "${DATESTR/_/ }" "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${DATESTR}.list.gz" + cd "${OWD}" + [ ! -d "${SNAPSHOT_DST}/${HOST_SRC}/md5-log" ] && mkdir -p "${SNAPSHOT_DST}/${HOST_SRC}/md5-log" + cp -al "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${DATESTR}.list.gz" "${SNAPSHOT_DST}/${HOST_SRC}/md5-log/${NAME}.${DATESTR}.list.gz" + mv "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${DATESTR}.list.gz" "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000/${NAME}.${DATESTR}.list.gz" + touch -d "${DATESTR/_/ }" "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000" +fi + + + + +# ------------- finish and clean up ------------------------------------ +# protect the backup against modification with chattr +immutable +if [ "${CHATTR}" -eq 1 ]; then + echo "$(date +%Y-%m-%d_%H:%M:%S) Setting recursively immutable flag of ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000 ..." + chattr -R +i "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.000" &>/dev/null +fi + +# rotate the backups +if [ -d "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.512" ]; then #remove snapshot.512 + echo "Removing ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.512 ..." + [ "${CHATTR}" -eq 1 ] && chattr -R -i "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.512" &>/dev/null + rm -rf "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.512" +fi +[ -h "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" ] && rm -f "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" +for i in $(seq -f'%03g' 511 -1 000); do + if [ -d "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i}" ]; then + let j=${i##+(0)}+1 + j=$(printf "%.3d" "${j}") + echo "Renaming ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i} into ${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${j} ..." + [ "${CHATTR}" -eq 1 ] && chattr -i "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i}" &>/dev/null + mv "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${i}" "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${j}" + [ "${CHATTR}" -eq 1 ] && chattr +i "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.${j}" &>/dev/null + [ ! -h "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" ] && ln -s "${NAME}.${j}" "${SNAPSHOT_DST}/${HOST_SRC}/${NAME}.last" + fi +done + +# remove additional backups if free disk space is short +OVERWRITE_LAST=0 #next call of remove_snapshot() will not remove snapshot.001 +remove_snapshot ${MIN_MIBSIZE} ${MAX_MIBSIZE} +echo "$(date +%Y-%m-%d_%H:%M:%S) ${HOST_SRC}: === Snapshot backup successfully done in $(($(date +%s) - ${STARTDATE})) sec. ===" +exit 0 +#eof +