Komplet backupscript til en server

Dette forum bruges på EGET ANSVAR til at lege med scripts og andre ting med risiko for at beskadige sit eget og andres systemer.
Brugeravatar
NickyThomassen
Admin
Indlæg: 3652
Tilmeldt: 5. mar 2010, 19:58
IRC nickname: nicky
Geografisk sted: 192.168.20.42

Komplet backupscript til en server

Indlæg af NickyThomassen »

Da forummet skulle rykkes over på en server, og jeg oprettede mit firma, fik jeg brug for en backupløsning, som
* er grundig
* kan fortsætte fornuftigt på trods af fejl
* kan modificeres til dagligdagen uden for meget arbejde
* kan bruges på flere systemer uden tilpasning

Løsningen blev et 471 liniers shell script, som jeg deler så andre måske også kan få glæde af det, eller blive inspireret til nye løsninger. Scriptet er grundigt testet, og har kørt på 2 servere i snart en måned uden at vise fejl.

Kode: Vælg alt

#!/bin/bash
#
# VPS backupscript
# Made by mad hound nicky
#
# Modular built, this script can easily be configured through it's header.
# It can back up the database and selected filesystem locations, while keeping and rotating
# optional local copys, and rotating remote copys through the use of FTP, if need be.
# Good luck.
#
# License = /dev/null
#
######################################################################################
# *** Start Settings ***
######################################################################################
#
# Set to anything but "yes" to deactivate sending mail
MAIL_SEND="yes"
#
# What's the mail of the server admin. Used in the body as a reply to, in case of problems
# Standard = SERVER_ADMIN_MAIL=""
SERVER_ADMIN_MAIL=""
#
# Who should recieve the mail  -  Multiple adresses --> "a@a.tld,b@b.tld"
# Standard = MAIL_ADDRESS="a@a.tld"
MAIL_ADDRESS=""
#
# And what should the subject be
# Standard = MAIL_SUBJECT="Status for *** at time $(date +%d.%m.%Y) - $(date +%k:%M)"
MAIL_SUBJECT="Status for backup af *** d.$(date +%d.%m.%Y) kl.$(date +%k:%M)"
#
# Absolute path for the backup dir (with no trailing slash please)
# Note that directorys will be made if need be
# Standard = BACKUP_DIR="/home/backup"
BACKUP_DIR="/home/backup"
#
# Should any local backups be kept and rotatet
# Please note that if FTP_USE is set further down, then 1 local backup will be kept no matter this setting
# Standard = KEEP_LOCAL="yes"
KEEP_LOCAL="yes"
#
# If set, what should the backup be called - Letters and underscore only please
# The name will be $BACKUP_NAME.tar.gz.$KEEP_LOCAL_ROTATE, and if changed, then old ones have to be deleted by hand
# Standard = BACKUP_NAME="backup"
BACKUP_NAME="backup"
#
# If set above, how many local backups should be kept
# Standard = KEEP_LOCAL_ROTATE="3"
KEEP_LOCAL_ROTATE=3
#
# Set to anything but yes to deaktivate local rotation of the database
# This will deactivate backup of the databases altogether if not set to yes
# Standard = DUMP_DATABASE="yes"
DUMP_DATABASE="yes"
#
# If set, what user and password should be used by mysqldump
# Standard = DUMP_USER="backup" --- DUMP_PASS="some_random_stuff"
DUMP_USER="backup"
DUMP_PASS=""
#
# If set, what should the dump of the database be called  -  Letters and underscore only please
# The name will be $DUMP_NAME.sql.gz.$ROTATE_DATABASE, and if changed, then old ones have to be deleted by hand
# Standard = DUMP_NAME="dump"
DUMP_NAME="dump"
#
# If set, how many dumps of the database will need to be kept and rotatet
# Be aware that if the number is reduced a manuel delete of the dumps over the rotation number is needed
# Standard = ROTATE_DATABASE=7
ROTATE_DATABASE=7
#
# Set to anything but "yes" to deactivate the use of FTP  -  Please insure that 'ftp' is installed if needed
# Note that limitations prevent errorchecking of the FTP transfer itself. However, if mail is used,
# a copy of the FTP servers contents will be mailed after the rotation has taken place
# Standard = FTP_USE="yes"
FTP_USE="yes"
#
# If set above, how many backups should be kept and rotatet through FTP
# Be aware that if the number is reduced, then a manuel delete of the backups over the rotation number is needed
# Standard = ROTATE_FTP=14
ROTATE_FTP=14
#
# Set to anything but yes to deactivate weekly backup to FTP
# The name of this backup will be $BACKUP_NAME.weekly.tar.gz.$ROTATE_FTP
# Standard = FTP_USE_WEEKLY="yes"
FTP_USE_WEEKLY="yes"
#
# If FTP is set, what day should the weekly backup to FTP run  -  1 = monday  ...  7 = sunday
# Standard = FTP_USE_WEEKLY_DAY=5
FTP_USE_WEEKLY_DAY=5
#
# If FTP is set, how many copys should be kept and rotatet through FTP on a weekly basis
# Be aware that if the number is reduced, then a manuel delete of the backups over the rotaion number is needed
# Standard = ROTATE_FTP_WEEKLY=12
ROTATE_FTP_WEEKLY=12
#
# If FTP is set, then these 3 needs to be filled out with the proper information
# Set host to hostname or public IP address, and modify user and pass to something appropriate
# Standard = FTP_HOST="" --- FTP_USER="" --- FTP_PASS=""
FTP_HOST=""
FTP_USER=""
FTP_PASS=""
#
# Set to anything but "yes" to deactivate the use of rsync  -  Please insure that 'rsync' is installed if needed
# Standard = RSYNC_USE="yes"
RSYNC_USE="yes"
#
# If set above, what dir under $BACKUP_DIR should rsync use (with no slahes)
# Standard = RSYNC_DIR="rsync"
RSYNC_DIR="rsync"
#
# Also if set, from where should rsync back stuff up from
# Standard = RSYNC_ORIGIN="/from/dir1 \
# /from/dir2"
RSYNC_ORIGIN="/etc/apache2 \
/etc/dovecot \
/etc/postfix \
/home/vmail"
#
# Set to anything but "yes" to deactivate backup of single files
# Standard = COPY_SINGLES_USE="yes"
COPY_SINGLES_USE="yes"
#
# If set above, what dir under the backup dir should the singles be placed in (with no slahes)
# Standard = SINGLES_DIR="singles"
SINGLES_DIR="singles"
#
# And if set above, list of single config files to be backed up. Errors in COPY_SINGLES() will not be processed
# Standard = COPY_SINGLES() {
# cp -u /dir/file $BACKUP_DIR/$SINGLES_DIR/
# }
COPY_SINGLES() {
cp -u /etc/default/varnish $BACKUP_DIR/$SINGLES_DIR/
cp -u /etc/varnish/default.vcl $BACKUP_DIR/$SINGLES_DIR/
cp -u /etc/webalizer/webalizer.conf $BACKUP_DIR/$SINGLES_DIR/
}
######################################################################################
# *** Settings End ***
# Changes below here is not needed for daily usage
######################################################################################
#
# We really need which and can't (nor won't we) go on without it
if [ -e "/usr/bin/which" ] ; then
   WHICH="/usr/bin/which"
else
   # If mail is set we'll try and inform about this error in a mail
   if [ "$MAIL_SEND" = "yes" ] ; then
      MAIL_BODY="Programmet which blev ikke fundet, men skal bruges. Scriptet afslutter"
      echo $MAIL_BODY | /usr/bin/mail -s "$MAIL_SUBJECT" $MAIL_ADDRESS
   fi
   exit 10
fi
#
# We prefer mail but don't really need it, and only if set will we check it
if [ "$MAIL_SEND" = "yes" ] ; then
   MAIL_BIN=$($WHICH mail)
   if [ "$?" -ne "0" ] ; then
      MAIL_SEND="NO"
      MAIL_BODY="Programmet mmail kunne ikke findes, så ingen email vil blive lavet."
      echo $MAIL_BODY | /usr/bin/mail -s "$MAIL_SUBJECT" $MAIL_ADDRESS
      MAIL_SEND="NO"
      break
   fi
   # If need be we can now create the temp file needed to hold the body of the mail in the variable $MAIL_MESSAGE
   MAIL_MESSAGE="/tmp/Backup_Mail_Message.$(date +%j)"
   touch $MAIL_MESSAGE
   if [ "$?" -ne "0" ] ; then
      MAIL_BODY="Tempfilen til email'en kunne ikke oprettes, så ingen email vil blive lavet."
      echo $MAIL_BODY | /usr/bin/mail -s "$MAIL_SUBJECT" $MAIL_ADDRESS
      MAIL_SEND="NO"
      break
   fi
fi
#
# MySQL-dump is also preferred, but not needed. Plenty of other other fish in the seas
if [ "$DUMP_DATABASE" = "yes" ] ; then
   MYSQLDUMP=$($WHICH mysqldump)
   if [ "$?" -ne "0" ] ; then
      echo >> $MAIL_MESSAGE
      echo "MySQL-dump kunne ikke findes, så databasen FÅR IKKE lavet backup!" >> $MAIL_MESSAGE
      DUMP_DATABASE="NO"
   fi
fi
#
# Same goes for rsync
if [ "$RSYNC_USE" = "yes" ] ; then
   RSYNC=$($WHICH rsync)
   if [ "$?" -ne "0" ] ; then
      echo >> $MAIL_MESSAGE
      echo "rsync kunne ikke findes, så $RSYNC_ORIGIN FÅR IKKE lavet backup!" >> $MAIL_MESSAGE
      RSYNC_USE="NO"
   fi
fi
#
# And ftp
if [ "$FTP_USE" = "yes" ] ; then
   FTP=$($WHICH ftp)
   if [ "$?" -ne "0" ] ; then
      echo >> $MAIL_MESSAGE
      echo "ftp kunne ikke findes, så backuppen KAN IKKE kopieres til en FTP-server!" >> $MAIL_MESSAGE
      FTP_USE="NO"
   fi
fi
#
# We won't be checking cp, echo and so on, since Linux in general depends on those
#
# We'd better check the backupdir and make it if it's nonexistent
if [ ! -d "$BACKUP_DIR" ] ; then
   mkdir -p "$BACKUP_DIR"
   # Since the entire backup depends on this dir we'll check it
   if [ "$?" -ne "0" ] ; then
   # In case a mail should be sent we need to preserve the exit status of mkdir
   EXIT_STATUS="$?"
      if [ "$MAIL_SEND" = "yes" ] ; then
         MAIL_BODY="Destinationen = $BACKUP_DIR til backuppen kunne ikke oprettes med fejl $EXIT_STATUS."
         MAIL_BODY="$MAIL_BODY Scriptet afslutter UDEN at have kørt nogen backup!"
         echo $MAIL_BODY | $MAIL_BIN -s "$MAIL_SUBJECT" $MAIL_ADDRESS
      fi
   # We can't very well do a backup without a destination
   exit $EXIT_STATUS
   fi
fi
#
# Lets dump any and all databases on the system if it is wanted and possible
if [ "$DUMP_DATABASE" = "yes" ] ; then
   # The dump itself
   $MYSQLDUMP -u$DUMP_USER -p$DUMP_PASS --all-databases > "$BACKUP_DIR/$DUMP_NAME.sql"
   # And only if dump is successfull will we go on with the ratotion
   if [ "$?" -ne "0" ] ; then
      echo >> $MAIL_MESSAGE
      echo "MySQL-dump kunne ikke gennemføres med fejl $?, så INGEN BACKUP AF DATABASEN!" >> $MAIL_MESSAGE
      break
   fi
   #
   # Note the missing fi here. It's further down as we only work with database/rotate if set
   #
   # We'll start by removing the oldest dump. No error needed if it's not there
   if [ -e "$BACKUP_DIR/$DUMP_NAME.sql.gz.$ROTATE_DATABASE" ] ; then
      rm $BACKUP_DIR/$DUMP_NAME.sql.gz.$ROTATE_DATABASE
   fi
   #
   # Then comes the rotation itself, starting by setting the required extra variable for renaming
   ROTATE_LOOP=$ROTATE_DATABASE
   ROTATE_DATABASE=$((ROTATE_DATABASE-1))
   # This loop will run until $ROTATE_DATABASE reaches a value of zero
   while [ "$ROTATE_DATABASE" -ne "0" ] ; do
      # No error needed if the dump ain't there
      if [ -e "$BACKUP_DIR/$DUMP_NAME.sql.gz.$ROTATE_DATABASE" ] ; then
         mv "$BACKUP_DIR/$DUMP_NAME.sql.gz.$ROTATE_DATABASE" "$BACKUP_DIR/$DUMP_NAME.sql.gz.$ROTATE_LOOP"
      fi
      # One rotation down, X minus 1 rotations to go
      ROTATE_DATABASE=$((ROTATE_DATABASE-1))
      ROTATE_LOOP=$((ROTATE_LOOP-1))
   done
   #
   # If the dump is in existence it's time to compress the sucker and save it in the right place
   if [ -e "$BACKUP_DIR/$DUMP_NAME.sql" ] ; then
      gzip -c "$BACKUP_DIR/$DUMP_NAME.sql" > "$BACKUP_DIR/$DUMP_NAME.sql.gz.1"
      # Let's see if gzip ran probably
      if [ "$?" -ne "0" ] ; then
         echo >> $MAIL_MESSAGE
         echo "gzip afsluttede med fejl $?," >> $MAIL_MESSAGE
         echo "så dump af databasen kunne ikke afsluttes med succes." >> $MAIL_MESSAGE
         break
      fi
      # Appends time of completion for MySQL-dump to the mail since the databases where dumped with success
      echo >> $MAIL_MESSAGE
      echo "Dump af MySQL blev gennemført og roteret." >> $MAIL_MESSAGE
   else
      # No dump is kind of wierd. we need to know about that
      echo >> $MAIL_MESSAGE
      echo "MySQL dump kunne ikke findes efter dump, hvilket er lidt underligt." >> $MAIL_MESSAGE
      echo "Grunden hertil bør nok undersøges før end siden." >> $MAIL_MESSAGE
   fi
#
# This is the one and only missing fi from "if [ "$ROTATE_DATABASE ... "
# Without her we'd be lost http://tomorrow-series.eu/en/books/fiona-maxwell/
fi
#
# And if the dump still lives in $BACKUP_DIR it's time to free some space
# Since errors about this dump should've been taken care of we won't check the result in any way
if [ -e "$BACKUP_DIR/$DUMP_NAME.sql" ] ; then
   rm "$BACKUP_DIR/$DUMP_NAME.sql"
fi
#
# Before we unleash rsync some tests should be conducted as per usual
if [ "$RSYNC_USE" = "yes" ] ; then
   if [ ! -d "$BACKUP_DIR/$RSYNC_DIR" ] ; then
      mkdir "$BACKUP_DIR/$RSYNC_DIR"
      # And we'll check that too
      if [ "$?" -ne "0" ] ; then
         # If the dir could'nt be made we'll break out of this loop and skip rsync all together
         echo >> $MAIL_MESSAGE
         echo "Mappen $BACKUP_DIR/$RSYNC_DIR til rsync kunne ikke oprettes med fejl $?." >> $MAIL_MESSAGE
         echo "Dette betyder at rsync IKKE HAR KØRT!" >> $MAIL_MESSAGE
         break
      fi
   fi
   # It shuold now be safe and sound to run rsync
   $RSYNC --delete-after -avz $RSYNC_ORIGIN "$BACKUP_DIR/$RSYNC_DIR"
   # Cheking now ...
   if [ "$?" -ne "0" ] ; then
      echo >> $MAIL_MESSAGE
      echo "rsync afsluttede med fejl $?." >> $MAIL_MESSAGE
      echo "Grunden hertil bør nok undersøges før end siden." >> $MAIL_MESSAGE
      break
   fi
   # If rsync ran with success we'll want to know it
   echo >> $MAIL_MESSAGE
   echo "Backup med rsync blev gennemført." >> $MAIL_MESSAGE
fi
#
# Time to copy and/or update single config files if wanted
if [ "$COPY_SINGLES_USE" = "yes" ] ; then
   # Better check the dest dir for the singles
   if [ ! -d "$BACKUP_DIR/$SINGLES_DIR" ] ; then
      mkdir "$BACKUP_DIR/$SINGLES_DIR"
      # No point in going on without a dest dir, but we surely wants to know about this
      if [ "$?" -ne "0" ] ; then
         echo >> $MAIL_MESSAGE
         echo "Mappen til enkeltfilerne kunne ikke oprettes med fejl $?," >> $MAIL_MESSAGE
         echo "så enkeltfilerne er ikke blevet sikkerhedskopieret." >> $MAIL_MESSAGE
         break
      fi
   fi
   # Let the copy/update of singles begin with no errorchecking whatsoever
   COPY_SINGLES
   echo >> $MAIL_MESSAGE
   echo "Backup af enkeltfiler gennemført." >> $MAIL_MESSAGE
fi
#
# Wether or not we'll need local backup, the tar and gunzip routine will be made because FTP might use it later on
# But since we only wants to run tar_local once, we'll check both cases in the next if
tar_local() {
# tar will throw errors if dirs and files don't exists, but it will go on and finish the job anyway
# Please note that this command spand multiple lines
tar -cf - "$BACKUP_DIR/$DUMP_NAME.sql.gz.1" "$BACKUP_DIR/$RSYNC_DIR/" "$BACKUP_DIR/$SINGLES_DIR" |\
gzip > "$BACKUP_DIR/$BACKUP_NAME.tar.gz.1"
}
#
# Now we'll look at the local backups if set
if [ "$KEEP_LOCAL" = "yes" ] ; then
   # First goes the old one, if any
   if [ -e "$BACKUP_DIR/$BACKUP_NAME.tar.gz.$KEEP_LOCAL_ROTATE" ] ; then
      rm "$BACKUP_DIR/$BACKUP_NAME.tar.gz.$KEEP_LOCAL_ROTATE"
   fi
   #
   # Then we'll rotate 'em all, starting by setting a variable for the loop like we did with the dump
   ROTATE_LOOP=$KEEP_LOCAL_ROTATE
   KEEP_LOCAL_ROTATE=$((KEEP_LOCAL_ROTATE-1))
   # This loop will run until $KEEP_LOCAL_ROTATE reaches a value of zero
   while [ "$KEEP_LOCAL_ROTATE" -ne "0" ] ; do
   # No error needed if the backups ain't here
      if [ -e "$BACKUP_DIR/$BACKUP_NAME.tar.gz.$KEEP_LOCAL_ROTATE" ] ; then
         mv "$BACKUP_DIR/$BACKUP_NAME.tar.gz.$KEEP_LOCAL_ROTATE" "$BACKUP_DIR/$BACKUP_NAME.tar.gz.$ROTATE_LOOP"
      fi
      # One rotation down, X minus 1 rotations to go
      KEEP_LOCAL_ROTATE=$((KEEP_LOCAL_ROTATE-1))
      ROTATE_LOOP=$((ROTATE_LOOP-1))
   done
   #
   # And now a new backup.tar.gz.1 can be made
   tar_local
#
# If $KEEP_LOCAL is not set, but FTP is, we still need to tar and gunzip from $BACKUP_DIR
else
   # Checking FTP
   if [ "$FTP_USE" = "yes" ] ; then
      # If an old backup exists we need to get rid of it
      if [ -e "$BACKUP_DIR/$BACKUP_NAME.tar.gz.1" ] ; then
         rm "$BACKUP_DIR/$BACKUP_NAME.tar.gz.1"
      fi
   # And now a backup to use with FTP can be created
   tar_local
   fi
fi
#
# It's FTP time (if set and all)
if [ "$FTP_USE" = "yes" ] ; then
   # To make life easier for ourselves we'll use ftp_con() to do the hard labor
   ftp_con() {
   echo   "open $FTP_HOST
      user $FTP_USER $FTP_PASS
      verbose
      $1 $2 $3
      close
      quit" | $FTP -n           
   }
   #
   # First up the old one have to go at the FTP site with the use of the FTP function
   # If it's nonexistent the server will throw a error but that does'nt really matter
   ftp_con delete $BACKUP_NAME.tar.gz.$ROTATE_FTP
   #
   # Then the variables for the loop needs setting
   ROTATE_LOOP=$ROTATE_FTP
   ROTATE_FTP=$((ROTATE_FTP-1))
   # And then comes the rotate itself
   while [ "$ROTATE_FTP" -ne "0" ] ; do
      # Again errors will be thrown if the files does'nt exists but it's of no consequence
      ftp_con rename $BACKUP_NAME.tar.gz.$ROTATE_FTP $BACKUP_NAME.tar.gz.$ROTATE_LOOP
   # One rotation down, X minus 1 rotations to go
   ROTATE_FTP=$((ROTATE_FTP-1))
   ROTATE_LOOP=$((ROTATE_LOOP-1))
   done
   #
   # And up goes the new backup
   ftp_con put "$BACKUP_DIR/$BACKUP_NAME.tar.gz.1" "$BACKUP_NAME.tar.gz.1"
   #
   # Better inform someone that daily backup tp FTP is done
   echo >> $MAIL_MESSAGE
   echo "Daglig backup til FTP gennemført." >> $MAIL_MESSAGE
   #
   # If weekly backup to FTP is set we'll rotate that too
   if [ "$FTP_USE_WEEKLY" = "yes" ] ; then
      # But only if it's the right weekday
      if [ "$(date +%u)" = "$FTP_USE_WEEKLY_DAY" ] ; then
         #
         # Better inform someone that we'll go on with the weekly backup to FTP
         echo >> $MAIL_MESSAGE
         echo "Æteren siger det er $(date +%A), så fortsætter med ugebackup til FTP." >> $MAIL_MESSAGE
         # If it is so, then the old one have to go
         ftp_con delete $BACKUP_NAME.weekly.tar.gz.$ROTATE_FTP_WEEKLY
         #
         # Then the variables
         ROTATE_LOOP=$ROTATE_FTP_WEEKLY
         ROTATE_FTP_WEEKLY=$((ROTATE_FTP_WEEKLY-1))
         # And then the rotate goes
         while [ "$ROTATE_FTP_WEEKLY" -ne "0" ] ; do
         # Still no care if errors are thrown
            ftp_con rename $BACKUP_NAME.weekly.tar.gz.$ROTATE_FTP_WEEKLY $BACKUP_NAME.weekly.tar.gz.$ROTATE_LOOP
            # One rotation down, X minus 1 rotations to go
            ROTATE_FTP_WEEKLY=$((ROTATE_FTP_WEEKLY-1))
            ROTATE_LOOP=$((ROTATE_LOOP-1))
         done
         #
         # And up goes the new backup, with a twist in the filename on the recieving side
         ftp_con put "$BACKUP_DIR/$BACKUP_NAME.tar.gz.1" "$BACKUP_NAME.weekly.tar.gz.1"
         #
         # Better inform someone that the weekly backup to FTP is done
         echo "Ugentlig backup til FTP gennemført." >> $MAIL_MESSAGE
      fi
   fi
   #
   # Since no error checking is done on the FTP transfers itself, we'll mail a list of files on the FTP server
   FTP_FILE_LIST=$(ftp_con ls)
   echo >> $MAIL_MESSAGE
   echo "Filer på FTP-serveren:" >> $MAIL_MESSAGE
   echo "$FTP_FILE_LIST" >> $MAIL_MESSAGE
fi
#
# Last desprate act of the show: Send an mail if $MAIL_SEND = "yes"
if [ "$MAIL_SEND" = "yes" ] ; then
   # Let's see if the file is empty
   if [ ! -s "$MAIL_MESSAGE" ] ; then
      echo "Alle poster i hovedet er deaktiveret, så ingenting er blevet kørt." >> $MAIL_MESSAGE
      echo "Exit status 42, hav en god dag :)" >> $MAIL_MESSAGE
   fi
   # Time to send the mail with a contact note
   echo >> $MAIL_MESSAGE
   echo "Der kan ikke svares direkte på denne mail, kontakt i stedet serverens administrator på" >> $MAIL_MESSAGE
   echo "$SERVER_ADMIN_MAIL, og bed om at blive fjernet fra modtagerlisten." >> $MAIL_MESSAGE
   echo "Email sendt kl.$(date +%k:%M)." >> $MAIL_MESSAGE
   $MAIL_BIN -s "$MAIL_SUBJECT" $MAIL_ADDRESS < $MAIL_MESSAGE
   #
   # Better remove the mail from /tmp if it should be there
   if [ -e "$MAIL_MESSAGE" ] ; then
      rm "$MAIL_MESSAGE"
   fi
fi
#
# Yeah, we made it!
exit 0