#!/bin/bash # Stop on any error and provide some info if DEBUG set set -e [ -n "$DEBUG" ] && set -x # NOTE: # The repo variable is used in a few different ways - it should be the name of: # 1. an existing borg repository # 2. a config file in $borg_dir/server_config/ # 3. an entry in your ssh config main() { local borg_old=0 while getopts "cp:h" option; do case "$option" in c) borg_old=1 ;; p) BORG_REPO_PATH=$OPTARG ;; h) display_usage 0 ;; *) echo "Invalid option" display_usage 1 ;; esac done shift "$((OPTIND - 1))" if [ $# -ne 2 ]; then display_usage 1 fi if [ `id -u` -eq 0 ]; then echo "This script must not be run as root!" display_usage 1 fi if [ -z ${BORG_REPO_PATH} ]; then echo "[ERROR] BORG_REPO_PATH is empty or unset." display_usage 1 fi local mode=$1 local repo=$2 local borg_dir="${HOME}/.borg" if [ $mode != "create" ] && [ $mode != "extract" ]; then display_usage 1 fi # Server config # The config file is expected to provide several arrays: # include, exclude and volatile # TODO: Move config to more standard location #local config_file="${borg_dir}/server_config/${repo}" local config_file="server_config/${repo}" if [[ ! -f ${config_file} ]]; then echo "[ERROR] Config file missing; expected ${config_file}" exit 1 fi source "${config_file}" # Make sure the config defined the required arrays for var in include exclude; do if [[ -z ${!var} ]]; then echo "[ERROR] ${repo}: The config requires an '${var}' array" exit 1 fi done # borg-backup requires --exclude to prepend each excluded item exclude=( "${exclude[@]/#/--exclude }" ) ## Logging local log_dir="${borg_dir}/log" local log_month_dir=${log_dir}/$(date --utc "+%Y-%m") local log_file=${log_month_dir}/$(date --utc "+%Y-%m-%d_%H.%M.%SZ")_${repo}.log if [[ ! -d "${log_month_dir}" ]]; then echo "== Creating log directory" mkdir --parent "${log_month_dir}" \ || { echo "[ERROR] Failed to create log directory. Aborting."; exit 1; } fi touch ${log_file} \ || { echo "[ERROR] Failed to create log file. Aborting"; exit 1; } echo "== NOTE: To monitor progress, run this from another terminal:" echo " $ tail -f ${log_file}" # Temporarilly disallow glob set -o noglob local borg_options=(--show-rc --list --stats --one-file-system --exclude-caches) if [[ -n $DEBUG ]]; then borg_options+=(--verbose) fi if [ -z $BORG_OLD ]; then borg_options+=(--show-version --exclude-nodump --keep-exclude-tags) fi local socket_local="/tmp/borg-local.sock" local socket_remote="/tmp/borg-remote.sock" if [ $mode == 'create' ]; then echo "== CREATING" # NOTE: "backup-server" is arbitrary and can be anything # the socat-wrapper will ignore it server_wrap $socket_local $socket_remote $mode $repo \ borg create \ "${borg_options[@]}" \ "${exclude[@]}" "${volatile[@]}" \ ssh://backup-server/$BORG_REPO_PATH/$repo::{utcnow:%Y-%m-%d} \ "${include[@]}" \ 2>&1 | tee ${log_file} # TODO: Test and fix up extract procedure else echo "== EXTRACTING" local archive=$(borg info $BORG_REPO_PATH/$repo --last 1 | grep 'Archive name' | awk '{print $3}') server_wrap $socket_local $socket_remote $mode $repo \ borg extract \ --dry-run \ ssh://backup-server/$BORG_REPO_PATH/$repo::$archive \ 2>&1 | tee $log_file fi # Re-allow glob set +o noglob # Tidy up # NOTE: socket_remote is dealt with in 'server_wrap.sh' if [ -f $socket_local ]; then rm $socket_local fi } server_wrap() { if [ $# -lt 5 ]; then echo "[ERROR] Missing argument(s)" exit 1 fi set -o noglob local socket_local="$1" local socket_remote="$2" local mode="$3" local repo="$4" local borg_command="${@:5}; rm $socket_remote" local remote_socat_command="'bash -c \"exec socat STDIO UNIX-CONNECT:$socket_remote\"'" # Make command more robust against premature expansion borg_command=`echo $borg_command | sed "s/--exclude\s\(\S\+\)/--exclude \'\1\'/g"` # TODO: Handle extract #if [ $mode == "extract" ]; then # SH_CMD="cd /mnt" #fi exec socat UNIX-LISTEN:"$socket_local" \ "EXEC:borg serve --append-only --restrict-to-path $BORG_REPO_PATH --umask 077" & ssh -t -R "$socket_remote":"$socket_local" $repo \ sudo BORG_RSH="$remote_socat_command" "$borg_command" set +o noglob } display_usage() { echo "Usage: $(basename $0) [-ch] [-p path] create|extract repo-name" echo " -c Use flags compatible with borg < (version tbd)" echo " -p Repository path. Overrides BORG_REPO_PATH" exit $1 } main "$@"