backup-container/src/backup-wrapper.sh
Andras Schmelczer 136f21d272
All checks were successful
ShellCheck / shellcheck (push) Successful in 5s
Docker / build (push) Successful in 14s
Wait for others
2026-10-01 13:00:00 +01:00

140 lines
5.1 KiB
Shell
Executable file

#!/bin/bash
echo "Starting backup wrapper script at $(date)"
mkdir -p /health || exit 1
# Called indirectly by the EXIT trap, including configuration failures.
# shellcheck disable=SC2317
finish() {
local status=$?
if (( status != 0 )); then
touch /health/backup_failed || echo "Cannot record backup failure" >&2
fi
rm -f /health/backup_completion_time.log.$$
exit "$status"
}
trap 'finish' EXIT
if [ -e /health/check_failed ]; then
echo "A previous repository check failed. Investigate and run full checks before clearing /health/check_failed." >&2
exit 1
fi
# shellcheck source=src/borg-common.sh
source /src/borg-common.sh || exit 1
execute_script() {
echo "Executing $operation with:"
if [ -n "$BORG_PASSPHRASE" ]; then
echo "BORG_PASSPHRASE=<redacted>"
fi
echo "BORG_REMOTE_PATH='${BORG_REMOTE_PATH}'"
echo "BORG_REPO='${BORG_REPO}'"
local status=0
if [[ $operation == backup ]]; then
/src/backup.sh || status=$?
else
echo "Checking repository for up to $check_duration seconds"
borg_wait check --repository-only --max-duration="$check_duration" --info || status=$?
if (( status != 0 )); then
# Later partial checks may skip the damaged segment. Only clear
# this marker manually after investigating and fully checking it.
touch /health/check_failed || status=1
echo "Repository check failed (Borg exit status $status). Stopping backup cycle." >&2
exit "$status"
fi
fi
if (( status != 0 )); then
# Report failure while later targets are still running, and keep it
# across retries and container restarts until every target succeeds.
touch /health/backup_failed || return 1
fi
return "$status"
}
configure_environment() {
local repo_var="BORG_REPO_$1" passphrase_var="BORG_PASSPHRASE_$1" remote_var="BORG_REMOTE_PATH_$1"
[[ -n ${!repo_var} && -n ${!passphrase_var} ]] || return 1
export BORG_REPO="${!repo_var}" BORG_PASSPHRASE="${!passphrase_var}"
if [[ -n ${!remote_var} ]]; then
export BORG_REMOTE_PATH="${!remote_var}"
else
unset BORG_REMOTE_PATH
fi
}
main() {
local check_budget check_duration check_deadline operation
local index target_count=1 any_failed=false completed_indices indexed_var_name
# Preserve the existing SLEEP_TIME setting, including fractional durations.
check_budget=$(/src/interval.sh budget "${SLEEP_TIME:-1h}") || return 1
# Finish every backup before spending the former sleep interval checking.
for operation in backup check; do
check_duration=$((check_budget / target_count))
if (( check_duration == 0 )); then
echo "SLEEP_TIME must allow at least one second per repository."
return 1
fi
if [[ $operation == check ]]; then
check_deadline=$(/src/interval.sh deadline "${SLEEP_TIME:-1h}") || return 1
# Borg can sleep for up to 60 seconds between lock attempts. Give
# every waiter a chance to acquire the released locks before checks.
echo "Waiting 60 seconds for queued backups before repository checks"
sleep 60 || return 1
fi
if [ -n "$BORG_REPO" ]; then
execute_script || any_failed=true
else
index=0
completed_indices=" "
while configure_environment "$index"; do
execute_script || any_failed=true
completed_indices+="$index "
unset BORG_PASSPHRASE BORG_REMOTE_PATH BORG_REPO
((index++))
done
target_count=$index
if (( index == 0 )); then
echo "No valid configuration found. Please ensure environment variables are set properly."
return 1
fi
# A missing or incomplete target must not silently hide later targets.
for indexed_var_name in ${!BORG_REPO_@} ${!BORG_PASSPHRASE_@} ${!BORG_REMOTE_PATH_@}; do
[[ -n ${!indexed_var_name} ]] || continue
case "$completed_indices" in
*" ${indexed_var_name##*_} "*) ;;
*)
echo "Invalid or non-contiguous backup configuration: $indexed_var_name. Skipping completion log."
return 1
;;
esac
done
fi
done
if [[ $any_failed == true ]]; then
echo "Skipping completion log due to backup or check failure(s)"
return 1
fi
# Clear the failure only after publishing a complete, successful timestamp.
date -u '+%Y-%m-%dT%H:%M:%SZ' > /health/backup_completion_time.log.$$ &&
mv -fT /health/backup_completion_time.log.$$ /health/backup_completion_time.log &&
rm -f /health/backup_failed || return 1
# A partial check can finish early. Keep the configured interval between
# backup rounds, counting the handoff pause, checks and lock waits toward it.
/src/interval.sh wait "$check_deadline"
}
main
backup_status=$?
echo "Finished backup wrapper script at $(date)"
exit "$backup_status"