-#!/bin/sh
-set -e
-cd $(dirname "$0")
-. lib/abort
-. lib/constants_borg # PATH_BORG_CONF
-. lib/expect_n_args
-. lib/get_passphrase
-. lib/path_tmp_timestamped
-. lib/print_usage
-. lib/retry_until
-cd - > /dev/null
-
-USAGE_DESCRIPTION='Wrapper around certain borgbackup usages.\n\nAvailable commands:'
-USAGE_LINES='COMMAND [ARGUMENT]...'
-
-PATH_BORG_CONF_SECURITY="${PATH_BORG_CONF}/security"
-PATH_BORG_CONF_KEYS="${PATH_BORG_CONF}/keys"
-
-NAME_ARCHIVE_ORG=orgdir
-
-location_from_servername() { printf 'ssh://borg@%s/./borgrepo' "$1"; }
-servername_from_location() { echo "$1" | cut -d'/' -f3 | cut -d'@' -f2; }
-path_repo_location() { printf '%s' "${PATH_BORG_CONF_SECURITY}/${1}/location"; }
-
-explore_key_id() {
- local KEY_ID=$1
- local PATH_LOC="$(path_repo_location ${KEY_ID})"
- if [ ! -f "${PATH_LOC}" ]; then
- printf '?'
- return
- fi
- local LOCATION=$(cat "${PATH_LOC}")
- local NAME_SERVER=$(servername_from_location "${LOCATION}")
- if [ "${LOCATION}" = $(location_from_servername "${NAME_SERVER}") ]; then
- local PREFIX='+'
- else
- local PREFIX='-'
- fi
- printf '%s %s %s' "${PREFIX} ${NAME_SERVER} ${LOCATION}"
-}
-
-keydata_obsolete() {
- KEY_DATA=$1
- local PREFIX=$(echo "${KEY_DATA}" | cut -d' ' -f1)
- if [ "${PREFIX}" = '-' ]; then
- echo "Ignoring ${KEY_ID}, location doesn't match expected pattern."
- return 0
- fi
- return 1
-}
-
-# exits
-export BORG_EXIT_CODES=modern
-error_exit() { abort "Aborting due to $1"; }
-error_exit_with_usage() { error_exit "${1}\n\n$(print_usage)"; }
-exit_ok() { echo "$@"; exit 0; }
-
-# commands
-USAGE_INDICES='backup_keys claim help info init keys orgpull orgpush'
-
-_run_borg_with_passphrase() {
- _FIRST_RUN=1
- _ON_LOOP_START='
- if [ -z "${BORG_PASSPHRASE}" ]; then
- printf "Passphrase:"
- export BORG_PASSPHRASE="$(get_passphrase)"
- echo ""
- fi
- '
- _TO_TEST='borg '"$@"
- _ON_FAIL='echo "unexpected borg error, code ${_RESULT}."'
- _ON_LOOP_END='export BORG_PASSPHRASE='
- retry_until 52 "${_ON_LOOP_START}" "${_TO_TEST}" "${_ON_FAIL}" "${_ON_LOOP_END}"
-}
-
-_id_from_file() { head -1 "$1" | cut -d' ' -f2; }
-
-USAGE_ARGS_backup_keys='PATH'
-USAGE_DESC_backup_keys="copy known keys to PATH/, with their repos' server names as filenames"
-CMDFNC_backup_keys() {
- expect_n_args 1 1 "${USAGE_ARGS_backup_keys}" $@
- local PATH_TARGET_DIR=$(realpath "$1")
- if [ -e "${PATH_TARGET_DIR}" ] && [ ! -d "${PATH_TARGET_DIR}" ]; then
- error_exit "non-directory at ${PATH_TARGET_DIR}"
- fi
- mkdir -p "${PATH_TARGET_DIR}"
- cd "${PATH_BORG_CONF_KEYS}"
- ls -1 | while read _FILENAME; do
- local KEY_ID=$(_id_from_file "${PATH_BORG_CONF_KEYS}/${_FILENAME}")
- local KEY_DATA=$(explore_key_id "${KEY_ID}")
- if keydata_obsolete "${KEY_DATA}"; then
- continue;
- fi
- local NAME_SERVER=$(echo "${KEY_DATA}" | cut -d' ' -f2)
- local PATH_TARGET="${PATH_TARGET_DIR}/${NAME_SERVER}"
- echo "Copying ${_FILENAME} to ${PATH_TARGET} …"
- cp "${_FILENAME}" "${PATH_TARGET}"
- done
- cd - > /dev/null
-}
-
-USAGE_ARGS_claim='PATH'
-USAGE_DESC_claim="register file of PATH as key to repo at \"$(location_from_servername SERVER_NAME)\", with SERVER_NAME the filename portion of PATH"
-CMDFNC_claim() {
- expect_n_args 1 1 "${USAGE_ARGS_claim}" $@
- _PATH_SOURCE="$1"
- _ensure_no_overwrite_at() {
- if [ -f "$1" ]; then
- error_exit "refusing to overwrite pre-existing file at $1"
- fi
- }
-
- _FILENAME=$(basename ${_PATH_SOURCE})
- _PATH_TARGET_KEY="${PATH_BORG_CONF_KEYS}/${_FILENAME}"
- if [ ! -f "${_PATH_SOURCE}" ]; then
- error_exit "no file at ${_PATH_SOURCE}"
- fi
- _ensure_no_overwrite_at "${_PATH_TARGET_KEY}"
- _REPO_ID="$(_id_from_file ${_PATH_SOURCE})"
- if [ ! -z "$(echo ${_REPO_ID} | sed 's/[a-f0-9]//g')" ]; then
- error_exit "inability to parse valid repo ID from alleged key file at $1"
- fi
- _PATH_TARGET_LOCATION="$(path_repo_location ${_REPO_ID})"
- _ensure_no_overwrite_at "${_PATH_TARGET_LOCATION}"
- mkdir -p "${PATH_BORG_CONF_KEYS}" "$(dirname ${_PATH_TARGET_LOCATION})"
- echo "Copying ${_PATH_SOURCE} to ${_PATH_TARGET_KEY} …"
- cp "${_PATH_SOURCE}" "${_PATH_TARGET_KEY}"
- echo "Writing ${_PATH_TARGET_LOCATION} …"
- printf '%s' "$(location_from_servername ${_FILENAME})" > "${_PATH_TARGET_LOCATION}"
- chmod a-rwx,u+rw "${_PATH_TARGET_KEY}" "${_PATH_TARGET_LOCATION}"
-}
-
-USAGE_DESC_help='print this help and exit'
-CMDFNC_help() {
- expect_n_args 0 0 '' $@
- print_usage
-}
-
-USAGE_ARGS_info='SERVER_NAME [ARCHIVE]'
-USAGE_DESC_info="run 'borg info' against repo at \"$(location_from_servername SERVER_NAME)\", optionally only against ARCHIVE; if latter not provided, list most recent archives of repo"
-CMDFNC_info() {
- expect_n_args 1 2 "${USAGE_ARGS_info}" $@
- _TARGET_REPO="$(location_from_servername ${1})"
- if [ ! -z "$2" ]; then
- exit_ok $(borg info "${_TARGET_REPO}::$2")
- fi
- _run_borg_with_passphrase info "${_TARGET_REPO}"
- echo "${_OUTPUT}\n\nMost recent archives:"
- borg list "${_TARGET_REPO}" | tail -5
-}
-
-USAGE_ARGS_init='SERVER_NAME'
-USAGE_DESC_init="create repo at \"$(location_from_servername SERVER_NAME)\""
-CMDFNC_init() {
- expect_n_args 1 1 "${USAGE_ARGS_init}" $@
- _STASHED_BORG_PASSPHRASE="$(env | grep -E '^BORG_PASSPHRASE=' | cut -d'=' -f2-)"
- unset BORG_PASSPHRASE
- borg init --encryption=keyfile "$(location_from_servername $1)"
- if [ ! -z "${_STASHED_BORG_PASSPHRASE}" ]; then
- export BORG_PASSPHRASE="${_STASHED_BORG_PASSPHRASE}"
- _STASHED_BORG_PASSPHRASE=
- fi
-}
-
-USAGE_DESC_keys='list known repos in ID, key filename, and alleged location'
-CMDFNC_keys() {
- expect_n_args 0 0 '' $@
- echo "Known keys, as per ${PATH_BORG_CONF_KEYS}:"
- local EXIT_OK_PREFIX='(none, since directory'
- if [ ! -d "${PATH_BORG_CONF_KEYS}" ]; then
- exit_ok "${EXIT_OK_PREFIX} non-existant)"
- fi
- local KEYFILES="$(ls -1 ${PATH_BORG_CONF_KEYS})"
- if [ -z "${KEYFILES}" ]; then
- exit_ok "${EXIT_OK_PREFIX} empty)"
- fi
- echo "${KEYFILES}" | while read _FILENAME; do
- local KEY_ID=$(_id_from_file "${PATH_BORG_CONF_KEYS}/${_FILENAME}")
- local KEY_DATA=$(explore_key_id "${KEY_ID}")
- local PREFIX=$(echo "${KEY_DATA}" | cut -d' ' -f1)
- local LOCATION=$(echo "${KEY_DATA}" | cut -d' ' -f3)
- printf '%s %s %s %s\n' "${PREFIX}" "${KEY_ID}" "${_FILENAME}" "${LOCATION}"
- done
-}
-
-USAGE_DESC_orgpull='pull most recent org directory available in repos'
-CMDFNC_orgpull() {
- expect_n_args 0 0 '' $@
-
- # determine server and repo
- local PATH_PIPE="$(path_tmp_timestamped 'pipe')"
- mkfifo "${PATH_PIPE}"
- ls -1 "${PATH_BORG_CONF_SECURITY}/" > "${PATH_PIPE}" &
- while read _FILENAME; do
- local KEY_DATA=$(explore_key_id "${_FILENAME}")
- if keydata_obsolete "${KEY_DATA}"; then
- continue;
- fi
- local NAME_SERVER=$(echo "${KEY_DATA}" | cut -d' ' -f2)
- local LOCATION=$(echo "${KEY_DATA}" | cut -d' ' -f3)
- if ping -c1 -W2 "${NAME_SERVER}" > /dev/null 2>&1; then
- local REPO="${LOCATION}"
- break
- else
- echo "Cannot reach ${NAME_SERVER}, skipping."
- fi
- done < "${PATH_PIPE}"
- rm "${PATH_PIPE}"
- if [ -z "${REPO}" ]; then
- error_exit 'no repo being available.'
- fi
-
- # determine passphrase and archive
- echo "Checking out ${REPO} …"
- _run_borg_with_passphrase list "${REPO}"
- local ARCHIVE=$(echo "${_OUTPUT}" | grep "${NAME_ARCHIVE_ORG}" | tail -1 | cut -f1 -d' ')
-
- # pull archive
- echo "Pulling archive: ${ARCHIVE}"
- cd /
- borg extract --verbose "${REPO}::${ARCHIVE}"
-}
-
-USAGE_DESC_orgpush='push org directory to repos'
-CMDFNC_orgpush() {
- expect_n_args 0 0 '' $@
-
- for _FILENAME in $(ls "${PATH_BORG_CONF_SECURITY}/"); do
- local KEY_DATA=$(explore_key_id "${_FILENAME}")
- if keydata_obsolete "${KEY_DATA}"; then
- continue;
- fi
- local LOCATION=$(echo "${KEY_DATA}" | cut -d' ' -f3)
- local ARCHIVE="${LOCATION}::${NAME_ARCHIVE_ORG}-{utcnow:%Y-%m-%dT%H:%M}"
- _run_borg_with_passphrase create --verbose "${ARCHIVE}" "${HOME}/org"
- done
-}
-
-# parse args to execution
-if [ "$#" -lt 1 ]; then
- error_exit_with_usage 'missing command.'
-fi
-for CMD in ${USAGE_INDICES}; do
- if [ "$1" = "${CMD}" ]; then
- break
- fi
- CMD=
-done
-if [ -z "${CMD}" ]; then
- error_exit_with_usage "unknown command: ${1}"
-fi
-shift 1
-"CMDFNC_${CMD}" $@