@@ -155,8 +155,9 @@ To run the backup automatically, edit the root crontab.
155
155
# !! IMPORTANT !! Set file permissions to 600 (chmod 600 backup.conf)
156
156
157
157
# --- Source and Destination ---
158
- # IMPORTANT: LOCAL_DIR must end with a trailing slash!
159
- LOCAL_DIR="/home/user/"
158
+ # List all source directories to back up, separated by spaces.
159
+ # IMPORTANT: Each path MUST end with a trailing slash!
160
+ BACKUP_DIRS="/home/user/ /usr/scripts/ /etc/apps/"
160
161
BOX_DIR="/home/myvps/"
161
162
162
163
# --- Connection Details ---
@@ -221,7 +222,7 @@ END_EXCLUDES
221
222
222
223
# =================================================================
223
224
# SCRIPT INITIALIZATION & SETUP
224
- # v0.8 - 2025.08.09
225
+ # v0.9 - 2025.08.09
225
226
# =================================================================
226
227
set -Euo pipefail
227
228
umask 077
266
267
fi
267
268
268
269
# --- Validate that all required configuration variables are set ---
269
- for var in LOCAL_DIR BOX_DIR HETZNER_BOX SSH_OPTS_STR LOG_FILE \
270
+ for var in BACKUP_DIRS BOX_DIR HETZNER_BOX SSH_OPTS_STR LOG_FILE \
270
271
NTFY_PRIORITY_SUCCESS NTFY_PRIORITY_WARNING NTFY_PRIORITY_FAILURE \
271
272
LOG_RETENTION_DAYS; do
272
273
if [ -z " ${! var:- } " ]; then
@@ -329,7 +330,8 @@ send_notification() {
329
330
330
331
run_integrity_check () {
331
332
local rsync_check_opts=(-ainc -c --delete --exclude-from=" $EXCLUDE_FILE_TMP " --out-format=" %n" -e " ssh ${SSH_OPTS_STR:- } " )
332
- LC_ALL=C rsync " ${rsync_check_opts[@]} " " $LOCAL_DIR " " $REMOTE_TARGET " 2>> " ${LOG_FILE:-/ dev/ null} "
333
+ # shellcheck disable=SC2086
334
+ LC_ALL=C rsync " ${rsync_check_opts[@]} " $BACKUP_DIRS " $REMOTE_TARGET " 2>> " ${LOG_FILE:-/ dev/ null} "
333
335
}
334
336
335
337
parse_stat () {
@@ -389,10 +391,12 @@ if ! ssh ${SSH_OPTS_STR:-} -o BatchMode=yes -o ConnectTimeout=10 "$HETZNER_BOX"
389
391
trap - ERR; exit 6
390
392
fi
391
393
392
- if [[ ! -d " $LOCAL_DIR " ]] || [[ " $LOCAL_DIR " != * / ]]; then
393
- send_notification " ❌ Backup FAILED: ${HOSTNAME} " " x" " ${NTFY_PRIORITY_FAILURE} " " failure" " FATAL: LOCAL_DIR must exist and end with a trailing slash ('/')."
394
- trap - ERR; exit 2
395
- fi
394
+ for dir in $BACKUP_DIRS ; do
395
+ if [[ ! -d " $dir " ]] || [[ " $dir " != * / ]]; then
396
+ send_notification " ❌ Backup FAILED: ${HOSTNAME} " " x" " ${NTFY_PRIORITY_FAILURE} " " failure" " FATAL: A directory in BACKUP_DIRS ('$dir ') must exist and end with a trailing slash ('/')."
397
+ trap - ERR; exit 2
398
+ fi
399
+ done
396
400
397
401
398
402
# =================================================================
@@ -411,37 +415,38 @@ if [[ "${1:-}" ]]; then
411
415
--dry-run)
412
416
trap - ERR
413
417
echo " --- DRY RUN MODE ACTIVATED ---"
414
- if ! rsync " ${RSYNC_BASE_OPTS[@]} " --dry-run --info=progress2 " $LOCAL_DIR " " $REMOTE_TARGET " ; then
418
+ # shellcheck disable=SC2086
419
+ if ! rsync " ${RSYNC_BASE_OPTS[@]} " --dry-run --info=progress2 $BACKUP_DIRS " $REMOTE_TARGET " ; then
415
420
echo " "
416
421
echo " ❌ Dry run FAILED. See the rsync error message above for details."
417
422
exit 1
418
423
fi
419
424
echo " --- DRY RUN COMPLETED ---" ; exit 0 ;;
420
- --checksum)
425
+ --checksum | --summary)
426
+ # Both modes use the same check, just with different reporting
421
427
echo " --- INTEGRITY CHECK MODE ACTIVATED ---"
422
- echo " Comparing checksums... this may take a while ."
428
+ echo " Calculating differences.. ."
423
429
FILE_DISCREPANCIES=$( run_integrity_check)
424
- if [ -z " $FILE_DISCREPANCIES " ]; then
425
- echo " ✅ Checksum validation passed. No discrepancies found."
426
- log_message " Checksum validation passed. No discrepancies found."
427
- send_notification " ✅ Backup Integrity OK: ${HOSTNAME} " " white_check_mark" " ${NTFY_PRIORITY_SUCCESS} " " success" " Checksum validation passed. No discrepancies found."
428
- else
429
- log_message " Backup integrity check FAILED. Found discrepancies."
430
- ISSUE_LIST=$( echo " ${FILE_DISCREPANCIES} " | head -n 10)
431
- printf " ❌ Backup integrity check FAILED. First 10 differing files:\n%s\n" " ${ISSUE_LIST} "
432
- printf -v FAILURE_MSG " Backup integrity check FAILED.\n\nFirst 10 differing files:\n%s\n\nCheck log for full details." " ${ISSUE_LIST} "
433
- send_notification " ❌ Backup Integrity FAILED: ${HOSTNAME} " " x" " ${NTFY_PRIORITY_FAILURE} " " failure" " ${FAILURE_MSG} "
430
+
431
+ if [[ " $1 " == " --summary" ]]; then
432
+ MISMATCH_COUNT=$( echo " $FILE_DISCREPANCIES " | wc -l)
433
+ printf " 🚨 Total files with checksum mismatches: %d\n" " $MISMATCH_COUNT "
434
+ log_message " Summary mode check found $MISMATCH_COUNT mismatched files."
435
+ send_notification " 📊 Backup Summary: ${HOSTNAME} " " bar_chart" " ${NTFY_PRIORITY_SUCCESS} " " success" " Mismatched files found: $MISMATCH_COUNT "
436
+ else # --checksum
437
+ if [ -z " $FILE_DISCREPANCIES " ]; then
438
+ echo " ✅ Checksum validation passed. No discrepancies found."
439
+ log_message " Checksum validation passed. No discrepancies found."
440
+ send_notification " ✅ Backup Integrity OK: ${HOSTNAME} " " white_check_mark" " ${NTFY_PRIORITY_SUCCESS} " " success" " Checksum validation passed. No discrepancies found."
441
+ else
442
+ log_message " Backup integrity check FAILED. Found discrepancies."
443
+ ISSUE_LIST=$( echo " ${FILE_DISCREPANCIES} " | head -n 10)
444
+ printf " ❌ Backup integrity check FAILED. First 10 differing files:\n%s\n" " ${ISSUE_LIST} "
445
+ printf -v FAILURE_MSG " Backup integrity check FAILED.\n\nFirst 10 differing files:\n%s\n\nCheck log for full details." " ${ISSUE_LIST} "
446
+ send_notification " ❌ Backup Integrity FAILED: ${HOSTNAME} " " x" " ${NTFY_PRIORITY_FAILURE} " " failure" " ${FAILURE_MSG} "
447
+ fi
434
448
fi
435
449
exit 0 ;;
436
-
437
- --summary)
438
- echo " --- INTEGRITY SUMMARY MODE ---"
439
- echo " Calculating differences..."
440
- MISMATCH_COUNT=$( run_integrity_check | wc -l)
441
- printf " 🚨 Total files with checksum mismatches: %d\n" " $MISMATCH_COUNT "
442
- log_message " Summary mode check found $MISMATCH_COUNT mismatched files."
443
- send_notification " 📊 Backup Summary: ${HOSTNAME} " " bar_chart" " ${NTFY_PRIORITY_SUCCESS} " " success" " Mismatched files found: $MISMATCH_COUNT "
444
- exit 0 ;;
445
450
esac
446
451
fi
447
452
@@ -451,7 +456,6 @@ flock -n 200 || { echo "Another instance is running, exiting."; exit 5; }
451
456
if [ -f " $LOG_FILE " ] && [ " $( stat -c%s " $LOG_FILE " ) " -gt " $MAX_LOG_SIZE " ]; then
452
457
mv " $LOG_FILE " " ${LOG_FILE} .$( date +%Y%m%d_%H%M%S) "
453
458
touch " $LOG_FILE "
454
- # Clean up old log files
455
459
find " $( dirname " $LOG_FILE " ) " -name " $( basename " $LOG_FILE " ) .*" -type f -mtime +" $LOG_RETENTION_DAYS " -delete
456
460
fi
457
461
@@ -466,11 +470,13 @@ RSYNC_OPTS=("${RSYNC_BASE_OPTS[@]}")
466
470
467
471
if [[ " $VERBOSE_MODE " == " true" ]]; then
468
472
RSYNC_OPTS+=(--info=stats2,progress2)
469
- nice -n 19 ionice -c 3 rsync " ${RSYNC_OPTS[@]} " " $LOCAL_DIR " " $REMOTE_TARGET " 2>&1 | tee " $RSYNC_LOG_TMP "
473
+ # shellcheck disable=SC2086
474
+ nice -n 19 ionice -c 3 rsync " ${RSYNC_OPTS[@]} " $BACKUP_DIRS " $REMOTE_TARGET " 2>&1 | tee " $RSYNC_LOG_TMP "
470
475
RSYNC_EXIT_CODE=${PIPESTATUS[0]}
471
476
else
472
477
RSYNC_OPTS+=(--info=stats2)
473
- nice -n 19 ionice -c 3 rsync " ${RSYNC_OPTS[@]} " " $LOCAL_DIR " " $REMOTE_TARGET " > " $RSYNC_LOG_TMP " 2>&1 || RSYNC_EXIT_CODE=$?
478
+ # shellcheck disable=SC2086
479
+ nice -n 19 ionice -c 3 rsync " ${RSYNC_OPTS[@]} " $BACKUP_DIRS " $REMOTE_TARGET " > " $RSYNC_LOG_TMP " 2>&1 || RSYNC_EXIT_CODE=$?
474
480
fi
475
481
476
482
END_TIME= $( date +%s)
0 commit comments