Merge pull request #14 from robru/master
Time Machine style backup pruning.
This commit is contained in:
@@ -40,14 +40,10 @@ An optional exclude file can be provided as a third parameter. It should be comp
|
|||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
* Manage the backups in a way similar to Time Machine - hourly backups for the past 24 hours; daily backups for the past month; weekly backups for the previous months.
|
* Minor changes (see TODO comments in the source).
|
||||||
|
|
||||||
* Other minor changes (see TODO comments in the source).
|
|
||||||
|
|
||||||
# LICENSE
|
# LICENSE
|
||||||
|
|
||||||
[MIT](http://opensource.org/licenses/MIT)
|
[MIT](http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
|
||||||
[](https://bitdeli.com/free "Bitdeli Badge")
|
[](https://bitdeli.com/free "Bitdeli Badge")
|
||||||
|
|
||||||
|
|||||||
+71
-22
@@ -27,6 +27,34 @@ fn_terminate_script() {
|
|||||||
|
|
||||||
trap 'fn_terminate_script' SIGINT
|
trap 'fn_terminate_script' SIGINT
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Small utility functions for reducing code duplication
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn_parse_date() {
|
||||||
|
# Converts YYYY-MM-DD-HHMMSS to YYYY-MM-DD HH:MM:SS and then to Unix Epoch.
|
||||||
|
case "$OSTYPE" in
|
||||||
|
linux*) date -d "${1:0:10} ${1:11:2}:${1:13:2}:${1:15:2}" +%s ;;
|
||||||
|
darwin*) date -j -f "%Y-%m-%d-%H%M%S" "$1" "+%s" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
fn_find_backups() {
|
||||||
|
find "$DEST_FOLDER" -type d -name "????-??-??-??????" -prune
|
||||||
|
}
|
||||||
|
|
||||||
|
fn_expire_backup() {
|
||||||
|
# Double-check that we're on a backup destination to be completely
|
||||||
|
# sure we're deleting the right folder
|
||||||
|
if [ -z "$(fn_is_backup_destination "$(dirname -- "$1")")" ]; then
|
||||||
|
fn_log_error "$1 is not on a backup destination - aborting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
fn_log_info "Expiring $1"
|
||||||
|
rm -rf -- "$1"
|
||||||
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Source and destination information
|
# Source and destination information
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -53,15 +81,10 @@ fn_backup_marker_path() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn_is_backup_destination() {
|
fn_is_backup_destination() {
|
||||||
DEST_MARKER_FILE="$(fn_backup_marker_path $1)"
|
find "$(fn_backup_marker_path "$1")" 2>/dev/null
|
||||||
if [ -f "$DEST_MARKER_FILE" ]; then
|
|
||||||
echo "1"
|
|
||||||
else
|
|
||||||
echo "0"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ "$(fn_is_backup_destination $DEST_FOLDER)" != "1" ]; then
|
if [ -z "$(fn_is_backup_destination $DEST_FOLDER)" ]; then
|
||||||
fn_log_info "Safety check failed - the destination does not appear to be a backup folder or drive (marker file not found)."
|
fn_log_info "Safety check failed - the destination does not appear to be a backup folder or drive (marker file not found)."
|
||||||
fn_log_info "If it is indeed a backup folder, you may add the marker file by running the following command:"
|
fn_log_info "If it is indeed a backup folder, you may add the marker file by running the following command:"
|
||||||
fn_log_info ""
|
fn_log_info ""
|
||||||
@@ -74,12 +97,18 @@ fi
|
|||||||
# Setup additional variables
|
# Setup additional variables
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
BACKUP_FOLDER_PATTERN=????-??-??-??????
|
# Date logic
|
||||||
NOW=$(date +"%Y-%m-%d-%H%M%S")
|
NOW=$(date +"%Y-%m-%d-%H%M%S")
|
||||||
|
EPOCH=$(date "+%s")
|
||||||
|
KEEP_ALL_DATE=$(($EPOCH - 86400)) # 1 day ago
|
||||||
|
KEEP_DAILIES_DATE=$(($EPOCH - 2678400)) # 31 days ago
|
||||||
|
|
||||||
|
|
||||||
|
export IFS=$'\n' # Better for handling spaces in filenames.
|
||||||
PROFILE_FOLDER="$HOME/.rsync_tmbackup"
|
PROFILE_FOLDER="$HOME/.rsync_tmbackup"
|
||||||
LOG_FILE="$PROFILE_FOLDER/$NOW.log"
|
LOG_FILE="$PROFILE_FOLDER/$NOW.log"
|
||||||
DEST=$DEST_FOLDER/$NOW
|
DEST=$DEST_FOLDER/$NOW
|
||||||
PREVIOUS_DEST=$(find "$DEST_FOLDER" -type d -name "$BACKUP_FOLDER_PATTERN" -prune | sort | tail -n 1)
|
PREVIOUS_DEST=$(fn_find_backups | sort | tail -n 1)
|
||||||
INPROGRESS_FILE=$DEST_FOLDER/backup.inprogress
|
INPROGRESS_FILE=$DEST_FOLDER/backup.inprogress
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -100,10 +129,10 @@ if [ -f "$INPROGRESS_FILE" ]; then
|
|||||||
# - Last backup is moved to current backup folder so that it can be resumed.
|
# - Last backup is moved to current backup folder so that it can be resumed.
|
||||||
# - 2nd to last backup becomes last backup.
|
# - 2nd to last backup becomes last backup.
|
||||||
fn_log_info "$INPROGRESS_FILE already exists - the previous backup failed or was interrupted. Backup will resume from there."
|
fn_log_info "$INPROGRESS_FILE already exists - the previous backup failed or was interrupted. Backup will resume from there."
|
||||||
LINE_COUNT=$(find "$DEST_FOLDER" -type d -name "$BACKUP_FOLDER_PATTERN" -prune | sort | tail -n 2 | wc -l)
|
LINE_COUNT=$(fn_find_backups | sort | tail -n 2 | wc -l)
|
||||||
mv -- "$PREVIOUS_DEST" "$DEST"
|
mv -- "$PREVIOUS_DEST" "$DEST"
|
||||||
if [ "$LINE_COUNT" -gt 1 ]; then
|
if [ "$LINE_COUNT" -gt 1 ]; then
|
||||||
PREVIOUS_PREVIOUS_DEST=$(find "$DEST_FOLDER" -type d -name "$BACKUP_FOLDER_PATTERN" -prune | sort | tail -n 2 | head -n 1)
|
PREVIOUS_PREVIOUS_DEST=$(fn_find_backups | sort | tail -n 2 | head -n 1)
|
||||||
PREVIOUS_DEST=$PREVIOUS_PREVIOUS_DEST
|
PREVIOUS_DEST=$PREVIOUS_PREVIOUS_DEST
|
||||||
else
|
else
|
||||||
PREVIOUS_DEST=""
|
PREVIOUS_DEST=""
|
||||||
@@ -138,6 +167,34 @@ while [ "1" ]; do
|
|||||||
mkdir -p -- "$DEST"
|
mkdir -p -- "$DEST"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Purge certain old backups before beginning new backup.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Default value for $prev ensures that the most recent backup is never deleted.
|
||||||
|
prev="0000-00-00-000000"
|
||||||
|
for fname in $(fn_find_backups | sort -r); do
|
||||||
|
date=$(basename "$fname")
|
||||||
|
stamp=$(fn_parse_date $date)
|
||||||
|
|
||||||
|
# Skip if failed to parse date...
|
||||||
|
[ -n "$stamp" ] || continue
|
||||||
|
|
||||||
|
if [ $stamp -ge $KEEP_ALL_DATE ]; then
|
||||||
|
true
|
||||||
|
|
||||||
|
elif [ $stamp -ge $KEEP_DAILIES_DATE ]; then
|
||||||
|
# Delete all but the most recent of each day.
|
||||||
|
[ "${date:0:10}" == "${prev:0:10}" ] && fn_expire_backup "$fname"
|
||||||
|
|
||||||
|
else
|
||||||
|
# Delete all but the most recent of each month.
|
||||||
|
[ "${date:0:7}" == "${prev:0:7}" ] && fn_expire_backup "$fname"
|
||||||
|
fi
|
||||||
|
|
||||||
|
prev=$date
|
||||||
|
done
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Start backup
|
# Start backup
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -199,27 +256,19 @@ while [ "1" ]; do
|
|||||||
|
|
||||||
fn_log_warn "No space left on device - removing oldest backup and resuming."
|
fn_log_warn "No space left on device - removing oldest backup and resuming."
|
||||||
|
|
||||||
BACKUP_FOLDER_COUNT=$(find "$DEST_FOLDER" -type d -name "$BACKUP_FOLDER_PATTERN" -prune | wc -l)
|
BACKUP_FOLDER_COUNT=$(fn_find_backups | wc -l)
|
||||||
if [ "$BACKUP_FOLDER_COUNT" -lt "2" ]; then
|
if [ "$BACKUP_FOLDER_COUNT" -lt "2" ]; then
|
||||||
fn_log_error "No space left on device, and no old backup to delete."
|
fn_log_error "No space left on device, and no old backup to delete."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
OLD_BACKUP_PATH=$(find "$DEST_FOLDER" -type d -name "$BACKUP_FOLDER_PATTERN" -prune | head -n 1)
|
OLD_BACKUP_PATH=$(fn_find_backups | head -n 1)
|
||||||
if [ "$OLD_BACKUP_PATH" == "" ]; then
|
if [ "$OLD_BACKUP_PATH" == "" ]; then
|
||||||
fn_log_error "No space left on device, and cannot get path to oldest backup to delete."
|
fn_log_error "No space left on device, and cannot get path to oldest backup to delete."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Double-check that we're on a backup destination to be completely sure we're deleting the right folder
|
fn_expire_backup "$OLD_BACKUP_PATH"
|
||||||
OLD_BACKUP_PARENT_PATH=$(dirname -- "$OLD_BACKUP_PATH")
|
|
||||||
if [ "$(fn_is_backup_destination $OLD_BACKUP_PARENT_PATH)" != "1" ]; then
|
|
||||||
fn_log_error "'$OLD_BACKUP_PATH' is not on a backup destination - aborting."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
fn_log_info "Deleting '$OLD_BACKUP_PATH'..."
|
|
||||||
rm -rf -- "$OLD_BACKUP_PATH"
|
|
||||||
|
|
||||||
# Resume backup
|
# Resume backup
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user