#!/bin/bash 
#
# $Id: systembackup.sh,v 1.48 2004/12/05 14:34:39 pj Exp $
#
# Copyright 2002, 2003 Per Jensen, Net-Es
# Email: pj@net-es.dk
#
# Note: 
# THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
# APPLICABLE LAW.
#
# Licensed under GPL2
# The file COPYING should accompany this script detailing the license terms.
# It not, please visit this URL 'http://www.gnu.org/licenses/gpl.txt' to 
# read the terms
#
# Description:
# Backup script using 'star' as backup engine. See 'star' homepage for more
# information on 'star'
# http://www.fokus.gmd.de/research/cc/glone/employees/\
# joerg.schilling/private/star.html 
# 
# See systembackup-manual.txt for documentation of this script
#

# ------------------------------------------------------------
# Set american sort so that 'sort' gets splitted parts right
unset LANG
unset LC_ALL
unset LC_COLLATE
# --------------------------------------------------------------


#-------------------------------------------------------------
# Variables
#-------------------------------------------------------------
date=`date +%Y%m%d`
star=/opt/schily/bin/star
hostname=`hostname -f`

archivedir=/home/pj/cvs/linuxconf/backupscripts/systembackup/tarfiles/
archiveincdir=${archivedir}inc/

docarchivedir=/mnt/hdh1/documents

installdir=/home/pj/cvs/linuxconf/backupscripts/systembackup
logfile=${installdir}/systembackup.log
templogfile=${installdir}/temp.log

backupoptions=" c -M  -atime -fifo -no-statistics -silent -dump "
restoreoptions=" x -p -nowarn -xdir " 

email="per@net-es.dk"

# chunksize used by 'split'
chunksize=1024m

# top of directory hierarchy we will perform tests on 
testdir="/tmp/systembackuptest/"


# --------------------------------------------------------------------
#  don't change these
# --------------------------------------------------------------------
result=0 
leveltemplate=level.
# level is the incrementals level
level=0
doincbackup=0
url="http://www.net-es.dk/net-es/Systembackup.do"

#---------------------------------------------------------------------
#    Start of functions
#---------------------------------------------------------------------

#
# print some dashes
#
function printAFewDashes()
{
    echo "----" |tee -a ${templogfile}
}


#
# prints argument to stdout and templogfile
#
function print()
{
    echo $1 |tee -a ${templogfile}
}


#
# print current time
#
printtime()
{
    echo `date +'%Y-%m-%d %H:%M:%S'` |tee -a ${templogfile}
}


#
# Extract filelist from tarfile and write to file
# Parameters:
# $1: tarfilename without path
# $2: filelistname without path
function generate_filelist()
{
    printtime

    if [[ ${doincbackup} > 0 ]]; then
        archive=`${installdir}/abspath.py "${archiveincdir}/$1"`
        archive="${archive}-inc-${newlevel}"
        filelistname=`${installdir}/abspath.py "${archiveincdir}/$2"`
        filelistname="${filelistname}-inc-${newlevel}"
        print "Filelist for archive: \"${archive}\""
    else
        archive=`${installdir}/abspath.py "${archivedir}/$1"`
        print "Filelist for archive: \"${archive}\"" 
        filelistname=`${installdir}/abspath.py "${archivedir}/$2"`
    fi

    cat `ls ${archive}* |sort` |${star} tv  > "${filelistname}" 2>&1 |tee -a ${templogfile}
}


#
# Verify archive by executing a -diff
# Diff on filesize as this is faster than content. 'star' prints information
# about checksum errors if found 
# All output from 'star' is collected in the temporary logfile ${templogfile}
# which is later mailed to the administrator 
#
# ${archivedir} is defined in top of script
#
# Parameters:
# $1: tarfilename without path
# $2: toplevel directory in archive
function verify()
{
    printtime

    topleveldir=`${installdir}/abspath.py "$2"` 

    if [[ ${doincbackup} > 0 ]]; then
        archive=`${installdir}/abspath.py "${archiveincdir}/$1"`
        archive="${archive}-inc-${newlevel}"
    else
        archive=`${installdir}/abspath.py "${archivedir}/$1"`

    fi

    print "Verifying archive: \"${archive}\""
    cat `ls ${archive}* |sort` |${star} -diff diffopts=size -C "${topleveldir}"  2>> ${templogfile}

    if [[ $? == 0 ]]; then
   	  print "Verification succeeded"
    else
	  result=1 # for mail subject
	  print "Verification failed, check log"
    fi
}


#
# restore archive to directory using patterns
# $1 directory to restore from 
# $2 tar archive to restore from
# $3 patterns optional patterns for file selection in tar archive
function restore()
{
    printtime
    print "Restore to:   \"$1\""
    print "Restore from: \"$2\""
    print "Patterns:     \"$3\""

    directory=`${installdir}/abspath.py "$1"`
    archive=`${installdir}/abspath.py "$2"`

    ${installdir}/makedirs.py "${directory}"

    curdir=`pwd`
    if [[ $? = 0 ]]; then
	cd ${directory}; cat `ls ${archive}* |sort` |${star} ${restoreoptions} $3  
    else 
        echo "Failed creating \"$1\""
    fi 
    cd "${curdir}"
}


#
# Parameters:
# $1: tarfilename
# $2: directory
# $3: 'pattens in single string'
function backup()
{
    printAFewDashes
    printtime
    print "Backing up: \"$2\""
    print "Patterns:   \"$3\"" 
    

    if [[ ${doincbackup} > 0 ]]; then
        archive=`${installdir}/abspath.py "${archiveincdir}/$1"`
        archive="${archive}-inc-${newlevel}"
    else
        archive=`${installdir}/abspath.py "${archivedir}/$1"`
    fi

    curdir=`pwd`
    print "Archive: \"${archive}\""

    if [[ ${doincbackup} > 0 ]]; then
	    cd "$2"; ${star} ${backupoptions} newer=${installdir}/${leveltemplate}${level} $3 . |split -b ${chunksize} - "${archive}" 2>&1 |tee -a  ${templogfile}
    else
	    cd "$2"; ${star} ${backupoptions} $3 .  |split -b ${chunksize} - "${archive}"  |tee -a  ${templogfile} 
    fi
    cd "${curdir}"
}


#
# print filelist according to pattern from existing tar archive
# Parameters:
# $1: tar file, path'ed
# $2: patterns
function dirlist()
{
    printtime
    print "List files in: \"$1\""
    print "Patterns:      \"$2\""  
    nice ${star} tv -file "$1" $2
}


# 
# Append temporary logfile to main one
# Send status mail
#
function cleanup()
{
    cat ${templogfile} >> ${logfile}
    
    #
    # Send mail to administrator
    #
    if [[ $result == 0 ]]; then
        resulttext="Backup ${date} succeeded"
    else
        resulttext="Backup ${date} failed"
    fi
    
    mail -s "${resulttext}" ${email} < ${templogfile}
}



#
# Performs setup of incremental backup. Find max level, create new levelfil
# set incremental flag which is checked in backup()
# 
function sysinc()
{
    # find max current level
    level=`find ${installdir} -name 'level.*' |awk -f ${installdir}/getlevel.awk`
    print "Max incrementals level is: $level"

    newlevel=`expr ${level} + 1`
    touch ${installdir}/${leveltemplate}${newlevel}
    print "Touched ${installdir}/${leveltemplate}${newlevel}"

    # signal an incremental backup
    doincbackup=1

    systembackup
}


#
# - Remove all levelfiles in 'installdir' and create new level.0
# - Reset incremental flag
#
function preparesystembackup()
{
    find ${installdir} -name 'level.*' -exec rm -f {} \; 
    touch ${installdir}/level.0
    
    # make sure we are doing a full backup
    doincbackup=0
}


#
# Performs a system backup on server
#
# This function is tied *very* closely to the server it is executed on
# It really should be put in a config file......
#
function systembackup()
{
    print "---------------------------------------------"
    print "Hostname: ${hostname}"
    print "Starting full System Backup: `date`"
    

    cp /etc/raidtab   ${archivedir}/
    cp /etc/fstab     ${archivedir}/
    /sbin/fdisk -l /dev/hda >  ${archivedir}/partitions.txt 2>&1

    echo >>  ${archivedir}/partitions.txt
    echo >>  ${archivedir}/partitions.txt

    /sbin/fdisk -l /dev/hdc >> ${archivedir}/partitions.txt 2>&1
 
    # / file system 
    rootfile=root-fs-${date}.tar
    backup             ${rootfile}  /  "-not pattern=proc/* -not pattern=tmp/*" 
    generate_filelist  ${rootfile}  root-fs-${date}.txt
    verify             ${rootfile}  /
    
    # /home/pj hierarchy
    pjfile=home-pj-${date}.tar
    backup             ${pjfile}  /home/pj  "-not pattern=\.film/* -not pattern=\.pan/* -not pattern=src/* -not pattern=tmp/*"
    generate_filelist  ${pjfile}  home-pj-${date}.txt
    verify             ${pjfile}  /home/pj
    
    # /home excl. jkl, pj and hp hierarchies
    homefile=home-fs-${date}.tar
    backup             ${homefile}  /home  "-not pattern=pj/* -not pattern=jkl/* -not pattern=hp/*"
    generate_filelist  ${homefile}  home-fs-${date}.txt
    verify             ${homefile}  /home
    
    # /home/jkl hierarchy
    jklfile=home-jkl-${date}.tar
    backup             ${jklfile}  /home/jkl  "pattern=Dokumenter/* pattern=public_html/*"
    generate_filelist  ${jklfile}  home-jkl-${date}.txt
    verify             ${jklfile}  /home/jkl
    
    # /usr filesystem
    usrfile=usr-fs-${date}.tar
    backup            ${usrfile}  /usr  "-not pattern=opt/*"
    generate_filelist ${usrfile}  usr-fs-${date}.txt
    verify            ${usrfile}  /usr

    
    # JSP Wiki 
    optfile=opt-fs-${date}.tar
    backup            ${optfile}  /opt  "pattern=jakarta/JSPWiki/*"
    generate_filelist ${optfile}  opt-fs-${date}.txt
    verify            ${optfile}  /opt

    print "JSP Wiki backed up..."
    printAFewDashes    

    print "Closing down Cyrus master process" 
    ps ax |grep cyrus\/bin\/master |grep -v grep |kill `awk '{print $1}'` 
    
    # /var filesystem

    # create flat file representation of berkeley mailbox db
    su - cyrus -c /usr/cyrus/bin/ctl_mboxlist -d > /var/imap/mailboxes.txt

    varfile=var-fs-${date}.tar
    backup             ${varfile} /var 
    generate_filelist  ${varfile} var-fs-${date}.txt
    verify             ${varfile} /var
    
    print "Restarting Cyrus..." 
    ps ax > processes.log
    grep cyrus\/bin\/master  processes.log 2>&1 > /dev/null
    # make sure Cyrus is not running
    if [ $? == 1 ]; then
        nohup /usr/cyrus/bin/master & 2>&1 |tee -a ${templogfile}
    fi
    
    printAFewDashes 
    print "Ending System backup:  `date`"
    print "---------------------------------------------"
}


#
# Performs a backup of document directories
# used by me to take monthly snapshots
#
# This function is tied *very* closely to the server it is executed on
# It really should be put in a config file......
#
function docbackup()
{
    print "---------------------------------------------"
    print "Hostname: ${hostname}"
    print "Starting document backup: `date`"
    
    archivedir=${docarchivedir}

    # documents
    docfile=documents-${date}.tar
    backup             ${docfile}  /home  "pattern=jkl/dokumenter/* pattern=hlr/*Documents/* pattern=pj/Documents/*  pattern=pj/Dokumenter/* pattern=net-es/*"

    generate_filelist  ${docfile}  documents-${date}.txt
    verify             ${docfile}  /home
    

    # JSP Wiki 
    optfile=wiki-${date}.tar
    backup            ${optfile}  /opt  "pattern=jakarta/JSPWiki/*"
    generate_filelist ${optfile}  wiki-${date}.txt
    verify            ${optfile}  /opt

    print "JSP Wiki backed up..."
    printAFewDashes    

    printAFewDashes 
    print "Ending System backup:  `date`"
    print "---------------------------------------------"
}



function printhelp()
{
echo "
Commands:
-system                                 : performs system backup as defined in
                                        : systembackup() function
-sysinc                                 : performs incremental backup since 
                                        : latest timestamp
-docs                                   : backup docs to docs directory
-list    archive ['patterns']           : lists files in archive 
-restore directory archive ['patterns'] : restores files to directory 
-diff    archive topleveldirInArchive   : find diff between filesystem/archive
-version                                : print cvs version

Patterns are like this: 'pattern=*.sh pattern=*AndThisDir*'

This pattern:  '-not pattern=ThisDir* pattern=*.sh' operates on .sh files but 
those in ThisDir/

See doc for testing script.
"
}


function version()
{
    VERSION=`cat $0 |awk 'match($0, /([0-9]+\.[0-9]+ [0-9]+.[0-9]+.[0-9]+)/) {print substr($0, RSTART, RLENGTH)}'`
    print "systembackup.sh cvs version: $VERSION"
    print "Systembackup.sh lives here: ${url}"
}


function testdirs()
{
    # create /test hierarchy
    rm -fr ${testdir}
    mkdir  ${testdir}
    cp -R  /home/pj/cvs/websites ${testdir}
}


function test()
{
    testdirs
    preparesystembackup
    testbackup
    testrestore
}


function testbackup()
{
    backup             test.tgz  ${testdir}
    generate_filelist  test.tgz  test.txt
    verify             test.tgz  ${testdir}  
    printtime
    echo "End of test backup"
}


function testrestore()
{
    rm -fr kurttest 
    restore            kurttest  ${archivedir}/test.tgz "pattern=*"
    echo "End of test restore"
}

function testincrestore()
{
    rm -fr kurttestinc
    restore            kurttestinc  ${archiveincdir}/test.tgz-inc-${newlevel} " pattern=*"
    echo "End of test restore"
}


function testinc()
{

    # find max current level
    level=`find ${installdir} -name 'level.*' |awk -f ${installdir}/getlevel.awk`
    print "Max incrementals level is: $level"

    # create test files after touching level.0 - let's have some files to back up
    testdirs
    print "Touching all files - restore must equal full restore"
    find ${testdir} -exec touch {} \;

    newlevel=`expr ${level} + 1`
    touch ${installdir}/${leveltemplate}${newlevel}
    print "Touched ${installdir}/${leveltemplate}${newlevel}"

    # signal an incremental backup
    doincbackup=1

    testbackup
    testincrestore
}


function testincpartial()
{

    # find max current level
    level=`find ${installdir} -name 'level.*' |awk -f ${installdir}/getlevel.awk`
    print "Max incrementals level is: $level"

    print "Backing up manually touched files"
    print "Try \"find ${testdir}/some/sub/dir -exec touch {} \;\""

    newlevel=`expr ${level} + 1`
    touch ${installdir}/${leveltemplate}${newlevel}
    print "Touched ${installdir}/${leveltemplate}${newlevel}"

    # signal an incremental backup
    doincbackup=1

    testbackup
    testincrestore
}



#---------------------------------------------------------------------
#   Parse commandline
#---------------------------------------------------------------------

# system backup
if [[ $1 = "-system" ]]; then
     rm ${templogfile} > /dev/null 2>&1
     preparesystembackup
     systembackup
     cleanup
# Incremental system backup
elif [[ $1 = "-sysinc" ]]; then
     sysinc
# system test backup
elif [[ $1 = "-test" ]]; then
     test
# system testinc backup
elif [[ $1 = "-testinc" ]]; then
     testinc
# documents backup
elif [[ $1 = "-docs" ]]; then
     docbackup
     cleanup
# system testincpartial backup
elif [[ $1 = "-testincpartial" ]]; then
     testincpartial
# print cvs version
elif [[ $1 = "-version" ]]; then
     version
# list
elif [[ $1 = "-list" ]]; then
    if [ -z "$3" ]; then
	dirlist $2 'pattern=*'
    else
	dirlist $2 "$3"
    fi
# restore
elif [[ $1 = "-restore" ]]; then
    if [ -z "$4" ]; then
        restore $2 $3 'pattern=*'
    else
	restore "$2" "$3" "$4"
    fi
# diff
elif [[ $1 = "-diff" ]]; then
       verify "$2" "$3"  
elif [[ $1 = "-help" ]]; then
    printhelp
else
    printhelp 
fi


