Compare commits

...

10 Commits

Author SHA1 Message Date
Clemens Schwaighofer
bf0364a0e7 Move lock file clean up to before any exit info is printed
Just try to clean lock file always on exit
2022-04-25 08:57:01 +09:00
Clemens Schwaighofer
8e87503528 Add auto check functionality
CHECK_INTERVAL or SUB_CHECK_INTERVAL in module settings.
If set to 1, will check everytime.
Ever other number is for n days after last check.

FORCE_CHECK for check if repository has been setup os now renamed
FORCE_VERIFY but FORCE_CHECK is currently still honored but deprecated

all *function* shell scripts will abort if run on their own

Print info on last check time if set in info block

Internal updates:
All separator lines are now printf strings for central control.
All module used config/settings file names use $MODULE name
Check functionality is in its own file
2022-04-25 06:49:57 +09:00
Clemens Schwaighofer
34277483e9 Borg check -p (progress) is only set if -v (verbose) flag is set
Avoid any progress output if run from script
2022-04-18 16:00:41 +09:00
Clemens Schwaighofer
dffb7c6450 Fix borg check prefix settings 2022-04-18 15:58:03 +09:00
Clemens Schwaighofer
2ae05f5302 Add borg check and rename options
Because we added borg check functionality, some of the Options have been
renamed:
-C -> -V
-E -> -e (as it is a sub)

-C: check
-y: --verify-data
-p: prefix or glob for check

Internal variables with CHECK have been renamed or changed to VERIFY

Borg -C without any extra parameters is equal to borg check.
-y adds the --verify-data and -p is a mix of the -P and -a options. If
there is a "*" in the option then -a will be used, else -P

Note that repair command has to be run manually. Run -C with -v
(verbose) to see the repair command structure.

borg check can take a long time on very large repositories.
2022-04-18 15:41:28 +09:00
Clemens Schwaighofer
b5ead9a2e1 Show host name and module init date in the start info block 2022-04-01 11:16:38 +09:00
Clemens Schwaighofer
fa77876440 Update Version ID to 4.2.4 2022-03-31 11:09:11 +09:00
Clemens Schwaighofer
f128b7ebc4 Bug fix for no keep options set check 2022-03-31 11:01:02 +09:00
Clemens Schwaighofer
77977207c8 Change close parameter check to pure string type 2022-03-31 09:21:23 +09:00
Clemens Schwaighofer
c1f6bb443a Fix for close with empty parameter 2022-03-31 09:18:23 +09:00
14 changed files with 745 additions and 467 deletions

View File

@@ -59,15 +59,24 @@ override the default borg executable found in path
### `-P`
print list of archives created
### `-C`
check if repository exists, if not abort
### `-V`
verify if repository exists, if not abort
### `-E`
exit after check
### `-e`
exit after running verify `-V`
### `-I`
init repository (must be run first)
### `-C`
run `borg check` over given repository
#### `-y`
Add `--verify-data` to `borg check`. Only works with `-C`
#### `-p <prefix|glob>`
Only `borg check` data that has given prefix or glob (with *). Only works with `-C`
### `-i`
print out only info
@@ -109,7 +118,8 @@ All below have default values if not set in the main settings file
* COMPRESSION: zstd
* COMPRESSION_LEVEL: 3
* ENCRYPTION: none
* FORCE_CHECK: false
* FORCE_VERIFY: false
* CHECK_INTERVAL: none
* KEEP_LAST: 0
* KEEP_HOURS: 0
* KEEP_DAYS: 7
@@ -121,6 +131,7 @@ All module settings files can have the following prefixed with `SUB_` to overrid
* SUB_BACKUP_FILE
* SUB_COMPRESSION
* SUB_COMPRESSION_LEVEL
* SUB_CHECK_INTERVAL
* SUB_BACKUP_SET
* SUB_KEEP_LAST
* SUB_KEEP_HOURS
@@ -148,6 +159,14 @@ and `TARGET_BORG_PATH="";` if the target borg is in a non default path
`BORG_EXECUTABLE="<full path to borg>"`
## Note on CHECK_INTERVAL and SUB_CHECK_INTERVAL
If set to empty or 0 it will not run an automatic check. If set to 1 it will run a check after each backup. Any other value means days differente to the last check.
Running check manually (`-C`) will not reset the last check timestamp.
Automatic checks always add `--verify-data`, with manual `-C` the option `-y` has to be set.
## File backup settings
On new setups it is recommended to use the `borg.backup.file.setings` and set

View File

@@ -1,23 +1,26 @@
#!/usr/bin/env bash
# Run -I first to initialize repository
# There are no automatic repository checks unless -C is given
# Plain file backup
# set last edit date + time
MODULE="file";
MODULE_VERSION="1.2.0";
MODULE_VERSION="1.2.1";
DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
. "${DIR}/borg.backup.functions.init.sh";
# include and exclude file
INCLUDE_FILE="borg.backup.file.include";
EXCLUDE_FILE="borg.backup.file.exclude";
# init check file
BACKUP_INIT_CHECK="borg.backup.file.init";
INCLUDE_FILE="borg.backup.${MODULE}.include";
EXCLUDE_FILE="borg.backup.${MODULE}.exclude";
# init verify and check file
BACKUP_INIT_FILE="borg.backup.${MODULE}.init";
BACKUP_CHECK_FILE="borg.backup.${MODULE}.check";
# lock file
BACKUP_LOCK_FILE="borg.backup.${MODULE}.lock";
. "${DIR}/borg.backup.functions.check.sh";
# verify valid data
. "${DIR}/borg.backup.functions.verify.sh";
# exit if include file is missing
if [ ! -f "${BASE_FOLDER}${INCLUDE_FILE}" ]; then
@@ -25,7 +28,7 @@ if [ ! -f "${BASE_FOLDER}${INCLUDE_FILE}" ]; then
. "${DIR}/borg.backup.functions.close.sh" 1;
exit 1;
fi;
echo "--- [INCLUDE: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUB_BLOCK}" "INCLUDE" "$(date +'%F %T')" "${MODULE}";
# folders to backup
FOLDERS=();
# this if for debug output with quoted folders
@@ -90,7 +93,7 @@ done<"${BASE_FOLDER}${INCLUDE_FILE}";
# exclude list
if [ -f "${BASE_FOLDER}${EXCLUDE_FILE}" ]; then
echo "--- [EXCLUDE: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUB_BLOCK}" "EXCLUDE" "$(date +'%F %T')" "${MODULE}";
# check that the folders in that exclude file are actually valid,
# remove non valid ones and warn
#TMP_EXCLUDE_FILE=$(mktemp --tmpdir ${EXCLUDE_FILE}.XXXXXXXX); # non mac
@@ -159,7 +162,7 @@ COMMAND=${COMMAND}${REPOSITORY}::${ONE_TIME_TAG}${BACKUP_SET_PREFIX}${BACKUP_SET
. "${DIR}/borg.backup.functions.info.sh";
if [ $FOLDER_OK -eq 1 ]; then
echo "--- [BACKUP: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUB_BLOCK}" "BACKUP" "$(date +'%F %T')" "${MODULE}";
# show command
if [ ${DEBUG} -eq 1 ]; then
echo $(echo ${COMMAND} | sed -e 's/[ ][ ]*/ /g') ${FOLDERS_Q[*]};
@@ -182,7 +185,7 @@ fi;
# clean up, always verbose, but only if we do not run one time tag
if [ -z "${ONE_TIME_TAG}" ]; then
echo "--- [PRUNE : $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUB_BLOCK}" "PRUNE" "$(date +'%F %T')" "${MODULE}";
# build command
COMMAND="${BORG_COMMAND} prune ${OPT_REMOTE} -v --list ${OPT_PROGRESS} ${DRY_RUN_STATS} -P ${BACKUP_SET_PREFIX} ${KEEP_OPTIONS[*]} ${REPOSITORY}";
echo "Prune repository with keep${KEEP_INFO:1}";
@@ -193,6 +196,8 @@ if [ -z "${ONE_TIME_TAG}" ]; then
$(echo "${COMMAND}" | sed -e 's/[ ][ ]*/#/g') 2>&1 || echo "[!] Borg prune aborted";
# if this is borg version >1.2 we need to run compact after prune
. "${DIR}/borg.backup.functions.compact.sh";
# check in auto mode
. "${DIR}/borg.backup.functions.check.sh" "auto";
else
echo "[#] No prune with tagged backup";
fi;

View File

@@ -1,376 +1,83 @@
#!/usr/bin/env bash
# start time in seconds
START=$(date +'%s');
# start logging from here
exec &> >(tee -a "${LOG}");
echo "=== [START : $(date +'%F %T')] ==[${MODULE}]====================================>";
# show info for version always
echo "Script version: ${VERSION}";
# show type
echo "Backup module : ${MODULE}";
echo "Module version: ${MODULE_VERSION}";
# borg version
echo "Borg version : ${BORG_VERSION}";
# show base folder always
echo "Base folder : ${BASE_FOLDER}";
# if force check is true set CHECK to 1unless INFO is 1
# Needs bash 4.0 at lesat for this
if [ "${FORCE_CHECK,,}" = "true" ] && [ ${INFO} -eq 0 ]; then
CHECK=1;
if [ ${DEBUG} -eq 1 ]; then
echo "Force repository check";
fi;
fi;
# remote borg path
if [ ! -z "${TARGET_BORG_PATH}" ]; then
if [[ "${TARGET_BORG_PATH}" =~ \ |\' ]]; then
echo "Space found in ${TARGET_BORG_PATH}. Aborting";
echo "There are issues with passing on paths with spaces"
echo "as parameters"
exit;
fi;
OPT_REMOTE="--remote-path="$(printf "%q" "${TARGET_BORG_PATH}");
fi;
if [ -z "${TARGET_FOLDER}" ]; then
echo "[! $(date +'%F %T')] No target folder has been set yet";
if [ -z "${MODULE}" ]; then
echo "Script cannot be run on its own";
exit 1;
else
# There are big issues with TARGET FOLDERS with spaces
# we should abort anything with this
if [[ "${TARGET_FOLDER}" =~ \ |\' ]]; then
echo "Space found in ${TARGET_FOLDER}. Aborting";
echo "There is some problem with passing paths with spaces as";
echo "repository base folder"
exit;
fi;
# run borg check (NOT REPAIR)
RUN_CHECK=0;
if [ $# -ge 1 ] && [ "$1" = "auto" ]; then
# strip any spaces and convert to int
CHECK_INTERVAL=$(echo "${CHECK_INTERVAL}" | sed -e 's/ //g');
# not a valid check interval, do no check
if [ -z "${CHECK_INTERVAL##*[!0-9]*}" ]; then
CHECK_INTERVAL=0;
fi;
# This does not care for multiple trailing or leading slashes
# it just makes sure we have at least one set
# for if we have a single slash, remove it
TARGET_FOLDER=${TARGET_FOLDER%/}
TARGET_FOLDER=${TARGET_FOLDER#/}
# and add slash front and back and escape the path
TARGET_FOLDER=$(printf "%q" "/${TARGET_FOLDER}/");
fi;
# if we have user/host then we build the ssh command
TARGET_SERVER='';
# allow host only (if full setup in .ssh/config)
# user@host OR ssh://user@host:port/ IF TARGET_PORT is set
# user/host/port
if [ ! -z "${TARGET_USER}" ] && [ ! -z "${TARGET_HOST}" ] && [ ! -z "${TARGET_PORT}" ]; then
TARGET_SERVER="ssh://${TARGET_USER}@${TARGET_HOST}:${TARGET_PORT}/";
# host/port
elif [ ! -z "${TARGET_HOST}" ] && [ ! -z "${TARGET_PORT}" ]; then
TARGET_SERVER="ssh://${TARGET_HOST}:${TARGET_PORT}/";
# user/host
elif [ ! -z "${TARGET_USER}" ] && [ ! -z "${TARGET_HOST}" ]; then
TARGET_SERVER="${TARGET_USER}@${TARGET_HOST}:";
# host
elif [ ! -z "${TARGET_HOST}" ]; then
TARGET_SERVER="${TARGET_HOST}:";
fi;
# we dont allow special characters, so we don't need to special escape it
REPOSITORY="${TARGET_SERVER}${TARGET_FOLDER}${BACKUP_FILE}";
echo "Repository : ${REPOSITORY}";
# check compression if given is valid and check compression level is valid if given
OPT_COMPRESSION='';
if [ ! -z "${COMPRESSION}" ]; then
# valid compression
if [ "${COMPRESSION}" = "lz4" ] || [ "${COMPRESSION}" = "zlib" ] || [ "${COMPRESSION}" = "lzma" ] || [ "${COMPRESSION}" = "zstd" ]; then
OPT_COMPRESSION="-C=${COMPRESSION}";
# if COMPRESSION_LEVEL, check it is a valid regex
# for zlib, zstd, lzma
if [ ! -z "${COMPRESSION_LEVEL}" ] && ([ "${COMPRESSION}" = "zlib" ] || [ "${COMPRESSION}" = "lzma" ] || [ "${COMPRESSION}" = "zstd" ]); then
MIN_COMPRESSION=0;
MAX_COMPRESSION=0;
case "${COMPRESSION}" in
zlib|lzma)
MIN_COMPRESSION=0;
MAX_COMPRESSION=9;
;;
zstd)
MIN_COMPRESSION=1;
MAX_COMPRESSION=22;
;;
*)
MIN_COMPRESSION=0;
MAX_COMPRESSION=0;
;;
esac;
# if [ "${COMPRESSION}" = "zlib" ] || [ "${COMPRESSION}" = "lzma" ]
# MIN_COMPRESSION=0;
# MAX_COMPRESSION=9;
# elif [ "${COMPRESSION}" = "zstd" ]; then
# MIN_COMPRESSION=1;
# MAX_COMPRESSION=22;
# fi;
error_message="[! $(date +'%F %T')] Compression level for ${COMPRESSION} needs to be a numeric value between ${MIN_COMPRESSION} and ${MAX_COMPRESSION}: ${COMPRESSION_LEVEL}";
if ! [[ "${COMPRESSION_LEVEL}" =~ ${REGEX_NUMERIC} ]]; then
echo ${error_message};
exit 1;
elif [ ${COMPRESSION_LEVEL} -lt ${MIN_COMPRESSION} ] || [ ${COMPRESSION_LEVEL} -gt ${MAX_COMPRESSION} ]; then
echo ${error_message};
exit 1;
else
OPT_COMPRESSION=${OPT_COMPRESSION}","${COMPRESSION_LEVEL};
fi;
# get current date timestmap
CURRENT_DATE=$(date +%s);
# if =1 always ok
if [ ${CHECK_INTERVAL} -eq 1 ]; then
RUN_CHECK=1;
# always add verify data for automatic check
OPT_CHECK_VERIFY_DATA="--verify-data";
# set new check time here
echo ${CURRENT_DATE} > "${BASE_FOLDER}${BACKUP_CHECK_FILE}";
elif [ ${CHECK_INTERVAL} -gt 1 ]; then
# else load last timestamp and check if today - last time stamp > days
if [ -z "${LAST_CHECK_DATE}" ]; then
LAST_CHECK_DATE=$(cat "${BASE_FOLDER}${BACKUP_CHECK_FILE}" 2>/dev/null | sed -e 's/ //g');
fi;
else
echo "[! $(date +'%F %T')] Compress setting need to be lz4, zstd, zlib or lzma. Or empty for no compression: ${COMPRESSION}";
exit 1;
fi;
fi;
# home folder, needs to be set if there is eg a HOME=/ in the crontab
if [ ! -w "${HOME}" ] || [ "${HOME}" = '/' ]; then
HOME=$(eval echo "$(whoami)");
fi;
# keep optionfs (for files)
KEEP_OPTIONS=("");
# keep info string (for files)
KEEP_INFO="";
# override standard keep for tagged backups
if [ ! -z "${ONE_TIME_TAG}" ]; then
BACKUP_SET="{now:%Y-%m-%dT%H:%M:%S}";
else
# build options and info string,
# also flag BACKUP_SET check if hourly is set
BACKUP_SET_CHECK=0;
if [ ${KEEP_LAST} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-last=${KEEP_LAST}");
KEEP_INFO="${KEEP_INFO}, last: ${KEEP_LAST}";
fi;
if [ ${KEEP_HOURS} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-hourly=${KEEP_HOURS}");
KEEP_INFO="${KEEP_INFO}, hourly: ${KEEP_HOURS}";
BACKUP_SET_CHECK=1;
fi;
if [ ${KEEP_DAYS} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-daily=${KEEP_DAYS}");
KEEP_INFO="${KEEP_INFO}, daily: ${KEEP_DAYS}";
fi;
if [ ${KEEP_WEEKS} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-weekly=${KEEP_WEEKS}");
KEEP_INFO="${KEEP_INFO}, weekly: ${KEEP_WEEKS}";
fi;
if [ ${KEEP_MONTHS} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-monthly=${KEEP_MONTHS}");
KEEP_INFO="${KEEP_INFO}, monthly: ${KEEP_MONTHS}";
fi;
if [ ${KEEP_YEARS} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-yearly=${KEEP_YEARS}");
KEEP_INFO="${KEEP_INFO}, yearly: ${KEEP_YEARS}";
fi;
if [ ! -z "${KEEP_WITHIN}" ]; then
# check for invalid string. can only be number + H|d|w|m|y
if [[ "${KEEP_WITHIN}" =~ ^[0-9]+[Hdwmy]{1}$ ]]; then
KEEP_OPTIONS+=("--keep-within=${KEEP_WITHIN}");
KEEP_INFO="${KEEP_INFO}, within: ${KEEP_WITHIN}";
if [[ "${KEEP_WITHIN}" == *"H"* ]]; then
BACKUP_SET_CHECK=1;
fi;
else
echo "[! $(date +'%F %T')] KEEP_WITHIN has invalid string.";
exit 1;
# file date is not a timestamp
if [ -z "${LAST_CHECK_DATE##*[!0-9]*}" ]; then
LAST_CHECK_DATE=0;
fi;
# if the difference greate than check date, run. CHECK INTERVAL is in days
if [ $(($CURRENT_DATE-$LAST_CHECK_DATE)) -ge $((${CHECK_INTERVAL}*86400)) ]; then
RUN_CHECK=1;
# always add verify data for automatic check
OPT_CHECK_VERIFY_DATA="--verify-data";
# set new check time here
echo ${CURRENT_DATE} > "${BASE_FOLDER}${BACKUP_CHECK_FILE}";
fi;
fi;
# abort if KEEP_OPTIONS is empty
if [ -z "${KEEP_OPTIONS}" ]; then
echo "[! $(date +'%F %T')] It seems no KEEP_* entries where set in a valid format.";
exit 1;
fi;
# set BACKUP_SET if empty, set to Year-month-day
if [ -z "${BACKUP_SET}" ]; then
BACKUP_SET="{now:%Y-%m-%d}";
fi;
# backup set check, and there is no hour entry (%H) in the archive string
# we add T%H:%M:%S in this case, before the last }
if [ ${BACKUP_SET_CHECK} -eq 1 ] && [[ "${BACKUP_SET}" != *"%H"* ]]; then
BACKUP_SET=$(echo "${BACKUP_SET}" | sed -e "s/}/T%H:%M:%S}/");
fi;
elif [ ${CHECK} -eq 1 ]; then
RUN_CHECK=1;
fi;
# for folders list split set to "#" and keep the old setting as is
_IFS=${IFS};
IFS="#";
# turn off for non file
if [ "${MODULE}" != "file" ]; then
if [ ${RUN_CHECK} -eq 1 ]; then
# run borg check command
IFS=${_IFS};
fi;
# borg call, replace ##...## parts during run
# used in all modules, except 'file'
_BORG_CALL="${BORG_COMMAND} create ${OPT_REMOTE} -v ${OPT_LIST} ${OPT_PROGRESS} ${OPT_COMPRESSION} -s --stdin-name ##FILENAME## ${REPOSITORY}::##BACKUP_SET## -";
_BORG_PRUNE="${BORG_COMMAND} prune ${OPT_REMOTE} -v --list ${OPT_PROGRESS} ${DRY_RUN_STATS} -P ##BACKUP_SET_PREFIX## ${KEEP_OPTIONS[*]} ${REPOSITORY}";
# general borg settings
# set base path to config directory to keep cache/config separated
export BORG_BASE_DIR="${BASE_FOLDER}";
# ignore non encrypted access
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=${_BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK};
# ignore moved repo access
export BORG_RELOCATED_REPO_ACCESS_IS_OK=${_BORG_RELOCATED_REPO_ACCESS_IS_OK};
# and for debug print that tout
if [ ${DEBUG} -eq 1 ]; then
echo "export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=${_BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK};";
echo "export BORG_RELOCATED_REPO_ACCESS_IS_OK=${_BORG_RELOCATED_REPO_ACCESS_IS_OK};";
echo "export BORG_BASE_DIR=\"${BASE_FOLDER}\";";
fi;
# prepare debug commands only
COMMAND_EXPORT="export BORG_BASE_DIR=\"${BASE_FOLDER}\";"
COMMAND_INFO="${COMMAND_EXPORT}${BORG_COMMAND} info ${OPT_REMOTE} ${REPOSITORY}";
# if the is not there, call init to create it
# if this is user@host, we need to use ssh command to check if the file is there
# else a normal check is ok
# unless explicit given, check is skipped
if [ ${CHECK} -eq 1 ] || [ ${INIT} -eq 1 ]; then
echo "--- [CHECK : $(date +'%F %T')] --[${MODULE}]------------------------------------>";
if [ ! -z "${TARGET_SERVER}" ]; then
if [ ${DEBUG} -eq 1 ]; then
echo "${BORG_COMMAND} info ${OPT_REMOTE} ${REPOSITORY} 2>&1|grep \"Repository ID:\"";
fi;
# use borg info and check if it returns "Repository ID:" in the first line
REPO_CHECK=$(${BORG_COMMAND} info ${OPT_REMOTE} ${REPOSITORY} 2>&1|grep "Repository ID:");
# this is currently a hack to work round the error code in borg info
# this checks if REPO_CHECK holds this error message and then starts init
if [[ -z "${REPO_CHECK}" ]] || [[ "${REPO_CHECK}" =~ ${REGEX_ERROR} ]]; then
INIT_REPOSITORY=1;
fi;
elif [ ! -d "${REPOSITORY}" ]; then
INIT_REPOSITORY=1;
printf "${PRINTF_SUB_BLOCK}" "CHECK" "$(date +'%F %T')" "${MODULE}";
# repare command
OPT_GLOB="";
if [[ "${CHECK_PREFIX}" =~ $REGEX_GLOB ]]; then
OPT_GLOB="-a '${CHECK_PREFIX}'"
elif [ ! -z "${CHECK_PREFIX}" ]; then
OPT_GLOB="-P ${CHECK_PREFIX}";
fi;
# if check but no init and repo is there but init file is missing set it
if [ ${CHECK} -eq 1 ] && [ ${INIT} -eq 0 ] && [ ${INIT_REPOSITORY} -eq 0 ] &&
[ ! -f "${BASE_FOLDER}${BACKUP_INIT_CHECK}" ]; then
# write init file
echo "[!] Add missing init check file";
echo "$(date +%s)" > "${BASE_FOLDER}${BACKUP_INIT_CHECK}";
fi;
# end if checked but repository is not here
if [ ${CHECK} -eq 1 ] && [ ${INIT} -eq 0 ] && [ ${INIT_REPOSITORY} -eq 1 ]; then
echo "[! $(date +'%F %T')] No repository. Please run with -I flag to initialze repository";
. "${DIR}/borg.backup.functions.close.sh" 1;
exit 1;
fi;
if [ ${EXIT} -eq 1 ] && [ ${CHECK} -eq 1 ] && [ ${INIT} -eq 0 ]; then
echo "Repository exists";
echo "For more information run:"
echo "${COMMAND_INFO}";
. "${DIR}/borg.backup.functions.close.sh";
exit;
fi;
fi;
if [ ${INIT} -eq 1 ] && [ ${INIT_REPOSITORY} -eq 1 ]; then
echo "--- [INIT : $(date +'%F %T')] --[${MODULE}]------------------------------------>";
# debug/dryrun
if [ ${DEBUG} -eq 1 ] || [ ${DRYRUN} -eq 1 ]; then
echo "${BORG_COMMAND} init ${OPT_REMOTE} -e ${ENCRYPTION} ${OPT_VERBOSE} ${REPOSITORY}";
fi
if [ ${DRYRUN} -eq 0 ]; then
# should trap and exit properly here
${BORG_COMMAND} init ${OPT_REMOTE} -e ${ENCRYPTION} ${OPT_VERBOSE} ${REPOSITORY};
# write init file
echo "$(date +%s)" > "${BASE_FOLDER}${BACKUP_INIT_CHECK}";
echo "Repository initialized";
echo "For more information run:"
echo "${COMMAND_INFO}";
fi
. "${DIR}/borg.backup.functions.close.sh";
# exit after init
exit;
elif [ ${INIT} -eq 1 ] && [ ${INIT_REPOSITORY} -eq 0 ]; then
echo "[! $(date +'%F %T')] Repository already initialized";
echo "For more information run:"
echo "${COMMAND_INFO}";
. "${DIR}/borg.backup.functions.close.sh" 1;
exit 1;
fi;
# check for init file
if [ ! -f "${BASE_FOLDER}${BACKUP_INIT_CHECK}" ]; then
echo "[! $(date +'%F %T')] It seems the repository has never been initialized."
echo "Please run -I to initialize or if already initialzed run with -C for init update."
. "${DIR}/borg.backup.functions.close.sh" 1;
exit 1;
fi;
# PRINT OUT current data, only do this if REPO exists
if [ ${PRINT} -eq 1 ]; then
echo "--- [PRINT : $(date +'%F %T')] --[${MODULE}]------------------------------------>";
FORMAT="{archive:<45} {comment:6} {start} - {end} [{id}] ({username}@{hostname}){NL}"
# show command on debug or dry run
if [ ${DEBUG} -eq 1 ] || [ ${DRYRUN} -eq 1 ]; then
echo "export BORG_BASE_DIR=\"${BASE_FOLDER}\";${BORG_COMMAND} list ${OPT_REMOTE} --format ${FORMAT} ${REPOSITORY}";
echo "export BORG_BASE_DIR=\"${BASE_FOLDER}\";${BORG_COMMAND} check ${OPT_PROGRESS} ${OPT_CHECK_VERIFY_DATA} ${OPT_GLOB} ${REPOSITORY}";
fi;
# run info command if not a dry drun
if [ ${DRYRUN} -eq 0 ]; then
${BORG_COMMAND} list ${OPT_REMOTE} --format "${FORMAT}" ${REPOSITORY} ;
# if glob add glob command directly
if [[ "${CHECK_PREFIX}" =~ $REGEX_GLOB ]]; then
${BORG_COMMAND} check ${OPT_PROGRESS} ${OPT_CHECK_VERIFY_DATA} -a "${CHECK_PREFIX}" ${REPOSITORY};
else
${BORG_COMMAND} check ${OPT_PROGRESS} ${OPT_CHECK_VERIFY_DATA} ${OPT_GLOB} ${REPOSITORY};
fi;
fi;
if [ ${VERBOSE} -eq 1 ]; then
# print additional info for use --repair command
# but only for manual checks
if [ ${VERBOSE} -eq 1 ] && [ ${CHECK} -eq 1 ]; then
echo "";
echo "Base command info:"
echo "export BORG_BASE_DIR=\"${BASE_FOLDER}\";${BORG_COMMAND} [COMMAND] ${OPT_REMOTE} ${REPOSITORY}::[BACKUP] [PATH]";
echo "Replace [COMMAND] with list for listing or extract for restoring backup data."
echo "Replace [BACKUP] with archive name."
echo "If no [PATH] is given then all files will be restored."
echo "Before extracting -n (dry run) is recommended to use."
echo "If archive size is needed the info command with archive name has to be used."
echo "When listing files in an archive set (::SET) the --format command can be used."
echo "Example: \"{mode} {user:6} {group:6} {size:8d} {csize:8d} {dsize:8d} {dcsize:8d} {mtime} {path}{extra} [{health}]{NL}\""
else
echo "export BORG_BASE_DIR=\"${BASE_FOLDER}\";${BORG_COMMAND} [COMMAND] ${OPT_REMOTE} [FORMAT] ${REPOSITORY}::[BACKUP] [PATH]";
echo "In case of needed repair: "
echo "export BORG_BASE_DIR=\"${BASE_FOLDER}\";${BORG_COMMAND} check ${OPT_PROGRESS} --repair ${OPT_GLOB} ${REPOSITORY}";
echo "Before running repair, a copy from the backup should be made because repair might damage a backup"
fi;
. "${DIR}/borg.backup.functions.close.sh";
exit;
fi;
# DELETE ONE TIME TAG
if [ ! -z "${DELETE_ONE_TIME_TAG}" ]; then
echo "--- [DELETE: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
# if a "*" is inside we don't do ONE archive, but globbing via -a option
DELETE_ARCHIVE=""
OPT_GLOB="";
# this is more or less for debug only
if [[ "${DELETE_ONE_TIME_TAG}" =~ $REGEX_GLOB ]]; then
OPT_GLOB="-a '${DELETE_ONE_TIME_TAG}'"
else
DELETE_ARCHIVE="::"${DELETE_ONE_TIME_TAG};
fi
# if this is borg <1.2 OPT_LIST does not work
if [ $(version $BORG_VERSION) -lt $(version "1.2.0") ]; then
OPT_LIST="";
fi;
# if exists, delete and exit
# show command on debug or dry run
if [ ${DEBUG} -eq 1 ]; then
echo "${BORG_COMMAND} delete ${OPT_REMOTE} ${OPT_LIST} -s ${OPT_GLOB} ${REPOSITORY}${DELETE_ARCHIVE}";
fi;
# run delete command if not a dry drun
# NOTE seems to be glob is not working if wrapped into another variable
if [[ "${DELETE_ONE_TIME_TAG}" =~ $REGEX_GLOB ]]; then
${BORG_COMMAND} delete ${OPT_REMOTE} ${OPT_LIST} ${DRY_RUN_STATS} -a "${DELETE_ONE_TIME_TAG}" ${REPOSITORY};
else
${BORG_COMMAND} delete ${OPT_REMOTE} ${OPT_LIST} ${DRY_RUN_STATS} ${REPOSITORY}${DELETE_ARCHIVE};
fi;
# if not a dry run, compact repository after delete
# not that compact only works on borg 1.2
if [ $(version $BORG_VERSION) -ge $(version "1.2.0") ]; then
if [ ${DRYRUN} -eq 0 ]; then
${BORG_COMMAND} compact ${REPOSITORY};
fi;
if [ ${DEBUG} -eq 1 ]; then
echo "${BORG_COMMAND} compact ${REPOSITORY}";
fi;
fi;
. "${DIR}/borg.backup.functions.close.sh";
exit;
fi;
# __END__

View File

@@ -1,14 +1,24 @@
#!/usr/bin/env bash
if [ -z "${MODULE}" ]; then
echo "Script cannot be run on its own";
exit 1;
fi;
# unset borg settings
unset BORG_BASE_DIR BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK BORG_RELOCATED_REPO_ACCESS_IS_OK
# delete lock file if it exists
if [ -f "${BASE_FOLDER}${BACKUP_LOCK_FILE}" ]; then
rm "${BASE_FOLDER}${BACKUP_LOCK_FILE}";
fi;
# error abort without duration and error notice
if [ $# -ge 1 ] && [ $1 -eq 1 ]; then
echo "=== [ERROR: $(date +'%F %T')] ==[${MODULE}]====================================>";
if [ $# -ge 1 ] && [ "$1" = "1" ]; then
printf "${PRINTF_MASTER_BLOCK}" "ERROR" "$(date +'%F %T')" "${MODULE}";
else
# running time calculation
DURATION=$[ $(date +'%s')-$START ];
echo "=== [Run time: $(convert_time ${DURATION})]";
echo "=== [END : $(date +'%F %T')] ==[${MODULE}]====================================>";
printf "${PRINTF_MASTER_BLOCK}" "END" "$(date +'%F %T')" "${MODULE}";
fi;
# __END__

View File

@@ -1,12 +1,17 @@
#!/usr/bin/env bash
if [ -z "${MODULE}" ]; then
echo "Script cannot be run on its own";
exit 1;
fi;
# compact (only if BORG COMPACT is set)
# only for borg 1.2
# reset to normal IFS, so command works here
IFS=${_IFS};
if [ $(version $BORG_VERSION) -ge $(version "1.2.0") ]; then
echo "--- [COMPACT:$(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUB_BLOCK}" "COMPACT" "$(date +'%F %T')" "${MODULE}";
BORG_COMPACT="${BORG_COMMAND} compact -v ${OPT_PROGRESS} ${REPOSITORY}";
if [ ${DEBUG} -eq 1 ]; then
echo "${BORG_COMPACT}";

View File

@@ -1,7 +1,12 @@
#!/usr/bin/env bash
if [ -z "${MODULE}" ]; then
echo "Script cannot be run on its own";
exit 1;
fi;
if [ ${INFO} -eq 1 ]; then
echo "--- [INFO : $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUB_BLOCK}" "INFO" "$(date +'%F %T')" "${MODULE}";
# show command on debug or dry run
if [ ${DEBUG} -eq 1 ] || [ ${DRYRUN} -eq 1 ]; then
echo "export BORG_BASE_DIR=\"${BASE_FOLDER}\";${BORG_COMMAND} info ${OPT_REMOTE} ${REPOSITORY}";

View File

@@ -1,5 +1,10 @@
#!/usr/bin/env bash
if [ -z "${MODULE}" ]; then
echo "Script cannot be run on its own";
exit 1;
fi;
set -ETu #-e -o pipefail
trap cleanup SIGINT SIGTERM ERR
@@ -19,7 +24,7 @@ function version {
}
# version for all general files
VERSION="4.2.3";
VERSION="4.3.0";
# borg version and borg comamnd
BORG_VERSION="";
@@ -30,6 +35,8 @@ _LOG_FOLDER="/var/log/borg.backup/";
LOG_FOLDER="";
# should be there on everything
TEMPDIR="/tmp/";
# HOSTNAME (as set on server)
HOSTNAME=$(hostname);
# creates borg backup based on the include/exclude files
# if base borg folder (backup files) does not exist, it will automatically init it
# base folder
@@ -39,18 +46,25 @@ SETTINGS_FILE="borg.backup.settings";
# include files
INCLUDE_FILE="";
EXCLUDE_FILE="";
# backup folder initialzed check
BACKUP_INIT_CHECK="";
# backup folder initialzed verify
BACKUP_INIT_FILE="";
BACKUP_INIT_DATE="";
# fiel with last check timestamp
BACKUP_CHECK_FILE="";
# one time backup prefix tag, if set will use <tag>.<prefix>-Y-M-DTh:m:s type backup prefix
ONE_TIME_TAG="";
DELETE_ONE_TIME_TAG="";
# debug/verbose
# check command prefix/glob
CHECK_PREFIX="";
# debug/verbose/other flags
VERBOSE=0;
LIST=0;
DEBUG=0;
DRYRUN=0;
INFO=0;
VERIFY=0;
CHECK=0;
CHECK_VERIFY_DATA=0;
INIT=0;
EXIT=0;
PRINT=0;
@@ -73,6 +87,12 @@ PRUNE_DEBUG="";
INIT_REPOSITORY=0;
FOLDER_OK=0;
TMP_EXCLUDE_FILE="";
# printf strings
PRINTF_INFO_STRING="%-21s: %s\n";
PRINTF_MASTER_BLOCK="=== [%-8s: %19s] ==[%s]====================================>\n";
PRINTF_SUB_BLOCK="|-- [%-8s: %19s] --[%s]------------------------------------>\n";
PRINTF_SUBEXT_BLOCK="|-- [%-8s: %s: %19s] --[%s]------------------------------------>\n";
PRINTF_DB_SUB_BLOCK=">>- [%-8s: %s] =======================[%s]====================================>\n";
# opt flags
OPT_VERBOSE="";
OPT_PROGRESS="";
@@ -80,6 +100,7 @@ OPT_LIST="";
OPT_REMOTE="";
OPT_LOG_FOLDER="";
OPT_EXCLUDE="";
OPT_CHECK_VERIFY_DATA="";
# config variables (will be overwritten from .settings file)
TARGET_USER="";
TARGET_HOST="";
@@ -102,9 +123,15 @@ SUB_COMPRESSION_LEVEL="";
# encryption settings
DEFAULT_ENCRYPTION="none";
ENCRYPTION="";
# force check always
DEFAULT_FORCE_CHECK="false";
FORCE_CHECK="";
# force verify always
DEFAULT_FORCE_VERIFY="false";
FORCE_VERIFY="";
FORCE_CHECK=""; # Deprecated name, use FORCE_VERIFY
# default interval is none
DEFAULT_CHECK_INTERVAL="";
CHECK_INTERVAL="";
SUB_CHECK_INTERVAL="";
# backup set names
BACKUP_SET="";
SUB_BACKUP_SET="";
# for database backup only
@@ -165,9 +192,12 @@ function usage()
-T <tag>: create one time stand alone backup prefixed with tag name
-D <tag backup set>: remove a tagged backup set, full name must be given
-b <borg executable>: override default path
-C: run borg check if repository is ok
-y: in combination with -C: add --verify-data
-p <archive prefix|glob>: in combinatio with -C: only check archives with prefix or glob
-P: print list of archives created
-C: check if repository exists, if not abort
-E: exit after check
-V: verify if repository exists, if not abort
-e: exit after verify
-I: init repository (must be run first)
-i: print out only info
-l: list files during backup
@@ -183,7 +213,7 @@ function usage()
}
# set options
while getopts ":c:L:T:D:b:vldniCEIPh" opt; do
while getopts ":c:L:T:D:b:p:vldniCVeIPyh" opt; do
case "${opt}" in
c|config)
BASE_FOLDER=${OPTARG};
@@ -201,17 +231,30 @@ while getopts ":c:L:T:D:b:vldniCEIPh" opt; do
OPT_BORG_EXECUTEABLE=${OPTARG};
;;
C|Check)
# will check if repo is there and abort if not
# will run borg check
# alt modes --repository-only, --archives-only,
# add mode --verify-data
# note that --repair has to be called manually is it might damange backups
CHECK=1;
;;
E|Exit)
# exit after check
y|Verify-Data)
CHECK_VERIFY_DATA=1;
;;
p|prefix-glob)
CHECK_PREFIX=${OPTARG};
;;
V|Verify)
# will verify if repo is there and abort if not
VERIFY=1;
;;
e|exit)
# exit after verify or init (default off)
EXIT=1;
;;
I|Init)
# will check if there is a repo and init it
# previoous this was default
CHECK=1;
VERIFY=1;
INIT=1;
;;
P|Print)
@@ -266,21 +309,21 @@ if [ ! -w "${BASE_FOLDER}" ]; then
fi;
# info -i && -C/-I cannot be run together
if [ ${CHECK} -eq 1 ] || [ ${INIT} -eq 1 ] && [ ${INFO} -eq 1 ]; then
echo "Cannot have -i info option and -C check or -I initialize option at the same time";
if [ ${VERIFY} -eq 1 ] || [ ${INIT} -eq 1 ] && [ ${INFO} -eq 1 ]; then
echo "Cannot have -i info option and -V verify or -I initialize option at the same time";
exit 1;
fi;
# print -P cannot be run with -i/-C/-I together
if [ ${PRINT} -eq 1 ] && ([ ${INIT} -eq 1 ] || [ ${CHECK} -eq 1 ] || [ ${INFO} -eq 1 ]); then
echo "Cannot have -P print option and -i info, -C check or -I initizalize option at the same time";
if [ ${PRINT} -eq 1 ] && ([ ${INIT} -eq 1 ] || [ ${VERIFY} -eq 1 ] || [ ${INFO} -eq 1 ]); then
echo "Cannot have -P print option and -i info, -V verify or -I initizalize option at the same time";
exit 1;
fi;
# if tag is set, you can't have init, check, info, etc
if [ ! -z "${ONE_TIME_TAG}" ] && ([ ${PRINT} -eq 1 ] || [ ${INIT} -eq 1 ] || [ ${CHECK} -eq 1 ] || [ ${INFO} -eq 1 ]); then
echo "Cannot have -T '${ONE_TIME_TAG}' option with -i info, -C check, -I initialize or -P print option at the same time";
# if tag is set, you can't have init, verify, info, etc
if [ ! -z "${ONE_TIME_TAG}" ] && ([ ${PRINT} -eq 1 ] || [ ${INIT} -eq 1 ] || [ ${VERIFY} -eq 1 ] || [ ${INFO} -eq 1 ]); then
echo "Cannot have -T '${ONE_TIME_TAG}' option with -i info, -V verify, -I initialize or -P print option at the same time";
exit 1;
fi;
# check only alphanumeric, no spaces, only underscore and dash
# verify only alphanumeric, no spaces, only underscore and dash
if [ ! -z "${ONE_TIME_TAG}" ] && ! [[ "${ONE_TIME_TAG}" =~ ^[A-Za-z0-9_-]+$ ]]; then
echo "One time tag '${ONE_TIME_TAG}' must be alphanumeric with dashes and underscore only.";
exit 1;
@@ -289,18 +332,34 @@ elif [ ! -z "${ONE_TIME_TAG}" ]; then
ONE_TIME_TAG=${ONE_TIME_TAG}".";
fi;
# if -D, cannot be with -T, -i, -C, -I, -P
if [ ! -z "${DELETE_ONE_TIME_TAG}" ] && ([ ! -z "${ONE_TIME_TAG}" ] || [ ${PRINT} -eq 1 ] || [ ${INIT} -eq 1 ] || [ ${CHECK} -eq 1 ] || [ ${INFO} -eq 1 ]); then
echo "Cannot have -D delete tag option with -T one time tag, -i info, -C check, -I initialize or -P print option at the same time";
if [ ! -z "${DELETE_ONE_TIME_TAG}" ] && ([ ! -z "${ONE_TIME_TAG}" ] || [ ${PRINT} -eq 1 ] || [ ${INIT} -eq 1 ] || [ ${VERIFY} -eq 1 ] || [ ${INFO} -eq 1 ]); then
echo "Cannot have -D delete tag option with -T one time tag, -i info, -V verify, -I initialize or -P print option at the same time";
exit 1;
fi;
# -D also must be in valid backup set format
# ! [[ "${DELETE_ONE_TIME_TAG}" =~ ^[A-Za-z0-9_-]+\.${MODULE},(\*-)?[0-9]{4}-[0-9]{2}-[0-9]{2}T\*$ ]]
if [ ! -z "${DELETE_ONE_TIME_TAG}" ] && ! [[ "${DELETE_ONE_TIME_TAG}" =~ ^[A-Za-z0-9_-]+\.${MODULE},([A-Za-z0-9_-]+-)?[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$ ]] && ! [[ "${DELETE_ONE_TIME_TAG}" =~ ^[A-Za-z0-9_-]+\.${MODULE},(\*-)?[0-9]{4}-[0-9]{2}-[0-9]{2}T\*$ ]]; then
echo "Delete one time tag '${DELETE_ONE_TIME_TAG}' is in an invalid format. Please check existing tags with -P option."
echo "Delete one time tag '${DELETE_ONE_TIME_TAG}' is in an invalid format. "
echo "Please verify existing tags with -P option."
echo "For a globing be sure it is in the format of: TAG.MODULE,*-YYYY-MM-DDT*";
echo "Note the dash (-) after the first *, also time (T) is a globa (*) must."
exit 1;
fi;
# -y can't be set without -C
if [ ${CHECK_VERIFY_DATA} -eq 1 ] && [ ${CHECK} -eq 0 ]; then
echo "-y (verify-data) cannot be run without -C (Check) option";
exit 1;
fi;
# -p can't be set without -C
if [ ! -z "${CHECK_PREFIX}" ] && [ ${CHECK} -eq 0 ]; then
echo "-p (pattern|glob) for check cannot be run without -C (Check) options";
exit 1;
fi;
# can't have -e if VERIFY or INIT is not set
if [ ${EXIT} -eq 1 ] && [ ${VERIFY} -eq 0 ] && [ ${INIT} -eq 0 ]; then
echo "-e (exit) can only be used with -V (Verify) and -I (Init)";
exit 1;
fi;
# verbose & progress
if [ ${VERBOSE} -eq 1 ]; then
@@ -317,6 +376,9 @@ if [ ${DRYRUN} -eq 1 ]; then
else
DRY_RUN_STATS="-s";
fi;
if [ ${CHECK_VERIFY_DATA} -eq 1 ] && [ ${CHECK} -eq 1 ]; then
OPT_CHECK_VERIFY_DATA="--verify-data";
fi;
# read config file
. "${BASE_FOLDER}${SETTINGS_FILE}";
@@ -336,12 +398,12 @@ elif [ ! -z "${BORG_EXECUTEABLE}" ]; then
exit;
fi;
elif ! command -v borg &> /dev/null; then
echo "borg backup seems not to be installed, please check paths";
echo "borg backup seems not to be installed, please verify paths";
exit;
fi;
# check that this is a borg executable, no detail check
_BORG_COMMAND_CHECK=$(${BORG_COMMAND} -V | grep "borg");
if [[ "${_BORG_COMMAND_CHECK}" =~ ${REGEX_ERROR} ]]; then
# verify that this is a borg executable, no detail check
_BORG_COMMAND_VERIFY=$(${BORG_COMMAND} -V | grep "borg");
if [[ "${_BORG_COMMAND_VERIFY}" =~ ${REGEX_ERROR} ]]; then
echo "Cannot extract borg info from command, is this a valid borg executable?: ${BORG_COMMAND}";
exit;
fi;
@@ -360,8 +422,15 @@ fi;
if [ -z "${ENCRYPTION}" ]; then
ENCRYPTION="${DEFAULT_ENCRYPTION}";
fi;
if [ -z "${FORCE_CHECK}" ]; then
FORCE_CHECK="${DEFAULT_FORCE_CHECK}";
if [ -z "${CHECK_INTERVAL}" ]; then
CHECK_INTERVAL="${DEFAULT_CHECK_INTERVAL}";
fi;
# deprecated name FORCE_CHECK, use FORCE_VERIFY instead
if [ ! -z "${FORCE_CHECK}" ]; then
FORCE_VERIFY="${FORCE_CHECK}";
fi;
if [ -z "${FORCE_VERIFY}" ]; then
FORCE_VERIFY="${DEFAULT_FORCE_VERIFY}";
fi;
if [ -z "${KEEP_LAST}" ]; then
KEEP_LAST="${DEFAULT_KEEP_LAST}";
@@ -402,6 +471,10 @@ if [ -f "${BASE_FOLDER}${SETTINGS_FILE_SUB}" ]; then
if [ ! -z "${SUB_COMPRESSION_LEVEL}" ]; then
COMPRESSION_LEVEL=${SUB_COMPRESSION_LEVEL};
fi;
# override check interval
if [ ! -z "${SUB_CHECK_INTERVAL}" ]; then
CHECK_INTERVAL="${SUB_CHECK_INTERVAL}";
fi;
# check override for keep time
if [ ! -z "${SUB_KEEP_LAST}" ]; then
KEEP_LAST=${SUB_KEEP_LAST};
@@ -464,6 +537,8 @@ if [[ ${TARGET_FOLDER} =~ ^~\/ ]]; then
exit 1;
fi
# CHECK_INTERVAL must be a number from -1 to 365
# log file set and check
# option folder overrides all other folders
if [ ! -z "${OPT_LOG_FOLDER}" ]; then

View File

@@ -0,0 +1,428 @@
#!/usr/bin/env bash
if [ -z "${MODULE}" ]; then
echo "Script cannot be run on its own";
exit 1;
fi;
# start time in seconds
START=$(date +'%s');
# set init date, or today if not file is set
BACKUP_INIT_DATE='';
if [ -f "${BASE_FOLDER}${BACKUP_INIT_FILE}" ]; then
BACKUP_INIT_DATE=$(printf '%(%c)T' $(cat "${BASE_FOLDER}${BACKUP_INIT_FILE}" 2>/dev/null));
fi;
# last check date if set
BACKUP_LAST_CHECK_DATE='';
LAST_CHECK_DATE='';
CONVERT_TIME='';
if [ -f "${BASE_FOLDER}${BACKUP_CHECK_FILE}" ]; then
LAST_CHECK_DATE=$(cat "${BASE_FOLDER}${BACKUP_CHECK_FILE}" 2>/dev/null);
BACKUP_LAST_CHECK_DATE=$(printf '%(%c)T' ${LAST_CHECK_DATE});
CONVERT_TIME=$(convert_time $(($(date +%s)-${LAST_CHECK_DATE})));
fi;
# start logging from here
exec &> >(tee -a "${LOG}");
printf "${PRINTF_MASTER_BLOCK}" "START" "$(date +'%F %T')" "${MODULE}";
# show info for version always
printf "${PRINTF_INFO_STRING}" "Script version" "${VERSION}";
# show type
printf "${PRINTF_INFO_STRING}" "Backup module" "${MODULE}";
printf "${PRINTF_INFO_STRING}" "Module version" "${MODULE_VERSION}";
# borg version
printf "${PRINTF_INFO_STRING}" "Borg version" "${BORG_VERSION}";
# host name
printf "${PRINTF_INFO_STRING}" "Hostname" "${HOSTNAME}";
# show base folder always
printf "${PRINTF_INFO_STRING}" "Base folder" "${BASE_FOLDER}";
# Module init date (when init file was writen)
printf "${PRINTF_INFO_STRING}" "Module init date" "${BACKUP_INIT_DATE}";
# print last check date if positive integer
if [ "${CHECK_INTERVAL##*[!0-9]*}" ]; then
printf "${PRINTF_INFO_STRING}" "Module check interval" "${CHECK_INTERVAL}";
printf "${PRINTF_INFO_STRING}" "Module last check" "${BACKUP_LAST_CHECK_DATE} (${CONVERT_TIME} ago)";
fi;
# if force verify is true set VERIFY to 1 unless INFO is 1
# Needs bash 4.0 at lesat for this
if [ "${FORCE_VERIFY,,}" = "true" ] && [ ${INFO} -eq 0 ]; then
VERIFY=1;
if [ ${DEBUG} -eq 1 ]; then
echo "Force repository verify";
fi;
fi;
# remote borg path
if [ ! -z "${TARGET_BORG_PATH}" ]; then
if [[ "${TARGET_BORG_PATH}" =~ \ |\' ]]; then
echo "Space found in ${TARGET_BORG_PATH}. Aborting";
echo "There are issues with passing on paths with spaces"
echo "as parameters"
exit;
fi;
OPT_REMOTE="--remote-path="$(printf "%q" "${TARGET_BORG_PATH}");
fi;
if [ -z "${TARGET_FOLDER}" ]; then
echo "[! $(date +'%F %T')] No target folder has been set yet";
exit 1;
else
# There are big issues with TARGET FOLDERS with spaces
# we should abort anything with this
if [[ "${TARGET_FOLDER}" =~ \ |\' ]]; then
echo "Space found in ${TARGET_FOLDER}. Aborting";
echo "There is some problem with passing paths with spaces as";
echo "repository base folder"
exit;
fi;
# This does not care for multiple trailing or leading slashes
# it just makes sure we have at least one set
# for if we have a single slash, remove it
TARGET_FOLDER=${TARGET_FOLDER%/}
TARGET_FOLDER=${TARGET_FOLDER#/}
# and add slash front and back and escape the path
TARGET_FOLDER=$(printf "%q" "/${TARGET_FOLDER}/");
fi;
# if we have user/host then we build the ssh command
TARGET_SERVER='';
# allow host only (if full setup in .ssh/config)
# user@host OR ssh://user@host:port/ IF TARGET_PORT is set
# user/host/port
if [ ! -z "${TARGET_USER}" ] && [ ! -z "${TARGET_HOST}" ] && [ ! -z "${TARGET_PORT}" ]; then
TARGET_SERVER="ssh://${TARGET_USER}@${TARGET_HOST}:${TARGET_PORT}/";
# host/port
elif [ ! -z "${TARGET_HOST}" ] && [ ! -z "${TARGET_PORT}" ]; then
TARGET_SERVER="ssh://${TARGET_HOST}:${TARGET_PORT}/";
# user/host
elif [ ! -z "${TARGET_USER}" ] && [ ! -z "${TARGET_HOST}" ]; then
TARGET_SERVER="${TARGET_USER}@${TARGET_HOST}:";
# host
elif [ ! -z "${TARGET_HOST}" ]; then
TARGET_SERVER="${TARGET_HOST}:";
fi;
# we dont allow special characters, so we don't need to special escape it
REPOSITORY="${TARGET_SERVER}${TARGET_FOLDER}${BACKUP_FILE}";
printf "${PRINTF_INFO_STRING}" "Repository" "${REPOSITORY}";
# check if given compression name and level are valid
OPT_COMPRESSION='';
if [ ! -z "${COMPRESSION}" ]; then
# valid compression
if [ "${COMPRESSION}" = "lz4" ] || [ "${COMPRESSION}" = "zlib" ] || [ "${COMPRESSION}" = "lzma" ] || [ "${COMPRESSION}" = "zstd" ]; then
OPT_COMPRESSION="-C=${COMPRESSION}";
# if COMPRESSION_LEVEL, check it is a valid regex
# for zlib, zstd, lzma
if [ ! -z "${COMPRESSION_LEVEL}" ] && ([ "${COMPRESSION}" = "zlib" ] || [ "${COMPRESSION}" = "lzma" ] || [ "${COMPRESSION}" = "zstd" ]); then
MIN_COMPRESSION=0;
MAX_COMPRESSION=0;
case "${COMPRESSION}" in
zlib|lzma)
MIN_COMPRESSION=0;
MAX_COMPRESSION=9;
;;
zstd)
MIN_COMPRESSION=1;
MAX_COMPRESSION=22;
;;
*)
MIN_COMPRESSION=0;
MAX_COMPRESSION=0;
;;
esac;
# if [ "${COMPRESSION}" = "zlib" ] || [ "${COMPRESSION}" = "lzma" ]
# MIN_COMPRESSION=0;
# MAX_COMPRESSION=9;
# elif [ "${COMPRESSION}" = "zstd" ]; then
# MIN_COMPRESSION=1;
# MAX_COMPRESSION=22;
# fi;
error_message="[! $(date +'%F %T')] Compression level for ${COMPRESSION} needs to be a numeric value between ${MIN_COMPRESSION} and ${MAX_COMPRESSION}: ${COMPRESSION_LEVEL}";
if ! [[ "${COMPRESSION_LEVEL}" =~ ${REGEX_NUMERIC} ]]; then
echo ${error_message};
exit 1;
elif [ ${COMPRESSION_LEVEL} -lt ${MIN_COMPRESSION} ] || [ ${COMPRESSION_LEVEL} -gt ${MAX_COMPRESSION} ]; then
echo ${error_message};
exit 1;
else
OPT_COMPRESSION=${OPT_COMPRESSION}","${COMPRESSION_LEVEL};
fi;
fi;
else
echo "[! $(date +'%F %T')] Compress setting need to be lz4, zstd, zlib or lzma. Or empty for no compression: ${COMPRESSION}";
exit 1;
fi;
fi;
# home folder, needs to be set if there is eg a HOME=/ in the crontab
if [ ! -w "${HOME}" ] || [ "${HOME}" = '/' ]; then
HOME=$(eval echo "$(whoami)");
fi;
# keep optionfs (for files)
KEEP_OPTIONS=();
# keep info string (for files)
KEEP_INFO="";
# override standard keep for tagged backups
if [ ! -z "${ONE_TIME_TAG}" ]; then
BACKUP_SET="{now:%Y-%m-%dT%H:%M:%S}";
# set empty to avoid problems
KEEP_OPTIONS=("");
else
# build options and info string,
# also flag BACKUP_SET check if hourly is set
BACKUP_SET_VERIFY=0;
if [ ${KEEP_LAST} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-last=${KEEP_LAST}");
KEEP_INFO="${KEEP_INFO}, last: ${KEEP_LAST}";
fi;
if [ ${KEEP_HOURS} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-hourly=${KEEP_HOURS}");
KEEP_INFO="${KEEP_INFO}, hourly: ${KEEP_HOURS}";
BACKUP_SET_VERIFY=1;
fi;
if [ ${KEEP_DAYS} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-daily=${KEEP_DAYS}");
KEEP_INFO="${KEEP_INFO}, daily: ${KEEP_DAYS}";
fi;
if [ ${KEEP_WEEKS} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-weekly=${KEEP_WEEKS}");
KEEP_INFO="${KEEP_INFO}, weekly: ${KEEP_WEEKS}";
fi;
if [ ${KEEP_MONTHS} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-monthly=${KEEP_MONTHS}");
KEEP_INFO="${KEEP_INFO}, monthly: ${KEEP_MONTHS}";
fi;
if [ ${KEEP_YEARS} -gt 0 ]; then
KEEP_OPTIONS+=("--keep-yearly=${KEEP_YEARS}");
KEEP_INFO="${KEEP_INFO}, yearly: ${KEEP_YEARS}";
fi;
if [ ! -z "${KEEP_WITHIN}" ]; then
# check for invalid string. can only be number + H|d|w|m|y
if [[ "${KEEP_WITHIN}" =~ ^[0-9]+[Hdwmy]{1}$ ]]; then
KEEP_OPTIONS+=("--keep-within=${KEEP_WITHIN}");
KEEP_INFO="${KEEP_INFO}, within: ${KEEP_WITHIN}";
if [[ "${KEEP_WITHIN}" == *"H"* ]]; then
BACKUP_SET_VERIFY=1;
fi;
else
echo "[! $(date +'%F %T')] KEEP_WITHIN has invalid string.";
exit 1;
fi;
fi;
# abort if KEEP_OPTIONS is empty
if [ "${#KEEP_OPTIONS[@]}" -eq "0" ]; then
echo "[! $(date +'%F %T')] It seems no KEEP_* entries where set in a valid format.";
exit 1;
fi;
# set BACKUP_SET if empty, set to Year-month-day
if [ -z "${BACKUP_SET}" ]; then
BACKUP_SET="{now:%Y-%m-%d}";
fi;
# backup set check, and there is no hour entry (%H) in the archive string
# we add T%H:%M:%S in this case, before the last }
if [ ${BACKUP_SET_VERIFY} -eq 1 ] && [[ "${BACKUP_SET}" != *"%H"* ]]; then
BACKUP_SET=$(echo "${BACKUP_SET}" | sed -e "s/}/T%H:%M:%S}/");
fi;
fi;
# check if we have lock file, check pid in lock file, if no matching pid found
# running remove lock file
if [ -f "${BASE_FOLDER}${BACKUP_LOCK_FILE}" ]; then
LOCK_PID=$(cat "${BASE_FOLDER}${BACKUP_LOCK_FILE}" 2>/dev/null);
# check if lock file pid has an active program attached to it
if [ -f /proc/${LOCK_PID}/cmdline ]; then
echo "Script is already running on PID: ${$}";
. "${DIR}/borg.backup.functions.close.sh" 1;
exit 1;
else
echo "[#] Clean up stale lock file for PID: ${LOCK_PID}";
rm "${BASE_FOLDER}${BACKUP_LOCK_FILE}";
fi;
fi;
echo "${$}" > "${BASE_FOLDER}${BACKUP_LOCK_FILE}";
# for folders list split set to "#" and keep the old setting as is
_IFS=${IFS};
IFS="#";
# turn off for non file
if [ "${MODULE}" != "file" ]; then
IFS=${_IFS};
fi;
# borg call, replace ##...## parts during run
# used in all modules, except 'file'
_BORG_CALL="${BORG_COMMAND} create ${OPT_REMOTE} -v ${OPT_LIST} ${OPT_PROGRESS} ${OPT_COMPRESSION} -s --stdin-name ##FILENAME## ${REPOSITORY}::##BACKUP_SET## -";
_BORG_PRUNE="${BORG_COMMAND} prune ${OPT_REMOTE} -v --list ${OPT_PROGRESS} ${DRY_RUN_STATS} -P ##BACKUP_SET_PREFIX## ${KEEP_OPTIONS[*]} ${REPOSITORY}";
# general borg settings
# set base path to config directory to keep cache/config separated
export BORG_BASE_DIR="${BASE_FOLDER}";
# ignore non encrypted access
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=${_BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK};
# ignore moved repo access
export BORG_RELOCATED_REPO_ACCESS_IS_OK=${_BORG_RELOCATED_REPO_ACCESS_IS_OK};
# and for debug print that tout
if [ ${DEBUG} -eq 1 ]; then
echo "export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=${_BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK};";
echo "export BORG_RELOCATED_REPO_ACCESS_IS_OK=${_BORG_RELOCATED_REPO_ACCESS_IS_OK};";
echo "export BORG_BASE_DIR=\"${BASE_FOLDER}\";";
fi;
# prepare debug commands only
COMMAND_EXPORT="export BORG_BASE_DIR=\"${BASE_FOLDER}\";"
COMMAND_INFO="${COMMAND_EXPORT}${BORG_COMMAND} info ${OPT_REMOTE} ${REPOSITORY}";
# if the is not there, call init to create it
# if this is user@host, we need to use ssh command to verify if the file is there
# else a normal verify is ok
# unless explicit given, verify is skipped
if [ ${VERIFY} -eq 1 ] || [ ${INIT} -eq 1 ]; then
printf "${PRINTF_SUB_BLOCK}" "VERIFY" "$(date +'%F %T')" "${MODULE}";
if [ ! -z "${TARGET_SERVER}" ]; then
if [ ${DEBUG} -eq 1 ]; then
echo "${BORG_COMMAND} info ${OPT_REMOTE} ${REPOSITORY} 2>&1|grep \"Repository ID:\"";
fi;
# use borg info and verify if it returns "Repository ID:" in the first line
REPO_VERIFY=$(${BORG_COMMAND} info ${OPT_REMOTE} ${REPOSITORY} 2>&1|grep "Repository ID:");
# this is currently a hack to work round the error code in borg info
# this checks if REPO_VERIFY holds this error message and then starts init
if [[ -z "${REPO_VERIFY}" ]] || [[ "${REPO_VERIFY}" =~ ${REGEX_ERROR} ]]; then
INIT_REPOSITORY=1;
fi;
elif [ ! -d "${REPOSITORY}" ]; then
INIT_REPOSITORY=1;
fi;
# if verrify but no init and repo is there but init file is missing set it
if [ ${VERIFY} -eq 1 ] && [ ${INIT} -eq 0 ] && [ ${INIT_REPOSITORY} -eq 0 ] &&
[ ! -f "${BASE_FOLDER}${BACKUP_INIT_FILE}" ]; then
# write init file
echo "[!] Add missing init verify file";
echo "$(date +%s)" > "${BASE_FOLDER}${BACKUP_INIT_FILE}";
fi;
# end if verified but repository is not here
if [ ${VERIFY} -eq 1 ] && [ ${INIT} -eq 0 ] && [ ${INIT_REPOSITORY} -eq 1 ]; then
echo "[! $(date +'%F %T')] No repository. Please run with -I flag to initialze repository";
. "${DIR}/borg.backup.functions.close.sh" 1;
exit 1;
fi;
if [ ${EXIT} -eq 1 ] && [ ${VERIFY} -eq 1 ] && [ ${INIT} -eq 0 ]; then
echo "Repository exists";
echo "For more information run:"
echo "${COMMAND_INFO}";
. "${DIR}/borg.backup.functions.close.sh";
exit;
fi;
fi;
if [ ${INIT} -eq 1 ] && [ ${INIT_REPOSITORY} -eq 1 ]; then
printf "${PRINTF_SUB_BLOCK}" "INIT" "$(date +'%F %T')" "${MODULE}";
if [ ${DEBUG} -eq 1 ] || [ ${DRYRUN} -eq 1 ]; then
echo "${BORG_COMMAND} init ${OPT_REMOTE} -e ${ENCRYPTION} ${OPT_VERBOSE} ${REPOSITORY}";
fi
if [ ${DRYRUN} -eq 0 ]; then
# should trap and exit properly here
${BORG_COMMAND} init ${OPT_REMOTE} -e ${ENCRYPTION} ${OPT_VERBOSE} ${REPOSITORY};
# write init file
echo "$(date +%s)" > "${BASE_FOLDER}${BACKUP_INIT_FILE}";
echo "Repository initialized";
echo "For more information run:"
echo "${COMMAND_INFO}";
fi
. "${DIR}/borg.backup.functions.close.sh";
# exit after init
exit;
elif [ ${INIT} -eq 1 ] && [ ${INIT_REPOSITORY} -eq 0 ]; then
echo "[! $(date +'%F %T')] Repository already initialized";
echo "For more information run:"
echo "${COMMAND_INFO}";
. "${DIR}/borg.backup.functions.close.sh" 1;
exit 1;
fi;
# verify for init file
if [ ! -f "${BASE_FOLDER}${BACKUP_INIT_FILE}" ]; then
echo "[! $(date +'%F %T')] It seems the repository has never been initialized."
echo "Please run -I to initialize or if already initialzed run with -C for init update."
. "${DIR}/borg.backup.functions.close.sh" 1;
exit 1;
fi;
# PRINT OUT current data, only do this if REPO exists
if [ ${PRINT} -eq 1 ]; then
printf "${PRINTF_SUB_BLOCK}" "PRINT" "$(date +'%F %T')" "${MODULE}";
FORMAT="{archive:<45} {comment:6} {start} - {end} [{id}] ({username}@{hostname}){NL}"
# show command on debug or dry run
if [ ${DEBUG} -eq 1 ] || [ ${DRYRUN} -eq 1 ]; then
echo "export BORG_BASE_DIR=\"${BASE_FOLDER}\";${BORG_COMMAND} list ${OPT_REMOTE} --format ${FORMAT} ${REPOSITORY}";
fi;
# run info command if not a dry drun
if [ ${DRYRUN} -eq 0 ]; then
${BORG_COMMAND} list ${OPT_REMOTE} --format "${FORMAT}" ${REPOSITORY} ;
fi;
if [ ${VERBOSE} -eq 1 ]; then
echo "";
echo "Base command info:"
echo "export BORG_BASE_DIR=\"${BASE_FOLDER}\";${BORG_COMMAND} [COMMAND] ${OPT_REMOTE} ${REPOSITORY}::[BACKUP] [PATH]";
echo "Replace [COMMAND] with list for listing or extract for restoring backup data."
echo "Replace [BACKUP] with archive name."
echo "If no [PATH] is given then all files will be restored."
echo "Before extracting -n (dry run) is recommended to use."
echo "If archive size is needed the info command with archive name has to be used."
echo "When listing files in an archive set (::SET) the --format command can be used."
echo "Example: \"{mode} {user:6} {group:6} {size:8d} {csize:8d} {dsize:8d} {dcsize:8d} {mtime} {path}{extra} [{health}]{NL}\""
else
echo "export BORG_BASE_DIR=\"${BASE_FOLDER}\";${BORG_COMMAND} [COMMAND] ${OPT_REMOTE} [FORMAT] ${REPOSITORY}::[BACKUP] [PATH]";
fi;
. "${DIR}/borg.backup.functions.close.sh";
exit;
fi;
# run borg check command and exit
if [ ${CHECK} -eq 1 ]; then
. "${DIR}/borg.backup.functions.check.sh";
. "${DIR}/borg.backup.functions.close.sh";
exit;
fi;
# DELETE ONE TIME TAG
if [ ! -z "${DELETE_ONE_TIME_TAG}" ]; then
printf "${PRINTF_SUB_BLOCK}" "DELETE" "$(date +'%F %T')" "${MODULE}";
# if a "*" is inside we don't do ONE archive, but globbing via -a option
DELETE_ARCHIVE=""
OPT_GLOB="";
# this is more or less for debug only
if [[ "${DELETE_ONE_TIME_TAG}" =~ $REGEX_GLOB ]]; then
OPT_GLOB="-a '${DELETE_ONE_TIME_TAG}'"
else
DELETE_ARCHIVE="::"${DELETE_ONE_TIME_TAG};
fi
# if this is borg <1.2 OPT_LIST does not work
if [ $(version $BORG_VERSION) -lt $(version "1.2.0") ]; then
OPT_LIST="";
fi;
# if exists, delete and exit
# show command on debug or dry run
if [ ${DEBUG} -eq 1 ]; then
echo "${BORG_COMMAND} delete ${OPT_REMOTE} ${OPT_LIST} -s ${OPT_GLOB} ${REPOSITORY}${DELETE_ARCHIVE}";
fi;
# run delete command if not a dry drun
# NOTE seems to be glob is not working if wrapped into another variable
if [[ "${DELETE_ONE_TIME_TAG}" =~ $REGEX_GLOB ]]; then
${BORG_COMMAND} delete ${OPT_REMOTE} ${OPT_LIST} ${DRY_RUN_STATS} -a "${DELETE_ONE_TIME_TAG}" ${REPOSITORY};
else
${BORG_COMMAND} delete ${OPT_REMOTE} ${OPT_LIST} ${DRY_RUN_STATS} ${REPOSITORY}${DELETE_ARCHIVE};
fi;
# if not a dry run, compact repository after delete
# not that compact only works on borg 1.2
if [ $(version $BORG_VERSION) -ge $(version "1.2.0") ]; then
if [ ${DRYRUN} -eq 0 ]; then
${BORG_COMMAND} compact ${REPOSITORY};
fi;
if [ ${DEBUG} -eq 1 ]; then
echo "${BORG_COMMAND} compact ${REPOSITORY}";
fi;
fi;
. "${DIR}/borg.backup.functions.close.sh";
exit;
fi;
# __END__

View File

@@ -1,18 +1,23 @@
#!/usr/bin/env bash
# Backup gitea database, all git folders and gitea settings
MODULE="gitea"
MODULE_VERSION="1.1.1";
MODULE_VERSION="1.1.2";
DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
# init system
. "${DIR}/borg.backup.functions.init.sh";
# init check file
BACKUP_INIT_CHECK="borg.backup.gitea.init";
# init verify and check file
BACKUP_INIT_FILE="borg.backup.${MODULE}.init";
BACKUP_CHECK_FILE="borg.backup.${MODULE}.check";
# lock file
BACKUP_LOCK_FILE="borg.backup.${MODULE}.lock";
# check valid data
. "${DIR}/borg.backup.functions.check.sh";
# verify valid data
. "${DIR}/borg.backup.functions.verify.sh";
# if info print info and then abort run
. "${DIR}/borg.backup.functions.info.sh";
@@ -52,7 +57,7 @@ BACKUP_SET_NAME="${ONE_TIME_TAG}${BACKUP_SET_PREFIX}${BACKUP_SET}";
# borg call
BORG_CALL=$(echo "${_BORG_CALL}" | sed -e "s/##FILENAME##/${FILENAME}/" | sed -e "s/##BACKUP_SET##/${BACKUP_SET_NAME}/");
BORG_PRUNE=$(echo "${_BORG_PRUNE}" | sed -e "s/##BACKUP_SET_PREFIX##/${BACKUP_SET_PREFIX}/");
echo "--- [git data and database: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUB_BLOCK}" "BACKUP: git data and database" "$(date +'%F %T')" "${MODULE}";
if [ ${DEBUG} -eq 1 ] || [ ${DRYRUN} -eq 1 ]; then
echo "sudo -u ${GIT_USER} ${GITEA_BIN} dump -c ${GITEA_CONFIG} -w ${GITEA_TMP} -L -f - | ${BORG_CALL}";
if [ -z "${ONE_TIME_TAG}" ]; then
@@ -74,11 +79,13 @@ if [ ${DRYRUN} -eq 0 ]; then
) | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g' # remove all ESC strings
fi;
if [ -z "${ONE_TIME_TAG}" ]; then
echo "--- [PRUNE : $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUB_BLOCK}" "PRUNE" "$(date +'%F %T')" "${MODULE}";
echo "Prune repository with keep${KEEP_INFO:1}";
${BORG_PRUNE};
# if this is borg version >1.2 we need to run compact after prune
. "${DIR}/borg.backup.functions.compact.sh";
# check in auto mode
. "${DIR}/borg.backup.functions.check.sh" "auto";
fi;
. "${DIR}/borg.backup.functions.close.sh";

View File

@@ -5,12 +5,9 @@
# config override set in borg.backup.mysql.settings
# if run as mysql user, be sure user is in the backup group
# Run -I first to initialize repository
# There are no automatic repository checks unless -C is given
# set last edit date + time
MODULE="mysql"
MODULE_VERSION="1.1.0";
MODULE_VERSION="1.1.1";
DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
@@ -18,14 +15,17 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
. "${DIR}/borg.backup.functions.init.sh";
# include and exclude file
INCLUDE_FILE="borg.backup.mysql.include";
EXCLUDE_FILE="borg.backup.mysql.exclude";
SCHEMA_ONLY_FILE="borg.backup.mysql.schema-only";
# init check file
BACKUP_INIT_CHECK="borg.backup.mysql.init";
INCLUDE_FILE="borg.backup.${MODULE}.include";
EXCLUDE_FILE="borg.backup.${MODULE}.exclude";
SCHEMA_ONLY_FILE="borg.backup.${MODULE}.schema-only";
# init verify and check file
BACKUP_INIT_FILE="borg.backup.${MODULE}.init";
BACKUP_CHECK_FILE="borg.backup.${MODULE}.check";
# lock file
BACKUP_LOCK_FILE="borg.backup.${MODULE}.lock";
# check valid data
. "${DIR}/borg.backup.functions.check.sh";
# verify valid data
. "${DIR}/borg.backup.functions.verify.sh";
# if info print info and then abort run
. "${DIR}/borg.backup.functions.info.sh";
@@ -50,10 +50,10 @@ if [ ! -f "${MYSQL_CMD}" ]; then
. "${DIR}/borg.backup.functions.close.sh" 1;
exit 1;
fi;
# check that the user can actually do, else abort here
# verify that the user can actually do, else abort here
# note: this is the only way to not error
_MYSQL_CHECK=$(mysqladmin ${MYSQL_DB_CONFIG_PARAM} ping 2>&1);
_MYSQL_OK=$(echo "${_MYSQL_CHECK}" | grep "is alive");
_MYSQL_VERIFY=$(mysqladmin ${MYSQL_DB_CONFIG_PARAM} ping 2>&1);
_MYSQL_OK=$(echo "${_MYSQL_VERIFY}" | grep "is alive");
if [ -z "${_MYSQL_OK}" ]; then
echo "[! $(date +'%F %T')] Current user has no access right to mysql database";
. "${DIR}/borg.backup.functions.close.sh" 1;
@@ -92,7 +92,7 @@ if [ ! -z "${DATABASE_FULL_DUMP}" ]; then
SCHEMA_ONLY='--no-data';
schema_flag='schema';
fi;
echo "---[BACKUP: all databases: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUBEXT_BLOCK}" "BACKUP" "all databases" "$(date +'%F %T')" "${MODULE}";
# We only do a full backup and not per table backup here
# Filename
FILENAME="all-${schema_flag}-${DB_TYPE}_${DB_VERSION}_${DB_HOST}_${DB_PORT}.sql"
@@ -119,15 +119,15 @@ if [ ! -z "${DATABASE_FULL_DUMP}" ]; then
fi;
fi;
if [ -z "${ONE_TIME_TAG}" ]; then
echo "--- [PRUNE : all databases: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUBEXT_BLOCK}" "PRUNE" "all databases" "$(date +'%F %T')" "${MODULE}";
echo "Prune repository with keep${KEEP_INFO:1}";
${BORG_PRUNE};
fi;
else
${MYSQL_CMD} ${MYSQL_DB_CONFIG_PARAM} -B -N -e "show databases" |
while read db; do
echo "========[DB: ${db}]========================[${MODULE}]====================================>";
echo "--- [BACKUP: ${db}: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_DB_SUB_BLOCK}" "DB" "${db}" "${MODULE}";
printf "${PRINTF_SUBEXT_BLOCK}" "BACKUP" "${db}" "$(date +'%F %T')" "${MODULE}";
# exclude checks
include=0;
if [ -s "${BASE_FOLDER}${INCLUDE_FILE}" ]; then
@@ -205,7 +205,7 @@ else
fi;
fi;
if [ -z "${ONE_TIME_TAG}" ]; then
echo "--- [PRUNE : ${db}: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUBEXT_BLOCK}" "PRUNE" "${db}" "$(date +'%F %T')" "${MODULE}";
echo "Prune repository prefixed ${BACKUP_SET_PREFIX} with keep${KEEP_INFO:1}";
${BORG_PRUNE};
fi;
@@ -218,6 +218,8 @@ fi;
if [ -z "${ONE_TIME_TAG}" ]; then
# if this is borg version >1.2 we need to run compact after prune
. "${DIR}/borg.backup.functions.compact.sh";
# check in auto mode
. "${DIR}/borg.backup.functions.check.sh" "auto";
fi;
. "${DIR}/borg.backup.functions.close.sh";

View File

@@ -5,12 +5,9 @@
# config override set in borg.backup.pgsql.settings
# if run as postgres user, be sure user is in the backup group
# Run -I first to initialize repository
# There are no automatic repository checks unless -C is given
# set last edit date + time
MODULE="pgsql"
MODULE_VERSION="1.2.0";
MODULE_VERSION="1.2.1";
DIR="${BASH_SOURCE%/*}"
@@ -19,15 +16,18 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
. "${DIR}/borg.backup.functions.init.sh";
# include and exclude file
INCLUDE_FILE="borg.backup.pgsql.include";
EXCLUDE_FILE="borg.backup.pgsql.exclude";
SCHEMA_ONLY_FILE="borg.backup.pgsql.schema-only";
DATA_ONLY_FILE="borg.backup.pgsql.data-only";
# init check file
BACKUP_INIT_CHECK="borg.backup.pgsql.init";
INCLUDE_FILE="borg.backup.${MODULE}.include";
EXCLUDE_FILE="borg.backup.${MODULE}.exclude";
SCHEMA_ONLY_FILE="borg.backup.${MODULE}.schema-only";
DATA_ONLY_FILE="borg.backup.${MODULE}.data-only";
# init verify and check file
BACKUP_INIT_FILE="borg.backup.${MODULE}.init";
BACKUP_CHECK_FILE="borg.backup.${MODULE}.check";
# lock file
BACKUP_LOCK_FILE="borg.backup.${MODULE}.lock";
# check valid data
. "${DIR}/borg.backup.functions.check.sh";
# verify valid data
. "${DIR}/borg.backup.functions.verify.sh";
# if info print info and then abort run
. "${DIR}/borg.backup.functions.info.sh";
@@ -101,7 +101,7 @@ if [ ! -z "${DATABASE_FULL_DUMP}" ]; then
SCHEMA_ONLY='-s';
schema_flag='schema';
fi;
echo "--- [BACKUP: all databases: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUBEXT_BLOCK}" "BACKUP" "all databases" "$(date +'%F %T')" "${MODULE}";
# Filename
FILENAME-"all.${DB_USER}.NONE.${schema_flag}-${DB_VERSION}_${DB_HOST}_${DB_PORT}.c.sql"
# backup set:
@@ -127,7 +127,7 @@ if [ ! -z "${DATABASE_FULL_DUMP}" ]; then
fi;
fi;
if [ -z "${ONE_TIME_TAG}" ]; then
echo "--- [PRUNE : all databases: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUBEXT_BLOCK}" "PRUNE" "all databases" "$(date +'%F %T')" "${MODULE}";
echo "Prune repository with keep${KEEP_INFO:1}";
${BORG_PRUNE};
fi;
@@ -135,7 +135,7 @@ else
# dump globals first
db="pg_globals";
schema_flag="data";
echo "--- [BACKUP: ${db}: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUBEXT_BLOCK}" "BACKUP" "${db}" "$(date +'%F %T')" "${MODULE}";
# Filename
FILENAME="${db}.${DB_USER}.NONE.${schema_flag}-${DB_VERSION}_${DB_HOST}_${DB_PORT}.c.sql"
# backup set:
@@ -161,7 +161,7 @@ else
fi;
fi;
if [ -z "${ONE_TIME_TAG}" ]; then
echo "--- [PRUNE : ${db}: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUBEXT_BLOCK}" "PRUNE" "${db}" "$(date +'%F %T')" "${MODULE}";
echo "Prune repository with keep${KEEP_INFO:1}";
${BORG_PRUNE};
fi;
@@ -172,8 +172,8 @@ else
owner=$(echo ${owner_db} | cut -d "," -f 1);
db=$(echo ${owner_db} | cut -d "," -f 2);
encoding=$(echo ${owner_db} | cut -d "," -f 3);
echo "========[DB: ${db}]========================[${MODULE}]====================================>";
echo "--- [BACKUP: ${db}: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_DB_SUB_BLOCK}" "DB" "${db}" "${MODULE}";
printf "${PRINTF_SUBEXT_BLOCK}" "BACKUP" "${db}" "$(date +'%F %T')" "${MODULE}";
include=0;
if [ -s "${BASE_FOLDER}${INCLUDE_FILE}" ]; then
while read incl_db; do
@@ -257,7 +257,7 @@ else
fi;
fi;
if [ -z "${ONE_TIME_TAG}" ]; then
echo "--- [PRUNE : ${db}: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUBEXT_BLOCK}" "PRUNE" "${db}" "$(date +'%F %T')" "${MODULE}";
echo "Prune repository prefixed ${BACKUP_SET_PREFIX} with keep${KEEP_INFO:1}";
${BORG_PRUNE};
fi;
@@ -270,6 +270,8 @@ fi;
if [ -z "${ONE_TIME_TAG}" ]; then
# if this is borg version >1.2 we need to run compact after prune
. "${DIR}/borg.backup.functions.compact.sh";
# check in auto mode
. "${DIR}/borg.backup.functions.check.sh" "auto";
fi;
. "${DIR}/borg.backup.functions.close.sh";

View File

@@ -27,8 +27,13 @@ COMPRESSION_LEVEL="";
# Blank passwords allowed for only key (if used, use keyfile)
# See: http://borgbackup.readthedocs.io/en/stable/faq.html#how-can-i-specify-the-encryption-passphrase-programmatically
ENCRYPTION="";
# force repository check, default is off, set to true for check
FORCE_CHECK="";
# force repository verirfy, default is off, set to true for verify on every run
FORCE_VERIFY="";
# check interval, if 0 or negative number, no check will ever run
# if empty fall back to default set
# if set to 1 then every time the script runs
# any other value it means ever n days, eg 90 would be every 90 days
CHECK_INTERVAL="";
# default is %Y-%m-%d
# todays date, if more than one per day add -%H%M for hour/minute
# it can also be "{hostname}-{user}-{now:%Y-%m-%dT%H:%M:%S.%f}"

View File

@@ -3,18 +3,21 @@
# Backup zabbix config and settings only
MODULE="zabbix"
MODULE_VERSION="1.1.1";
MODULE_VERSION="1.1.2";
DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
# init system
. "${DIR}/borg.backup.functions.init.sh";
# init check file
BACKUP_INIT_CHECK="borg.backup.zabbix.init";
# init verify and check file
BACKUP_INIT_FILE="borg.backup.${MODULE}.init";
BACKUP_CHECK_FILE="borg.backup.${MODULE}.check";
# lock file
BACKUP_LOCK_FILE="borg.backup.${MODULE}.lock";
# check valid data
. "${DIR}/borg.backup.functions.check.sh";
# verify valid data
. "${DIR}/borg.backup.functions.verify.sh";
# if info print info and then abort run
. "${DIR}/borg.backup.functions.info.sh";
@@ -64,7 +67,7 @@ if [ -z "${BACKUP_SET_PREFIX}" ]; then
BORG_PRUNE=$(echo "${BORG_PRUNE}" | sed -e 's/-P //');
fi;
echo "--- [BACKUP: zabbix settings: $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUB_BLOCK}" "BACKUP: zabbix settings" "$(date +'%F %T')" "${MODULE}";
if [ ${DEBUG} -eq 1 ] || [ ${DRYRUN} -eq 1 ]; then
echo "${ZABBIX_DUMP_BIN} -t ${ZABBIX_DATABASE} ${OPT_ZABBIX_UNKNOWN_TABLES} ${OPT_ZABBIX_DUMP} ${OPT_ZABBIX_CONFIG} -o - | ${BORG_CALL}"
if [ -z "${ONE_TIME_TAG}" ]; then
@@ -75,10 +78,12 @@ if [ ${DRYRUN} -eq 0 ]; then
${ZABBIX_DUMP_BIN} -t ${ZABBIX_DATABASE} ${OPT_ZABBIX_UNKNOWN_TABLES} ${OPT_ZABBIX_DUMP} ${OPT_ZABBIX_CONFIG} -o - | ${BORG_CALL};
fi;
if [ -z "${ONE_TIME_TAG}" ]; then
echo "--- [PRUNE : $(date +'%F %T')] --[${MODULE}]------------------------------------>";
printf "${PRINTF_SUB_BLOCK}" "PRUNE" "$(date +'%F %T')" "${MODULE}";
${BORG_PRUNE};
# if this is borg version >1.2 we need to run compact after prune
. "${DIR}/borg.backup.functions.compact.sh";
# check in auto mode
. "${DIR}/borg.backup.functions.check.sh" "auto";
fi;
. "${DIR}/borg.backup.functions.close.sh";

View File

@@ -1,5 +1,8 @@
#!/usr/bin/env bash
echo "${0} Currently not checked";
exit;
set -e -u -o pipefail
# mount this servers borg backup to a folder
@@ -13,7 +16,7 @@ SETTINGS_FILE="borg.backup.settings";
# base mount path (default)
MOUNT_PATH="/mnt/restore/";
# backup path to borg storage
ATTIC_BACKUP_FILE='';
BORG_BACKUP_FILE='';
# if we are mount or unmount (default is mount)
UMOUNT=0;
@@ -42,7 +45,7 @@ while getopts ":c:m:uf:h" opt do
UMOUNT=1;
;;
f|file)
ATTIC_BACKUP_FILE=${OPTARG};
BORG_BACKUP_FILE=${OPTARG};
;;
h|help)
usage;
@@ -69,7 +72,7 @@ fi;
if [ ${UMOUNT} -eq 0 ]; then
TARGET_SERVER='';
if [ -z "${ATTIC_BACKUP_FILE}" ]; then
if [ -z "${BORG_BACKUP_FILE}" ]; then
if [ ! -f "${BASE_FOLDER}${SETTINGS_FILE}" ]; then
echo "Cannot find ${BASE_FOLDER}${SETTINGS_FILE}";
exit 0;
@@ -82,7 +85,7 @@ if [ ${UMOUNT} -eq 0 ]; then
fi;
REPOSITORY=${TARGET_SERVER}${TARGET_FOLDER}${BACKUP_FILE};
else
REPOSITORY=${ATTIC_BACKUP_FILE};
REPOSITORY=${BORG_BACKUP_FILE};
fi;
# check that the repostiory exists
@@ -112,4 +115,4 @@ else
borg umount "${MOUNT_PATH}";
fi;
## END
# __END__