mirror of https://github.com/digint/btrbk
279 lines
7.8 KiB
Bash
Executable File
279 lines
7.8 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
## Wrapper script running "btrbk" and sending email with results
|
|
|
|
now=$(date +%Y%m%d)
|
|
|
|
declare -A rsync_src rsync_dst rsync_log rsync_rsh rsync_opt
|
|
declare -A sync_fs_onchange
|
|
|
|
##### start config section #####
|
|
|
|
# Email recipients, separated by whitespace:
|
|
mailto=${MAILTO:-root}
|
|
|
|
# Email subject:
|
|
mail_subject_prefix="btrbk <${HOSTNAME:-localhost}>"
|
|
|
|
# Add summary and/or detail (rsync/btrbk command output) to mail body.
|
|
# If both are not set, a mail is only sent on errors.
|
|
mail_summary=yes
|
|
mail_detail=no
|
|
|
|
# List of mountpoints to be mounted/unmounted (whitespace-separated)
|
|
# mount_targets="/mnt/btr_pool /mnt/backup"
|
|
mount_targets=
|
|
|
|
# rsync declarations (repeat complete block for more declarations):
|
|
rsync_src[example_data]="user@example.com:/data/"
|
|
rsync_dst[example_data]="/mnt/backup/example.com/data/"
|
|
rsync_log[example_data]="/mnt/backup/example.com/data-${now}.log"
|
|
rsync_rsh[example_data]="ssh -i /mnt/backup/ssh_keys/id_rsa"
|
|
rsync_opt[example_data]="-az --delete --inplace --numeric-ids --acls --xattrs"
|
|
# If set, add "rsync_dst" to "sync_fs" (see below) if rsync reports files transferred
|
|
#sync_fs_onchange[example_data]=yes
|
|
|
|
# Enable all rsync declarations (all indices of rsync_src array)
|
|
#rsync_enable=${!rsync_src[@]}
|
|
# Explicitly enable rsync declarations (whitespace-separated list)
|
|
#rsync_enable="example_data"
|
|
rsync_enable=
|
|
|
|
# If set, do not run btrbk if rsync reports no changes.
|
|
# If set to "quiet", do not send mail.
|
|
#skip_btrbk_if_unchanged=quiet
|
|
|
|
# Array of directories to sync(1) prior to running btrbk. This is
|
|
# useful for source subvolumes having "snapshot_create ondemand"
|
|
# configured in btrbk.conf.
|
|
#sync_fs=("/mnt/btr_data" "/mnt/btr_pool")
|
|
|
|
# btrbk command / options:
|
|
btrbk_command="run"
|
|
btrbk_opts="-c /etc/btrbk/btrbk.conf"
|
|
|
|
|
|
### Layout options:
|
|
|
|
# Prefix command output: useful when using mail clients displaying
|
|
# btrbk summary lines starting with ">>>" as quotations.
|
|
#mail_cmd_block_prefix='\\u200B' # zero-width whitespace
|
|
#mail_cmd_block_prefix=". "
|
|
|
|
# Newline character
|
|
BR=$'\n'
|
|
|
|
##### end config section #####
|
|
|
|
|
|
check_options()
|
|
{
|
|
[[ -n "$btrbk_command" ]] || die "btrbk_command is not set"
|
|
for key in $rsync_enable; do
|
|
[[ -n "${rsync_src[$key]}" ]] || die "rsync_src is not set for \"$key\""
|
|
[[ -n "${rsync_dst[$key]}" ]] || die "rsync_dst is not set for \"$key\""
|
|
[[ -n "${rsync_opt[$key]}" ]] || die "rsync_opt is not set for \"$key\""
|
|
done
|
|
}
|
|
|
|
send_mail()
|
|
{
|
|
# assemble mail subject
|
|
local subject="$mail_subject_prefix"
|
|
[[ -n "$has_errors" ]] && subject+=" ERROR";
|
|
[[ -n "$status" ]] && subject+=" - $status";
|
|
[[ -n "$xstatus" ]] && subject+=" (${xstatus:2})";
|
|
|
|
# assemble mail body
|
|
local body=
|
|
if [[ -n "$info" ]] && [[ -n "$has_errors" ]] || [[ "${mail_summary:-no}" = "yes" ]]; then
|
|
body+="$info"
|
|
fi
|
|
if [[ -n "$detail" ]] && [[ -n "$has_errors" ]] || [[ "${mail_detail:-no}" = "yes" ]]; then
|
|
[[ -n "$body" ]] && body+="${BR}${BR}DETAIL:${BR}"
|
|
body+="$detail"
|
|
fi
|
|
|
|
# skip sending mail on empty body
|
|
if [[ -z "$body" ]] && [[ -n "$has_errors" ]]; then
|
|
body+="FATAL: something went wrong (errors present but empty mail body)${BR}"
|
|
fi
|
|
[[ -z "$body" ]] && exit 0
|
|
|
|
# send mail
|
|
echo "$body" | mail -s "$subject" $mailto
|
|
if [[ $? -ne 0 ]]; then
|
|
echo "$0: Failed to send btrbk mail to \"$mailto\", dumping mail:${BR}" 1>&2
|
|
echo "<mail_subject>$subject</mail_subject>${BR}<mail_body>${BR}$body</mail_body>" 1>&2
|
|
fi
|
|
}
|
|
|
|
einfo()
|
|
{
|
|
info+="$1${BR}"
|
|
}
|
|
|
|
ebegin()
|
|
{
|
|
ebtext=$1
|
|
detail+="${BR}### $1${BR}"
|
|
}
|
|
|
|
eend()
|
|
{
|
|
if [[ $1 -eq 0 ]]; then
|
|
eetext=${3-success}
|
|
detail+="${BR}"
|
|
else
|
|
has_errors=1
|
|
eetext="ERROR (code=$1)"
|
|
[[ -n "$2" ]] && eetext+=": $2"
|
|
detail+="${BR}### $eetext${BR}"
|
|
fi
|
|
info+="$ebtext: $eetext${BR}"
|
|
return $1
|
|
}
|
|
|
|
die()
|
|
{
|
|
einfo "FATAL: ${1}, exiting"
|
|
has_errors=1
|
|
send_mail
|
|
exit 1
|
|
}
|
|
|
|
run_cmd()
|
|
{
|
|
cmd_out=$("$@" 2>&1)
|
|
local ret=$?
|
|
detail+="++ ${@@Q}${BR}"
|
|
if [[ -n "${mail_cmd_block_prefix:-}" ]] && [[ -n "$cmd_out" ]]; then
|
|
detail+=$(echo -n "$cmd_out" | sed "s/^/${mail_cmd_block_prefix}/")
|
|
detail+="${BR}"
|
|
else
|
|
detail+=$cmd_out
|
|
fi
|
|
return $ret
|
|
}
|
|
|
|
mount_all()
|
|
{
|
|
# mount all mountpoints listed in $mount_targets
|
|
mounted=""
|
|
for mountpoint in $mount_targets; do
|
|
ebegin "Mounting $mountpoint"
|
|
run_cmd findmnt -n $mountpoint
|
|
if [[ $? -eq 0 ]]; then
|
|
eend -1 "already mounted"
|
|
else
|
|
detail+="${BR}"
|
|
run_cmd mount --target $mountpoint
|
|
eend $? && mounted+=" $mountpoint"
|
|
fi
|
|
done
|
|
}
|
|
|
|
umount_mounted()
|
|
{
|
|
for mountpoint in $mounted; do
|
|
ebegin "Unmounting $mountpoint"
|
|
run_cmd umount $mountpoint
|
|
eend $?
|
|
done
|
|
}
|
|
|
|
|
|
check_options
|
|
mount_all
|
|
|
|
|
|
#
|
|
# run rsync for all $rsync_enable
|
|
#
|
|
for key in $rsync_enable; do
|
|
ebegin "Running rsync[$key]"
|
|
if [[ -d "${rsync_dst[$key]}" ]]; then
|
|
# There is no proper way to get a proper machine readable
|
|
# output of "rsync did not touch anything at destination", so
|
|
# we add "--info=stats2" and parse the output.
|
|
# NOTE: This also appends the stats to the log file (rsync_log).
|
|
# Another approach to count the files would be something like:
|
|
# "rsync --out-format='' | wc -l"
|
|
run_cmd rsync ${rsync_opt[$key]} \
|
|
--info=stats2 \
|
|
${rsync_log[$key]:+--log-file="${rsync_log[$key]}"} \
|
|
${rsync_rsh[$key]:+-e "${rsync_rsh[$key]}"} \
|
|
"${rsync_src[$key]}" \
|
|
"${rsync_dst[$key]}"
|
|
exitcode=$?
|
|
|
|
# parse stats2 (count created/deleted/transferred files)
|
|
REGEXP=$'\n''Number of created files: ([0-9]+)'
|
|
REGEXP+='.*'$'\n''Number of deleted files: ([0-9]+)'
|
|
REGEXP+='.*'$'\n''Number of regular files transferred: ([0-9]+)'
|
|
if [[ $cmd_out =~ $REGEXP ]]; then
|
|
rsync_stats="${BASH_REMATCH[1]}/${BASH_REMATCH[2]}/${BASH_REMATCH[3]}"
|
|
rsync_stats_long="${BASH_REMATCH[1]} created, ${BASH_REMATCH[2]} deleted, ${BASH_REMATCH[3]} transferred"
|
|
nfiles=$(( ${BASH_REMATCH[1]} + ${BASH_REMATCH[2]} + ${BASH_REMATCH[3]} ))
|
|
else
|
|
rsync_stats_long="failed to parse stats, assuming files transferred"
|
|
rsync_stats="-1/-1/-1"
|
|
nfiles=-1
|
|
fi
|
|
|
|
eend $exitcode "$rsync_stats_long" "$rsync_stats_long"
|
|
xstatus+=", rsync[$key]=$rsync_stats"
|
|
|
|
if [[ $nfiles -ne 0 ]]; then
|
|
# NOTE: on error, we assume files are transferred
|
|
rsync_files_transferred=1
|
|
[[ -n "${sync_fs_onchange[$key]}" ]] && sync_fs+=("${rsync_dst[$key]}")
|
|
fi
|
|
else
|
|
eend -1 "Destination directory not found, skipping: ${rsync_dst[$key]}"
|
|
fi
|
|
done
|
|
|
|
# honor skip_btrbk_if_unchanged (only if rsync is enabled and no files were transferred)
|
|
if [[ -n "$rsync_enable" ]] && [[ -n "$skip_btrbk_if_unchanged" ]] && [[ -z "$rsync_files_transferred" ]]; then
|
|
einfo "No files transferred, exiting"
|
|
status="No files transferred"
|
|
umount_mounted
|
|
if [[ "$skip_btrbk_if_unchanged" != "quiet" ]] || [[ -n "$has_errors" ]]; then
|
|
send_mail
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
|
|
#
|
|
# sync filesystems in sync_fs
|
|
#
|
|
if [[ ${#sync_fs[@]} -gt 0 ]]; then
|
|
ebegin "Syncing filesystems at ${sync_fs[@]}"
|
|
run_cmd sync -f "${sync_fs[@]}"
|
|
eend $?
|
|
fi
|
|
|
|
|
|
#
|
|
# run btrbk
|
|
#
|
|
ebegin "Running btrbk"
|
|
run_cmd btrbk ${btrbk_opts:-} ${btrbk_command}
|
|
exitcode=$?
|
|
case $exitcode in
|
|
0) status="All backups successful"
|
|
;;
|
|
3) status="Another instance of btrbk is running, no backup tasks performed!"
|
|
;;
|
|
10) status="At least one backup task aborted!"
|
|
;;
|
|
*) status="btrbk failed with error code $exitcode"
|
|
;;
|
|
esac
|
|
eend $exitcode "$status"
|
|
|
|
umount_mounted
|
|
send_mail
|