43 Commits

Author SHA1 Message Date
gahowork 9b3ea2d410 Log to destination (#280) 2025-05-27 13:30:01 +01:00
Laurent Cozic c0b2ccd7c1 Update README.md 2024-09-12 07:28:31 +01:00
qinjunjerry 7af3df3644 Update symlink and remove .inprogress only after success (#264) 2023-12-17 18:51:09 +00:00
Laurent Cozic 6664e6c5bc Fixed indentation and white space 2023-12-17 10:16:28 +00:00
Edwin Kofler d14cafde7b fix: Quote $0 and printed variable (#263) 2023-12-13 14:25:04 +00:00
Laurent Cozic c859a821b8 Update README.md 2023-03-14 16:03:18 +00:00
x4e-jonas ff223c9df0 README: Add --rsync-append-flags (#242) 2021-12-17 18:16:58 +00:00
Benjamin 7e3618c514 mod fn_parse_date, mismatch version/syntax of date (#240)
* mod fn_parse_date

combine identical cases, rm oft-failing darwin, subproc syntax

* repair my default spaces to not expand tabs
2021-06-25 16:44:49 +01:00
ettoetto 30339af32b corrected typo :-) (#233) 2021-03-30 14:11:44 +01:00
psmanek 6374c32a95 Update rsync_tmbackup.sh (#232)
Fix: Source path with spaces.
2021-02-28 18:02:59 +00:00
linleybao 88db869fe7 fix typo (#179) 2019-11-05 16:39:26 +00:00
omer-musa-battal da904fe66c File existence and file system type checks (#170)
Added error checking for nonexistent source file.
Also added file system type checks for destination and source, rsync flags are updated accordingly.
2019-09-29 15:57:59 +01:00
kapitainsky 27c56e9690 Fix lock to support more than one backup script running (#163) 2019-09-28 13:32:25 +01:00
Laurent Cozic b460078ab0 Tabs 2019-09-25 22:18:57 +01:00
kapitainsky 48b42bf6ba Fix edge case in backup expiration logic (#166)
* bugfix - fn_expire_backups function

* add BACKUP_TO_KEEP parameter

* correct PREVIOUS_DEST test

* variables case correction for consistency
2019-09-25 22:18:06 +01:00
Laurent Cozic eb5fe002e6 Create FUNDING.yml 2019-09-24 07:47:41 +01:00
Laurent Cozic 218e43e5bf Doc: Removed redundant example 2019-09-24 07:46:51 +01:00
AbdelKarim Mateos a9eb0efbad Doc: Update mount example to work on more systems (#147)
`0 */1 * * * if [[ -d /mnt/backup ]]; then rsync_tmbackup.sh /home /mnt/backup; fi` this is not valid for all sistems. Is not POSIX compilant. IMHO best soltuion is `if grep -qs /mnt/backup /proc/mounts; then rsync_tmbackup.sh /home /mnt/backup; fi`

Best regards and thanks for your script.
2019-09-24 07:46:28 +01:00
Laurent Cozic e2b4148855 Removed comment from --rsync-get-flags option 2019-09-06 15:29:31 +01:00
zwotzie 1b4319b827 move rsync flag --compress to ssh section. It will be used only if de… (#159)
* move rsync flag --compress to ssh section. It will be used only if destination is accessed via ssh

* fix identation, use tabs only
2019-08-16 13:44:27 +02:00
Wikinaut c40c3b2b77 typo corr (#145) 2019-02-23 23:36:59 +00:00
DeeeeLAN 72d694d8e4 Added rsync-append-flags method and method to specify id_rsa for ssh (#134)
* added rsync-append-flag method and method to specify id_rsa for ssh

* fixed formatting

* fixed formatting
2019-01-18 12:17:34 +00:00
Laurent Cozic ae564afa9c Merge pull request #124 from cr1901/netbsd
NetBSD Fixes
2018-06-13 20:13:30 +01:00
William D. Jones 487fd112c1 Use NetBSD ps in place of pgrep to determine whether a backup is currently running. 2018-06-13 18:54:57 +00:00
William D. Jones 2ba179ca7d Add NetBSD date parsing. 2018-06-12 13:33:21 +00:00
Laurent Cozic 5e0f3bb09d Merge pull request #120 from laurent22/expiration-strategy
Expiration strategy
2018-05-18 11:11:30 +01:00
Laurent Cozic 9eba8e81c4 Merge pull request #114 from neddy70/no-auto-expire
Add option to disable automatic purging of backups
2018-03-18 11:55:50 +00:00
Neddy70 9f9a4945e2 Merge branch 'expiration-strategy' into no-auto-expire
Merge upstream fixes
2018-03-18 21:54:36 +11:00
Laurent Cozic 1a731243f7 Merge branch 'expiration-strategy' of github.com:laurent22/rsync-time-backup into expiration-strategy 2018-03-18 10:07:31 +00:00
Neddy70 b0654c96e0 Added option to disable automatically purging old backups when out of space. 2018-03-17 11:41:14 +11:00
Laurent Cozic 7f1431c13a Fixed date parsing issue 2018-03-05 19:29:14 +00:00
Laurent Cozic 4af7610dd1 Update README.md 2018-02-26 10:57:58 +00:00
Laurent Cozic c5af8528bb Merge branch 'master' into expiration-strategy 2018-02-24 12:26:46 +00:00
Laurent Cozic 051733df1e Fixed variable names 2018-02-23 18:19:09 +00:00
Laurent Cozic 7383c50134 Documented backup expiration strategy and added test file 2018-02-23 18:16:37 +00:00
Laurent Cozic a77169818e Changed order of expiration strategy items 2018-02-23 17:55:12 +00:00
Laurent Cozic c211541848 Merge pull request #110 from Loki3000/master
Fix work with symlink as a destination
2018-02-23 09:53:45 +00:00
Loki3000 80c4d26573 Fix work with symlink as a destination
https://github.com/laurent22/rsync-time-backup/issues/92
2018-02-23 12:25:36 +03:00
Laurent Cozic 5aac62cfc0 Merge pull request #109 from NathanFrench/master
Fix to assure only one instance is running.
2018-02-12 12:38:49 +01:00
Nathan French bfa1fd092e Fix to assure only one instance is running.
Since this script runs under /usr/bin/env, running 'pgrep $0' will fail
due to it being a child of 'bash'. In order to fix this, pgrep must use
the `-o` flag (oldest pid, or in this case, the leader), and `-f` which
searches the full process name.
2018-01-19 13:42:54 -05:00
Laurent Cozic c313e0a322 Merge pull request #102 from mmolinac/tigercompat
Replaced operator '=~' with 'grep -Eq' and using a perl one-liner to …
2017-12-09 11:07:56 +00:00
Manuel Molina Cuberos 19ecacad25 Newer versions of bash reports OSTYPE differently. This copes with the case. 2017-12-09 11:28:22 +01:00
Manuel Molina Cuberos 31a7bac4b4 Replaced operator '=~' with 'grep -Eq' and using a perl one-liner to avoid 'date -j'. Both are not compatible with Mac OS X Tiger. 2017-12-08 22:20:53 +01:00
5 changed files with 275 additions and 93 deletions
+1
View File
@@ -0,0 +1 @@
github: laurent22
+2
View File
@@ -1,3 +1,5 @@
.idea
test.sh
*~
tests/TestDest/
tests/TestSource/
+37 -23
View File
@@ -15,16 +15,25 @@ On macOS, it has a few disadvantages compared to Time Machine - in particular it
Usage: rsync_tmbackup.sh [OPTION]... <[USER@HOST:]SOURCE> <[USER@HOST:]DESTINATION> [exclude-pattern-file]
Options
-p, --port SSH port.
-h, --help Display this help message.
--rsync-get-flags Display the default rsync flags that are used for backup.
--rsync-set-flags Set the rsync flags that are going to be used for backup.
--log-dir Set the log file directory. If this flag is set, generated files will
not be managed by the script - in particular they will not be
automatically deleted.
--strategy Set the expiration strategy. Default: "365:30 30:7 1:1" means after one
day, keep one backup per day. After 30 days, keep one backup every 7 days.
After 365 days keep one backup every 30 days.
-p, --port SSH port.
-h, --help Display this help message.
-i, --id_rsa Specify the private ssh key to use.
--rsync-get-flags Display the default rsync flags that are used for backup. If using remote
drive over SSH, --compress will be added.
--rsync-set-flags Set the rsync flags that are going to be used for backup.
--rsync-append-flags Append the rsync flags that are going to be used for backup.
--log-dir Set the log file directory. If this flag is set, generated files will
not be managed by the script - in particular they will not be
automatically deleted.
Default: /home/backuper/.rsync_tmbackup
--log-to-destination Set the log file directory to the destination directory. If this flag
is set, generated files will not be managed by the script - in particular
they will not be automatically deleted.
--strategy Set the expiration strategy. Default: "1:1 30:7 365:30" means after one
day, keep one backup per day. After 30 days, keep one backup every 7 days.
After 365 days keep one backup every 30 days.
--no-auto-expire Disable automatically deleting backups when out of space. Instead an error
is logged, and the backup is aborted.
## Features
@@ -64,20 +73,22 @@ On macOS, it has a few disadvantages compared to Time Machine - in particular it
rsync_tmbackup.sh user@example.com:/home /mnt/backup_drive
* To mimic Time Machine's behaviour, a cron script can be setup to backup at regular interval. For example, the following cron job checks if the drive "/mnt/backup" is currently connected and, if it is, starts the backup. It does this check every 1 hour.
0 */1 * * * if [[ -d /mnt/backup ]]; then rsync_tmbackup.sh /home /mnt/backup; fi
0 */1 * * * if grep -qs /mnt/backup /proc/mounts; then rsync_tmbackup.sh /home /mnt/backup; fi
## Backup expiration logic
The script automatically deletes old backups using the following logic:
- Within the last 24 hours, all the backups are kept.
- Within the last 31 days, the most recent backup of each day is kept.
- After 31 days, only the most recent backup of each month is kept.
- Additionally, if the backup destination directory is full, the oldest backups are deleted until enough space is available.
Backup sets are automatically deleted following a simple expiration strategy defined with the `--strategy` flag. This strategy is a series of time intervals with each item being defined as `x:y`, which means "after x days, keep one backup every y days". The default strategy is `1:1 30:7 365:30`, which means:
## Exclude file
- After **1** day, keep one backup every **1** day (**1:1**).
- After **30** days, keep one backup every **7** days (**30:7**).
- After **365** days, keep one backup every **30** days (**365:30**).
An optional exclude file can be provided as a third parameter. It should be compatible with the `--exclude-from` parameter of rsync. See [this tutorial](https://sites.google.com/site/rsync2u/home/rsync-tutorial/the-exclude-from-option) for more information.
Before the first interval (i.e. by default within the first 24h) it is implied that all backup sets are kept. Additionally, if the backup destination directory is full, the oldest backups are deleted until enough space is available.
## Exclusion file
An optional exclude file can be provided as a third parameter. It should be compatible with the `--exclude-from` parameter of rsync. See [this tutorial](https://web.archive.org/web/20230126121643/https://sites.google.com/site/rsync2u/home/rsync-tutorial/the-exclude-from-option) for more information.
## Built-in lock
@@ -85,10 +96,13 @@ The script is designed so that only one backup operation can be active for a giv
## Rsync options
To display the rsync options that are used for backup, run `./rsync_tmbackup.sh --rsync-get-flags`. It is also possible to add or remove options using the `--rsync-set-flags` option. For example, to exclude backing up permissions and groups:
To display the rsync options that are used for backup, run `./rsync_tmbackup.sh --rsync-get-flags`. It is also possible to add or remove options using the `--rsync-append-flags` or `--rsync-set-flags` option. For example, to exclude backing up permissions and groups:
rsync_tmbackup --rsync-set-flags "--numeric-ids --links --hard-links \
--one-file-system --archive --no-perms --no-groups --itemize-changes" /src /dest
rsync_tmbackup --rsync-append-flags "--no-perms --no-group" /src /dest
## No automatic backup expiration
An option to disable the default behaviour to purge old backups when out of space. This option is set with the `--no-auto-expire` flag.
## How to restore
@@ -110,7 +124,7 @@ The script creates a backup in a regular directory so you can simply copy the fi
The MIT License (MIT)
Copyright (c) 2013-2017 Laurent Cozic
Copyright (c) 2013-2024 Laurent Cozic
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+205 -70
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
APPNAME=$(basename $0 | sed "s/\.sh$//")
APPNAME=$(basename "$0" | sed "s/\.sh$//")
# -----------------------------------------------------------------------------
# Log functions
@@ -32,20 +32,28 @@ trap 'fn_terminate_script' SIGINT
# Small utility functions for reducing code duplication
# -----------------------------------------------------------------------------
fn_display_usage() {
echo "Usage: $(basename $0) [OPTION]... <[USER@HOST:]SOURCE> <[USER@HOST:]DESTINATION> [exclude-pattern-file]"
echo "Usage: $(basename "$0") [OPTION]... <[USER@HOST:]SOURCE> <[USER@HOST:]DESTINATION> [exclude-pattern-file]"
echo ""
echo "Options"
echo " -p, --port SSH port."
echo " -h, --help Display this help message."
echo " --rsync-get-flags Display the default rsync flags that are used for backup."
echo " --rsync-set-flags Set the rsync flags that are going to be used for backup."
echo " --log-dir Set the log file directory. If this flag is set, generated files will"
echo " not be managed by the script - in particular they will not be"
echo " automatically deleted."
echo " Default: $LOG_DIR"
echo " --strategy Set the expiration strategy. Default: \"365:30 30:7 1:1\" means after one"
echo " day, keep one backup per day. After 30 days, keep one backup every 7 days."
echo " After 365 days keep one backup every 30 days."
echo " -p, --port SSH port."
echo " -h, --help Display this help message."
echo " -i, --id_rsa Specify the private ssh key to use."
echo " --rsync-get-flags Display the default rsync flags that are used for backup. If using remote"
echo " drive over SSH, --compress will be added."
echo " --rsync-set-flags Set the rsync flags that are going to be used for backup."
echo " --rsync-append-flags Append the rsync flags that are going to be used for backup."
echo " --log-dir Set the log file directory. If this flag is set, generated files will"
echo " not be managed by the script - in particular they will not be"
echo " automatically deleted."
echo " Default: $LOG_DIR"
echo " --log-to-destination Set the log file directory to the destination directory. If this flag"
echo " is set, generated files will not be managed by the script - in particular"
echo " they will not be automatically deleted."
echo " --strategy Set the expiration strategy. Default: \"1:1 30:7 365:30\" means after one"
echo " day, keep one backup per day. After 30 days, keep one backup every 7 days."
echo " After 365 days keep one backup every 30 days."
echo " --no-auto-expire Disable automatically deleting backups when out of space. Instead an error"
echo " is logged, and the backup is aborted."
echo ""
echo "For more detailed help, please see the README file:"
echo ""
@@ -53,39 +61,27 @@ fn_display_usage() {
}
fn_parse_date() {
local date_string="$1"
local date_format="$2"
# Converts YYYY-MM-DD-HHMMSS to YYYY-MM-DD HH:MM:SS and then to Unix Epoch.
if [[ -z "$date_format" || "$date_format" == "Y-m-d H:i:s" ]]; then
case "$OSTYPE" in
linux*) date -d "${date_string:0:10} ${date_string:1date_string:2}:${date_string:13:2}:${date_string:15:2}" +%s ;;
cygwin*) date -d "${date_string:0:10} ${date_string:1date_string:2}:${date_string:13:2}:${date_string:15:2}" +%s ;;
darwin*) date -j -f "%Y-%m-%d-%H%M%S" "$date_string" "+%s" ;;
FreeBSD*) date -j -f "%Y-%m-%d-%H%M%S" "$date_string" "+%s" ;;
esac
else
case "$OSTYPE" in
linux*) date -d "${date_string:0:10} 00:00:00" +%s ;;
cygwin*) date -d "${date_string:0:10} 00:00:00" +%s ;;
darwin*) date -j -f "%Y-%m-%d" "$date_string" "+%s" ;;
FreeBSD*) date -j -f "%Y-%m-%d" "$date_string" "+%s" ;;
esac
fi
}
fn_run_cmd() {
if [ -n "$SSH_DEST_FOLDER_PREFIX" ]
then
eval "$SSH_CMD '$1'"
else
eval $1
fi
case "$OSTYPE" in
linux*|cygwin*|netbsd*)
date -d "${1:0:10} ${1:11:2}:${1:13:2}:${1:15:2}" +%s ;;
FreeBSD*) date -j -f "%Y-%m-%d-%H%M%S" "$1" "+%s" ;;
darwin*)
# Under MacOS X Tiger
# Or with GNU 'coreutils' installed (by homebrew)
# 'date -j' doesn't work, so we do this:
yy=$(expr ${1:0:4})
mm=$(expr ${1:5:2} - 1)
dd=$(expr ${1:8:2})
hh=$(expr ${1:11:2})
mi=$(expr ${1:13:2})
ss=$(expr ${1:15:2})
perl -e 'use Time::Local; print timelocal('$ss','$mi','$hh','$dd','$mm','$yy'),"\n";' ;;
esac
}
fn_find_backups() {
fn_run_cmd "find "$DEST_FOLDER" -maxdepth 1 -type d -name \"????-??-??-??????\" -prune | sort -r"
fn_run_cmd "find "$DEST_FOLDER/" -maxdepth 1 -type d -name \"????-??-??-??????\" -prune | sort -r"
}
fn_expire_backup() {
@@ -104,11 +100,16 @@ fn_expire_backups() {
local current_timestamp=$EPOCH
local last_kept_timestamp=9999999999
# Process each backup dir from most recent to oldest
for backup_dir in $(fn_find_backups | sort -r); do
# we will keep requested backup
backup_to_keep="$1"
# we will also keep the oldest backup
oldest_backup_to_keep="$(fn_find_backups | sort | sed -n '1p')"
# Process each backup dir from the oldest to the most recent
for backup_dir in $(fn_find_backups | sort); do
local backup_date=$(basename "$backup_dir")
local backup_day=${backup_date:0:10}
local backup_timestamp=$(fn_parse_date $backup_day "Y-m-d")
local backup_timestamp=$(fn_parse_date "$backup_date")
# Skip if failed to parse date...
if [ -z "$backup_timestamp" ]; then
@@ -116,59 +117,110 @@ fn_expire_backups() {
continue
fi
if [ "$backup_dir" == "$backup_to_keep" ]; then
# this is the latest backup requsted to be kept. We can finish pruning
break
fi
if [ "$backup_dir" == "$oldest_backup_to_keep" ]; then
# We dont't want to delete the oldest backup. It becomes first "last kept" backup
last_kept_timestamp=$backup_timestamp
# As we keep it we can skip processing it and go to the next oldest one in the loop
continue
fi
# Find which strategy token applies to this particular backup
for strategy_token in $(echo $EXPIRATION_STRATEGY | tr " " "\n" | sort -r -n); do
IFS=':' read -r -a t <<< "$strategy_token"
# After which date (relative to today) this token applies (X)
# After which date (relative to today) this token applies (X) - we use seconds to get exact cut off time
local cut_off_timestamp=$((current_timestamp - ${t[0]} * 86400))
# Every how many days should a backup be kept past the cut off date (Y)
local cut_off_interval=$((${t[1]} * 86400))
# Every how many days should a backup be kept past the cut off date (Y) - we use days (not seconds)
local cut_off_interval_days=$((${t[1]}))
# If we've found the strategy token that applies to this backup
if [ "$backup_timestamp" -le "$cut_off_timestamp" ]; then
# Special case: if Y is "0" we delete every time
if [ $cut_off_interval -eq "0" ]; then
if [ $cut_off_interval_days -eq "0" ]; then
fn_expire_backup "$backup_dir"
break
fi
# we calculate days number since last kept backup
local last_kept_timestamp_days=$((last_kept_timestamp / 86400))
local backup_timestamp_days=$((backup_timestamp / 86400))
local interval_since_last_kept_days=$((backup_timestamp_days - last_kept_timestamp_days))
# Check if the current backup is in the interval between
# the last backup that was kept and Y
local interval_since_last_kept=$((last_kept_timestamp - backup_timestamp))
if [ "$interval_since_last_kept" -lt "$cut_off_interval" ]; then
# to determine what to keep/delete we use days difference
if [ "$interval_since_last_kept_days" -lt "$cut_off_interval_days" ]; then
# Yes: Delete that one
fn_expire_backup "$backup_dir"
# backup deleted no point to check shorter timespan strategies - go to the next backup
break
else
# No: Keep it
# No: Keep it.
# this is now the last kept backup
last_kept_timestamp=$backup_timestamp
# and go to the next backup
break
fi
break
fi
done
done
}
fn_parse_ssh() {
if [[ "$DEST_FOLDER" =~ ^[A-Za-z0-9\._%\+\-]+@[A-Za-z0-9.\-]+\:.+$ ]]
# To keep compatibility with bash version < 3, we use grep
if echo "$DEST_FOLDER"|grep -Eq '^[A-Za-z0-9\._%\+\-]+@[A-Za-z0-9.\-]+\:.+$'
then
SSH_USER=$(echo "$DEST_FOLDER" | sed -E 's/^([A-Za-z0-9\._%\+\-]+)@([A-Za-z0-9.\-]+)\:(.+)$/\1/')
SSH_HOST=$(echo "$DEST_FOLDER" | sed -E 's/^([A-Za-z0-9\._%\+\-]+)@([A-Za-z0-9.\-]+)\:(.+)$/\2/')
SSH_DEST_FOLDER=$(echo "$DEST_FOLDER" | sed -E 's/^([A-Za-z0-9\._%\+\-]+)@([A-Za-z0-9.\-]+)\:(.+)$/\3/')
SSH_CMD="ssh -p $SSH_PORT ${SSH_USER}@${SSH_HOST}"
if [ -n "$ID_RSA" ] ; then
SSH_CMD="ssh -p $SSH_PORT -i $ID_RSA ${SSH_USER}@${SSH_HOST}"
else
SSH_CMD="ssh -p $SSH_PORT ${SSH_USER}@${SSH_HOST}"
fi
SSH_DEST_FOLDER_PREFIX="${SSH_USER}@${SSH_HOST}:"
elif [[ "$SRC_FOLDER" =~ ^[A-Za-z0-9\._%\+\-]+@[A-Za-z0-9.\-]+\:.+$ ]]
elif echo "$SRC_FOLDER"|grep -Eq '^[A-Za-z0-9\._%\+\-]+@[A-Za-z0-9.\-]+\:.+$'
then
SSH_USER=$(echo "$SRC_FOLDER" | sed -E 's/^([A-Za-z0-9\._%\+\-]+)@([A-Za-z0-9.\-]+)\:(.+)$/\1/')
SSH_HOST=$(echo "$SRC_FOLDER" | sed -E 's/^([A-Za-z0-9\._%\+\-]+)@([A-Za-z0-9.\-]+)\:(.+)$/\2/')
SSH_SRC_FOLDER=$(echo "$SRC_FOLDER" | sed -E 's/^([A-Za-z0-9\._%\+\-]+)@([A-Za-z0-9.\-]+)\:(.+)$/\3/')
SSH_CMD="ssh -p $SSH_PORT ${SSH_USER}@${SSH_HOST}"
if [ -n "$ID_RSA" ] ; then
SSH_CMD="ssh -p $SSH_PORT -i $ID_RSA ${SSH_USER}@${SSH_HOST}"
else
SSH_CMD="ssh -p $SSH_PORT ${SSH_USER}@${SSH_HOST}"
fi
SSH_SRC_FOLDER_PREFIX="${SSH_USER}@${SSH_HOST}:"
fi
}
fn_run_cmd() {
if [ -n "$SSH_DEST_FOLDER_PREFIX" ]
then
eval "$SSH_CMD '$1'"
else
eval $1
fi
}
fn_run_cmd_src() {
if [ -n "$SSH_SRC_FOLDER_PREFIX" ]
then
eval "$SSH_CMD '$1'"
else
eval $1
fi
}
fn_find() {
fn_run_cmd "find '$1'" 2>/dev/null
}
@@ -198,6 +250,18 @@ fn_ln() {
fn_run_cmd "ln -s -- '$1' '$2'"
}
fn_test_file_exists_src() {
fn_run_cmd_src "test -e '$1'"
}
fn_df_t_src() {
fn_run_cmd_src "df -T '${1}'"
}
fn_df_t() {
fn_run_cmd "df -T '${1}'"
}
# -----------------------------------------------------------------------------
# Source and destination information
# -----------------------------------------------------------------------------
@@ -209,15 +273,18 @@ SSH_CMD=""
SSH_DEST_FOLDER_PREFIX=""
SSH_SRC_FOLDER_PREFIX=""
SSH_PORT="22"
ID_RSA=""
SRC_FOLDER=""
DEST_FOLDER=""
EXCLUSION_FILE=""
LOG_DIR="$HOME/.$APPNAME"
AUTO_DELETE_LOG="1"
EXPIRATION_STRATEGY="365:30 1:1 30:7"
LOG_TO_DEST="0"
EXPIRATION_STRATEGY="1:1 30:7 365:30"
AUTO_EXPIRE="1"
RSYNC_FLAGS="-D --compress --numeric-ids --links --hard-links --one-file-system --itemize-changes --times --recursive --perms --owner --group --stats --human-readable"
RSYNC_FLAGS="-D --numeric-ids --links --hard-links --one-file-system --itemize-changes --times --recursive --perms --owner --group --stats --human-readable"
while :; do
case $1 in
@@ -229,24 +296,39 @@ while :; do
shift
SSH_PORT=$1
;;
-i|--id_rsa)
shift
ID_RSA="$1"
;;
--rsync-get-flags)
shift
echo $RSYNC_FLAGS
echo "$RSYNC_FLAGS"
exit
;;
--rsync-set-flags)
shift
RSYNC_FLAGS="$1"
;;
--rsync-append-flags)
shift
RSYNC_FLAGS="$RSYNC_FLAGS $1"
;;
--strategy)
shift
STRATEGY="$1"
EXPIRATION_STRATEGY="$1"
;;
--log-dir)
shift
LOG_DIR="$1"
AUTO_DELETE_LOG="0"
;;
--log-to-destination)
LOG_TO_DEST="1"
AUTO_DELETE_LOG="0"
;;
--no-auto-expire)
AUTO_EXPIRE="0"
;;
--)
shift
SRC_FOLDER="$1"
@@ -295,6 +377,12 @@ if [ -n "$SSH_SRC_FOLDER" ]; then
SRC_FOLDER="$SSH_SRC_FOLDER"
fi
# Exit if source folder does not exist.
if ! fn_test_file_exists_src "${SRC_FOLDER}"; then
fn_log_error "Source folder \"${SRC_FOLDER}\" does not exist - aborting."
exit 1
fi
# Now strip off last slash from source folder.
SRC_FOLDER="${SRC_FOLDER%/}"
@@ -323,6 +411,22 @@ if [ -z "$(fn_find_backup_marker "$DEST_FOLDER")" ]; then
exit 1
fi
# Check source and destination file-system (df -T /dest).
# If one of them is FAT, use the --modify-window rsync parameter
# (see man rsync) with a value of 1 or 2.
#
# The check is performed by taking the second row
# of the output of the first command.
if [[ "$(fn_df_t_src "${SRC_FOLDER}" | awk '{print $2}' | grep -c -i -e "fat")" -gt 0 ]]; then
fn_log_info "Source file-system is a version of FAT."
fn_log_info "Using the --modify-window rsync parameter with value 2."
RSYNC_FLAGS="${RSYNC_FLAGS} --modify-window=2"
elif [[ "$(fn_df_t "${DEST_FOLDER}" | awk '{print $2}' | grep -c -i -e "fat")" -gt 0 ]]; then
fn_log_info "Destination file-system is a version of FAT."
fn_log_info "Using the --modify-window rsync parameter with value 2."
RSYNC_FLAGS="${RSYNC_FLAGS} --modify-window=2"
fi
# -----------------------------------------------------------------------------
# Setup additional variables
# -----------------------------------------------------------------------------
@@ -343,6 +447,10 @@ MYPID="$$"
# Create log folder if it doesn't exist
# -----------------------------------------------------------------------------
if [[ $LOG_TO_DEST == "1" ]]; then
LOG_DIR="$DEST_FOLDER/.$APPNAME"
fi
if [ ! -d "$LOG_DIR" ]; then
fn_log_info "Creating log folder in '$LOG_DIR'..."
mkdir -- "$LOG_DIR"
@@ -368,9 +476,16 @@ if [ -n "$(fn_find "$INPROGRESS_FILE")" ]; then
fn_log_error "Previous backup task is still active - aborting (command: $RUNNINGCMD)."
exit 1
fi
else
elif [[ "$OSTYPE" == "netbsd"* ]]; then
RUNNINGPID="$(fn_run_cmd "cat $INPROGRESS_FILE")"
if [ "$RUNNINGPID" = "$(pgrep "$APPNAME")" ]; then
if ps -axp "$RUNNINGPID" -o "command" | grep "$APPNAME" > /dev/null; then
fn_log_error "Previous backup task is still active - aborting."
exit 1
fi
else
RUNNINGPID="$(fn_run_cmd "cat $INPROGRESS_FILE")"
if ps -p "$RUNNINGPID" -o command | grep "$APPNAME"
then
fn_log_error "Previous backup task is still active - aborting."
exit 1
fi
@@ -422,7 +537,13 @@ while : ; do
# Purge certain old backups before beginning new backup.
# -----------------------------------------------------------------------------
fn_expire_backups
if [ -n "$PREVIOUS_DEST" ]; then
# regardless of expiry strategy keep backup used for --link-dest
fn_expire_backups "$PREVIOUS_DEST"
else
# keep latest backup
fn_expire_backups "$DEST"
fi
# -----------------------------------------------------------------------------
# Start backup
@@ -436,7 +557,12 @@ while : ; do
CMD="rsync"
if [ -n "$SSH_CMD" ]; then
CMD="$CMD -e 'ssh -p $SSH_PORT -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'"
RSYNC_FLAGS="$RSYNC_FLAGS --compress"
if [ -n "$ID_RSA" ] ; then
CMD="$CMD -e 'ssh -p $SSH_PORT -i $ID_RSA -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'"
else
CMD="$CMD -e 'ssh -p $SSH_PORT -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'"
fi
fi
CMD="$CMD $RSYNC_FLAGS"
CMD="$CMD --log-file '$LOG_FILE'"
@@ -460,6 +586,12 @@ while : ; do
NO_SPACE_LEFT="$(grep "No space left on device (28)\|Result too large (34)" "$LOG_FILE")"
if [ -n "$NO_SPACE_LEFT" ]; then
if [[ $AUTO_EXPIRE == "0" ]]; then
fn_log_error "No space left on device, and automatic purging of old backups is disabled."
exit 1
fi
fn_log_warn "No space left on device - removing oldest backup and resuming."
if [[ "$(fn_find_backups | wc -l)" -lt "2" ]]; then
@@ -493,11 +625,14 @@ while : ; do
# -----------------------------------------------------------------------------
# Add symlink to last backup
# -----------------------------------------------------------------------------
if [ "$EXIT_CODE" = 0 ]; then
# Create the latest symlink only when rsync succeeded
fn_rm_file "$DEST_FOLDER/latest"
fn_ln "$(basename -- "$DEST")" "$DEST_FOLDER/latest"
fn_rm_file "$DEST_FOLDER/latest"
fn_ln "$(basename -- "$DEST")" "$DEST_FOLDER/latest"
fn_rm_file "$INPROGRESS_FILE"
# Remove .inprogress file only when rsync succeeded
fn_rm_file "$INPROGRESS_FILE"
fi
exit $EXIT_CODE
done
+30
View File
@@ -0,0 +1,30 @@
<?php
// This PHP script can be used to test the expiration strategy.
// It is going to populate a directory with fake backup sets (directories named Y-m-d-His) over several months.
// Then the backup script can be run on it to check what directories are going to be deleted.
// rm -rf ./tests/TestDest/201* && php ./tests/populate_dest.php && ./rsync_tmbackup.sh ./tests/TestSource/ ./tests/TestDest/
$baseDir = dirname(__FILE__);
$destDir = $baseDir . '/TestDest';
$backupsPerDay = 2;
$totalDays = 500;
$intervalBetweenBackups = null;
if ($backupsPerDay === 1) {
$intervalBetweenBackups = 'PT1D';
} else if ($backupsPerDay === 2) {
$intervalBetweenBackups = 'PT12H';
} else {
throw new Exception('Not implemented');
}
$d = new DateTime();
$d->sub(new DateInterval('P' . $totalDays . 'D'));
for ($i = 0; $i < $backupsPerDay * $totalDays; $i++) {
$d->add(new DateInterval($intervalBetweenBackups));
mkdir($destDir . '/' . $d->format('Y-m-d-His'), 0777, true);
}