@@ -156,8 +156,12 @@ To run the backup automatically, edit the root crontab.
156
156
157
157
# --- Source and Destination ---
158
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/"
159
+ #
160
+ # IMPORTANT: Follow these two rules for each path:
161
+ # 1. End the path with a trailing slash (e.g., "user/").
162
+ # 2. Use "/./" to mark the part of the path you want to create on the destination.
163
+ # Example: "/home/./user/" will create a "user" directory in your BOX_DIR.
164
+ BACKUP_DIRS="/home/./user/ /var/./log/ /etc/./nginx/"
161
165
BOX_DIR="/home/myvps/"
162
166
163
167
# --- Connection Details ---
@@ -222,7 +226,7 @@ END_EXCLUDES
222
226
223
227
# =================================================================
224
228
# SCRIPT INITIALIZATION & SETUP
225
- # v0.15 - 2025.08.10
229
+ # v0.16 - 2025.08.10
226
230
# =================================================================
227
231
set -Euo pipefail
228
232
umask 077
@@ -286,7 +290,7 @@ LOCK_FILE="/tmp/backup_rsync.lock"
286
290
MAX_LOG_SIZE=10485760 # 10 MB in bytes
287
291
288
292
RSYNC_BASE_OPTS=(
289
- -a -z --delete --partial --timeout=60
293
+ -aR -z --delete --partial --timeout=60
290
294
--exclude-from=" $EXCLUDE_FILE_TMP "
291
295
-e " ssh ${SSH_OPTS_STR:- } "
292
296
)
@@ -331,7 +335,7 @@ send_notification() {
331
335
}
332
336
333
337
run_integrity_check () {
334
- local rsync_check_opts=(-ainc -c --delete --mkpath --exclude-from=" $EXCLUDE_FILE_TMP " --out-format=" %n" -e " ssh ${SSH_OPTS_STR:- } " )
338
+ local rsync_check_opts=(-aincR -c --delete --mkpath --exclude-from=" $EXCLUDE_FILE_TMP " --out-format=" %n" -e " ssh ${SSH_OPTS_STR:- } " )
335
339
336
340
for dir in $BACKUP_DIRS ; do
337
341
local remote_path=" ${REMOTE_TARGET}${dir#/ } "
441
445
if [[ " ${1:- } " ]]; then
442
446
trap - ERR
443
447
case " ${1} " in
448
+
444
449
--dry-run)
445
450
trap - ERR
446
451
echo " --- DRY RUN MODE ACTIVATED ---"
447
452
DRY_RUN_FAILED=false
448
- for dir in $BACKUP_DIRS ; do
449
- remote_path=" ${REMOTE_TARGET}${dir#/ } "
450
- echo " --- Checking dry run for: $dir -> $remote_path "
451
- # shellcheck disable=SC2086
452
- if ! rsync " ${RSYNC_BASE_OPTS[@]} " --dry-run --info=progress2 " $dir " " $remote_path " ; then
453
+ full_dry_run_output=" "
454
+
455
+ read -ra DIRS_ARRAY <<< " $BACKUP_DIRS"
456
+ for dir in " ${DIRS_ARRAY[@]} " ; do
457
+ echo -e " \n--- Checking dry run for: $dir ---"
458
+
459
+ rsync_dry_opts=(" ${RSYNC_BASE_OPTS[@]} " --dry-run --itemize-changes --info=stats2)
460
+
461
+ DRY_RUN_LOG_TMP=$( mktemp)
462
+
463
+ if ! rsync " ${rsync_dry_opts[@]} " " $dir " " $REMOTE_TARGET " > " $DRY_RUN_LOG_TMP " 2>&1 ; then
453
464
DRY_RUN_FAILED=true
454
465
fi
466
+
467
+ echo " ---- Preview of changes ----"
468
+ grep ' ^[*<>]' " $DRY_RUN_LOG_TMP " | head -n 20 || true
469
+ echo " ----------------------------"
470
+
471
+ full_dry_run_output+=$' \n ' " $( < " $DRY_RUN_LOG_TMP " ) "
472
+ rm -f " $DRY_RUN_LOG_TMP "
455
473
done
456
474
475
+ echo -e " \n--- Overall Dry Run Summary ---"
476
+ BACKUP_STATS=$( format_backup_stats " $full_dry_run_output " )
477
+ echo -e " $BACKUP_STATS "
478
+ echo " -------------------------------"
479
+
457
480
if [[ " $DRY_RUN_FAILED " == " true" ]]; then
458
- echo " "
459
- echo " ❌ Dry run FAILED for one or more directories. See the rsync error messages above for details."
481
+ echo -e " \n❌ Dry run FAILED for one or more directories. See rsync errors above."
460
482
exit 1
461
483
fi
462
484
echo " --- DRY RUN COMPLETED ---" ; exit 0 ;;
0 commit comments