--- /dev/null
+#!/bin/bash
+
+MOUNT_POINT_PREFIX="/mnt/backup"
+TEST=1
+DEBUG=1
+
+# Check if the CSV file is provided
+if [ "$#" -ne 1 ]; then
+ echo "Usage: $0 <path_to_csv_file>"
+ exit 1
+fi
+
+CSV_FILE="$1"
+
+# Declare associative arrays to hold paths and rsync command for each drive
+declare -A INCLUDE_PATHS
+declare -A EXCLUDE_PATHS
+declare -A RSYNC_CMDS
+
+if [ $TEST -eq 1 ]; then
+ echo "Reading index $CSV_FILE"
+fi
+
+# Read the CSV file line by line
+while IFS=',' read -r FILE_PATH DRIVE _; do
+ # Trim whitespace
+ FILE_PATH=$(echo "$FILE_PATH" | xargs)
+ DRIVE=$(echo "$DRIVE" | xargs)
+
+ # Check if row is commented with a #
+ if [[ "$FILE_PATH" == "#"* ]]; then
+ continue
+ fi
+
+ # Check if path begins with a slash
+ if [[ "$FILE_PATH" != /* ]]; then
+ echo "File path $FILE_PATH is not relative - exiting"
+ exit 1
+ fi
+
+ # Determine the actual drive path
+ if [[ "$DRIVE" =~ ^- ]]; then
+ # Exclusion case
+ DRIVE=${DRIVE:1} # Remove the leading '-'
+ EXCLUDE_PATHS["$FILE_PATH"]=1
+ else
+ # Inclusion case
+ INCLUDE_PATHS["$FILE_PATH"]="$DRIVE"
+ RSYNC_CMDS["$DRIVE"]=""
+ fi
+done < "$CSV_FILE"
+
+
+# Now process the paths to copy
+for FILE_PATH in "${!INCLUDE_PATHS[@]}"; do
+ DRIVE="${INCLUDE_PATHS[$FILE_PATH]}"
+ MOUNT_POINT="$MOUNT_POINT_PREFIX$DRIVE"
+
+ # Check if the mount point exists
+ if [[ $TEST -eq 0 && $(mountpoint -q "$MOUNT_POINT") ]]; then
+ echo "Nothing mounted to $MOUNT_POINT - skipping $FILE_PATH"
+ continue
+ fi
+
+ # Check if the path is excluded
+ if [[ -n "${EXCLUDE_PATHS[$FILE_PATH]}" ]]; then
+ echo "Excluding $FILE_PATH"
+ continue
+ fi
+
+ # Get parent directory
+ PARENT="$(dirname "$FILE_PATH")"
+ MKDIR_CMD="mkdir -p $MOUNT_POINT$PARENT"
+ if [ $DEBUG -eq 1 ]; then
+ echo "$MKDIR_CMD"
+ if [ $TEST -eq 0 ]; then
+ eval "$MKDIR_CMD"
+ fi
+
+ # Check if the path is a directory
+ EXCLUDE_ARGS=()
+ if [ -d "$FILE_PATH" ]; then
+ for EXCLUDE_PATH in "${!EXCLUDE_PATHS[@]}"; do
+ if [[ "$EXCLUDE_PATH" =~ ^"$FILE_PATH"* ]]; then
+ if [ -d "$EXCLUDE_PATH" ] && [[ ! "$EXCLUDE_PATH" =~ /$ ]]; then
+ EXCLUDE_ARGS+=("--exclude=\"$EXCLUDE_PATH/\"")
+ else
+ EXCLUDE_ARGS+=("--exclude=\"$EXCLUDE_PATH\"")
+ fi
+ if [[ $TEST -eq 1 ]]; then
+ echo "Excluding $EXCLUDE_PATH from $FILE_PATH"
+ fi
+ fi
+ done
+ fi
+ RSYNC_CMD="rsync -Par"
+ if [[ $TEST -eq 1 ]]; then
+ RSYNC_CMD+="vn"
+ if [[ $DEBUG -eq 0 ]]; then
+ RSYNC_CMD+="q"
+ fi
+ RSYNC_CMD+=" "${EXCLUDE_ARGS[@]}" \"$FILE_PATH\" \"$MOUNT_POINT$PARENT/\""
+ if [[ -n "${RSYNC_CMDS[$DRIVE]}" ]]; then
+ RSYNC_CMDS["$DRIVE"]+=" & $RSYNC_CMD"
+ else
+ RSYNC_CMDS["$DRIVE"]="$RSYNC_CMD"
+ fi
+done
+
+for DRIVE in "${!RSYNC_CMDS[@]}"; do
+ if [[ $DEBUG -eq 1 ]]; then
+ echo "${RSYNC_CMDS[$DRIVE]}"
+ fi
+ if [[ $TEST -eq 0 ]]; then
+ eval "${RSYNC_CMDS[$DRIVE]}" &
+ fi
+done
+
+wait
+
+echo ""
+
+for DRIVE in "${!RSYNC_CMDS[@]}"; do
+ DRIVE_SERIAL="$(lsblk -o MOUNTPOINT,SERIAL | awk -v target="$MOUNT_POINT_PREFIX$DRIVE" '$1 == target { getline; print $1; exit }')"
+ echo "-------------------------------------------"
+ echo "Backup summary for Cold Backup Drive $DRIVE"
+ echo "-------------------------------------------"
+ echo ""
+ echo "S/N $DRIVE_SERIAL"
+ echo "Backup finished $(date)"
+ echo ""
+ for FILE_PATH in "${!INCLUDE_PATHS[@]}"; do
+ if [[ "${INCLUDE_PATHS[$FILE_PATH]}" == "$DRIVE" ]]; then
+ echo "$FILE_PATH ("$(du -sh "$FILE_PATH" 2>/dev/null | cut -f1)")"
+ if [ -d "$FILE_PATH" ]; then
+ for EXCLUDE_PATH in "${!EXCLUDE_PATHS[@]}"; do
+ if [[ "$EXCLUDE_PATH" =~ ^"$FILE_PATH"* ]]; then
+ echo " Excluding .${EXCLUDE_PATH:${#FILE_PATH}} ($(du -sh "$EXCLUDE_PATH" 2>/dev/null | cut -f1))"
+ fi
+ done
+ fi
+ fi
+ done
+ echo ""
+ echo ""
+done