Skip to content

Commit

Permalink
Add some logic to deduplicate if the previous backup is the same as the
Browse files Browse the repository at this point in the history
new backup.

Basically only useful for things like the crontab backup scheme which
stores them as plaintext.
  • Loading branch information
JeffFaer committed Mar 13, 2022
1 parent bc0d48e commit f35c78e
Showing 1 changed file with 43 additions and 16 deletions.
59 changes: 43 additions & 16 deletions backup/common.cron
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,56 @@ _display_seconds="$(realpath "$(dirname "${BASH_SOURCE}")/../display-seconds")"
# Implements a periodic backup with a maximum number of backups by
# 1) Checking to see if the most recently modified file in a directory was
# modified after the backup interval
# 2) Invoking a passed in backup command
# 3) Keeping only the n most recently modified files
# 2) Invoking a passed in backup command that creates a single, new backup
# file
# 3) Check if the new backup is different than the most recent backup
# 4) Keeping only the n most recent backups
#
# $1: Backup directory to check modified times, number of backups in
# $1: Backup directory. Used to check modified times, number of backups
# $2: Backup interval in seconds
# $3: The number of backups we should retain
# $3: The number of backups to retain
# $4+: The backup command
backup::cron::periodic_with_maximum_backups() {
local backup_dir="${1:?}"
local backup_interval="${2:?}"
local num_backups="${3:?}"
local cmd=( "${@:4}" )

if [[ "${backup_interval}" -ne 0 ]]; then
# 1) Check to see if we need to run a new backup.
local previous_backup
previous_backup="$(\
backup::cron::most_recently_modified_file "${backup_dir}")"
if [[ -n "${previous_backup}" && "${backup_interval}" -ne 0 ]]; then
local seconds_since_last_backup=$(\
backup::cron::seconds_since_last_file_modified "${backup_dir}")
if [[ -n "${seconds_since_last_backup}" \
&& "${seconds_since_last_backup}" -lt "${backup_interval}" ]]; then
local remaining=$((backup_interval-seconds_since_last_backup))
backup::cron::seconds_since_last_modified "${previous_backup}")
if [[ "${seconds_since_last_backup}" -lt "${backup_interval}" ]]; then
local remaining=$((backup_interval - seconds_since_last_backup))
echo "Will backup again in $("${_display_seconds}" ${remaining})" 1>&2
return 0
fi
fi

# 2) Run a new backup.
"${cmd[@]}"
if [[ $? != 0 ]]; then
echo "backup command failed" 1>&2
return 1
fi

# 3) See if the new backup is the same as the previous backup.
local new_backup
new_backup="$(backup::cron::most_recently_modified_file "${backup_dir}")"
if [[ $? != 0 || -z "${new_backup}" || "${previous_backup}" == "${new_backup}" ]]; then
echo "Something seems to have gone wrong generating a new backup" 1>&2
return 1
fi
if [[ -n "${previous_backup}" ]] \
&& diff -qr "${previous_backup}" "${new_backup}" >/dev/null; then
touch -r "${new_backup}" "${previous_backup}"
rm -r "${new_backup}"
fi

# 4) Keep only the n most recent backups.
backup::cron::keep_n_most_recent_files "${num_backups}" "${backup_dir}"
if [[ $? != 0 ]]; then
echo "Could not prune old backups." 1>&2
Expand Down Expand Up @@ -63,12 +83,14 @@ backup::cron::copy_external_backup_folder() {
return 1
fi

local seconds_since_last_backup="$(\
backup::cron::seconds_since_last_file_modified "${backup_dir}")"
local most_recent
most_recent="$(backup::cron::most_recently_modified_file "${backup_dir}")"
if [[ $? != 0 ]]; then
echo "There are no backup files in ${external_dir}." 1>&2
return 1
fi
local seconds_since_last_backup="$(\
backup::cron::seconds_since_last_modified "${most_recent}")"

echo "Last backed up $("${_display_seconds}" "${seconds_since_last_backup}") ago." 1>&2
if [[ "${seconds_since_last_backup}" -gt "${backup_interval}" ]]; then
Expand All @@ -77,20 +99,25 @@ backup::cron::copy_external_backup_folder() {
fi
}

# Prints the number of seconds since the most recently modified file in $1 was
# modified.
# Prints the name of the most recently modified file in a directory ($1)
#
# $1: A directory containing files.
# @returns 1 if $1 is an empty directory.
backup::cron::seconds_since_last_file_modified() {
backup::cron::most_recently_modified_file() {
local most_recent="$(ls -1td "$1"/* 2>/dev/null | head -1)"
if [[ -z "${most_recent}" ]]; then
return 1
fi
echo "${most_recent}"
}

# Prints the number of seconds since $1 was last modified.
#
# $1: A file
backup::cron::seconds_since_last_modified() {
local now="$(date +%s)"
local mtime="$(stat -c "%Y" "${most_recent}")"
echo $((now-mtime))
local mtime="$(stat -c "%Y" "${1}")"
echo $((now - mtime))
}

# Deletes all but the n most recently modified files from $2.
Expand Down

0 comments on commit f35c78e

Please sign in to comment.