diff options
| author | Laurent Cozic <laurent@pogopixels.com> | 2013-11-16 22:20:07 +0800 |
|---|---|---|
| committer | Laurent Cozic <laurent@pogopixels.com> | 2013-11-16 22:20:07 +0800 |
| commit | 81d6b4fb990cc46f29cc2a3ea5906a37564c806d (patch) | |
| tree | 3fe129e062ad8c6c149f1119d412fbfa307dd155 | |
| parent | e092d5c2fd20cf6975efa44aff3af15adcdbe7d9 (diff) | |
| parent | 12784d1d327cbb1dae622bc96810bd62797f05a1 (diff) | |
Merge branch 'master' of https://github.com/laurent22/rsync-time-backup
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | rsync_tmbackup.sh | 111 |
2 files changed, 81 insertions, 36 deletions
@@ -42,14 +42,10 @@ An optional exclude file can be provided as a third parameter. It should be comp # 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. - -* Other minor changes (see TODO comments in the source). +* Minor changes (see TODO comments in the source). # LICENSE [MIT](http://opensource.org/licenses/MIT) - [](https://bitdeli.com/free "Bitdeli Badge") - diff --git a/rsync_tmbackup.sh b/rsync_tmbackup.sh index 45f8478..8b2a0e1 100644 --- a/rsync_tmbackup.sh +++ b/rsync_tmbackup.sh @@ -28,6 +28,34 @@ fn_terminate_script() { 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 # ----------------------------------------------------------------------------- @@ -53,15 +81,10 @@ fn_backup_marker_path() { } fn_is_backup_destination() { - DEST_MARKER_FILE="$(fn_backup_marker_path $1)" - if [ -f "$DEST_MARKER_FILE" ]; then - echo "1" - else - echo "0" - fi + find "$(fn_backup_marker_path "$1")" 2>/dev/null } -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 "If it is indeed a backup folder, you may add the marker file by running the following command:" fn_log_info "" @@ -74,12 +97,18 @@ fi # Setup additional variables # ----------------------------------------------------------------------------- -BACKUP_FOLDER_PATTERN=????-??-??-?????? +# Date logic 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" LOG_FILE="$PROFILE_FOLDER/$NOW.log" 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 # ----------------------------------------------------------------------------- @@ -100,10 +129,10 @@ if [ -f "$INPROGRESS_FILE" ]; then # - Last backup is moved to current backup folder so that it can be resumed. # - 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." - 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" 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 else PREVIOUS_DEST="" @@ -139,6 +168,34 @@ while [ "1" ]; do 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 # ----------------------------------------------------------------------------- @@ -187,9 +244,9 @@ while [ "1" ]; do grep --quiet "Result too large (34)" "$LOG_FILE" NO_SPACE_LEFT="$?" fi - + rm -- "$LOG_FILE" - + if [ "$NO_SPACE_LEFT" == "0" ]; then # TODO: -y flag read -p "It looks like there is no space left on the destination. Delete old backup? (Y/n) " yn @@ -198,29 +255,21 @@ while [ "1" ]; do esac 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 fn_log_error "No space left on device, and no old backup to delete." exit 1 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 fn_log_error "No space left on device, and cannot get path to oldest backup to delete." exit 1 fi - - # Double-check that we're on a backup destination to be completely sure we're deleting the right folder - 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" - + + fn_expire_backup "$OLD_BACKUP_PATH" + # Resume backup continue fi @@ -229,16 +278,16 @@ while [ "1" ]; do fn_log_error "Exited with error code $RSYNC_EXIT_CODE" exit $RSYNC_EXIT_CODE fi - + # ----------------------------------------------------------------------------- # Add symlink to last successful backup # ----------------------------------------------------------------------------- - + cd "$DEST_FOLDER" rm -f -- "latest" ln -s -- $(basename -- "$DEST") "latest" cd - - + rm -- "$INPROGRESS_FILE" # TODO: grep for "^rsync error:.*$" in log fn_log_info "Backup completed without errors." |
