#! /bin/bash

# UUID of the backup device. Retrive with:
# lsblk -o +UUID
ext_luks_dev="00000000-0000-0000-0000-000000000000"

# Key file to encrypt/decrypt LUKS container.
ext_luks_key="/path/to/keyfile"

# Mount point of the (decrypted) LUKS container.
ext_mnt_point="/mnt"

# Folder to be created inside the root of the backup device. All the backed up
# data will be inside this folder -- or the root of the device, if this
# variable is kept empty.
ext_root_backup_folder="BACKUP"

# MUST contain absolute paths!
loc=()
loc+=( "$HOME/folder1" )
loc+=( "$HOME/folder2" )

# --- User settable variables end here. ---

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() {
  # 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 "Mounting encrypted drive failed. Exiting..."
    exit 1
  fi

  # 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 "${loc[@]}" "$ext_mnt_point/$ext_root_backup_folder"

  sudo -- sh -c "umount $ext_mnt_point ; cryptsetup close extbck"
}

display_usage() {
  echo -e "Options for $0: run without arguments to make backup, run with -r option to restore a backup. Use the -y option, either when doing a backup or when restoring one, to run rsync with the --dry-run option (simulation)."
}

function restore() {
  # 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 "Mounting encrypted drive failed. Exiting..."
    exit 1
  fi

  # 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/./" /

  sudo -- sh -c "umount $ext_mnt_point ; cryptsetup close extbck"
}

# 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() {
  local op="backup"

  for i in $@ ; do
    if [[ "$i" == "-h" ]] ; then
      display_usage
      exit 0
    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 loc array, and replace any location that
  # ends with a forward slash. This is done because while rsync'ing folder a
  # transfers the folder a itself onto the backup location, rsync'ing a/
  # transfers only the CONTENTS of the folder a. Among other things, this makes
  # it impossible to restore the backup automatically.
  for i in "${!loc[@]}" ; do
    if [[ "${loc[$i]}" =~ $re ]]; then
      loc[$i]=${loc[$i]%?}
    fi
  done

  if [[ "$op" == "backup" ]]; then
    backup
  elif [[ "$op" == "restore" ]]; then
    restore
  else
    echo "Unknown operation $op. Exiting..."
    exit 1
  fi
}

main "$@"