Skip to content

Instantly share code, notes, and snippets.

@jarrettgilliam
Last active September 6, 2020 21:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jarrettgilliam/a7350c7600ee1418ba20 to your computer and use it in GitHub Desktop.
Save jarrettgilliam/a7350c7600ee1418ba20 to your computer and use it in GitHub Desktop.
Rsync backup script
#!/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