#! /bin/bash
config_file=""
default_config_file="$HOME/.config/ext_luks_bck/config.sh"
re="\/$" # Matches strings that end with a forward slash.
# Rsync command. Options:
# -a : archive mode. Among other things, ensure that timestamps are preserved.
# -v : verbose mode. Because a bit more info doesn't hurt...
# --relative : recreate full paths of files in backup device. This allows
# restoring files to their previous location automatically.
rsync_cmd_base="rsync -av --relative "
# Exit on Ctrl-C. This is required because otherwise, if the user pressed C-c
# say, at the sudo prompt, the sudo command would halt, but the script would
# proceed to open the LUKS device, etc.
trap ctrl_c INT
function ctrl_c() {
echo "Interrupted."
exit 0
}
function backup() {
mount_luks
for i in "${excludes[@]}" ; do
rsync_excludes+=( --exclude "$i" )
done
# The $rsync_cmd_base command is explained above. Here I only explain the
# --delete option: it is here to ensure that when the user deletes a file or
# folder that had been previously backed up, then it is also deleted on the
# backup copy.
$rsync_cmd_base --delete "${locations[@]}" "${rsync_excludes[@]}" "$ext_mnt_point/$ext_root_backup_folder" | grep -E -v '/$'
umount_luks
}
function display_usage() {
echo -e "\nOptions for $0:\n- Run without arguments to make backup, run with -r option to restore a backup.\n- Use the -y option, either when doing a backup or when restoring one, to run rsync with the --dry-run option (simulation).\n- Use -m to mount LUKS volume only; -u to dismount LUKS volume only.\n"
}
function mount_luks {
# Even "inside" sudo, $USER is the user *running* the sudo command.
sudo -- sh -c "cryptsetup --key-file=$ext_luks_key open UUID=$ext_luks_dev extbck ; mount /dev/mapper/extbck $ext_mnt_point ; chown -R $USER $ext_mnt_point"
if [[ $? != 0 ]]; then
echo "$0: Mounting encrypted volume failed. Exiting..."
exit 1
else
echo "$0: Mounted encrypted volume."
fi
}
function restore() {
mount_luks
# The $rsync_cmd_base command is explained above. Here I only explain the
# --update option: it is here to ensure that when restoring a backup, files
# that have been modified AFTER the backup was made, are NOT overwritten.
# Hence, the newer version of the files is preserved.
$rsync_cmd_base --update "$ext_mnt_point/$ext_root_backup_folder/./" / | grep -E -v '/$'
umount_luks
}
function umount_luks {
sudo -- sh -c "umount $ext_mnt_point ; cryptsetup close extbck"
if [[ $? != 0 ]]; then
echo "$0: Unmounting encrypted volume failed. Exiting..."
exit 1
else
echo "$0: Unmounted encrypted volume."
fi
}
# main() is the last function because it calls all the other functions -- and
# with shell script, this means all those other functions must be defined
# before. Or equivalently, main() must be the last function.
function main {
config_file=""
# First, find if user provided a custom config file.
got_dash_f_in_prev_iter=0
for i in $@ ; do
if [[ $got_dash_f_in_prev_iter -eq 1 ]]; then
config_file="$i"
break
fi
if [[ "$i" == "-f" ]] ; then
got_dash_f_in_prev_iter=1
continue
fi
done
if [[ $got_dash_f_in_prev_iter -eq 0 ]]; then
# If no -f handle provided, then use default config file.
config_file="$default_config_file"
elif [[ $got_dash_f_in_prev_iter -eq 1 ]]; then
if [[ -z "$config_file" ]]; then
# If -f handle was provided, but without a config a file, then throw
# error.
echo "ERROR: -f provied without a config file!"
exit 1
fi
else
echo "ERROR: \$got_dash_f_in_prev_iter has value $got_dash_f_in_prev_iter"
exit 1
fi
# If control reaches here, there is a config provided, so let us check if it
# is proper.
if ! [[ -f "$config_file" ]]; then
echo "Could not read file $config_file!"
exit 1
fi
# Config file is proper, so source it and move on...
source "$config_file"
# Now let us deal with other command line arguments, if any.
local op="backup"
for i in $@ ; do
if [[ "$i" == "-h" ]] ; then
display_usage
exit 0
elif [[ "$i" == "-m" ]] ; then
mount_luks
exit $?
elif [[ "$i" == "-u" ]] ; then
umount_luks
exit $?
elif [[ "$i" == "-r" ]] ; then
op="restore"
elif [[ "$i" == "-y" ]] ; then
rsync_cmd_base="$rsync_cmd_base --dry-run"
fi
done
if ! [[ -f "$ext_luks_key" ]]; then
echo "LUKS key file $ext_luks_key could not be read. Exiting..."
exit 1
fi
if ! [[ -d "$ext_mnt_point" ]]; then
echo "Mount point $ext_mnt_point does not exist (or is not directory). Exiting..."
exit 1
fi
# Iterate over the (indices of) the locations array, and in any location that
# ends with a forward slash, remove said forward slash. This is done because
# while rsync'ing folder foobar transfers the folder foobar itself onto the
# backup location, rsync'ing foobar/ transfers only the CONTENTS of the
# folder foobar. Among other things, this makes it impossible to restore the
# backup automatically.
for i in "${!locations[@]}" ; do
if [[ "${locations[$i]}" =~ $re ]]; then
locations[$i]=${locations[$i]%?}
fi
done
if [[ "$op" == "backup" ]]; then
backup
elif [[ "$op" == "restore" ]]; then
restore
else
echo "Unknown operation $op. Exiting..."
exit 1
fi
}
main "$@"