#!/bin/sh

#############################################################################
#
# VMware Backup Script written by Julian Field (c) 2009.
# For help contact <Jules@ecs.soton.ac.uk>.
#
# You may do what you like with this script, except make any profit from it
# at all in any way whatsoever, including but not limited to incorporating
# any part of it in any product you sell, rent, loan or lease for money.
#
# You will need to change
# 1. The NFS definitions at the top of this script.
# 2. The "Cycle" definition underneath it if you don't want a rotating 0-4
#    backup daily structure.
# 3. The code labelled "README" around line 270, which chooses which
#    datastores to backup.
#
# For command-line usage, run the command with the "--help" option as the
# only argument on the command-line.
#
#############################################################################

#


# Definition of NFS share that is backup destination
NFSStoreName=NFS-Backup2
NFSServer=delirium
NFSShare=/mystere2

# Change the next bit of code to produce a "Backup Name" or number
# We use a cycle of 0,1,2,3,4 moving to the next one each day
Cycle=`date +%s`
Cycle=`expr $Cycle / 86400`
Cycle=`expr $Cycle % 5`

#############################################################################

PATH=/bin:/sbin
export PATH
DATE=`date +%Y-%m-%d`

# Print the command-line syntax
Usage() {
  echo
  echo Usage: $0 '[ --off ] [ --on ] [ --compress ]'
  echo
  echo '--off      = Backup VMs which are powered off, and templates'
  echo '--on       = Backup VMs which are powered on'
  echo '--compress = Compress backed up disk files'
  echo
  echo 'Enabling compression will put more CPU load on your VMware host,'
  echo 'but will reduce the quantity of NFS traffic and save disk space'
  echo 'on your NFS backup datastore.'
  echo
}

# Process command-line arguments of --help --on and --off and --compress
# --help     ==> Print the command-line usage and exit
# --on       ==> Backup Powered On  VMs
# --off      ==> Backup Powered Off VMs
# --compress ==> Gzip up the .vmdk files
CommandLine() {
  BackupPoweredOnVMs=0
  BackupPoweredOffVMs=0
  Compress=0
  if [ "$1" = "--help" -o "$2" = "--help" -o "$3" = "--help" ]; then
    Usage
    exit 1
  fi
  if [ "$1" = "--on" -o "$2" = "--on" -o "$3" = "--on" ]; then
    BackupPoweredOnVMs=1
  fi
  if [ "$1" = "--off" -o "$2" = "--off" -o "$3" = "--off" ]; then
    BackupPoweredOffVMs=1
  fi
  if [ "$1" = "--compress" -o "$2" = "--compress" -o "$3" = "--compress" ]; then
    Compress=1
  fi
}

# Get a list of all the VMs and produce a list of
# <numeric id>\t<path of .vmx file>
# in /tmp/ECS-Backups-VMlist.txt.$$
ListVMIDs() {
  vim-cmd vmsvc/getallvms | \
  sed -e 's/[[:blank:]]\{3,\}/\t/g' | \
  cut -f 1,3 | \
  sed -e '1,1d' | \
  sed -e 's/\[/\/vmfs\/volumes\//' | \
  sed -e 's/\] /\//' > /tmp/ECS-Backups-VMlist.txt.$$
}

# Mount the NFS datastore to store all the backups in
MountNFS() {
  # First test if it is already mounted
  if ( vim-cmd hostsvc/summary/fsvolume | \
       awk '{ print $1 }' | \
       fgrep -q $NFSStoreName ); then
    echo NFS Backup store already mounted
    NFSMOUNTED=0
  else
    echo NFS mounting ${NFSServer}:$NFSShare on /vmfs/volumes/$NFSStoreName
    # 0 on the end means read/write, 1 = read-only
    vim-cmd hostsvc/datastore/nas_create $NFSStoreName $NFSServer $NFSShare 0
    if [ ! $? -eq 0 ]; then
      echo NFS mount failed, terminating.
      exit 1
    fi
    # Now test that we can actually write to the new NFS store
    mkdir /vmfs/volumes/$NFSStoreName/$Cycle
    ls -l /vmfs/volumes/$NFSStoreName/$Cycle
    if [ ! $? -eq 0 ]; then
      echo mkdir on NFS mount failed, terminating.
      exit 1
    fi
    NFSMOUNTED=1
  fi
}

# Print a list of all the .vmdk files that aren't snapshots disks.
FindVMDKs() {
  for VMDK in *vmdk
  do
    # Measure the size of the .vmdk file
    #echo VMDK = \"$VMDK\"
    SIZE=`ls -l "$VMDK" | awk '{ print $5 }'`
    if [ `expr "$SIZE" \< 100000` -eq 1 ]; then
      # .vmdk is smaller than 100Kbytes so it's a config file
      grep VMFS "$VMDK" | \
      sed -e 's/^[^\"]*\"//' -e 's/\"[^\"]*$//' | \
      while read FLAT
      do
        # Add all but the "deltas" to the file list.
        #echo FLAT file is \"$FLAT\"
        echo $FLAT | sed -e '/-delta/d'
      done
      # Backup config .vmdk files
      echo $VMDK
    fi
  done
}

# Find the list of .vmx or .vmtx files and the Virtual machine ID.
# Writes results into $VMXLIST and $VMID
FindVMXList() {
  # Translate the .vmx path into a numeric id of the VM.
  # Templates have a .vmtx instead of a .vmx.
  # Assumption: You do not have multiple VMs in the same directory, but
  #             you can have multiple .vmx files in there.
  ISATEMPLATE=0
  DOBACKUP=1
  VMXLIST=`ls -d *.vmx 2>/dev/null`
  if [ -z "$VMXLIST" ]; then
    # No .vmx files so it must be a template with .vmtx files
    VMXLIST=`ls -d *.vmtx 2>/dev/null`
    ISATEMPLATE=1
    # Templates are powered off by definition
    if [ $BackupPoweredOffVMs -eq 0 ]; then
      DOBACKUP=0
    fi
    VMXFILE="$VMXLIST"
    echo The VM is a template described in $VMXFILE
  else
    # There are one or more .vmx files, get the VMID from all of them,
    # assuming only 1 is the real one with a matching VMID.
    # Assumption: You do not have several VMs in the same directory.
    VMID=
    for VMX in *.vmx
    do
      if [ ! -z "${VMX}" ]; then
        POSSIBLEVMID=`fgrep "/vmfs/volumes/$VOL/$VM/$VMX" /tmp/ECS-Backups-VMlist.txt.$$ | \
                      cut -f1 | head -1`
        if [ ! -z "$POSSIBLEVMID" ]; then
          # Found a real registered .vmx file
          VMID="$POSSIBLEVMID"
          VMXFILE="$VMX"
          echo Virtual Machine ID of $VMX = $VMID
        fi
      fi
    done
  fi
}
        
# If it is a VM and not a template, and it is powered on, then take a snapshot
SnapshotVM() {
  ###########################################
  # If it is not a template, then snapshot it
  TOOKSNAPSHOT=0
  if [ $ISATEMPLATE -eq 0 ]; then
    # Snapshot it
    if [ -z "$VMID" ]; then
      echo It does not have a machine id
      DOBACKUP=0
    else
      # Is it powered on?
      if ( vim-cmd vmsvc/power.getstate $VMID | fgrep -q 'Powered on' ); then
        # It is powered on. Do we need to back it up?
        echo It is powered on.
        if [ $BackupPoweredOnVMs -eq 0 ]; then
          TOOKSNAPSHOT=0
          DOBACKUP=0
        else
          echo Taking snapshot ECS-Backups-$VMID-$$ ...
          vim-cmd vmsvc/snapshot.create $VMID ECS-Backups-$VMID-$$ "$DATE ECS Backup service snapshot"
          TOOKSNAPSHOT=1
          DOBACKUP=1
        fi
      else
        echo VM was powered off, no snapshot needed
        # Do we need to back it up as it is powered off?
        if [ $BackupPoweredOffVMs -eq 1 ]; then
          DOBACKUP=1
        else
          DOBACKUP=0
        fi
      fi
    fi
  fi
}

# Backup the VM or template.
# We copy all the VMXFILEs and the log files and then copy/compress everything
# in ECS-Backups.filelist.$$ which is the list of files in this dir that
# need backing up.
BackupDir() {
  # Now back up all the files in the current directory whose names are in
  # /tmp/ECS-Backups.filelist.$$
  echo About to backup these files:
  echo $VMXFILE
  cat /tmp/ECS-Backups.filelist.$$

  TO=/vmfs/volumes/${NFSStoreName}/${Cycle}/"$VM"/
  mkdir -p "$TO"
  cp "$VMXFILE" "$TO"
  cp *.log "$TO"
  cat /tmp/ECS-Backups.filelist.$$ | \
  while read a
  do
    if [ $Compress -eq 1 ]; then
      echo Compressing $a to $TO
      gzip -c "$a" > ${TO}${a}.gz
    else
      echo Copying $a to $TO
      cp "$a" "$TO"
    fi
  done
}

# If we took a snapshot then delete it again
DeleteSnapshot() {
  # Now remove the snapshot again
  if [ $TOOKSNAPSHOT -eq 1 ]; then
    echo Removing snapshot ECS-Backups-$VMID-$$ ...
    vim-cmd vmsvc/snapshot.remove $VMID
  fi
}

# Umount the NFS datastore we stored all the backups in, if we mounted it
DismountNFS() {
  # Now unmount the NFS store if we mounted it
  if [ $NFSMOUNTED -eq 1 ]; then
    echo Dismounting NFS backup store /vmfs/volumes/$NFSStoreName
    vim-cmd hostsvc/datastore/destroy $NFSStoreName
  fi
}

##############################################################################
# Main code starts here
##############################################################################

# Process command-line arguments --help, --on, --off and --compress
CommandLine $1 $2 $3

ListVMIDs   # List all the .vmx files and their VM numeric ids
MountNFS    # Mount the NFS datastore we are backing up to

# Loop through each of the datastores. All of ours have "localDisk" or "store"
# in their name.
##############################################################################
# README: You will probably need to change the next 2 lines
cd /vmfs/volumes
for VOL in mystere-localDisk2 # *localDisk* *store*
##############################################################################
do
  cd "/vmfs/volumes/$VOL"
  # Foreach VM
  for VM in *
  do
    echo
    date
    echo Starting backup of $VM
    echo

    cd "/vmfs/volumes/$VOL"
    echo Considering `pwd`/${VM}

    # Is it really a VM directory
    if [ -d "/vmfs/volumes/$VOL/$VM" -a -e "/vmfs/volumes/$VOL/$VM/${VM}.vmdk" ]; then
      cd "/vmfs/volumes/$VOL/$VM"
      
      # Find all the flat .vmdk disk files to backup
      FindVMDKs > /tmp/ECS-Backups.filelist.$$
      
      # Find the list of .vmx or .vmtx files and the virtual machine ID
      FindVMXList

      # Take a snapshot if appropriate and necessary
      SnapshotVM
      
      # Backup the VM or template, we have a list of most of the files already
      if [ $DOBACKUP -eq 1 ]; then
        BackupDir
        DeleteSnapshot
      fi

      # Clean up temporary file
      rm -f /tmp/ECS-Backups.filelist.$$
    fi
  done
  echo
  date
  echo Finished backup of $VM
  echo ========================================================================
done

# Now unmount the NFS store if we mounted it
DismountNFS

echo
echo Backup job finished.
date

