Simplify things by placing the server wrap code into it's own function
in the main script.
Move to predominantly lowercase and local variables.

Introduce usage of separate config files for each server.
master
mj-saunders 2022-11-04 16:55:16 +04:00
parent f648dc627f
commit 41580f9a0d
5 changed files with 226 additions and 171 deletions

2
.gitignore vendored 100644
View File

@ -0,0 +1,2 @@
server_config/*
!server_config/README*

15
README.md 100644
View File

@ -0,0 +1,15 @@
# BorgBackup Reverse
If you find yourself in a situation where your only backup solution is remote
to local, this may provide a semi-sane method to achieve this without having to
store any credentials on your remote machines.
## Description
TODO
## Usage
TODO

223
backup.sh
View File

@ -4,124 +4,179 @@
set -e set -e
[ -n "$DEBUG" ] && set -x [ -n "$DEBUG" ] && set -x
display_usage() { # NOTE:
echo -e "Usage: $0 create|extract repo-name\n" # 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
exit_abnormal () { main()
display_usage {
exit 1 local borg_old=0
}
if [ $# -ne 2 ]; then while getopts "cp:h" option; do
exit_abnormal
fi
if [ `id -u` -eq 0 ]; then
echo "This script must not be run as root!"
exit_abnormal
fi
#user_name=borg
#if [ "$(id --user --name)" != "$bbbs_user_name" ]; then
# echo "$0 must be run as $bbbs_user_name"
# exit
#fi
while getopts "h?" option; do
case "$option" in case "$option" in
h|\?) c) borg_old=1
display_usage ;;
exit 0 p) BORG_REPO_PATH=$OPTARG
;;
h)
display_usage 0
;;
*)
echo "Invalid option"
display_usage 1
;; ;;
esac esac
done done
shift "$((OPTIND - 1))"
MODE=$1 if [ $# -ne 2 ]; then
REPO=$2 display_usage 1
fi
if [ $MODE != "create" ] && [ $MODE != "extract" ]; then if [ `id -u` -eq 0 ]; then
echo "Mode must be either 'create' or 'extract'" echo "This script must not be run as root!"
exit_abnormal display_usage 1
fi
if [ -z ${BORG_REPO_PATH} ]; then
echo "[ERROR] BORG_REPO_PATH is empty or unset."
display_usage 1
fi fi
log_base_dir="${HOME}/.borg/logs" local mode=$1
local repo=$2
local borg_dir="${HOME}/.borg"
month_dir=$(date --utc "+%Y-%m") if [ $mode != "create" ] && [ $mode != "extract" ]; then
log_dir=${log_base_dir}/${month_dir} display_usage 1
fi
log_file=${log_dir}/$(date --utc "+%Y-%m-%d_%H.%M.%SZ")_${REPO}.log # 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}"
mkdir --parent "${log_dir}" # 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
echo "To monitor progress, run this in another terminal:" # 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}" echo " $ tail -f ${log_file}"
# Temporarilly disallow glob # Temporarilly disallow glob
set -o noglob set -o noglob
BORG_OPTIONS="--show-rc --list --stats --one-file-system --exclude-caches" 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 if [ -z $BORG_OLD ]; then
BORG_OPTIONS+=" --show-version --exclude-nodump --keep-exclude-tags" borg_options+=(--show-version --exclude-nodump --keep-exclude-tags)
fi fi
[ -n $DEBUG ] && BORG_OPTIONS+=" --verbose" local socket_local="/tmp/borg-local.sock"
local socket_remote="/tmp/borg-remote.sock"
COMMON_TARGET="/ /boot /etc /root /home /opt /srv /var /var/log /usr" if [ $mode == 'create' ]; then
echo "== CREATING"
COMMON_EXCLUDE="--exclude /sys/ --exclude /proc/ --exclude /dev/ --exclude /run/ --exclude /var/run/ --exclude /var/lock --exclude /mnt/" # NOTE: "backup-server" is arbitrary and can be anything
# the socat-wrapper will ignore it
VOLATILE_EXCLUDE="--exclude /tmp/ --exclude /var/tmp/ --exclude /lost+found --exclude /var/cache/ --exclude /root/.cache --exclude /home/*/.cache" server_wrap $socket_local $socket_remote $mode $repo \
LXD_BASE="/var/lib/lxd/*/rootfs"
LXD_EXCLUDE="--exclude $LXD_BASE/lost+found --exclude $LXD_BASE/media/* --exclude $LXD_BASE/mnt/* \
--exclude $LXD_BASE/proc/* --exclude $LXD_BASE/run/* --exclude $LXD_BASE/sys/* \
--exclude $LXD_BASE/tmp/*"
LXC_BASE="/home/*/.local/share/lxc/*/rootfs"
LXC_EXCLUDE="--exclude $LXC_BASE/lost+found --exclude $LXC_BASE/media/* --exclude $LXC_BASE/mnt/* \
--exclude $LXC_BASE/proc/* --exclude $LXC_BASE/run/* --exclude $LXC_BASE/sys/* \
--exclude $LXC_BASE/tmp/*"
# Especially when not using --one-file-system
# --exclude /var/run # -> /run
SOCK_LOCAL="/tmp/borg-local.sock"
SOCK_REMOTE="/tmp/borg-remote.sock"
if [ $MODE == 'create' ]; then
echo "=== CREATING"
. ./server-wrap.sh -s $SOCK_LOCAL $SOCK_REMOTE $MODE $REPO \
borg create \ borg create \
"$BORG_OPTIONS" \ "${borg_options[@]}" \
"$COMMON_EXCLUDE" "$VOLATILE_EXCLUDE" \ "${exclude[@]}" "${volatile[@]}" \
"$LXD_EXCLUDE" "$LXC_EXCLUDE" \ ssh://backup-server/$BORG_REPO_PATH/$repo::{utcnow:%Y-%m-%d} \
ssh://backup-server/$BORG_REPO_PATH/$REPO::{utcnow:%Y-%m-%d} \ "${include[@]}" \
"$COMMON_TARGET" \
2>&1 | tee ${log_file} 2>&1 | tee ${log_file}
# Tidy up Client .sock - dealt with in 'server-wrap.sh'
# . Chance are, on error, the .sock file will not be cleaned up properly
# Current setup requires no specific files on remote machine
# Could perhaps require one small script that just handles any necessary cleanup
# --exclude /var/lib/lxd/ \
# --exclude /var/lib/vz/images/ \
# TODO: Test and fix up extract procedure # TODO: Test and fix up extract procedure
else else
echo "=== EXTRACTING" echo "== EXTRACTING"
ARCHIVE=$(borg info $BORG_REPO_PATH/$REPO --last 1 | grep 'Archive name' | awk '{print $3}') local archive=$(borg info $BORG_REPO_PATH/$repo --last 1 | grep 'Archive name' | awk '{print $3}')
. ./server-wrap.sh -s $SOCK_LOCAL $SOCK_REMOTE $MODE $REPO \ server_wrap $socket_local $socket_remote $mode $repo \
borg extract \ borg extract \
--dry-run \ --dry-run \
ssh://backup-server/$BORG_REPO_PATH/$REPO::$ARCHIVE \ ssh://backup-server/$BORG_REPO_PATH/$repo::$archive \
2>&1 | tee $log_file 2>&1 | tee $log_file
fi fi
# Re-allow glob # Re-allow glob
set +o noglob set +o noglob
# Tidy up - if necessary # Tidy up
if [ -f $SOCK_LOCAL ]; then # NOTE: socket_remote is dealt with in 'server_wrap.sh'
rm $SOCK_LOCAL if [ -f $socket_local ]; then
rm $socket_local
fi 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 "$@"

View File

@ -1,55 +0,0 @@
#!/bin/bash
if [ -z ${BORG_REPO_PATH} ]; then
echo "BORG_REPO_PATH is empty or unset. Please set and try again."
exit 1
fi
# Temporarilly disallow glob
set -o noglob
SOCK_SRV="$2"
SOCK_CLI="$3"
CLIENT_SOCAT="'bash -c \"exec socat STDIO UNIX-CONNECT:$SOCK_CLI\"'"
MODE="$4"
REPO="$5"
BORG_CMD="${@:6}"
# Add some cleanup to the command
BORG_CMD+=" && rm $SOCK_CLI"
# Make command more robust from premature expansion
BORG_CMD=`echo $BORG_CMD | sed "s/--exclude\s\(\S\+\)/--exclude \'\1\'/g"`
echo $BORG_CMD
if [ $MODE == "extract" ]; then
SH_CMD="cd /mnt"
fi
#user_name="borg"
#if [ "$(id --user --name)" != "$user_name" -o $# -lt 6 ]; then
if [ $# -lt 6 ]; then
echo "$0 must be run as $user_name"
echo "usage: sudo -u $user_name [env vars] $0 [-s|--socket] path-to/local-listening.sock path-to/remote-connecting.sock path-to/socat-wrapper user@sourcehost <client borg command>"
echo "usage: sudo -u $user_name [env vars] $0 [-t|--tcp] local-listening-port remote-connecting-port path-to/socat-wrapper user@sourcehost <client borg command>"
echo
echo "example: sudo -u $user_name BORGW_RESTRICT_PATH=/path/to/repos $0 -s /tmp/local.sock /tmp/remote.sock /opt/borg/client-wrap"\
"\"backuped-server -p 22\" sudo borg create ssh://backup-server/./my-repo::{hostname}_{utcnow} paths to backup"
echo "example: sudo -u $user_name SSH_ARGS=\"-o ProxyCommand=ssh -W %h:%p gateway-server -p 22\" BORGW_RESTRICT_REPOSITORY=/path/to/repos/repo"\
"$0 -t 12345 12345 /opt/borg/client-wrap backuped-server sudo borg"\
"create ssh://backup-server/./::{hostname}_{utcnow} paths to backup"
echo
echo "Note: \"backup-server\" is arbitrary and can be anything - the socat-wrapper will ignore it"
else
exec socat UNIX-LISTEN:"$SOCK_SRV" \
"EXEC:borg serve --append-only --restrict-to-path $BORG_REPO_PATH --umask 077" &
ssh -t -R "$SOCK_CLI":"$SOCK_SRV" $REPO sudo BORG_RSH="$CLIENT_SOCAT" "$BORG_CMD"
fi
# Re-allow glob
set +o noglob

View File

@ -0,0 +1,38 @@
# Server Configuration
Create a file for each of your remote servers to be backed up.
The contents is used to determine what and what not to back up.
## File Names
Each filename must match an entry in your `~/.ssh/config`.
It will be used to connect to the remote machine.
It must also match the name of the target borg-backup repository.
A minimal example:
```
~/.ssh/config
Host repo-name
HostName <ip-address>
User foo
```
## File Content
We use bash arrays to list `include` and `exclude` paths.
These are then sourced by the backup script and prepared for use with `borg`.
See the `template` file for a more complete example.
```
include=( path/to/include ... )
exclude=(
# Comments should be safe to use
path/to/exclude
...
)
```