#!/bin/bash # These can be overridden when running this script HOSTNAME=${HOSTNAME:-$(hostname)} BACKUP_HOST=${BACKUP_HOST:-backup.jim.sh} BACKUP_USER=${BACKUP_USER:-jim-backups} BACKUP_REPO=${BACKUP_REPO:-borg/${HOSTNAME}} # Main dir is where this repo was checked out BORG_DIR="$(realpath "$(dirname "$0")")" cd "${BORG_DIR}" BORG_BIN="${BORG_DIR}/bin/borg.$(uname -m)" # Use stable host ID in case MAC address changes. # Note that this host ID is only used to manage locks, so it's # not crucial that it remains stable. HOSTID="${HOSTNAME}@$(python3 -c 'import uuid;print(uuid.getnode())')" function error_handler() { echo "Error at $1 line $2:" echo -n '>>> ' ; tail -n +"$2" < "$1" | head -1 echo "... exited with code $3" exit "$3" } trap 'error_handler ${BASH_SOURCE} ${LINENO} $?' ERR set -o errexit set -o errtrace # Create pip environment setup_venv() { if ! which pipenv >/dev/null 2>&1 ; then echo "pipenv not found, try: sudo apt install pipenv" exit 1 fi mkdir -p .venv pipenv install } # Create shell script with environment variables create_borg_vars() { VARS=${BORG_DIR}/vars.sh # These variables are used elsewhere in this script BORG_REPO="ssh://${BACKUP_USER}@${BACKUP_HOST}/./${BACKUP_REPO}" BORG=${BORG_DIR}/borg.sh SSH=$BORG_DIR/ssh cat >"$VARS" </dev/null ; then error "Can't run the borg wrapper; does borg work?" fi } # Update paths in README and backup.py update_paths() { sed -i \ -e "s!\${HOSTNAME}!${HOSTNAME}!g" \ -e "s!\${BORG_DIR}!${BORG_DIR}!g" \ -e "s!\${BORG_BIN}!${BORG_BIN}!g" \ -e "s!\${BACKUP_USER}!${BACKUP_USER}!g" \ -e "s!\${BACKUP_HOST}!${BACKUP_HOST}!g" \ -e "s!\${BACKUP_REPO}!${BACKUP_REPO}!g" \ README.md sed -i\ -e "1c#!${BORG_DIR}/.venv/bin/python" \ backup.py } # See if we're just supposed to update an existing install if [ "$1" == "--update-paths" ] || [ "$1" == "--update" ] ; then if [ -e "vars.sh" ]; then echo "Updating paths and variables" update_paths setup_venv create_borg_vars exit 0 else echo "Can't update, not set up yet" exit 1 fi fi if [ -e "vars.sh" ]; then echo "Error: BORG_DIR $BORG_DIR already looks set up; giving up." echo "Use \"git clean\" to return it to original state if desired" exit 1 fi # Make a temp dir to work in TMP=$(mktemp -d) # Install some cleanup handlers cleanup() { set +o errexit set +o errtrace trap - ERR ssh -o ControlPath="$TMP"/ssh-control -O exit x >/dev/null 2>&1 rm -rf -- "$TMP" } cleanup_int() { echo cleanup exit 1 } trap cleanup 0 trap cleanup_int 1 2 15 msg() { color="$1" shift echo -ne "\033[1;${color}m===\033[0;${color}m" "$@" echo -e "\033[0m" } log(){ msg 33 "$@" ; } notice() { msg 32 "$@" ; } warn() { msg 31 "$@" ; } error() { msg 31 "Error:" "$@" ; exit 1 ; } print_random_key() { dd if=/dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 16 } generate_keys() { PASS_REPOKEY=$(print_random_key) echo "$PASS_REPOKEY" > passphrase chmod 600 passphrase } # Run a command on the remote host over an existing SSH tunnel run_ssh_command() { ssh -o ControlPath="$TMP"/ssh-control use-existing-control-tunnel "$@" } # Configure SSH key-based login configure_ssh() { mkdir "$SSH" # Create keys log "Creating SSH keys" ssh-keygen -N "" -t ecdsa \ -C "backup-appendonly@$HOSTID" -f "$SSH/id_ecdsa_appendonly" ssh-keygen -N "" -t ecdsa \ -C "backup-notify@$HOSTID" -f "$SSH/id_ecdsa_notify" # Create config snippets log "Creating SSH config and wrapper script" cat >> "$SSH/config" </dev/null 2>&1 $keys" run_ssh_command "if cmp -s $backup $keys; then rm $backup ; fi" run_ssh_command "cat >> .ssh/authorized_keys" <>key.txt < "$TIMER_UNIT" <> "$SERVICE_UNIT" <