From: Andrew Lorimer Date: Mon, 24 Nov 2025 09:32:58 +0000 (+1100) Subject: add zfs backup script X-Git-Url: https://git.lorimer.id.au/scripts.git/diff_plain/b4fa151d6120b6f29c9a87ea5d10292e4d313db2 add zfs backup script --- diff --git a/backup-tank-2.sh b/backup-tank-2.sh new file mode 100755 index 0000000..0ac49d1 --- /dev/null +++ b/backup-tank-2.sh @@ -0,0 +1,146 @@ +#!/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 " + 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