Moorfleeter Deich 216, 22113 Hamburg
+49 1774661716info@bastcom.deMo. - Fr.: 09:00 - 18:00

Blog

ITWeb Knowledge

Das bastCOM-Blogarchiv: Wissen und Anleitungen aus der Web- und IT-Praxis.

Backup Scripts

← Zurück zum Blog

Backup Bash Script für eine Liste von Servern auf Basis von Bash

 

 

Backup Bash Script für eine Liste von Servern auf Basis von Bash

 

backup.config

 

#!/bin/bash

##### basic configuration #######################################
# where is the list of servers?                                 #
SERVERLIST=/usr/local/bin/backup/lists/backup.list              #
MIXED_SERVERLIST=/usr/local/bin/backup/lists/backup.list.mixed  #
MIXED_SERVERLIST_TMP=/usr/local/bin/backup/lists/backup.list.tmp#
                                                                #
# thread management                                             #
THREAD_COUNT_FILE=/tmp/.backup.threads.tmp                      #
MAX_THREADS=5                                                   #
LOCKFILE=/tmp/.backup.lock.tmp                                  #
WAIT_FOR_FREE_THREAD=5                                          #
WAIT_FOR_LOCKFILE=2                                             #
                                                                #
# where should the backup be saved?                             #
DATA_PATH=/raid/backup                                          #
                                                                #
# test if enough space is left?                                 #
CHECK_HDMINFREE=true                                            #
HDMINFREE=99                                                    #
HDMINFREE_WARN=97                                               #
                                                                #
# administrator email address                                   #
ADMIN_EMAIL=admin@digicol.de                                    #
                                                                #
# tools dir                                                     #
TOOLS_DIR=tools/                                                #
                                                                #
#stopfile                                                       #
STOPFILE=/tmp/backup.stop                                       #
#################################################################

##### configuration of rsync part ###############################
# excludelist of filepatterns – excludes them from backup       #
EXCLUDES=‘–exclude=/proc ‚                                     #
EXCLUDES=$EXCLUDES‘–exclude=/tmp ‚                             #
EXCLUDES=$EXCLUDES‘–exclude=/home/digicol/install ‚            #
EXCLUDES=$EXCLUDES‘–exclude=*Thumbs.db ‚                       #
EXCLUDES=$EXCLUDES‘–exclude=*.vmdk ‚                           #
#################################################################

##### configuration of rotate part ##############################
# remove garbage?                                               #
# this only happens if the „days of backup“ have been changed   #
COLLECT_GARBAGE=true                                            #
#################################################################

##### configuration of statusmail part ##########################
STATS_MAILTEXT=/tmp/mail.backup                                 #
STATS_MAIL_SUBJECT=“Statusmail dcbackup“                        #
STATS_TMPSIZE=/tmp/.size                                        #
#################################################################

##### functions for mailing errors ##############################
# $1 = subject ; $2 = mailtext                                  #
# exit with error!                                              #
mail_err()                                                      #
{                                                               #
  echo $2 | mail -s „BACKUP ERROR: $1“ $ADMIN_EMAIL             #
  exit 1                                                        #
}                                                               #
                                                                #
mail_warn()                                                     #
{                                                               #
  echo $2 | mail -s „BACKUP WARNING: $1“ $ADMIN_EMAIL           #
}                                                               #
#################################################################

 

backup-rotate

 

#!/bin/bash

# include the config file
. /usr/local/bin/backup/backup.config

# enough params?
if [ -n „$1“ ] && [ -n „$2“ ]; then
        SERVER=“$1″
        DAYS=“$2″
else
        echo „Error: Usage $0 <fqdn-hostname> <days>“
        logger „BACKUP Error: Usage $0 <fqdn-hostname> <days>“
        mail_err „wrong usage of script $0“ „wrong usage of script $0. Server: $SERVER“

fi

# fix the DAYS var that we can start with the 0!
DAYS=$((DAYS-1))

# checks the minimum percentage of free space
if $CHECK_HDMINFREE; then
        PERCENTAGE=`df $DATA_PATH | tail -n1 | awk ‚{print $4}‘ | sed -e ’s/%//g’`
        if [ $PERCENTAGE -ge $HDMINFREE_WARN ]; then
                echo „Warning: device nearly full ($PERCENTAGE%)“
                logger „BACKUP Warning: device nearly full ($PERCENTAGE%)“
                mail_warn „nearly out of space!“ „Warning: device nearly full ($PERCENTAGE%)“
        fi
        if [ $PERCENTAGE -ge $HDMINFREE ]; then
                echo „Fatal: not enough space left on device for rotating backups“
                logger „BACKUP Fatal: not enough space left on device for rotating backups“
                mail_err „ran out of space!“ „Fatal: not enough space left on device ($DATA_PATH) for rotating backups. stopped at server: $SERVER“
        fi
fi

# create a first directory if needed
if ! [ -d $DATA_PATH/$SERVER/daily.0 ] ; then
        mkdir -p $DATA_PATH/$SERVER/daily.0
fi

echo „backup of $SERVER is being rotated“
logger „BACKUP backup of $SERVER is being rotated“

# deleting highest snapshot
if [ -d $DATA_PATH/$SERVER/daily.$DAYS ] ; then
        rm -rf $DATA_PATH/$SERVER/daily.$DAYS
fi

# deleting garbage of old snapshots
if $COLLECT_GARBAGE; then
        if [ `ls -d $DATA_PATH/$SERVER/daily.* | wc -l` -gt $((DAYS+1)) ]; then
                NUM_TO_DELETE=`ls -d $DATA_PATH/$SERVER/daily.* | wc -l`

                until [ $NUM_TO_DELETE -lt $((DAYS + 1)) ]; do
                        rm -r $DATA_PATH/$SERVER/daily.$NUM_TO_DELETE
                        NUM_TO_DELETE=$((NUM_TO_DELETE – 1))
                done
        fi
fi

# move all other backups one step higher
for OLD in `seq 1 $DAYS | tac`  ; do
        if [ -d $DATA_PATH/$SERVER/daily.$OLD ] ; then
                NEW=$[ $OLD + 1 ]
                # save timestamp
                touch $DATA_PATH/.timestamp -r $DATA_PATH/$SERVER/daily.$OLD
                mv $DATA_PATH/$SERVER/daily.$OLD $DATA_PATH/$SERVER/daily.$NEW
                # return timestamp
                touch $DATA_PATH/$SERVER/daily.$NEW -r $DATA_PATH/.timestamp
        fi
done

# hardlink copy from level-0 to level-1 of the snapshot
if [ -d $DATA_PATH/$SERVER/daily.0 ] ; then
        cp -al $DATA_PATH/$SERVER/daily.0 $DATA_PATH/$SERVER/daily.1
fi

# finished!
echo „$SERVER finished rotating“
logger „BACKUP $SERVER finished rotating“

 

backup-rsync

 

#!/bin/bash

# include the config file
. /usr/local/bin/backup/backup.config

# enough params?
if [ -n „$1“ ] && [ -n „$2“ ]; then
    SERVER=“$1″
        FOLDER=“$2″

        echo $SERVER
        echo $FOLDER
else
        echo „Error: Usage $0 <hostname or ip> <folder>“
        logger „BACKUP Error: Usage $0 <hostname or ip> <folder>“
        mail_err „wrong usage of script $0“ „wrong usage of script $0. Server: $SERVER“
fi

# check if it is a windows machine
WIN=`expr match „$FOLDER“ ‚[:a-z]*//’`
if [ $WIN -eq 0 ]; then
  SSH=‘-e ssh‘
fi
    
# checks the minimum percentage of free space
if $CHECK_HDMINFREE; then
        PERCENTAGE=`df $DATA_PATH | tail -n1 | awk ‚{print $4}‘ | sed -e ’s/%//g’`
        if [ $PERCENTAGE -ge $HDMINFREE_WARN ]; then
                echo „Warning: device nearly full ($PERCENTAGE%)“
                logger „BACKUP Warning: device nearly full ($PERCENTAGE%)“
                mail_warn „nearly out of space!“ „Warning: device nearly full ($PERCENTAGE%)“
        fi
        if [ $PERCENTAGE -ge $HDMINFREE ]; then
                echo „Fatal: not enough space left on device for syncing backups“
                logger „BACKUP Fatal: not enough space left on device for syncing backups“
                mail_err „ran out of space!“ „Fatal: not enough space left on device ($DATA_PATH) for syncing backups. stopped at server: $SERVER“
        fi
fi

# create a first directory if needed
if ! [ -d $DATA_PATH/$SERVER/daily.0 ] ; then
        mkdir -p $DATA_PATH/$SERVER/daily.0
fi

# create a excludes file if needed
if ! [ -f excludes/$SERVER ] ; then
        touch excludes/$SERVER
fi

echo „backup from: $SERVER“
logger „BACKUP backup from: $SERVER“

rsync -avzR –numeric-ids $SSH –delete –delete-excluded $EXCLUDES –exclude-from=“excludes/$SERVER“ $EXTRAOPT $SERVER:$FOLDER $DATA_PATH/$SERVER/daily.0

# test the return of rsync
# 0 = no error
# 23 = harmless – why? i don’t know, really! simply believe it…
# 24 = harmless – why? i don’t know, really! simply believe it…
# 255 = harmless – why? i don’t know, really! simply believe it…
# everything else is absolutely bad!
ERROR_CODE=$?
if ! [ $ERROR_CODE = 24 -o $ERROR_CODE = 0 -o $ERROR_CODE = 23 -o $ERROR_CODE = 255 ] ; then
    echo „error on $SERVER while rsync. error code: $ERROR_CODE“
    logger „BACKUP error on $SERVER while rsync. error code: $ERROR_CODE“

        # only send mail if it’s not a Windows machine.
        # Somehow rsync always exits with error if it’s a Windows rsync client.
        if [ $WIN -eq 0 ]; then
                mail_warn „rsync error!“ „error on $SERVER ($FOLDER) while rsync. error code: $ERROR_CODE“
        fi
fi

# touch path to save timestamp
touch $DATA_PATH/$SERVER/daily.0

# finished!
echo „$SERVER finished successfully“
logger „BACKUP $SERVER finished successfully“

# for safety
sync

# count down the threads file
while true ; do
    if [ ! -f $LOCKFILE ] ; then
        (set -C; : > $LOCKFILE) 2> /dev/null
        if [ $? == 0 ] ; then
            THREAD_COUNT=`cat $THREAD_COUNT_FILE`
            echo $((THREAD_COUNT – 1)) > $THREAD_COUNT_FILE
            rm $LOCKFILE
            break
        else
            logger „BACKUP ERROR creating lockfile, trying again“
            sleep $WAIT_FOR_LOCKFILE
        fi
    else
        sleep $WAIT_FOR_LOCKFILE
    fi
done

 

do_backup

 

#!/bin/bash -x

. backup.config

# touch a stopfile
logger „BACKUP touching Stopfile“
(set -C; : > $STOPFILE) 2> /dev/null

###############################################################################
#                              rotate                                         #
###############################################################################

#hack for the oracle dumps of dc5projektor
#only leave 10 days of the dump

#deactivated because tar’ed dumps are way smaller
#so we can hold a few more days

# find $DATA_PATH/dc5projektor.digicol.local/daily.*/dot/oracle/expdp/ -name ‚*dc5projektor_*.dmp.gz‘ -mtime +10 -delete

#hack for the demo device. only leave the last (daily.0) dump to save space
#rm -rf „$DATA_PATH“/demo.digicol.local/daily.[!^0]*/dot/dc/device/

#hack for vmware server – leave only fullbackup of the vmware machines
#rm -rf „$DATA_PATH“/vmware.digicol.local/daily.[!^0]*/datastore2-slow/*
#rm -rf /raid/backup/vmware.digicol.local/daily.[!^0]*/vmfs/volumes/4d0a2233-30b397db-3d59-0024e874c898*

if [ „$1“ != „norotate“  ] ; then
    while read LINE ; do
        if [[ `echo $LINE | grep „^#####“` ]]; then
        SERVER=`echo $LINE | awk ‚{print $2}’`
        DAYS=`echo $LINE | awk ‚{print $4}’`
        ./backup-rotate $SERVER $DAYS
    fi
    done < $SERVERLIST
fi
###############################################################################
#                            end rotate                                       #
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
#                               rsync                                         #
###############################################################################

# only to be shure that there’s no lockfile
if [ -f $LOCKFILE ] ; then rm $LOCKFILE ; fi

# clear the mixed serverlist
> $MIXED_SERVERLIST
> $MIXED_SERVERLIST_TMP

# reset the threads
echo „0“ > $THREAD_COUNT_FILE

# mix the serverlist
grep -Ev „^#|^$“ $SERVERLIST > $MIXED_SERVERLIST_TMP
perl „$TOOLS_DIR“randomize.pl $MIXED_SERVERLIST_TMP > $MIXED_SERVERLIST
rm $MIXED_SERVERLIST_TMP

# bring up the threads and sync!
while read IP DIR ; do
    while true ; do
        THREAD_COUNT=`cat $THREAD_COUNT_FILE`
        if [ $THREAD_COUNT -lt $MAX_THREADS ] ; then
            ### lockfile handling
            while true ; do
                if [ ! -f $LOCKFILE ] ; then

                    (set -C; : > $LOCKFILE) 2> /dev/null
                    if [ $? == 0 ] ; then
                        ./backup-rsync $IP $DIR &
                    
                        ### once again for shure
                        THREAD_COUNT=`cat $THREAD_COUNT_FILE`
                        echo $((THREAD_COUNT + 1)) > $THREAD_COUNT_FILE
                        rm $LOCKFILE
                    
                        break
                    else
                        logger „BACKUP ERROR creating lockfile, trying again“
                        sleep $WAIT_FOR_LOCKFILE
                    fi
                else
                    sleep $WAIT_FOR_LOCKFILE
                fi
            done
            ### end lockfile handling
            break
        else
            sleep $WAIT_FOR_FREE_THREAD
        fi
    done
done < $MIXED_SERVERLIST

# remove stopfile only if no rsync process is running!
while true ; do
    if [ `cat $THREAD_COUNT_FILE` -eq 0 ] ; then
        logger „BACKUP removing Stopfile“
        rm $STOPFILE
        break
    else
        sleep 60
    fi
done

###############################################################################
#                            end rsync                                        #
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
#                              stats                                          #
###############################################################################

echo „The directory sizes:“ > $STATS_MAILTEXT
echo „“ >> $STATS_MAILTEXT

for SERVER in `ls -d $DATA_PATH/*`; do
 
  # manually exclude the demo server
  if [ „$SERVER“ = $DATA_PATH“/demo.digicol.local“ ]; then continue; fi

  if [ -f $SERVER/.size ]; then
    OLDSIZE=`cat $SERVER/.size`
  else
    OLDSIZE=“0″
  fi
 
  du -sm $SERVER | awk ‚{print $1}‘ > $STATS_TMPSIZE
  NEWSIZE=`cat $STATS_TMPSIZE`

  if [ $NEWSIZE -gt $OLDSIZE ] ; then SIGN=“+“ ; else SIGN=““ ; fi

  echo „Server: ${SERVER##*/}“ >> $STATS_MAILTEXT
  echo „Total:  „$NEWSIZE“M“ >> $STATS_MAILTEXT
  echo „Diff:   $SIGN$((NEWSIZE – OLDSIZE))M“ >> $STATS_MAILTEXT
  echo „“ >> $STATS_MAILTEXT

  mv $STATS_TMPSIZE $SERVER
done

cat „$STATS_MAILTEXT“ | mail -s „$STATS_MAIL_SUBJECT“ „$ADMIN_EMAIL“

 #extra stats

 bash „$TOOLS_DIR“stats
###############################################################################
#                             end stats                                       #
###############################################################################

 

lists/backup.list

 

# please leave the structure of this file as-is
# it’s for your own good 😉

# if you remove a directory from the list you
# remove it manually from the filesystem too!
# example: rm -r /raid/backup/server/daily.*/path/

##### 10.20.0.1 days: 7 #####
10.20.0.1 /etc

##### tux-03.digicol.dc days: 7 #####
tux-03.digicol.dc /etc
tux-03.digicol.dc /root
tux-03.digicol.dc /var/www
tux-03.digicol.dc /var/subversion
tux-03.digicol.dc /var/spool/cron

##### db1.digicol.dc days: 4 #####
db1.digicol.dc /etc
db1.digicol.dc /root
db1.digicol.dc /var/spool/cron
db1.digicol.dc /backup
db1.digicol.dc /usr/local/bin

##### db2.digicol.dc days: 4 #####
db2.digicol.dc /etc
db2.digicol.dc /root
db2.digicol.dc /var/spool/cron
db2.digicol.dc /backup/
db2.digicol.dc /usr/local/bin

##### dcx.digicol.dc days: 7 #####
dcx.digicol.dc /etc
dcx.digicol.dc /root
dcx.digicol.dc /var/opt/dcx

### END ###

 

tools/randomize.pl

 

#!/usr/bin/perl -w

# Set the random seed
srand(time|$$);

# Suck in everything in the file.
@a = <>;

# Get random lines, write ‚em out, mark ‚em done.
while ( @a ) {
    $choice = splice(@a, rand @a, 1);
    print $choice;
}

 

tools/randomize.rb

 

#!/usr/bin/ruby

abort „usage: #{$0} <file>“ if ARGV.size != 1

#shuffle modified from „Programming in Ruby“ by Thomas and Hunt

# get the lines:
lines = IO.readlines(ARGV[0])

# pick a random line, remove it, and print it
lines.size.times do
  print lines.delete_at(rand(lines.size))
end

 

tools/stats

 

#!/bin/bash

STATS_MAILTEXT=/tmp/stats.tmp
SERVERLIST=/usr/local/bin/backup/lists/backup.list
OFFSET=30

function getDate()
{
  DAY=`date | awk ‚{print $3}’`
  if [[ ${#DAY} -lt 2 ]] ; then
    DATE=`date | awk ‚{print $2″  „$3}’`
  else
    DATE=`date | awk ‚{print $2″ „$3}’`
  fi
}  

function getStats()
{
  echo „Harddisk:“     > $STATS_MAILTEXT
  echo „`df -h`“      >> $STATS_MAILTEXT
  echo „“             >> $STATS_MAILTEXT
  echo „Times:“       >> $STATS_MAILTEXT

  getDate

  while read line ; do
    if [[ `echo $line | grep „#####“` ]]; then

      SERVER=`echo $line | awk ‚{print $2}’`

      START_STRING=`grep „$DATE“ /var/log/messages | grep BACKUP | grep „backup from: $SERVER“`
      END_STRING=`grep „$DATE“ /var/log/messages | grep BACKUP | grep „$SERVER finished successfully“`

      START_TIME=`echo „$START_STRING“ | head -n1 | awk ‚{print $3}’`
      START_TIME=`date -d $START_TIME +%s`
      END_TIME=`echo „$END_STRING“ | tail -n1 | awk ‚{print $3}’`
      END_TIME=`date -d $END_TIME +%s`

      TIME_DIFF=$((END_TIME – START_TIME))

      TIME_DIFF_TEMP=$((TIME_DIFF – OFFSET))

      STRIPES=“|“
      
      while true ; do
        if [[ $TIME_DIFF_TEMP -gt $OFFSET ]] ; then
          STRIPES=$STRIPES“-„
          TIME_DIFF_TEMP=$((TIME_DIFF_TEMP – $OFFSET))
        else
          STRIPES=$STRIPES“>“
          break
        fi
      done

      if [[ $TIME_DIFF -le 60 ]] ; then
        DIFF_TEXT=$TIME_DIFF“ seconds“
      elif [[ $TIME_DIFF -gt 60 ]] && [[ $TIME_DIFF -le 120 ]] ; then
        DIFF_TEXT=$((TIME_DIFF / 60))“ minute“
      else
        DIFF_TEXT=$((TIME_DIFF / 60))“ minutes“
      fi

      echo „$SERVER:“           >> $STATS_MAILTEXT
      echo „$DIFF_TEXT“         >> $STATS_MAILTEXT
      echo „$STRIPES“           >> $STATS_MAILTEXT
      echo „“                   >> $STATS_MAILTEXT
    fi
  done < $SERVERLIST

  echo „`grep „$DATE“ /var/log/messages |
         grep BACKUP |
         sed -e „s/loo logger: BACKUP //g“ |
         grep -v „testing VPN connection“ |
         grep -v „VPN connection already established“ |
         sed -e „s/$DATE //g“`“ >> $STATS_MAILTEXT
 
  cat „$STATS_MAILTEXT“ | /bin/mail -s „Times for dcbackup“ „bastian@digicol.de„
  # cat $STATS_MAILTEXT

  rm $STATS_MAILTEXT
}

getStats