#!/bin/bash # Overview of backup process # 1) Read and parse configuration file # 2) Loop through each backup # 3) Create a FS snapshot using LVM # 4) Mount the snapshot FS # 5) Mount our backup file in a loop filesystem (we can guarantee the space # will be present this way, and it allows selective recovery of files if # need be) # 6) rsync the differences between the snapshot FS and the backup FS # 7) Unmount the backup FS # 8) Unmount the snapshot FS # 9) Delete the snapshot LVM (leaving it will degrade performance) # 10) Repeat 3-9 if necessary # Save the old IFS variable so that we can restore it when we're done ORIGIFS=$IFS IFS=$(echo -en "\n\b") # User where the reports of the backup are sent to MAILTO=root # Read from the configuration file any exclude filters for the rsync output LOG_EXCLUDE=$(cat "$PWD/backup.conf" | egrep "^[ \t]*LOG_EXCLUDE=" | cut -d'=' -f 2-) LOG_FILE=/tmp/backup$$.log # Get each line from the configuration file one at a time, ignoring anything # that starts with a pound (#) sign for i in $(cat "$PWD/backup.conf" | egrep -v "^[ \t]*#.*|^[ \t]*LOG_EXCLUDE=.*"); do # Parse out the locations we need VOL=$(echo $i | sed "s@^\([^:]*\):\(.*\)@\1@") TARG=$(echo $i | sed "s@^\([^:]*\):\(.*\)@\2@") # Check to make sure the paths are valid [ -b $VOL ] || { echo "Source volume not valid: $VOL. Skipping"; continue; } [ -f $TARG ] || { echo "Target image not valid: $TARG. Skipping"; continue; } # Get necessary information about the source volume. # Get only the necessary line, and not the headers, and then compress the # info so it is only separated by a single space (makes finding fields # easier in the next steps). There will be an extra space at the beginning, # so fields are 1 greater than what they seem. # # Make sure the volume is a LVM Logical Volume lvs -a $VOL 2>/dev/null 1>/dev/null || { echo "$VOL not an LVM Logical Volume ($?). Skipping."; continue; } # Now we actually collect the info INFO=$(lvs -a $VOL | tail -n 1 | sed 's/[ ][ ]*/ /g') # Field 1: Logical Volume Name VOL_NAME=$(echo $INFO | cut -d' ' -f 2) # Field 2: Volume Group Name GROUP_NAME=$(echo $INFO | cut -d' ' -f 3) # Field 3: Attributes - Not needed right now, but may come in handy ATTRS=$(echo $INFO | cut -d' ' -f 4) # Field 4: Volume Size VOL_SIZE=$(echo $INFO | cut -d' ' -f 5) # Now we just create a name for the snapshot volume, and then we can # create it SNAP_NAME=${VOL_NAME}$$ SNAP_PATH=/dev/${GROUP_NAME}/${SNAP_NAME} lvcreate -s -L${VOL_SIZE} -n $SNAP_NAME $VOL >/dev/null || { echo "Snapshot of '${GROUP_NAME}/${SNAP_NAME}' failed ($?). Skipping."; continue; } # Mount our filesystems at temporary mount locations SNAP_MOUNT=/tmp/backup-$SNAP_NAME TARG_MOUNT=/tmp/backup-target$$ mkdir $SNAP_MOUNT || { echo "Failed to create snapshot mount point ($?). Skipping."; cleanup; continue; } mkdir $TARG_MOUNT || { echo "Failed to create target mount point ($?). Skipping."; cleanup; continue; } mount $SNAP_PATH $SNAP_MOUNT || { echo "Mounting snapshot failed ($?). Skipping."; cleanup; continue; } mount -o loop $TARG $TARG_MOUNT || { echo "Mounting target image failed ($?). Skipping."; cleanup; continue; } # Update the backup file system with files from the snapshot # IMPORTANT!!: The trailing slash on the source is necessary for it to # work in the expected way, or else all backups will end up within a # subdirectory with the name $SNAP_MOUNT rsync -aHEAXxv --stats --delete --ignore-errors $SNAP_MOUNT/ $TARG_MOUNT | \ egrep -v "$LOG_EXCLUDE" >> $LOG_FILE # Cleanup stuff is put into a function so that it can be called before # an exit call function cleanup() { # Unmount the backups and remove the temporary dirs # Errors may occur here, but we don't really care. Just pipe the errors # to /dev/null umount $SNAP_MOUNT 2>/dev/null 1>/dev/null umount $TARG_MOUNT 2>/dev/null 1>/dev/null [ -d $SNAP_MOUNT ] && rmdir $SNAP_MOUNT [ -d $TARG_MOUNT ] && rmdir $TARG_MOUNT # Delete the snapshot [ -b $SNAP_PATH ] && lvremove -f $SNAP_PATH >/dev/null } # Now call our cleanup routine on normal completions cleanup done # Mail the log file, and then delete the file mail -s "Backups on $(hostname)" $MAILTO < $LOG_FILE rm $LOG_FILE # Restore the IFS IFS=$ORIGIFS