Created
March 31, 2025 14:11
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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