Last active
September 6, 2020 21:10
-
-
Save jarrettgilliam/a7350c7600ee1418ba20 to your computer and use it in GitHub Desktop.
Rsync backup script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Backups using rsync | |
function is_integer() { | |
echo "$1" | grep -qE '^[0-9]+$' | |
return "$?" | |
} | |
function is_valid_host { | |
getent hosts "$1" >/dev/null | |
return "$?" | |
} | |
function print_script_usage_and_exit() { | |
if [ ! -z "$1" ]; then | |
echo -e "$1\n" >&2 | |
fi | |
echo "Usage: rsync-backup.sh -l '/path/to/backup' [-n 45] [-s] [-u user] [-h host] [-p 22]" >&2 | |
echo " -l : The backup location directory." >&2 | |
echo " -n : The number of backups to keep. Defaults to 45." >&2 | |
echo " -s : True or false whether to backup over SSH. Defaults to false." >&2 | |
echo " -u : The SSH user to use." >&2 | |
echo " -h : The SSH host to use." >&2 | |
echo " -p : The SSH port to use. Defaults to 22." >&2 | |
exit 1 | |
} | |
if [ `whoami` != 'root' ]; then | |
print_script_usage_and_exit "Error: this script must be run a root" | |
fi | |
backup_location='' | |
nbr_of_backups=45 | |
use_ssh=false | |
ssh_user='' | |
ssh_host='' | |
ssh_port=22 | |
# parse arguments | |
while getopts :l:n:su:h:p: option; do | |
case "$option" in | |
l) backup_location="$OPTARG" ;; | |
n) nbr_of_backups="$OPTARG" ;; | |
s) use_ssh=true ;; | |
u) ssh_user="$OPTARG" ;; | |
h) ssh_host="$OPTARG" ;; | |
p) ssh_port="$OPTARG" ;; | |
\?) print_script_usage_and_exit ;; | |
:) print_script_usage_and_exit "Error: Option '-$OPTARG' requires an argument" ;; | |
esac | |
done | |
shift $((OPTIND-1)) | |
# validate non ssh input | |
if [ -z "$backup_location" ]; then | |
print_script_usage_and_exit "Error: Backup location not specified" | |
elif [ "$use_ssh" = false -a ! -d "$backup_location" ]; then | |
print_script_usage_and_exit "Error: Backup location '$backup_location' doesn't exist" | |
elif ! is_integer "$nbr_of_backups"; then | |
print_script_usage_and_exit "Error: Number of backups must be an integer" | |
fi | |
# validate ssh input | |
if [ "$use_ssh" = true ]; then | |
if [ -z "$ssh_user" ]; then | |
print_script_usage_and_exit "Error: SSH user not specified" | |
elif [ -z "$ssh_host" ]; then | |
print_script_usage_and_exit "Error: SSH host not specified" | |
elif ! is_valid_host "$ssh_host" >/dev/null; then | |
print_script_usage_and_exit "Error: SSH host is invalid" | |
elif ! is_integer "$ssh_port"; then | |
print_script_usage_and_exit "Error: SSH port must be an integer" | |
elif [ "$ssh_port" -le 0 -o "$ssh_port" -ge "65535" ]; then | |
print_script_usage_and_exit "Error: SSH port number is invalid" | |
fi | |
else | |
ssh_user='' | |
ssh_host='' | |
ssh_port=22 | |
fi | |
# Build work variables | |
current_backup_name="${backup_location}"/"CurrentBackup" | |
backup_prefix="Backup." | |
backup_when="$(date +'%F.%T.%Z')" | |
this_backup="${backup_location}"/"${backup_prefix}${backup_when}" | |
backup_log="/tmp/rsync-backup.$backup_when.log" | |
rsync_ssh_parms=() | |
rsync_ssh_path='' | |
ssh_exec=() | |
if [ "$use_ssh" = true ]; then | |
rsync_ssh_parms+=( '-e' ) | |
rsync_ssh_parms+=( "ssh -p $ssh_port" ) | |
rsync_ssh_parms+=( "-M--fake-super" ) | |
rsync_ssh_path="$ssh_user@$ssh_host:" | |
ssh_exec+=( 'ssh' ) | |
ssh_exec+=( '-p' ) | |
ssh_exec+=( "$ssh_port" ) | |
ssh_exec+=( "$ssh_user@$ssh_host" ) | |
fi | |
# Backup everything. use hard links when possible | |
# From here: https://wiki.archlinux.org/index.php/Rsync#Full_system_backup | |
echo "Started on ${backup_when}" > "$backup_log" | |
if "${ssh_exec[@]}" test -L "${current_backup_name}"; then | |
rsync "${rsync_ssh_parms[@]}" --delete -aAX /* "$rsync_ssh_path${this_backup}" \ | |
--exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} \ | |
--link-dest="${current_backup_name}"/ 2>> "$backup_log" >/dev/null | |
else | |
rsync "${rsync_ssh_parms[@]}" --delete -aAX /* "$rsync_ssh_path${this_backup}" \ | |
--exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} 2>> "$backup_log" >/dev/null | |
fi | |
rc="$?" | |
# Handle errors | |
if [ "$rc" -ne 0 -a \ | |
"$rc" -ne 23 -a \ | |
"$rc" -ne 24 ]; then | |
echo -e "Error: Backup failed with error code $rc, deleting it.\n" >&2 | |
cat "$backup_log" >&2 | |
rm "$backup_log" | |
"${ssh_exec[@]}" rm -rf "${this_backup}" | |
exit "$rc" | |
fi | |
# Delete old backups | |
backups="$("${ssh_exec[@]}" find "$backup_location" -maxdepth 1)" | |
backups="$(echo "$backups" | sort | grep -F "$backup_prefix")" | |
IFS=$(echo -en "\n\b") | |
if [ $(echo "$backups" | wc -l) -gt "${nbr_of_backups}" ]; then | |
"${ssh_exec[@]}" rm -rf $(echo "$backups" | head -n $(expr $(echo "$backups" | wc -l) - "${nbr_of_backups}")) | |
fi | |
unset IFS | |
# Replace symlink for most current backup (this one) | |
"${ssh_exec[@]}" rm -f "${current_backup_name}" | |
"${ssh_exec[@]}" ln --relative -s "${this_backup}" "${current_backup_name}" | |
echo "Ended on $(date +'%F.%T.%Z')" >> "$backup_log" | |
if [ "$use_ssh" = true ]; then | |
scp -q -P "$ssh_port" "$backup_log" "$ssh_user@$ssh_host:$this_backup" | |
else | |
cp "$backup_log" "${this_backup}" | |
fi | |
rm "$backup_log" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment