Update the post-run documentation to match reality
This commit is contained in:
		
							
								
								
									
										394
									
								
								ghetto/backup/imager.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										394
									
								
								ghetto/backup/imager.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,394 @@ | ||||
| #!/bin/bash | ||||
| # Create a recovery image for Linux using the System Rescue CD (www.system-rescue-cd.org) | ||||
| # | ||||
| # 30 September, 2016 by Orien "Nautical Hazard" Vandenbergh (orien.vandenbergh@lmco.com) | ||||
| # Modest improvements by Justin "Sleepy Sandman"  Mercier (justin.mercier@ndpgroup.com) | ||||
| # | ||||
| # This script will create a system image of your Linux system that can be restored from the same | ||||
| # recovery CD in the event of a failure. | ||||
| # | ||||
| # CAVEAT:  The target restore disk must be the same size or larger as the original | ||||
| # CAVEAT:  The target restore disk must have no existing partitions | ||||
| # CAVEAT:  Regardless of messages below, only NFSv3 (and not iSCSI) is currently supported | ||||
| # | ||||
| # USAGE: | ||||
| #  To BACKUP: | ||||
| #         0.  Disconnect any SAN connections or zone out non-boot LUNs | ||||
| #         1.  Make sure you have a mountable backup system (i.e. NAS or secondary disk) | ||||
| #         2.  Boot from the System Rescue CD media or image | ||||
| #         3.  If using NFS or iSCSI, run 'net-setup' to configure your network | ||||
| #         4.  Run this script and follow the prompts | ||||
| #         5.  Reconnect any SAN connections or zone in needed non-boot LUNs | ||||
| #         6.  Reboot | ||||
| # | ||||
| #  To RESTORE: | ||||
| #         0.  Disconnect any SAN connections or zone out non-boot LUNs | ||||
| #         1.  Make sure you have connectivity to the backup location (i.e. NAS or secondary disk) | ||||
| #         2.  Boot from the System Rescure CD media or image | ||||
| #         3.  If using NFS or iSCSI, run 'net-setup' to configure your network | ||||
| #         5.  Mount the backup target on /mnt/backup (i.e. mount -t nfs 192.168.10.20:/backups /mnt/backup) | ||||
| #         6.  Run the restore.sh file in the backup location to get the initial backup completed | ||||
| #         7.  Consult the restore.sh file in the backup location for further instructions | ||||
| #         8.  Reconnect any SAN connections or zone in needed non-boot LUNs | ||||
| #         9.  Reboot | ||||
| # | ||||
|  | ||||
| # Turn on extended glob magic | ||||
| shopt -s extglob | ||||
|  | ||||
| CONFIG="./imager.cfg" | ||||
|  | ||||
| # If the user didn't provide some values, use the ones from the configuration file | ||||
| function process_defaults() { | ||||
|   PARANOIA_DELAY="${PARANOIA_DELAY:-$DEF_PARANOIA_DELAY}" | ||||
|   NAS_Server="${NAS_Server:-$DEF_NAS_Server}" | ||||
|   NAS_Proto="${NAS_Proto:-$DEF_NAS_Proto}" | ||||
|   NAS_Volume="${NAS_Volume:-$DEF_NAS_Volume}" | ||||
|   NAS_Mount="${NAS_Mount:-$DEF_NAS_Mount}" | ||||
|   TARGET="${TARGET:-$DEF_Target}" | ||||
|  | ||||
|   DEST=$TARGET/$hostname | ||||
|   FSARCHIVE=$DEST/fsarchive.fsa | ||||
| } | ||||
|  | ||||
| # Retrieve a list of volumes exported by the specified NAS Server | ||||
| function volume_list() { | ||||
|   showmount --no-headers -e $NAS_Server | /usr/bin/awk '{print $1}' | ||||
| } | ||||
|  | ||||
| # Test whether the specified volume is in the list of available volumes | ||||
| function volume_available() { | ||||
|   desired=$1 | ||||
|   echo `volume_list | /bin/egrep -c "^$desired\$"` | ||||
| } | ||||
|  | ||||
| # Test whether the filesystem mounted at $NAS_Mount is the one we are going to try to mount | ||||
| function already_mounted() { | ||||
|   MATCH=`/bin/mount | grep " on $NAS_Mount "| /usr/bin/tail` | ||||
|   PRECURSOR=`echo $MATCH | /usr/bin/awk '{print $1}'` | ||||
|   echo $MATCH | grep -q "^$NAS_Server:$NAS_Volume on $NAS_Mount type $NAS_Proto" | ||||
| } | ||||
|  | ||||
| # Attempt to mount the NAS filesystem, if all the conditions are in place to allow that. | ||||
| function mount_nas_if_mountable() { | ||||
|   [ -d "$NAS_Mount" ]   || mkdir -p $NAS_Mount || bad_syntax "NAS Mountpoint '-m' doesn't exist and can't be created" | ||||
|   if mountpoint $NAS_Mount >/dev/null; then | ||||
|     already_mounted || bad_syntax "A filesystem '$PRECURSOR' is already mounted on our NAS Mountpoint, cancelling" | ||||
|   else | ||||
|     mount -o nolock -t $NAS_Proto $NAS_Server:$NAS_Volume $NAS_Mount || die "Could not mount NAS!" | ||||
|   fi | ||||
| } | ||||
|  | ||||
| # Ensure all required arguments are provided and sane | ||||
| function validate_parameters() { | ||||
|   [ -z "$hostname" ]    && bad_syntax "No hostname '-h' specified" | ||||
|   [ -z "$TARGET" ]      && bad_syntax "No target directory '-t' specified" | ||||
|   [ -z "$NAS_Proto" ]   && bad_syntax "No NAS Protocol '-p' specified" | ||||
|   [ -z "$NAS_Server" ]  && bad_syntax "No NAS Server '-s' specified" | ||||
|   [ -z "$NAS_Volume" ]  && bad_syntax "No NAS Volume '-v' specified" | ||||
|   [ -z "$NAS_Mount" ]   && bad_syntax "No NAS Mountpoint '-m' specified" | ||||
|  | ||||
|   # Ensure that the volume specified is one of the volumes exported to us | ||||
|   [ `volume_available $NAS_Volume` -eq 1 ] || bad_volume | ||||
|   mount_nas_if_mountable | ||||
|  | ||||
|   # At this point, the NAS filesystem is mounted, and everything should be ready for us | ||||
|   [ ! -d "$TARGET" ]    && bad_syntax "Target directory specified with '-d' does not exist" | ||||
|   # An existing backup will prevent execution, we don't want to stomp a backup | ||||
|   [ -d "$DEST" ]        && bad_syntax "Backup directory '$DEST' already exists, please remove before running again" | ||||
| } | ||||
|  | ||||
| # Load the settings from the configuration file | ||||
| function load_configuration() { | ||||
|   [ -f $CONFIG ] && source $CONFIG | ||||
| } | ||||
|  | ||||
| # Display the countdown prior to making changes | ||||
| function countdown() { | ||||
|   # Generate the paranoia sequence | ||||
|   function pseq() { | ||||
|     seq `echo "$PARANOIA_DELAY - 1" | bc` -1 0 | ||||
|   } | ||||
|   # Countdown to execution to allow the user to abort | ||||
|   echo "We're about to do work, your warranty is about to expire..." | ||||
|   echo "$PARANOIA_DELAY seconds... (Press CTRL-C to abort)" | ||||
|   # (JFM) We should make this a while so it can be loop detected... | ||||
|   # Maybe also multiply by 1000 so we can do progressional updates. | ||||
|  | ||||
|   for i in `pseq`; do | ||||
|     sleep 1 | ||||
|     printf "\r$i seconds...\b" | ||||
|   done | ||||
|   echo -e "\rLudicrous Speed, GO!\b" | ||||
|   echo "" | ||||
| } | ||||
|  | ||||
| # A function to convert newlines to spaces (useful when invoking various syscalls herein) | ||||
| function nltos() { | ||||
|   sed -e ':a;N;$!ba;s/\n/ /g' | ||||
| } | ||||
|  | ||||
| # Basically the help function that is called when arguments are not as expected | ||||
| function display_syntax() { | ||||
|   echo "Syntax: $0 -h <hostname> [ -t <target> ] [ -m <nas_mount> ]" # Our little secret [ -p <nas_proto> ]" | ||||
|   echo "              [ -s <nas_server> ] [ -v <nas_volume> ] [ -d <delay> ]" | ||||
|   echo "" | ||||
|   echo "<hostname>   the name of the host you are creating the image for" | ||||
|   echo "<target>     the directory into which the backup should be created" | ||||
|   echo "             backups are placed into \$target/\$hostname/ (created as needed)" | ||||
|   echo "             [$DEF_Target]" | ||||
|   # Secret feature, don't tell anyone | ||||
|   #echo "<nas_proto>  Protocol NAS server uses to share (nfs only) [$DEF_NAS_Proto]" | ||||
|   echo "<nas_server> IP Address of the server sharing the backups filesystem [$DEF_NAS_Server]" | ||||
|   echo "<nas_volume> Which volume from the server should we mount [$DEF_NAS_Volume]" | ||||
|   echo "<nas_mount>  Which directory should the NAS storage be mounted on [$DEF_NAS_Mount]" | ||||
|   echo "<delay>      how long to delay before engaging in destructive behavior" | ||||
|   echo "" | ||||
|   exit 2 | ||||
| } | ||||
|  | ||||
| # die function to echo a message and exit with generic error | ||||
| function die() { | ||||
|    echo "[FATAL] - $@ ...exiting." | ||||
|    exit 1 | ||||
| } | ||||
| function bad_syntax() { | ||||
|   echo $@ | ||||
|   echo "" | ||||
|   display_syntax | ||||
| } | ||||
| function bad_volume() { | ||||
|   echo "Mountpoint selected is not available from the specified server" | ||||
|   echo "" | ||||
|   echo "Server: $NAS_Server" | ||||
|   echo "Mount requested: $NAS_Mount" | ||||
|   echo "Mounts available: " `volume_list` | ||||
|   echo "" | ||||
|   display_syntax | ||||
| } | ||||
|  | ||||
| # Make sure we be root, or else exit with non-zero status (5) | ||||
| if [[ $EUID -ne 0 ]]; then | ||||
|   echo "This script must be run as root, figure out a way to make that work..." | ||||
|   #exit 5 | ||||
| fi | ||||
|  | ||||
| # Parse the provided command line arguments | ||||
| while getopts "c:h:d:m:o:p:s:t:v:" opt; do | ||||
|   case $opt in | ||||
|     c) | ||||
|       # Override the default filename for the configuration file | ||||
|       CONFIG=$OPTARG | ||||
|       ;; | ||||
|     h) | ||||
|       # Parse the hostname from the arguments since the rescue CD has no idea what the local system is | ||||
|       hostname=$OPTARG | ||||
|       ;; | ||||
|     d) | ||||
|       PARANOIA_DELAY=$OPTARG | ||||
|       ;; | ||||
|     m) | ||||
|       NAS_Mount=$OPTARG | ||||
|       ;; | ||||
|     o) | ||||
|       if [ "$OPTARG" = "〠" ]; then | ||||
|         echo "Oh, you fancy now! The files are IN the computer!" | ||||
|       fi | ||||
|       ;; | ||||
|     p) | ||||
|       NAS_Proto=$OPTARG | ||||
|       ;; | ||||
|     s) | ||||
|       NAS_Server=$OPTARG | ||||
|       ;; | ||||
|     t) | ||||
|       # The NAS or secondary disk mount location, or wherever you would like | ||||
|       # you backups to go, to which $hostname is appended. | ||||
|       TARGET=$OPTARG | ||||
|       ;; | ||||
|     v) | ||||
|       NAS_Volume=$OPTARG | ||||
|       ;; | ||||
|   esac | ||||
| done | ||||
| # Load the defailts from the configuration file | ||||
| load_configuration | ||||
| # Use the defaults if the user didn't specify an override | ||||
| process_defaults | ||||
| # Validate that the parameters we're about to use are semi-valid | ||||
| validate_parameters | ||||
|  | ||||
| clear | ||||
| echo "Welcome to the Icebergh Imager!" | ||||
| echo "This utility will assist you in creating a rescue image for this system." | ||||
| echo "Sadly, only backup to NAS over NFS is currently supported." | ||||
| echo | ||||
|  | ||||
| # Inventory the local disk drives (WARNING: This may not be SAN safe!) | ||||
| DISKGUESS=`(cd /dev/; ls [shv]d[a-z]* | grep -v '[0-9]') | nltos` | ||||
| PARTGUESSRAW=`lsblk -nl | grep -v 'dm' | awk '$6 == "part" { print $1 }' | nltos` | ||||
|  | ||||
| # Inventory the LVM objects | ||||
| PVS=`vgs -o +pv_name --noheadings | awk '{print $NF}' | nltos | sed -e 's/\/dev\///g'` | ||||
| VGS=`vgs --noheadings | awk '{print $1}'` | ||||
| LVS=`find /dev/mapper -type l | grep -v swap | nltos` | ||||
|  | ||||
| # Attempt to determine the boot device | ||||
| PARTGUESSFS=$PARTGUESSRAW | ||||
| for pv in $PVS; do | ||||
|   PARTGUESSFS=${PARTGUESSFS//$pv?()} | ||||
| done | ||||
|  | ||||
| # Make sure the filesystem is supported | ||||
| for part in $PARTGUESSFS; do | ||||
|   TYPE="" | ||||
|   eval $(blkid /dev/$part | awk ' { print $3 } ') | ||||
|   if [[ $FS == *"$TYPE"* ]] && [ -n "$TYPE" ]; then | ||||
|     PARTGUESS="$PARTGUESS $part" | ||||
|   fi | ||||
| done | ||||
|  | ||||
| # I have no idea what this does.  Orien? | ||||
| PARTGUESS=`echo $PARTGUESS | xargs` | ||||
|  | ||||
| # Prompt the user to specify which disks to back up, defaulting to the above detection logic. | ||||
| printf "Which disk(s) would you like to backup? [$DISKGUESS]: " | ||||
| read DISKS | ||||
| echo | ||||
|  | ||||
| # Now that we have LVM covered, let's handle traditional filesystems such as /boot | ||||
| echo "Now we need a list of \"raw\" partitions to back up.  The utility we're using" | ||||
| echo "'fsarchiver' doesn't support backing up swap (for good reason).  If you" | ||||
| echo "specify a swap partition here, the backup will fail.  In addition, we detect" | ||||
| echo "the LVM partitions automatically, so only specify the non-lvm, non-swap" | ||||
| echo "partitions.  Generally this is only the device for /boot (normally sda1)" | ||||
| echo "" | ||||
|  | ||||
| # Prompt the user to override the auto-detected LVMs | ||||
| printf "Which non-lvm partitions [$PARTGUESS]: " | ||||
| read PARTS | ||||
| echo | ||||
|  | ||||
| # If no disks are specified, exit with help | ||||
| if [ -z "$DISKS" ]; then | ||||
|   DISKS=$DISKGUESS | ||||
|   if [ -z "$DISKS" ]; then | ||||
|     echo "No source disks specified" | ||||
|     display_syntax | ||||
|   fi | ||||
| fi | ||||
|  | ||||
| # If no partitions are specified, exit with help | ||||
| if [ -z "$PARTS" ]; then | ||||
|   PARTS=$PARTGUESS | ||||
|   if [ -z "$PARTS" ]; then | ||||
|     echo "No source partitions specified" | ||||
|     display_syntax | ||||
|   fi | ||||
| fi | ||||
|  | ||||
| # We now have all the info we need (WTWTCH), so we perform the backup | ||||
| echo "Performing a backup of the following to the directory '$DEST':" | ||||
| echo "  Disks: $DISKS" | ||||
| echo "  Partitions: $PARTS" | ||||
| echo "  Volume Groups: $VGS" | ||||
| echo "  Logical Volumes: $LVS" | ||||
| echo "" | ||||
| echo "" | ||||
|  | ||||
| # Display the cancellable countdown | ||||
| countdown | ||||
|  | ||||
| echo "-- [$DEST] Creating directory" | ||||
| mkdir $DEST | ||||
| echo "" | ||||
|  | ||||
| # This seems like an exercise in futility that can be trimmed since | ||||
| # we still need to reinstall grub | ||||
| for disk in $DISKS; do | ||||
|   echo "-- [$disk] Backing up MBR" | ||||
|   dd if=/dev/$disk of=$DEST/$disk.mbr bs=1M count=2 | ||||
|   echo "-- [$disk] Backing up partition layout" | ||||
|   sfdisk -d /dev/$disk > $DEST/$disk.part | ||||
| done | ||||
| echo "" | ||||
|  | ||||
| # Backup the LVM config to a flat file that we can reuse | ||||
| for vg in $VGS; do | ||||
|   echo "-- [$vg] Backing up LVM layout..." | ||||
|   vgcfgbackup -f $DEST/$vg.vgcfg $vg | ||||
| done | ||||
| echo "" | ||||
|  | ||||
| # Dump the requested filesystem(s) to a fsarchiver filesystem archive (FSAi) file | ||||
| echo "-- Starting fsarchiver, this will take some time..." | ||||
| LABEL=`date +"$hostname-%Y%m%d%H%M%S-backup"` | ||||
| FULLPARTS=`echo $PARTS | sed 's/[^ ]* */\/dev\/&/g'` | ||||
| fsarchiver savefs -j4 -L "$LABEL" -o $FSARCHIVE $FULLPARTS $LVS | ||||
|  | ||||
| echo "-- Compiling restore data..." | ||||
| echo "#!/bin/bash" > $DEST/restore.sh | ||||
| chmod +x $DEST/restore.sh | ||||
| echo "# There, now it's easy be careful out there" >> $DEST/restore.sh | ||||
| echo "" >> $DEST/restore.sh | ||||
| echo "DEST=$DEST" >> $DEST/restore.sh | ||||
|  | ||||
| for disk in $DISKS; do | ||||
|   echo "dd if=\$DEST/$disk.mbr of=/dev/$disk bs=1M count=2" >> $DEST/restore.sh | ||||
|   echo "sfdisk /dev/$disk < \$DEST/$disk.part" >> $DEST/restore.sh | ||||
| done | ||||
|  | ||||
| for vg in $VGS; do | ||||
|   pv=`vgs -o +pv_name --noheadings $vg | awk '{print $NF}'` | ||||
|   uuid=`pvs -o +uuid --noheadings $pv | awk '{print $NF}'` | ||||
|   echo "pvcreate --uuid $uuid --restorefile \$DEST/$vg.vgcfg $pv" >> $DEST/restore.sh | ||||
|   echo "vgcfgrestore -f \$DEST/$vg.vgcfg $vg" >> $DEST/restore.sh | ||||
|   echo "vgchange -a y $vg" >> $DEST/restore.sh | ||||
| done | ||||
|  | ||||
| id=0 | ||||
| RESTOREFLAGS="" | ||||
| for fs in $FULLPARTS $LVS; do | ||||
|   if [ -z $RESTOREFLAGS ]; then | ||||
|     RESTOREFLAGS="id=$id,dest=$fs" | ||||
|   else | ||||
|     RESTOREFLAGS="$RESTOREFLAGS id=$id,dest=$fs" | ||||
|   fi | ||||
|   id=`echo "$id + 1" | bc` | ||||
| done | ||||
|  | ||||
| echo "fsarchiver restfs \$DEST/fsarchive.fsa $RESTOREFLAGS" >> $DEST/restore.sh | ||||
|  | ||||
| echo "echo All done, reboot to experience your system as it was before it was imaged" >> $DEST/restore.sh | ||||
| echo "echo" >> $DEST/restore.sh | ||||
|  | ||||
| # This has be resolved by grabbing a bigger chunk of the disk header" | ||||
| #echo "" >> $DEST/restore.sh | ||||
| #echo "echo Please run the rest of the commands in restore.sh manually." >> $DEST/restore.sh | ||||
| #echo "# Now for the hard part, I know you can do it!" >> $DEST/restore.sh | ||||
| #echo "#   mkdir /mnt/backup" >> $DEST/restore.sh | ||||
| #echo "#   mount /dev/mapper/<system-root> /mnt/backup" >> $DEST/restore.sh | ||||
| #echo "#   mount -o bind /dev /mnt/backup/dev" >> $DEST/restore.sh | ||||
| #echo "#   chroot /mnt/backup /bin/bash" >> $DEST/restore.sh | ||||
| #echo "#   mount <system-boot> /boot" >> $DEST/restore.sh | ||||
| #echo "#   grub-install <boot-device (/dev/sda)>" >> $DEST/restore.sh | ||||
| #echo "#   umount /mnt/backup/boot" >> $DEST/restore.sh | ||||
| #echo "#   exit" >> $DEST/restore.sh | ||||
| #echo "#   umount /mnt/backup/dev" >> $DEST/restore.sh | ||||
| #echo "#   umount /mnt/backup" >> $DEST/restore.sh | ||||
| #echo "#" >> $DEST/restore.sh | ||||
| #echo "# The good news is you're done.  umount the partition the backups live" >> $DEST/restore.sh | ||||
| #echo "# on, repeat the team mantra (WTWTCH), and reboot." >> $DEST/restore.sh | ||||
|  | ||||
| echo "" | ||||
| echo "" | ||||
| fsarchiver archinfo $FSARCHIVE | more | ||||
| echo "" | ||||
| echo "Does this look right to you?" | ||||
| echo "" | ||||
| ls -l $DEST | ||||
| echo "" | ||||
| echo "If it does, you're free to reboot, otherwise, fix it now!" | ||||
|  | ||||
| umount $NAS_Mount 2>/dev/null | ||||
| exit 0 | ||||
		Reference in New Issue
	
	Block a user