Hear you get an perfect ISP Config backup script
#!/bin/bash version="0.9.4 from 2010-09-13" # Always download the latest version here: http://www.eurosistems.ro/back-res # Thanks or questions: http://www.howtoforge.com/forums/showthread.php?t=41609 # # CHANGELOG: # ----------------------------------------------------------------------------- # version 0.9.4 - 2010-09-13 # -------------------------- # Small fix: - Corrected small bug replaced tar with $TAR in the recovery line # of the databases. (The line: mysql -u$dbuser -p$dbpassword $rdb <) # Thanks goes to Nimarda and colo. # ----------------------------------------------------------------------------- # version 0.9.3 - 2010-08-01 # -------------------------- # Small fix: - Modified del_old_files function to remove "/" from the $to_del # variable used to delete old files # - Removed from del_old_files function the section used to keep old # databases (It's not working if there is no space left on device). Added # in TODO section # ----------------------------------------------------------------------------- # version 0.9.2 - 2010-04-18 # -------------------------- # Always download the latest version here: http://www.eurosistems.ro/back-res # Thanks or questions: http://www.howtoforge.com/forums/showthread.php?t=41609 # # Fixes: - First run now does not gives errors (Thanks nokia80, Snake12, # rudolfpietersma, HyperAtom, jmp51483, bseibenick, dipeshmehta, andypl # and all others) # - Modified the log function to accept first time dir createin # - Modified the starting sequence to not check the free space if the # primary backup directory does not exist # - If primary backup dir does not exist now it's created at the start # - Added a line to remove the maildata at the start if the user stops # the script before finishing his jobs. This prevents the script to send # incorect mails. # - Added link http://www.howtoforge.com/forums/showthread.php?t=41609 # maybe some of the downloaders will visit the forum. # - Added first TODO # ----------------------------------------------------------------------------- # beta version 0.9.1 - first public release last modified 2009-12-06 # moved to http://www.eurosistems.ro/back-res.0.9.1 # ----------------------------------------------------------------------------- # TODO: - Add required files check (tar, bzip2, mail, etc.) # - Create a better del_old_files function (2010-08-01) # - If you need anything else I'll be happy to do it in my spare time if # you ask here: http://www.howtoforge.com/forums/showthread.php?t=41609 # # Copyright (c) go0ogl3 gabi@eurosistems.ro # If you want to reward my work donate a small amount with Paypal (use my mail) # # If you enjoy my script please register and say thank you here: # http://www.howtoforge.com/forums/showthread.php?t=41609 # This is to keep the thread alive so this script can help other people too. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # The above copyright notice and this permission notice shall be included in # all copies of the script. # description: A backup and restore script for databases and directories # # The state of development is "It works for me"! # So don't blame me if anything bad will happen to you or to your computer # if you use this script. # I've done my best to make myself understood if you read on. # # Detailed Description # # Full dir, mysql and incremental backup script # Full and incremental restore script # It's meant to use minimum resources and space and keep a loooong backup. # I've tried to make as more checks as possible but I can't beat "smart" users. # Weird things can happen if your backup dirs includes the "-" or "_" chars. # Those chars are used by this script and files formed by the script. ### Backup part -============================= # # Important!!! Make sure your system has a correct date. Suggestion: use ntp. # Backup is not meant to be interactive, it's meant to be run daily from cron. # That's why the log for backup is kept in logdir $BACKUPDIR/log/backup.log # On the 1st of the month a permanet full backup is made. # The rest of the time an incremental backup is made, by date. # Databases are at full allways and the script makes an automatic repair and # optimizes the databases before the backup. ### Warning!!! ### # If you set the "del_en" variable to "yes" the script will delete the old # backups to make room for the new ones. Read on. ### Warning!!! ### # All incremental backups and databases for a month will be deleted if space # is less than the maximum percent of used space "maxp". # You need to take care to not enter in an endless loop if you set del_en="yes" # The loop can happen if deleted files form $BACKUPDIR don't decrease the # percent of used space # The script check for some dirs and files and it's supposed to be run as root # The script is supposed to be run daily from cron at night like # 40 3 * * * /etc/back-res 1>/dev/null 2>/dev/null # This scripts verifies and corrects all errors found in ALL mysql databases # The script also makes full backups of ALL mysql databases every time it's run # # Restore part -============================ # Restore is meant to be little interactive, the messages are on standard output # Dir's are restored verbose with tar by default. # Last minute of the day "$hm" is set to 2359 but the backup is started at 03:40 # so this should be set AFTER the backup has ended! At 23:59 of the backup day # we can have many files modified from the 03:40. The not so perfect solution is # to backup later in the day (23:00) and hope the backup finishes until 23:59 # My server is still loaded on the 23:00, so I use 03:40 in cron and hm=2359 # because a full backup last for more than 16 hours for tar.bz2 # For sure I will loose all files created between 03:40 and 23:59 of that day. # To prevent that I can restore files one day AFTER the day I want to restore # and use find --newer to delete unwanted files. # To restore dirs make sure you have the full backup from that month and use: # `back-res dir /etc 2009-11-23 /` # to restore the "/etc" dir from date 2009-11-23 to root # `back-res dir /etc 2009-11-23 /tmp` is used to restore the "/etc" dir to /tmp # # `back-res dir all 2009-11-23 /` # to restore all directories from date 2009-11-23 to root # To restore databases use: # `back-res db mysql 2009-11-23` # to restore the "mysql" database from date 2009-11-23 to local mysql server # # `back-res db all 2009-11-23` # to restore all databases from date 2009-11-23 to local mysql server ############################### ### Begin variables section ### ############################### # Change the variables below to fit your computer/backup COMPUTER=`cat /etc/HOSTNAME | awk 'NR==1{print $1}'` # name of this computer DIRECTORIES="/bin /boot /etc /home /lib /lib64 /root /sbin /usr /var /www" # directories to backup EXCLUDED="/bck /tmp /dev /proc /sys /srv /media /var/adm /var/cache /var/lib/mysql /var/run /var/lock /lib/init/rw /var/tmp /var/log/verlihub /var/lib/amavis /var/amavis /var/spool/postfix/p* /var/spool/postfix/var *.pid *.lock *.lck" # exclude those dir's and files BACKUPDIR="/bck/$COMPUTER" # where to store the backups dbuser="root" # database user dbpassword="yourpassword" # database password email="backup@yourdomain.com" # mail for the responsible person TAR=`which tar` # name and location of tar ARG="-cjpSPf" #sparse # tar arguments P = removed /. EARG="-xjpf" # tar extract arguments P = removed / tmpdir="/tmp/tmpbck" # temp dir for database dump and other stuff del_en="yes" # Enable delete of files if used space percent > than $maxp (yes or anything else) maxp="85" # Max percent of used space before start of delete hm="2359" # last minute of the day = last minute of the restored backup of the day restored ################################### ### End user editable variables ### ################################### ######################################################### # You should NOT have to change anything below here # ######################################################### me=`basename $0` headline=" ---------------------=== The back-res script by go0ogl3 ===--------------------- " usage="$headline The backup part requires some configuration in the header of the script and it's supposed to be run from cron. The restore part it's supposed to be run from command line. restore part Usage: t $me [type-of-restore] [dir|db] [YYYY-MM-DD] [path] t $me dir [dir-to-restore] [to-date] [path] t $me dir all [to-date] [path] t $me db [db-to-restore] [to-date] t $me db all [to-date] Where 'dir' or 'db' to restore is one of the configured dirs or db's to backup, or 'all' to restore all dirs or db's. Date format is full date, year sorted, YYYY-MM-DD, like 2009-01-30. 'path' is for dirs and is the path on which you want to extract the backup. If the path to extract is not set, then the backup is extracted on /. For more info read the header of this script! -===--===--===--===--===--===--===--===--===--===--===--===--===--===--===--===- " backup () { if [ -n "$1" ] ; then echo -e "$usage" exit fi DOM=`date +%d` # Date of the Month, DD, eg. 27 FDATE=`date +%F` # Full Date, YYYY-MM-DD, year sorted, eg. 2009-11-21 MDATE=`date +%Y-%m` # Date, YYYY-MM, eg. 2009-09 ################# ### Functions ### ################# function log { acum=`date "+%Y-%m-%d %H:%M:%S"` # I like this type of date. Syslog type doesn't use the year. if [ -e $BACKUPDIR/log/backup.log ]; then echo "$acum - `basename $0` - $1" >> $BACKUPDIR/log/backup.log echo "$acum - `basename $0` - $1" >> $tmpdir/maildata else if [ ! -d $BACKUPDIR/log ]; then mkdir $BACKUPDIR/log if [ -n "$log1" ]; then echo "$log1" >> $BACKUPDIR/log/backup.log echo "$log1" >> $tmpdir/maildata fi echo "$acum - `basename $0` - First run: log dir and log file created." >> $BACKUPDIR/log/backup.log echo "$acum - `basename $0` - First run: log dir and log file created." >> $tmpdir/maildata else echo "$acum - `basename $0` - First run: log file created." >> $BACKUPDIR/log/backup.log echo "$acum - `basename $0` - First run: log file created." >> $tmpdir/maildata fi echo "$acum - `basename $0` - $1" >> $BACKUPDIR/log/backup.log echo "$acum - `basename $0` - $1" >> $tmpdir/maildata fi } function check_mdir { log "Checking if month dir exist: $BACKUPDIR/$MDATE" if [ -d $BACKUPDIR/$MDATE ] ; then log "Backup dir $BACKUPDIR/$MDATE exists" else mkdir $BACKUPDIR/$MDATE log "Month dir $BACKUPDIR/$MDATE created" fi } function check_tempdir { log "Checking if temp dir exist: $tmpdir" if [ -d $tmpdir ] ; then log "Temp dir $tmpdir exists" else mkdir $tmpdir log "Temp dir $tmpdir created" fi } function del_old_files { to_del=`ls -ctF $BACKUPDIR | grep -v ^log/ | tail -n 1 | sed 's////g'` # sort files in ctime order and select the first modified, except the log dir #if [ -d "$BACKUPDIR/$to_del" ] ; then # # recover db backups and store only the ones from de first day of month or from the first full backup of dirs # # list all db backups in month dir, extract first date # day=`ls -ct $BACKUPDIR/$to_del | tail -n 1 | cut -d "-" -f 5 | cut -d "." -f 1` # # then list all db file names # dblist=`ls -ct $BACKUPDIR/$to_del | grep $to_del-$day` # for db in $dblist; do # mv $BACKUPDIR/$to_del/$db $BACKUPDIR/$db # moving files keeps creation date # done # log "Kept db's from $to_del-$day" #else rm -rf $BACKUPDIR/$to_del log "Deleted old: $BACKUPDIR/$to_del" count=0 while [ $count -lt 3 ] do count=$(($count+1)) #echo $count argmax # for test check_space done #fi } #pfs="95" # for test function check_space { #pfs=$(($pfs-1)) # for test pfs=`df -h $BACKUPDIR | awk 'NR==2{print $5}' | cut -d% -f 1` #pfs="90" if [ $pfs -gt $maxp ] ;then log "There is $pfs% space used on $BACKUPDIR" if [ $del_en = "yes" ] ; then del_old_files else log "No free space and del_en=$del_en so we abort here and send mail to $email" mail -s "Daily backup of $COMPUTER `date +'%F'`" "$email" < $tmpdir/maildata exit fi else log "Percent used space $pfs% on $BACKUPDIR ok." fi } function db_back { #Replace / with _ in dir name => filename #DIR_NAME=`echo "$DIRECTORIES" | awk '{gsub("/", "_", $0); print}'` ### All db's check and correct any errors found log "Starting automatic repair and optimize for all databases..." mysqlcheck -u$dbuser -p$dbpassword --all-databases --optimize --auto-repair --silent 2>&1 ### Starting database dumps for i in `mysql -u$dbuser -p$dbpassword -Bse 'show databases'`; do log "Starting mysqldump $i" `mysqldump -u$dbuser -p$dbpassword $i --allow-keywords --comments=false --add-drop-table > $tmpdir/db-$i-$FDATE.sql` $TAR $ARG $BACKUPDIR/$MDATE/db-$i-$FDATE.tar.bz2 -C $tmpdir db-$i-$FDATE.sql rm -rf $tmpdir/db-$i-$FDATE.sql log "Dump OK. $i database saved OK!" done } ############# ### START ### ############# rm -f $tmpdir/maildata if [ -d $BACKUPDIR ] ; then check_space else mkdir $BACKUPDIR log1="$acum - `basename $0` - First run: primary dir $BACKUPDIR created." log "First run: primary dir $BACKUPDIR created." fi check_mdir check_tempdir rm -rf $tmpdir/excluded for a in `echo $EXCLUDED` ; do exfile=`echo -e $a >> $tmpdir/excluded` done #exit db_back for i in `echo $DIRECTORIES` ; do XX=`echo $i | awk '{gsub("/", "_", $0); print}'` YX=`echo $i | awk '{print $1}'` fb=`ls $BACKUPDIR | grep ^full$XX-` if [ -z $fb ] ; then log "No full backup found for $YX. Full backup now!" echo > $tmpdir/full-backup$XX.lck $TAR $ARG $BACKUPDIR/full$XX-$FDATE.tar.bz2 $YX -X $tmpdir/excluded log "Backup of $YX done." fi # Monthly full backup if [ $DOM = "01" ] ; then log "Starting full monthly backup for: $YX" $TAR $ARG $BACKUPDIR/full$XX-$FDATE.tar.bz2 $YX -X $tmpdir/excluded log "Full monthly backup for $YX done." else # If it's not the first day of the month we make incremental backup if [ ! -e $tmpdir/full-backup$XX.lck ] ; then log "Starting daily backup for: $YX" NEWER="--newer $FDATE" $TAR $NEWER $ARG $BACKUPDIR/$MDATE/i$XX-$FDATE.tar.bz2 $YX -X $tmpdir/excluded log "Daily backup for $YX done." else log "Lock file for $YX full backup exists!" fi fi # Clean full backup directory lock file rm -rf $tmpdir/full-backup$XX.lck done #Clean temp dir rm -rf $tmpdir/excluded # End of script log "All backup jobs done. Exiting script!" } restore () { del_res () { # We now need to remove the newer files created after the restored backup date. to_rem=`find $path/$2 -newer $tmpdir/dateend` echo -en "n$headlinen For a clean backup restored at $3 we need now to delete the filesncreated after the backup date.n If exists, a list of files to be deleted follows:nn" for a in $to_rem ; do echo -e "To be removed: $a" done echo -en "nPlease input "yes" to delete those files, if they exist, and press [ENTER]: " read del if [[ "$del" = "yes" ]] ; then for a in $to_rem ; do rm -rf $a done echo -en "All restore jobs done!nDir $2 restored to date $3!n" exit fi } if [ -z "$4" ] ; then path="/" else path=$4 # this is the path where to extract the files fi RDATE=$3 DOM=`echo $RDATE | cut -d "-" -f3` # Date of the Month eg. 27 MDATE=`echo $RDATE | cut -d "-" -f2` YDATE=`echo $RDATE | cut -d "-" -f1` type=$1 dir=`echo $2 | awk '{gsub("/", "_", $0); print}'` if [ -z "$3" ] ; then echo -e "$usage" exit fi # poor date input verification: ${#RDATE} is 10 for a correct date 2009-01-30 # find the first possible restore date=day year=`ls -ctF $BACKUPDIR | grep -v ^log/ | tail -n 1 | cut -d "-" -f 2` md=`ls -ctF $BACKUPDIR | grep -v ^log/ | tail -n 1 | cut -d "-" -f 3` day=`ls -ctF $BACKUPDIR | grep -v ^log/ | tail -n 1 | cut -d "-" -f 4 | cut -d "." -f 1` resdate=$year$md$day dh="1234" err=`touch -t $YDATE$MDATE$DOM$dh $tmpdir/datestart 2>&1` if [ -n "$err" ] & [ ${#RDATE} != 10 ] ; then #echo "err = $err" echo -e "$usage" echo -e "Invalid date format. Correct YYYY-MM-DD. Ex.: 2009-01-14n" exit fi # check to see if user inputs date in future TD=`date +%s` # today in epoch ID=`date --date "$RDATE" +%s` # input date in epoch RD=`date --date "$resdate" +%s` # first backup date in epoch if [ "$ID" -ge "$TD" ] ; then echo -e "$usage" echo -e "Invalid date format. Date supplied $RDATE is in the future!n" exit fi if [ "$RD" -gt "$ID" ] ; then echo -e "$usage" echo -e "Invalid date format. Date supplied $RDATE is before the first backup on $year-$md-$day!n" exit fi #echo "Checking if path dir exist: $path" if [ $type = "dir" ] ; then # echo $dir and $path if [ -d $path ] ; then if [ -n "$path" ]; then mesaj="" fi else mesaj="Extraction dir $path invalid" exit fi fi # We now prompt the user with the info entered on the comand line. # clear echo -en "n You want to restore $1 $2 to date $3.nnPlease input "yes" if the above is ok with you and press [ENTER]: " read ok if [[ "$ok" = "yes" ]] ; then if [[ "$1" == "dir" ]] ; then if [[ "$2" == "all" ]] ; then echo -en "nExtracting all dir's backup from date $3 to $path:n" sleep 5 # We wait 5 secs for the user to see what's happening. else # We suppose the user uses /dir if [[ "$DIRECTORIES all" =~ "$2" ]] ; then echo -en "nTrying to restore $2 dir's backup from date $3 to $path:nn" # we say "trying" because if the requested dir is "al" it matches! sleep 5 fi fi elif [[ "$1" == "db" ]] ; then if [[ "$2" == "all" ]] ; then echo -en "nRestoring all mysql databases from date $3 to local server:n" sleep 5 else if [[ "$dblist" =~ "$2" ]] ; then echo -en "nTrying to restore $2 database backup from date $3 to local server:nn" # we say "trying" because it's an imperfect check, same as above sleep 5 fi fi fi else echo -en "nInvalid entry. Exiting script...nn" exit fi dst="010000" # first minute of the first day touch -t $YDATE$MDATE$dst $tmpdir/datestart 2>&1 touch -t $YDATE$MDATE$DOM$hm $tmpdir/dateend 2>&1 if [ $type = "dir" ] ; then if [[ "$DIRECTORIES all" =~ "$2" ]] ; then if [ $dir = "all" ] ; then farh=`find $BACKUPDIR -maxdepth 1 -type f -newer $tmpdir/datestart -a ! -newer $tmpdir/dateend | sed 's_.*/__' | grep ^full_` arh=`find $BACKUPDIR/$YDATE-$MDATE -maxdepth 1 -type f -newer $tmpdir/datestart -a ! -newer $tmpdir/dateend | sed 's_.*/__' | grep -v ^db-` # echo farh este $farh # echo arh este $arh else farh=`find $BACKUPDIR -maxdepth 1 -type f -newer $tmpdir/datestart -a ! -newer $tmpdir/dateend | sed 's_.*/__' | grep $dir | grep ^full_` # echo farh e $farh arh=`find $BACKUPDIR/$YDATE-$MDATE -maxdepth 1 -type f -newer $tmpdir/datestart -a ! -newer $tmpdir/dateend | sed 's_.*/__' | grep $dir | grep -v ^db-` # echo arh e $arh fi for f in $farh ; do echo -en "tExtracting $f...nn" $TAR $EARG $BACKUPDIR/$f -C $path &>/dev/null # if the day is 01 the the full backup is recovered so we need to clean newer files created after the backup date. if [ $DOM = "01" ] ; then del_res $path $2 $3 $tmpdir fi done for i in $arh ; do echo -en "tExtracting $i...nn" $TAR $EARG $BACKUPDIR/$YDATE-$MDATE/$i -C $path &>/dev/null done del_res $path $2 $3 $tmpdir else mesaj="Invalid directory to restore!" fi elif [ "$type" = "db" ] ; then db=$2 # here we build the db list to restore from the files we backed up before in the day requested dblist=`find $BACKUPDIR/$YDATE-$MDATE -maxdepth 1 -type f | sed 's_.*/__' | grep ^db- | grep $YDATE-$MDATE-$DOM | cut -d "-" -f2` dblist="$dblist all" #echo $dblist for d in $dblist ; do if [ "$d" == "$2" ] ; then if [ "$db" = "all" ] ; then # get db list from backup and restore all db's arh=`find $BACKUPDIR/$YDATE-$MDATE -maxdepth 1 -type f | sed 's_.*/__' | grep ^db- | grep $YDATE-$MDATE-$DOM` else arh=`find $BACKUPDIR/$YDATE-$MDATE -maxdepth 1 -type f | sed 's_.*/__' | grep ^db- | grep $db- | grep $YDATE-$MDATE-$DOM` fi for i in $arh ; do rdb=`echo $i | cut -d "-" -f2` mysql -u$dbuser -p$dbpassword $rdb < $TAR -xvjp $BACKUPDIR/$YDATE-$MDATE/$i done echo -en "All restore jobs done!nDatabase $2 restored to date $3!n" fi done if [ -z "$rdb" ] ; then mesaj="Invalid database to restore!" fi else echo -e "$usage" mesaj="Invalid type specified" fi if [ -n "$mesaj" ] ; then echo -e "$usage" echo -en "tt###t$mesajt###nn" fi # Send accumulated maildata an cleanup mail -s "Daily backup of $COMPUTER `date +'%F'`" "$email" < $tmpdir/maildata rm -rf $tmpdir/datestart rm -rf $tmpdir/dateend rm -rf $tmpdir/excluded rm -rf $tmpdir/maildata } case "$1" in dir) restore $1 $2 $3 $4 ;; db) restore $1 $2 $3 $4 ;; version) echo $headline echo -e "nVersion $versionn" ;; *) backup $1 exit 1 esac