borgScripts/borghelper.sh

398 lines
13 KiB
Bash
Executable File

#!/bin/sh
#
## borg helper script for viewing and restoring backups
##
## script written by Asif Bacchus, last updated March 26, 2020.
##
## The author of this script is not affiliated with 'borgbackup' in any way and
## does not warrant anything about this script, its operation, suitability or
## fitness for use in any environment or under any conditions. You are using
## this script entirely at your own risk.
#
### trap
trap trapExit 1 2 3 6
### functions
cleanup () {
# remove borg temp directory, if it exists
if [ -d "${borgBaseDir}/tmp" ]; then
if ! rm -rf "${borgBaseDir}/tmp" > /dev/null 2>&1; then
consoleError 3 "Script completed successfully but could not remove temporary directory at '$borgBaseDir/tmp'. Sorry to be messy."
fi
fi
if [ -f "${restorePath}/touch.test" ]; then
if ! rm -f "${restorePath}/touch.test" > /dev/null 2>&1; then
consoleError 5 "Script completed successfully but could not remove test file at '$restorePath/touch.test'. Sorry to be messy."
fi
fi
}
consoleError() {
printf "%s\n%s\n" "$err" "$2"
printf "Exiting.\n\n%s" "$norm"
exit "$1"
}
scriptHelp() {
printf "\n"
textblock "${bold}Usage: borghelper.sh [parameters]${norm}"
printf "\n"
textblock "Simple script to read connection parameters from a flat text file and process borg 'info', 'list' and 'restore' commands without the very long command lines that are required when specifying repo names and passwords, etc."
printf "\n"
textblock "${magenta}The script has the following parameters:${norm}"
printf "\n"
textblock "${magenta}--- Required Parameters ---${norm}"
printf "\n"
ptextblock "-v|--vars"
textblock "Path to the .borgvars file from which to read borg connection information. This is not required if run with '--makevars'."
printf "\n"
textblock "${magenta}--- Operation Modes ---${norm}"
printf "\n"
ptextblock "--makevars"
textblock "Create a sample .borgvars file that you can fill in and use with this script."
ptextblock "-i|--info"
textblock "Get information about a specified borg repo archive. Requires you supply '--archive'."
ptextblock "-l|--list"
textblock "List contents of a specified borg repo archive. Requires you supply '--archive'."
ptextblock "-la|--list-all"
textblock "List all available archives within the repo specified in your .borgvars file."
ptextblock "-r|--restore"
textblock "Restore the specified borg repo archive/file(s). Requires you supply '--archive'."
printf "\n"
textblock "${magenta}--- Selector Parameters ---${norm}"
printf "\n"
ptextblock "-a|--archive"
textblock "The archive within your borg repo you wish to work with."
ptextblock "--exclude"
textblock "Pattern (python/borg) of files to exclude from a restore operation."
ptextblock "-f|--file"
textblock "Specific file/pattern (python/borg) you want to restore from an archive. Requires that you supply '--archive'."
ptextblock "-p|--path"
textblock "Path to which you want your archive/files restored. This script will attempt to create the directory for you if it does not already exist."
printf "\n"
textblock "${magenta}--- Restore Options ---${norm}"
printf "\n"
ptextblock "--progress"
textblock "Display progress indicator during restore operations. WARNING: This can drastically slow down operations on larger archives!"
ptextblock "--verbose"
textblock "List the individual files being processed during restore operations."
printf "\n"
textblock "${magenta}--- Other Parameters ---${norm}"
printf "\n"
ptextblock "-h|-?|--help"
textblock "This help screen."
printf "\n"
exit 0
}
textblock() {
printf "%s\n" "$1" | fold -w "$width" -s
}
ptextblock() {
printf "%s%s%s\n" "$cyan" "$1" "$norm"
}
trapExit () {
cleanup
printf "%s\nScript execution terminated via signal.\n\n%s" "$err" "$norm"
exit 99
}
### text formatting presets
if command -v tput > /dev/null; then
bold=$(tput bold)
cyan=$(tput setaf 6)
err=$(tput bold)$(tput setaf 1)
magenta=$(tput setaf 5)
norm=$(tput sgr0)
width=$(tput cols)
else
bold=""
cyan=""
err="[ERROR] "
magenta=""
norm=""
width=80
fi
### pre-requisites
# is user root?
if [ ! "$( id -u )" -eq 0 ]; then
consoleError 1 'You must be root to run this script.'
fi
# has a parameter been passed to this script?
if [ -z "$1" ]; then
consoleError 1 "No operation requested. Please run this script with '--help' for valid parameters."
fi
# process startup parameters
while [ $# -gt 0 ]; do
case "$1" in
-a|--archive)
# name of backup archive
if [ -z "$2" ]; then
consoleError 1 "Please provide the name of the backup archive you want to work with or use '--list-all' to get a full list."
fi
archiveName="$2"
shift
;;
--exclude)
# exclude files from restore
if [ -z "$2" ]; then
consoleError 1 "Please provide a list of exclusions in the proper borg format."
fi
exclusions="$2"
shift
;;
-f|--file)
# specific file to restore
if [ -z "$2" ]; then
consoleError 1 'Please provide the name of the specific file you want to restore.'
fi
fileName="$2"
shift
;;
-h|-\?|--help)
# display help
scriptHelp
exit 0
;;
-i|--info)
# show archive information
operation='info'
;;
-l|--list)
# list contents of specific backup
operation='viewarchive'
;;
-la|--list-all)
# list all backup archives
operation='listall'
;;
--makevars)
# make a borgvars template file
operation='makevars'
;;
-p|--path)
# path to restore files
if [ -z "$2" ]; then
consoleError 1 'Please specify a path where you want files restored.'
fi
restorePath="${2%/}"
shift
;;
--progress)
# show progress
commonOptions="$commonOptions --progress"
;;
-r|--restore)
# restore archive/file
operation='restore'
;;
-v|--vars)
# location of borgvars file
if [ -z "$2" ]; then
consoleError 1 'Please provide the path to the file with your borg connection information.'
elif [ ! -f "$2" ]; then
consoleError 1 'The specified borg connection information file does not exist.'
exit 1
fi
varsFile="$2"
shift
;;
--verbose)
# display each file being processed
restoreOptions="$restoreOptions --list"
;;
*)
# invalid option
printf "%s\nUnknown option: %s\n" "$err" "$1"
printf "Use '--help' for valid options.\n\n%s" "$norm"
exit 1
;;
esac
shift
done
### process 'makevars' operation
if [ "$operation" = 'makevars' ]; then
if ! printf "sshKeyFile=\nborgBaseDir=\nborgRepo=\nborgRepoPassword=\nborgRemote=\n" > ./sample.borgvars; then
consoleError 4 'Could not write sample borgvars file.'
else
exit 0
fi
fi
### check parameter validity
# no operation
if [ -z "$operation" ]; then
consoleError 1 'Nothing to do!'
fi
# no borg information file
if [ -z "$varsFile" ]; then
consoleError 1 'You must provide a valid .borgvars file with information about your borg repo.'
fi
# info without archive
if [ "$operation" = 'info' ] && [ -z "$archiveName" ]; then
consoleError 1 "Info operation requested but no archive name provided. Please use '--list-all' for a list of all available archives."
fi
# list without archive
if [ "$operation" = 'viewarchive' ] && [ -z "$archiveName" ]; then
consoleError 1 "List operation requested but no archive name provided. Please use '--list-all' for a list of all available archives."
fi
# restore with no path
if [ "$operation" = 'restore' ] && [ -z "$restorePath" ]; then
consoleError 1 "Restore operation requested but no restore path provided."
# restore with no archive
elif [ "$operation" = 'restore' ] && [ -z "$archiveName" ]; then
consoleError 1 "Restore operation requested but no archive name provided."
fi
# file provided but no archive
if [ "$fileName" ] && [ -z "$archiveName" ]; then
consoleError 1 "Filename specified without an associated archive name."
fi
# clean-up leading spaces in option strings
if [ "$commonOptions" ]; then commonOptions=${commonOptions##[[:space:]]}; fi
if [ "$restoreOptions" ]; then restoreOptions=${restoreOptions##[[:space:]]}; fi
# check/create restore path
if [ -d "$restorePath" ]; then
if ! touch "${restorePath}/touch.test" > /dev/null 2>&1; then
consoleError 5 'Cannot write to specified restore directory.'
fi
else
if ! mkdir -p "${restorePath}" > /dev/null 2>&1; then
consoleError 5 'Cannot create specified restore directory.'
else
if ! touch "${restorePath}/touch.test" > /dev/null 2>&1; then
consoleError 5 'Cannot write to specified restore directory.'
fi
fi
fi
### read borg information file
# check if file was provided as a relative or absolute path
case "${varsFile}" in
/*)
# absolute path, no need to rewrite variable
. "${varsFile}"
;;
*)
# relative path, prepend './' to create absolute path
. "./${varsFile}"
;;
esac
# verify borg base directory
if [ -z "${borgBaseDir}" ]; then
consoleError 2 "$varsFile: 'borgBaseDir' is not specified."
elif [ ! -d "${borgBaseDir}" ]; then
consoleError 2 "$varsFile: 'borgBaseDir' does not exist."
fi
export BORG_BASE_DIR="${borgBaseDir%/}"
## check path to SSH keyfile
if [ -z "${sshKeyFile}" ]; then
consoleError 2 "$varsFile: 'sshKeyFile' is not specified."
elif [ ! -f "${sshKeyFile}" ]; then
consoleError 2 "$varsFile: 'sshKeyFile' does not exist."
fi
export BORG_RSH="ssh -i ${sshKeyFile}"
# check borg repo connect string
if [ -z "${borgRepo}" ]; then
consoleError 2 "$varsFile: 'borgRepo' is not specified."
fi
export BORG_REPO="${borgRepo}"
# check borg repo password
if [ -n "${borgRepoPassword}" ]; then
export BORG_PASSPHRASE="${borgRepoPassword}"
elif [ "${borgRepoPassword}" = 'none' ]; then
export BORG_PASSPHRASE=""
else
consoleError 2 "$varsFile: 'borgRepoPassword' must be specified or must be 'none' if no password has been set (VERY INSECURE!)."
fi
# export borg remote path, if specified
if [ -n "${borgRemote}" ]; then export BORG_REMOTE_PATH="${borgRemote}"; fi
### create borg temp dir:
## python requires a writable temporary directory when unpacking borg and
## executing commands. This defaults to /tmp but many systems mount /tmp with
## the 'noexec' option for security. Thus, we will use/create a 'tmp' folder
## within the BORG_BASE_DIR and instruct python to use that instead of /tmp
# check if BORG_BASE_DIR/tmp exists, if not, create it
if [ ! -d "${borgBaseDir}/tmp" ]; then
if ! mkdir "${borgBaseDir}/tmp"; then
consoleError 3 'Unable to create temp working directory for borg.'
fi
fi
export TMPDIR="${borgBaseDir}/tmp"
### execute borg operations
# info operations
if [ "$operation" = 'info' ]; then
borg info ::"${archiveName}"
# list operations
elif [ "$operation" = 'listall' ]; then
borg list
elif [ "$operation" = 'viewarchive' ]; then
borg list ::"${archiveName}"
# restore operations
elif [ "$operation" = 'restore' ]; then
cd "$restorePath" || consoleError 4 'Could not change to restore directory.'
if [ -z "$fileName" ]; then
# restore entire archive
if [ "$exclusions" ]; then
borg --show-rc ${commonOptions} extract ${restoreOptions} ::"${archiveName}" --exclude "${exclusions}"
else
borg --show-rc ${commonOptions} extract ${restoreOptions} ::"${archiveName}"
fi
elif [ "$fileName" ]; then
# restore file/pattern
if [ "$exclusions" ]; then
borg --show-rc ${commonOptions} extract ${restoreOptions} ::"${archiveName}" "${fileName}" --exclude "${exclusions}"
else
borg --show-rc ${commonOptions} extract ${restoreOptions} ::"${archiveName}" "${fileName}"
fi
fi
fi
### exit gracefully
cleanup
exit 0
### exit codes
# 0: no errors, script completed successfully
# 1: parameter error (missing, non-existant or invalid input)
# 2: parameter missing/invalid in .borgvars file
# 3: could not create/remove borg tmp directory
# 4: could not write sample borgvars file (permissions?)
# 5: cannot create/write to restore path or could not remove touch.test file
#EOF