Files
BashScripts/rsync-monitor.sh
ackitsme fc26387aeb Updated to version 4.1.0
# v4.1.0 - 2026-10-18
#   - Converted to getopts
#   - Added configurable logfile (-l)
#   - Added required option validation
#   - Improved error handling
#
# v4.0.0 - 2026-10-18
#   - Added logging framework
#   - Added trap handlers
#   - Added standardized help
#   - Fixed syntax issues
2026-10-18 13:44:59 -07:00

376 lines
8.6 KiB
Bash

#!/usr/bin/env bash
###############################################################################
# Script Name : RsyncMonitor.sh
# Author : Charlie Silverman
# Version : 4.1.0
# Date : 2026-10-18
#
# Description:
# Monitor rsync copy progress by comparing source and destination sizes.
# Calculates completion percentage, transfer rate, and estimated time
# remaining.
#
# Revision History
# -----------------------------------------------------------------------------
# v4.1.0 - 2026-10-18
# - Converted to getopts
# - Added configurable logfile (-l)
# - Added required option validation
# - Improved error handling
#
# v4.0.0 - 2026-10-18
# - Added logging framework
# - Added trap handlers
# - Added standardized help
# - Fixed syntax issues
#
# v3.5.0 - 2026-07-26
# - Original release
###############################################################################
SCRIPT_NAME="$(basename "$0")"
SCRIPT_VERSION="4.1.0"
SCRIPT_DATE="2026-10-18"
set -o pipefail
###############################################################################
# Defaults
###############################################################################
SRCFOLDER=""
DSTFOLDER=""
SLEEP=60
LOGFILE="/tmp/RsyncMonitor.log"
###############################################################################
# Logging Functions
###############################################################################
log_info() {
local MESSAGE="$1"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO ] $MESSAGE" | tee -a "$LOGFILE"
}
log_warn() {
local MESSAGE="$1"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN ] $MESSAGE" | tee -a "$LOGFILE"
}
log_error() {
local MESSAGE="$1"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $MESSAGE" | tee -a "$LOGFILE" >&2
}
###############################################################################
# Error Handling
###############################################################################
error_handler() {
local LINE="$1"
log_error "Unexpected error on line ${LINE}"
exit 1
}
interrupt_handler() {
log_warn "Execution interrupted."
exit 130
}
trap 'error_handler $LINENO' ERR
trap interrupt_handler SIGINT SIGTERM
###############################################################################
# Banner
###############################################################################
show_banner() {
cat << EOF
==================================================
${SCRIPT_NAME}
rsync Progress Monitor
Author : Charlie Silverman
Version : ${SCRIPT_VERSION}
Date : ${SCRIPT_DATE}
==================================================
EOF
}
###############################################################################
# Help
###############################################################################
show_help() {
cat << EOF
NAME
${SCRIPT_NAME}
SYNOPSIS
${SCRIPT_NAME} -s <source> -d <destination> [options]
OPTIONS
-s <path>
Source folder to monitor.
-d <path>
Destination folder to monitor.
-t <seconds>
Sleep interval between scans.
Default: 60
-l <file>
Log file path.
Default:
/tmp/RsyncMonitor.log
-h
Display help.
EXAMPLES
${SCRIPT_NAME} \\
-s /home/olduser \\
-d /home/newuser
${SCRIPT_NAME} \\
-s /source \
-d /destination \
-t 300 \
-l /var/log/RsyncMonitor.log
EOF
}
###############################################################################
# Utilities
###############################################################################
get_directory_size_mb() {
local DIR="$1"
local MOUNT
if [[ "${DIR: -1}" == "/" ]]; then
MOUNT=$(df "${DIR%?}" | awk 'NR==2 {print $6}')
else
MOUNT=$(df "$DIR" | awk 'NR==2 {print $6}')
fi
if [[ "$MOUNT" != "$DIR" ]]; then
du -sm "$DIR" | awk '{print $1}'
else
local USED_KB
USED_KB=$(df "$DIR" | awk 'NR==2 {print $3}')
echo $(( USED_KB / 1024 ))
fi
}
format_size() {
local SIZE_MB="$1"
if (( SIZE_MB >= 1024 )); then
local SIZE_GB
SIZE_GB=$(echo "scale=3;$SIZE_MB/1024" | bc)
printf "%'.3fGB" "$SIZE_GB"
else
echo "${SIZE_MB}MB"
fi
}
###############################################################################
# Option Processing
###############################################################################
while getopts ":s:d:t:l:h" opt; do
case "${opt}" in
s)
SRCFOLDER="$OPTARG"
;;
d)
DSTFOLDER="$OPTARG"
;;
t)
SLEEP="$OPTARG"
;;
l)
LOGFILE="$OPTARG"
;;
h)
show_help
exit 0
;;
:)
echo "Missing argument for -${OPTARG}"
exit 1
;;
\?)
echo "Invalid option: -${OPTARG}"
exit 1
;;
esac
done
###############################################################################
# Startup
###############################################################################
show_banner
touch "$LOGFILE" 2>/dev/null || {
echo "Unable to write logfile: $LOGFILE"
exit 1
}
###############################################################################
# Validation
###############################################################################
ERROR=0
if [[ -z "$SRCFOLDER" ]]; then
log_error "Source folder not specified."
ERROR=1
fi
if [[ -z "$DSTFOLDER" ]]; then
log_error "Destination folder not specified."
ERROR=1
fi
if [[ ! -d "$SRCFOLDER" ]]; then
log_error "Source folder does not exist: $SRCFOLDER"
ERROR=1
fi
if [[ ! -d "$DSTFOLDER" ]]; then
log_error "Destination folder does not exist: $DSTFOLDER"
ERROR=1
fi
if ! [[ "$SLEEP" =~ ^[0-9]+$ ]]; then
log_error "Sleep value must be numeric."
ERROR=1
fi
if (( ERROR > 0 )); then
exit 1
fi
###############################################################################
# Calculate Source Size
###############################################################################
log_info "Calculating source size."
MEGS=$(get_directory_size_mb "$SRCFOLDER")
MEGSGB=$(echo "scale=3;$MEGS/1024" | bc)
MEGSDISPLAY=$(format_size "$MEGS")
log_info "Source size: $MEGSDISPLAY"
###############################################################################
# Initialize Destination
###############################################################################
FIRST=$(get_directory_size_mb "$DSTFOLDER")
FIRSTGB=$(echo "scale=3;$FIRST/1024" | bc)
FIRSTDISPLAY=$(format_size "$FIRST")
STARTTIME=$(date +%s)
START="$FIRST"
if (( $(echo "$MEGSGB > 0" | bc) )); then
CURRENTPCT=$(echo "scale=0;($FIRSTGB/$MEGSGB)*100" | bc)
else
CURRENTPCT=0
fi
CURRENTTIME=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "[${CURRENTTIME}] ${FIRSTDISPLAY} of ${MEGSDISPLAY} (${CURRENTPCT}%) Starting | Sleeping ${SLEEP}s..."
###############################################################################
# Monitor Loop
###############################################################################
while true; do
sleep "$SLEEP"
END=$(get_directory_size_mb "$DSTFOLDER")
ENDDISPLAY=$(format_size "$END")
ENDGB=$(echo "scale=3;$END/1024" | bc)
TIMENOW=$(date +%s)
TIMEDIFF=$(( TIMENOW - STARTTIME ))
DOWNLOADED=$(( END - START ))
TOTALDOWNLOADED=$(( END - FIRST ))
CURRENTTIME=$(date '+%Y-%m-%d %H:%M:%S')
if (( DOWNLOADED <= 0 )); then
echo "[${CURRENTTIME}] ${ENDDISPLAY} of ${MEGSDISPLAY} (${CURRENTPCT}%) No change detected | Sleeping ${SLEEP}s..."
continue
fi
if (( TIMEDIFF <= 0 || TOTALDOWNLOADED <= 0 )); then
START="$END"
continue
fi
OVERALLMEGSPERSECOND=$(( TOTALDOWNLOADED / TIMEDIFF ))
if (( OVERALLMEGSPERSECOND < 1 )); then
OVERALLMEGSPERSECOND=1
fi
REMAINING=$(( MEGS - END ))
if (( REMAINING < 0 )); then
REMAINING=0
fi
OVERALLSECONDSLEFT=$(( REMAINING / OVERALLMEGSPERSECOND ))
OVERALLDAYS=$(( OVERALLSECONDSLEFT / 86400 ))
OVERALLHOURS=$(( (OVERALLSECONDSLEFT % 86400) / 3600 ))
OVERALLMINUTES=$(( (OVERALLSECONDSLEFT % 3600) / 60 ))
OVERALLSECONDS=$(( OVERALLSECONDSLEFT % 60 ))
if (( $(echo "$MEGSGB > 0" | bc) )); then
CURRENTPCT=$(echo "scale=0;($ENDGB/$MEGSGB)*100" | bc)
else
CURRENTPCT=0
fi
echo "[${CURRENTTIME}] ${ENDDISPLAY} of ${MEGSDISPLAY} (${CURRENTPCT}%) ~${OVERALLMEGSPERSECOND}MB/s | ${OVERALLDAYS}d ${OVERALLHOURS}h ${OVERALLMINUTES}m ${OVERALLSECONDS}s remaining | Sleeping ${SLEEP}s..."
START="$END"
done