183 lines
5.1 KiB
Bash
Executable File
183 lines
5.1 KiB
Bash
Executable File
#!/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 "$@"
|