#!/bin/bash

# Basically, invoked by
#
#    borg-user-backup TUUSER HOST SOURCE_DIRS ...
#
# the SOURCE_DIRS are backed up to HOST. This script invokes the command
#
#    borg create ${create_options} \
#	TUUSER@HOST:/mnt/backup/TUUSER/{hostname}.borg::{now:%Y-%m-%d_%H:%M} \
#       SOURCE_DIRS ...
#
# EXAMPLE:
#
#    borg-user-backup tloimer b.fluid.tuwien.ac.at /home/tloimer/data \
#							/usr/local/share
#
# Usage:
# On first usage, execute this script from the command line.
# Enter the password for your authentication to the host, if asked to do so.
# Then, create a cron-job to execute this script once a day. If this script is
# invoked by root, pay attention that CONFIG_DIR below references HOME.
#
# On first usage, this script creates a private/public key pair for passwordless
# authentication to the host. The public key is then uploaded to the host, the
# private key is stored on the local machine in the directory given by
# CONFIG_DIR. Any subsequent invocation backs up the data given in SOURCE_DIRS
# to HOST.
#
# Files and directories excluded from backup:
# Files or directories that match the patterns '*.nobackup', '*[cC]ache' or that
# contain a cachedir-tag (see http://www.bford.info/cachedir/spec.html) are
# excluded from backup. Create the file 'borg-exclude.txt' in CONFIG_DIR and
# add any patterns to exclude additional files or directories from backup.
#
# See man borg(1), borg help, borg help patterns.


# The authentication key and the file borg-exclude.txt, which
# contains the exclude patterns, will be stored in CONFIG_DIR. But see below.
CONFIG_DIR="${HOME}/.config/backup"

# The file providing patterns to exclude directories from backup.
# Files and directories matching the patterns `*.nobackup', `*[cC]ache' and
# directories containing a cachedir.tag are excluded anyhow.
EXCLUDE_FILE="${CONFIG_DIR}/borg-exclude.txt"

# The directory on the remote host, where the backup-repository is stored.
REMOTE_DIR="/mnt/backup"

# The location where the key pair is stored.
# Set this to "${HOME}/.config/sync-to-host" if you already have keys
# generated for the sync-to-host script.
KEY_DIR="${CONFIG_DIR}"

# The key types to try for authentication, sorted by decreasing preference.
KEY_TYPES="ed25519 rsa"


checkusage() {
    [[ $# -lt 3 ]] && err_exit "Usage: ${0##*/} TUUSER HOST SOURCE_DIRS ..."
}

err() { echo -e "$@" >&2; }

err_exit() {
    err "$@"
    exit 1
}

# Do not try passwordless login; The user might change his setup,
# inadvertently disabling the backup
#try_passwordless_login() {
#    ssh -q -o BatchMode=yes ${ssh_target} /bin/true && \
#	borg_rsh_command="" # instead of "BORG_RSH=\"ssh -i $KEY_FILE\""
#}

test_ssh() {
    local ssh_target=$1
    eval ${BORG_RSH} ${ssh_target} /bin/true &>/dev/null
}

find_existing_keys() {
    local key_type
    [[ -d "${KEY_DIR}" ]] || mkdir -p "${KEY_DIR}"
    for key_type in ${KEY_TYPES}; do
	key_file="${KEY_DIR}/id_${key_type}"
	[[ -f ${key_file} ]] && KEY_FILE="${key_file}" && break
    done
}

generate_key() {
    local key_type
    echo "generating key ..."
    for key_type in ${KEY_TYPES}; do
	key_file="${KEY_DIR}/id_${key_type}"
	ssh-keygen -q -t ${key_type} -N "" -f "${key_file}" && \
		KEY_FILE="$key_file" && return
    done;
    err_exit "Fatal: Generation of SSH key failed, exiting."
}

upload_key() {
    local ssh_target=$1
    # If the key should be exclusively used for borg-backup, write
    # the command to the command section of the key,
    #echo -n \
    #"command=\"borg serve --restrict-to-path ${REMOTE_DIR}/$user\",restrict " \
    # | cat - ${KEY_FILE}.pub | ssh...
    echo -e "uploading public key ${KEY_FILE}.pub to\n\
	${ssh_target}:.ssh/authorized_keys ..."
    local add_key="mkdir -p .ssh && cat >> .ssh/authorized_keys"
    cat ${KEY_FILE}.pub | ssh ${ssh_target} ${add_key} || \
        err_exit "Fatal: SSH key upload failed, exiting.\n\
The home directory for ${ssh_target} may be missing.\n\
In that case, ask the administrator to create it."
}

ensure_repo_exists() {
    local ssh_target=$1
    local target_dir="$2"
    local target_repo="$3"
    BORG_RSH="${BORG_RSH}" borg list "${target_repo}" &>/dev/null ||	\
	{ ! eval ${BORG_RSH} ${ssh_target}				\
		"test -d \"${target_dir}\" -a -w \"${target_dir}\"" &&	\
err_exit "The directory ${ssh_target}:${target_dir} is not writeable.\n\
Please ask your administrator to create it."; }
}

init_repo() {
    local target_repo="$1"
    echo "initializing repository ${target_repo} ..."
    BORG_RSH="${BORG_RSH}" borg init --encryption=none "${target_repo}" || \
err_exit "Fatal: Initialization of repository "${target_repo}" failed, exiting."
}

backup() {
    local target_repo="$1"
    local sources=("${@:2}")

    local archive="{now:%Y-%m-%d_%H:%M}"
    local create_options="-C zlib \
--exclude=*.nobackup \
--exclude=*[cC]ache  \
--exclude-caches"
    if [[ -f "${EXCLUDE_FILE}" ]]; then
	create_options+=" --exclude-from=\"${EXCLUDE_FILE}\""
    fi

    BORG_RSH="${BORG_RSH}" borg create ${create_options} \
				"${target_repo}::${archive}" "${sources[@]}"
}

main() {
    checkusage "$@"
    local user=$1
    local host=$2
    local sources=("${@:3}")
    local ssh_target="${user}@${host}"
    local target_dir="${REMOTE_DIR}/${user}"
    local target_repo="${ssh_target}:${target_dir}/{hostname}.borg"

    # By default, just do the backup; If not, go through initialization.
    find_existing_keys && BORG_RSH="ssh -qo BatchMode=yes -i \"${KEY_FILE}\"" \
		backup "${target_repo}" "${sources[@]}" && return	      \
	|| [[ -t 0 ]]							      \
		&& echo "*** Setting up backup to ${host}, ***"		      \
		|| err_exit "Backup failed."

    # find_existing_keys() sets $KEY_FILE, if it was successful
    [[ -z ${KEY_FILE} ]] && generate_key
    BORG_RSH="ssh -qo BatchMode=yes -i \"${KEY_FILE}\""
    test_ssh ${ssh_target} || upload_key ${ssh_target}

    ensure_repo_exists ${ssh_target} "${target_dir}" "${target_repo}" || \
	init_repo "${target_repo}"
    echo "Done."
}

[[ $- == *i* ]]								\
	&& echo "Please, execute this shell script, do not source it!"	\
	|| main "$@"
