diff --git a/README.md b/README.md index 5618a53..4cdf88a 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,41 @@ Just dive in and copy the files out of the snapshot: cd /backup/hosts/example.com/.zfs/snapshot/ && \ rsync -aH --numeric-ids 2012-11-04-15:40:49-1352004049/d/ example.com:/restore-point/ + +## Retention Policies + +Each backup has a TTL defined, after which the prune script will delete the +backup's snapshot. You can configure the retention/expiry of backups through +various configuration properties. Each of them defines the TTL by means of "fully days after +backup creation". A value of 0 defines "keep forever". + +Once a backup is created you can't change the TTL anymore, as it is stored +within the read-only snapshot. + +A specific expiry TTL given as last parameter to the backup script +overrules any of the other TTL configuration. + +The default TTL configuration is set in the required property ``EXPIRY``. It gets +applied when none of the other option matches. + +All other options apply until to the first complete/successful backup at a given day: + +* ``EXPIRY_DAY``: TTL for daily backups +* ``EXPIRY_WEEK``: TTL for the backup on a Monday +* ``EXPIRY_MONTH``: TTL for backup on the first day of a month +* ``EXPIRY_QUARTER``: TTL for backup on the first day of a quarter +* ``EXPIRY_YEAR``: TTL: for backup on the first day of a year + +If a given day matches multiple conditions, the expiry configuration for the longest +period that has a non-empty value gets selected. E.g when on January 1st ``EXPIRY_YEAR`` +is not set, but ``EXPIRY_QUARTER`` and ``EXPIRY_DAY`` are, then +EXPRIY_QUARTER gets selected. + +When a backup doesn't succeed completely (partial backups), then the configured TTL +gets also applied to the next attempt to do a backup at the same day. If all backups +at a given day (partially) fail, then you won't get a backup with the special TTL. +You then would have to trigger a manual backup with a TTL set explicitly. + ## Status This has been in production use for many years now and is stable. diff --git a/bin/backup.sh b/bin/backup.sh index 204acda..957a06a 100755 --- a/bin/backup.sh +++ b/bin/backup.sh @@ -70,7 +70,23 @@ fi sourceHostConfig $HOSTS_DIR $HOST # Options Overridable by backup.conf (or command line) -EXPIRY=$(expr ${3-$EXPIRY} \* 24 \* 60 \* 60 + `date +%s`) # Convert expiry to unix epoc + +if [ -z "${3}" ]; then + NUM_BACKUPS_TODAY=$(find ${HOSTS_DIR}${HOST}/.${POOL_TYPE}/snapshot -maxdepth 1 -mindepth 1 -name "@$(date +%F)*" | grep -v -e "-partial$" | wc -l) + if [ "${NUM_BACKUPS_TODAY}" -eq "0" ]; then + if [ -n "${EXPIRY_DAY}" ]; then EXPIRY=${EXPIRY_DAY}; fi + if [ -n "${EXPIRY_WEEK}" -a "$(date +%u)" == "1" ]; then EXPIRY=${EXPIRY_WEEK}; fi + if [ -n "${EXPIRY_MONTH}" -a "$(date +%-d)" == "1" ]; then EXPIRY=${EXPIRY_MONTH}; fi + if [ -n "${EXPIRY_QUARTER}" -a "$(date +%-d)" == "1" -a "$(expr $(date '+%-m') % 3)" == "1" ]; then EXPIRY=${EXPIRY_QUARTER}; fi + if [ -n "${EXPIRY_YEAR}" -a "$(date +%-j)" == "1" ]; then EXPIRY=${EXPIRY_YEAR}; fi + fi +else + EXPIRY=${3} +fi + +if [ "${EXPIRY}" -gt "0" ]; then + EXPIRY=$(expr ${EXPIRY} \* 24 \* 60 \* 60 + `date +%s`) # Convert expiry to unix epoc +fi # Check to see if the host backup is disabled. if [ "${DISABLED}" == "true" ] && [ -z "$FORCE" ]; then @@ -99,7 +115,10 @@ fi ( rm -f ${LOGFILE} # delete logfile from host dir before we begin. echo "inprogress" > $STATUSFILE -echo $EXPIRY > ${HOSTS_DIR}${HOST}/c/EXPIRY +rm -f ${HOSTS_DIR}${HOST}/c/EXPIRY +if [ "${EXPIRY}" -gt "0" ]; then + echo $EXPIRY > ${HOSTS_DIR}${HOST}/c/EXPIRY +fi echo $ANNOTATION > ${HOSTS_DIR}${HOST}/c/ANNOTATION STARTTIME=$(date +%s) diff --git a/bin/list-backups.sh b/bin/list-backups.sh index 6ac0811..c4e2f30 100755 --- a/bin/list-backups.sh +++ b/bin/list-backups.sh @@ -5,7 +5,7 @@ CWD="$(dirname $0)/" # Source Config -. ${CWD}../etc/backup.conf +. ${CWD}../etc/backup.conf # Source Functions . ${CWD}functions.sh; @@ -35,7 +35,7 @@ for host in $HOSTS; do EXPIRY=$(cat $snapshot/c/EXPIRY 2> /dev/null) ANNOTATION=$(cat $snapshot/c/ANNOTATION 2> /dev/null) STATUS=$(cat $snapshot/l/STATUS 2> /dev/null) - echo "$host $SNAPSHOT $EXPIRY $STATUS \"$ANNOTATION\"" + echo "$host $SNAPSHOT ${EXPIRY:-0} $STATUS \"$ANNOTATION\"" done fi done diff --git a/etc/backup.conf b/etc/backup.conf index f3baa52..2779da7 100644 --- a/etc/backup.conf +++ b/etc/backup.conf @@ -9,7 +9,12 @@ RSYNC_ARGS='-a --numeric-ids --hard-links --compress --delete-after --delete-exc SSH_USER='root' EXCLUDE='/dev /proc /sys /tmp /var/tmp /var/run /selinux /cgroups lost+found' BACKUP_PATHS='/' -EXPIRY='28' # Default backup expiry (in days) +EXPIRY=7 # Default backup expiry (in days) +EXPIRY_DAY=$(expr 7 \* 8) # 8 weeks (~2 month) +EXPIRY_WEEK=$(expr 7 \* 25) # twenty-five weeks (~6 month) +EXPIRY_MONTH=$(expr 2 \* 365) # two years +EXPIRY_QUARTER=$(expr 5 \* 365) # five years +EXPIRY_YEAR=0 # unlimited SSH_KEY=~root/.ssh/id_rsa.pub SNAPSHOT_ON_ERROR=false # Snapshot after rsync errors PRUNE=true # Prune old expired backup snapshots