#!/bin/bash

# Usage:
#
#   sync-to-host TUUSER HOST TARGET_DIR
#
# Synchronize the directories under '/', further specified by the
# include paths given in CONFIG_DIR/include.txt and the exclude patterns
# given in CONFIG_DIR/exclude.txt, to the TARGET_DIR on HOST.
# Log in to HOST as TUUSER.

# On first invocation, keywordless login to HOST is setup.
# The public/private key pair is stored in CONFIG_DIR.
# The include path in CONFIG_DIR/include.txt is set to $HOME and
# a small set of exclude patterns is written to CONFIG_DIR/exclude.txt.
#
# In principle, this script runs the command
#
#   rsync -ar <further options> --files-from=include.txt	\
#		--exclude-from=exclude.txt / "$TUUSER"@"$HOST":"$TARGET_DIR"

CONFIG_DIR="${HOME}/.config/backup"

# Try these keys types, from left to right.
KEY_TYPES="ed25519 rsa"

# Store the keys in CONFIG_DIR
KEY_DIR="${CONFIG_DIR}"


checkusage() {
    [[ $# -eq 3 ]] || err_exit 'Usage: sync-to-host USER HOST TARGET_DIR'
}

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

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

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
}

synchronize() {
    local ssh_target=$1
    local target_dir="${2}"
    local ssh_host=${ssh_target#*@}

    # silently return, if the host is not reachable, except during setup
    nc -w 1 -z ${ssh_host} 22 ||	\
	{ [[ -t 1 ]] && err_exit "No route to host ${ssh_host}"	|| return 0; }

    # with --files-from, -a does not imply -r, see man rsync(1)
    rsync -ar --delete-excluded -e "ssh -qo BatchMode=yes -i '${KEY_FILE}'" \
		--files-from "${CONFIG_DIR}/include.txt"	\
		--exclude-from "${CONFIG_DIR}/exclude.txt"	\
		/ "${ssh_target}:${target_dir}" &&		\
	ssh -qo BatchMode=yes -i "${KEY_FILE}" ${ssh_target}	\
		touch "${target_dir}/.TIMESTAMP"
}

test_ssh() {
    local ssh_target=$1
    ssh -qo BatchMode=yes -i ${KEY_FILE} ${ssh_target} /bin/true &>/dev/null
}

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

    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."
}


main() {
    checkusage "$@"
    local user="${1}"
    local host="${2}"
    local target_dir="${3}"
    local ssh_target="${user}@${host}"
    local include_file="${CONFIG_DIR}/include.txt"
    local exclude_file="${CONFIG_DIR}/exclude.txt"


    # if everything is set up, just synchronize and return
    find_existing_keys && synchronize ${ssh_target} ${target_dir} && return				 \
	|| [[ -t 0 ]]							 \
		&& echo "*** Setting up synchronization to ${host}, ***" \
		|| err_exit "Synchronization failed."

    # find_existing_keys() sets $KEY_FILE, if it was succesful
    [[ -z ${KEY_FILE} ]] && generate_key
    test_ssh ${ssh_target} || upload_key ${ssh_target}

    # write the include and exclude pattern files
    [[ -d "${CONFIG_DIR}" ]] || mkdir -p "${CONFIG_DIR}"
    [[ -f "${include_file}" ]] || echo ${HOME}/ > "${include_file}"
    [[ -f "${exclude_file}" ]] || cat >"${exclude_file}" <<EOF
*[cC]ache
*.nobackup
.thumbnails
.mozilla
.thunderbird
EOF
    cat <<EOF
... setup completed.
Please edit the file

    ${exclude_file}

which contains file patterns to exclude from synchronization.
Perhaps you also want to edit the file containing include paths,

   ${include_file}

Finally, e.g., set up a cron job to invoke this script once an hour.
For instance, copy this script to ~/bin, type "crontab -e" and add the line
$((RANDOM % 60)) * * * * \$HOME/bin/$0 ${user} ${host} ${HOSTNAME}
EOF
}

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