1#!/bin/bash
2
3MOUNT_POINT_PREFIX="/mnt/backup"
4TEST=1
5DEBUG=1
6
7# Check if the CSV file is provided
8if [ "$#" -ne 1 ]; then
9 echo "Usage: $0 <path_to_csv_file>"
10 exit 1
11fi
12
13CSV_FILE="$1"
14
15# Declare associative arrays to hold paths and rsync command for each drive
16declare -A INCLUDE_PATHS
17declare -A EXCLUDE_PATHS
18declare -A RSYNC_CMDS
19
20if [ $TEST -eq 1 ]; then
21 echo "Reading index $CSV_FILE"
22fi
23
24# Read the CSV file line by line
25while IFS=',' read -r FILE_PATH DRIVE _; do
26 # Trim whitespace
27 FILE_PATH=$(echo "$FILE_PATH" | xargs)
28 DRIVE=$(echo "$DRIVE" | xargs)
29
30 # Check if row is commented with a #
31 if [[ "$FILE_PATH" == "#"* ]]; then
32 continue
33 fi
34
35 # Check if path begins with a slash
36 if [[ "$FILE_PATH" != /* ]]; then
37 echo "File path $FILE_PATH is not relative - exiting"
38 exit 1
39 fi
40
41 # Determine the actual drive path
42 if [[ "$DRIVE" =~ ^- ]]; then
43 # Exclusion case
44 DRIVE=${DRIVE:1} # Remove the leading '-'
45 EXCLUDE_PATHS["$FILE_PATH"]=1
46 else
47 # Inclusion case
48 INCLUDE_PATHS["$FILE_PATH"]="$DRIVE"
49 RSYNC_CMDS["$DRIVE"]=""
50 fi
51done < "$CSV_FILE"
52
53
54# Now process the paths to copy
55for FILE_PATH in "${!INCLUDE_PATHS[@]}"; do
56 DRIVE="${INCLUDE_PATHS[$FILE_PATH]}"
57 MOUNT_POINT="$MOUNT_POINT_PREFIX$DRIVE"
58
59 # Check if the mount point exists
60 if [[ $TEST -eq 0 && $(mountpoint -q "$MOUNT_POINT") ]]; then
61 echo "Nothing mounted to $MOUNT_POINT - skipping $FILE_PATH"
62 continue
63 fi
64
65 # Check if the path is excluded
66 if [[ -n "${EXCLUDE_PATHS[$FILE_PATH]}" ]]; then
67 echo "Excluding $FILE_PATH"
68 continue
69 fi
70
71 # Get parent directory
72 PARENT="$(dirname "$FILE_PATH")"
73 MKDIR_CMD="mkdir -p $MOUNT_POINT$PARENT"
74 if [ $DEBUG -eq 1 ]; then
75 echo "$MKDIR_CMD"
76 if [ $TEST -eq 0 ]; then
77 eval "$MKDIR_CMD"
78 fi
79
80 # Check if the path is a directory
81 EXCLUDE_ARGS=()
82 if [ -d "$FILE_PATH" ]; then
83 for EXCLUDE_PATH in "${!EXCLUDE_PATHS[@]}"; do
84 if [[ "$EXCLUDE_PATH" =~ ^"$FILE_PATH"* ]]; then
85 if [ -d "$EXCLUDE_PATH" ] && [[ ! "$EXCLUDE_PATH" =~ /$ ]]; then
86 EXCLUDE_ARGS+=("--exclude=\"$EXCLUDE_PATH/\"")
87 else
88 EXCLUDE_ARGS+=("--exclude=\"$EXCLUDE_PATH\"")
89 fi
90 if [[ $TEST -eq 1 ]]; then
91 echo "Excluding $EXCLUDE_PATH from $FILE_PATH"
92 fi
93 fi
94 done
95 fi
96 RSYNC_CMD="rsync -Par"
97 if [[ $TEST -eq 1 ]]; then
98 RSYNC_CMD+="vn"
99 if [[ $DEBUG -eq 0 ]]; then
100 RSYNC_CMD+="q"
101 fi
102 RSYNC_CMD+=" "${EXCLUDE_ARGS[@]}" \"$FILE_PATH\" \"$MOUNT_POINT$PARENT/\""
103 if [[ -n "${RSYNC_CMDS[$DRIVE]}" ]]; then
104 RSYNC_CMDS["$DRIVE"]+=" & $RSYNC_CMD"
105 else
106 RSYNC_CMDS["$DRIVE"]="$RSYNC_CMD"
107 fi
108done
109
110for DRIVE in "${!RSYNC_CMDS[@]}"; do
111 if [[ $DEBUG -eq 1 ]]; then
112 echo "${RSYNC_CMDS[$DRIVE]}"
113 fi
114 if [[ $TEST -eq 0 ]]; then
115 eval "${RSYNC_CMDS[$DRIVE]}" &
116 fi
117done
118
119wait
120
121echo ""
122
123for DRIVE in "${!RSYNC_CMDS[@]}"; do
124 DRIVE_SERIAL="$(lsblk -o MOUNTPOINT,SERIAL | awk -v target="$MOUNT_POINT_PREFIX$DRIVE" '$1 == target { getline; print $1; exit }')"
125 echo "-------------------------------------------"
126 echo "Backup summary for Cold Backup Drive $DRIVE"
127 echo "-------------------------------------------"
128 echo ""
129 echo "S/N $DRIVE_SERIAL"
130 echo "Backup finished $(date)"
131 echo ""
132 for FILE_PATH in "${!INCLUDE_PATHS[@]}"; do
133 if [[ "${INCLUDE_PATHS[$FILE_PATH]}" == "$DRIVE" ]]; then
134 echo "$FILE_PATH ("$(du -sh "$FILE_PATH" 2>/dev/null | cut -f1)")"
135 if [ -d "$FILE_PATH" ]; then
136 for EXCLUDE_PATH in "${!EXCLUDE_PATHS[@]}"; do
137 if [[ "$EXCLUDE_PATH" =~ ^"$FILE_PATH"* ]]; then
138 echo " Excluding .${EXCLUDE_PATH:${#FILE_PATH}} ($(du -sh "$EXCLUDE_PATH" 2>/dev/null | cut -f1))"
139 fi
140 done
141 fi
142 fi
143 done
144 echo ""
145 echo ""
146done