Skip to content

Instantly share code, notes, and snippets.

@justinwagg
Created March 31, 2025 14:11
Show Gist options
  • Select an option

  • Save justinwagg/2c03a890c6cb03435362b5be206d54d0 to your computer and use it in GitHub Desktop.

Select an option

Save justinwagg/2c03a890c6cb03435362b5be206d54d0 to your computer and use it in GitHub Desktop.
A script to facilitate transfer of files from my NAS to an HDD
#!/bin/bash
RSYNC_BIN="/usr/local/opt/rsync/bin/rsync"
# sudo nohup ./rsync_auto_restart.sh > rsync_output.log 2>&1 &
SOURCE="/Volumes/photo/2025/"
DEST="/Volumes/2024/2025/"
# SOURCE="/data/source/"
# DEST="/data/dest/"
SPEED_THRESHOLD_MB=30 # Minimum acceptable speed in MB/min
INTERVAL_SEC=60 # Interval to check speed (in seconds)
RSYNC_LOG="/Users/justinwagg/Projects/photo-transport/rsync_speed_check_2025.log"
EVENT_LOG="/Users/justinwagg/Projects/photo-transport/rsync_restart_log_2025.txt"
# RSYNC_LOG="/logs/rsync_speed_check.log"
# EVENT_LOG="/logs/rsync_restart_log.txt"
rm -f "$RSYNC_LOG"
if [[ "$OSTYPE" == "darwin"* ]]; then
RSYNC_LAUNCH="$RSYNC_BIN"
else
RSYNC_LAUNCH="setsid rsync"
fi
# Helper to log timestamped messages
log_event() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$EVENT_LOG"
}
# This function runs rsync and logs it
run_rsync() {
log_event "Starting rsync process..."
$RSYNC_LAUNCH -avh \
--exclude='@eaDir/' \
--exclude='@SynoResource/' \
--exclude='@SynoRecycle/' \
--exclude='.AppleDouble/' \
--exclude='.DS_Store' \
--exclude='Thumbs.db' \
--exclude='._*' \
--exclude='.Spotlight-V100/' \
--exclude='.Trashes/' \
"$SOURCE" "$DEST" \
--log-file="$RSYNC_LOG" &
RSYNC_PID=$!
RSYNC_PID=$!
echo "$RSYNC_PID" > /tmp/rsync_pid
log_event "Started rsync with PID $RSYNC_PID"
}
get_dir_size_bytes() {
dir="$1"
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS: use du -sk and convert KB to bytes
du -sk "$dir" | awk '{print $1 * 1024}'
else
# Linux/Synology: use du -sb for exact byte count
du -sb "$dir" | awk '{print $1}'
fi
}
wait_for_disk_idle() {
device="$1"
low_write_threshold_kbps=1024
low_await_threshold_ms=50
required_idle_checks=2
idle_checks=0
log_event "Waiting for disk $device to become idle..."
while true; do
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS: no await data, only write KB/s
stats=$(iostat -d 1 2 | awk -v dev="$device" '$1 == dev' | tail -1)
write_kbps=$(echo "$stats" | awk '{print int($3)}') # macOS shows r/s, w/s, rKB/s, wKB/s
w_await=0 # Not available on macOS
else
# Linux/Synology
stats=$(iostat -dkx 1 2 | awk -v dev="$device" '$1 == dev' | tail -1)
write_kbps=$(echo "$stats" | awk '{print int($9)}') # write KB/s
w_await=$(echo "$stats" | awk '{print int($13)}') # await time in ms
fi
log_event "$device write speed: ${write_kbps} KB/s, w_await: ${w_await} ms"
if (( write_kbps < low_write_threshold_kbps && w_await < low_await_threshold_ms )); then
idle_checks=$((idle_checks + 1))
log_event "Idle check $idle_checks / $required_idle_checks"
else
idle_checks=0
log_event "Still busy. Resetting idle check counter."
fi
if (( idle_checks >= required_idle_checks )); then
log_event "Disk $device is idle."
break
fi
sleep 5
done
}
# This function checks the speed and restarts rsync if necessary
check_speed() {
bytes_before=$(get_dir_size_bytes "$DEST")
sleep "$INTERVAL_SEC"
bytes_after=$(get_dir_size_bytes "$DEST")
bytes_diff=$((bytes_after - bytes_before))
mbps=$((bytes_diff / INTERVAL_SEC / 1024 / 1024))
log_event "Transfer speed: ${mbps} MB/s (Δ: $((bytes_diff / 1024 / 1024)) MB over $INTERVAL_SEC sec)"
if (( mbps < SPEED_THRESHOLD_MB )); then
log_event "Transfer speed dropped below ${SPEED_THRESHOLD_MB} MB/s. Restarting rsync."
if kill -0 $RSYNC_PID 2>/dev/null; then
log_event "Killing rsync process group for PID $RSYNC_PID"
kill -- -$RSYNC_PID 2>/dev/null # graceful group kill
sleep 2
kill -9 -- -$RSYNC_PID 2>/dev/null # forceful group kill if still alive
else
log_event "rsync PID $RSYNC_PID not found — already exited?"
fi
sleep 5
pkill -f "[r]sync -avh"
if ps -p $RSYNC_PID > /dev/null; then
log_event "rsync still running after kill!"
else
log_event "rsync successfully terminated"
fi
wait_for_disk_idle sdq # Replace 'sdf' with your actual device find w df -h grep usb
sleep 5
run_rsync
fi
}
# Start rsync
run_rsync
# Wait a few minutes before the first speed check
FIRST_WAIT_SEC=600 # 10 minutes
log_event "Waiting $FIRST_WAIT_SEC seconds to allow rsync to initialize..."
sleep "$FIRST_WAIT_SEC"
# Monitor and restart if needed
while true; do
if ! kill -0 $RSYNC_PID 2>/dev/null; then
log_event "rsync process exited unexpectedly. Restarting..."
run_rsync
log_event "Waiting $FIRST_WAIT_SEC seconds after restart..."
sleep "$FIRST_WAIT_SEC"
else
check_speed
fi
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment