About Me

My photo
I know the last digit of PI
Showing posts with label backup linux rsync. Show all posts
Showing posts with label backup linux rsync. Show all posts

Friday, August 28, 2009

Как да си направим backup на диска под Линукс

Въпреки, че хардовете в днешно време са доста надеждни (поне на мен лично не ми е горял хард все още), не е зле човек да се потдотви и да си прави бакъпи редовно.
Понеже основната ми машинка която ползвам за SCM, Continuous integration, Issue trackig и т.н. е под линукс, затова реших да си напиша няколко скриптчета с които да си правя бакъпите. Изискванията ми не са кой знае какви - просто трябва да мога да си възтановя SCM и issue tracking даните без загуби и много труд. При евентуален срив и загуба на хард не очаквам да мога да възтановя цялата система. Пък и какъв е смисълът? Така никога няма да седна да инсталирам нова версия на софтуера! Всъщност от тази гледна точка сривовете са даже нещо много полезно... помагат ти да си разчистиш старите данни и боклуци, които са се насъбрали с години, кара те да качиш нови програмки и т.н. :-D Просто трябва да се случват в подходящ момент :-) когато имаш достатъчно време да си сетъпнеш нова система!...
Та да се върнем на въпроса - скриптът за бакъп трябва да може да копира някои директории, да ги архивира и да пази копия на тях. Също така е хубаво да може да се следи и история...случайно, ако се прецакат някакви настройки да могат да се проверят кога са променени, и да се възтановят правилните конфигурации. Та скрипта трябва да може да пази архив на старите бекъпи. В основата на всичко е rsync, която се използва за създаване на самите файлове. За да сработи скрипта трябва да се създаде една директория /etc/backup където са всички конфигурации включително и самият скрипт:

#!/bin/sh

VERBOSE=0
SCOPE=""
CONFIG="/etc/backup/backup.conf"

# Outputs the help message how to use the script
print_help()
{
echo "Usage: backup [-v | --verbose] <-s | --scope> " >&2
echo " -v or --verbose switch verbose mode on" >&2
echo " -s or --scope reads the list of directories to backup for " >&2
echo " the from the /etc/backup.conf " >&2
}

# Parses the passed command line and defines the backup scope, verbose mode, etc.
parse_command_line()
{
PREV_SCOPE=0
for p in "$@"; do
if [ $PREV_SCOPE -eq 1 ]; then
SCOPE=$p
PREV_SCOPE=0
else
if [ '--verbose' == $p -o '-v' == $p ]; then
VERBOSE=1
fi

if [ '--scope' == $p -o '-s' == $p ]; then
PREV_SCOPE=1
fi
fi
done
}

# Outputs message to the console if the verbose mode is selected
print()
{
if [ $VERBOSE -eq 1 ]; then
echo "$1"
fi
}


read_configuration()
{
# @author Michael Klier
match=0

while read line; do
# skip comments
[[ ${line:0:1} == "#" ]] && continue

# skip empty lines
[[ -z "$line" ]] && continue

# still no match? lets check again
if [ $match == 0 ]; then

# do we have an opening tag ?
if [[ ${line:$((${#line}-1))} == "{" ]]; then

# strip "{"
group=${line:0:$((${#line}-1))}
# strip whitespace
group=${group// /}

# do we have a match ?
if [[ "$group" == "$1" ]]; then
match=1
continue
fi

continue
fi

# found closing tag after config was read - exit loop
elif [[ ${line:0} == "}" && $match == 1 ]]; then
break

# got a config line eval it
else
eval $line
fi

done < "$CONFIG"
}


#
#
# The main script starts here
#
#

START_TIME=`date +%s`

# Parse the command line parameters
parse_command_line $@

if [ -z $SCOPE ]; then
print_help
echo $"Please enter the name of the backup scope" >&2
exit 1
fi

# Read LIST variable from the configuration file
read_configuration $SCOPE

# Example how the LIST variable should looks like
# LIST="etc root usr home var"

# Waits at 1 seconds, so if the user starts the script too fast the TIME_STAMP directory will be different
sleep 1

BACKUP_DIR="/backup/$SCOPE"

# Output the configuration information
print "Backup scope: $SCOPE"
print "Backup directory list: $LIST"
print "Exclude file: $EXCLUDE_FILE"
print "Backup dir is $BACKUP_DIR"

# Creates the backup directory
if [ ! -d $BACKUP_DIR ]; then
mkdir $BACKUP_DIR
fi

#
#
# Rotate backups, so we keep only the last 100
#
#

# Maximum number of backups to keep
maxInd=20

# Gets the oldest backup canonical path (it is a symbolic link to a time-stamped tar file)
REAL_PATH=`readlink -f $BACKUP_DIR/b.$maxInd.tar.gz`

# Remove the oldest backup
if [ -e $REAL_PATH ]; then
print "Removing tar file $REAL_PATH"
rm -rf $REAL_PATH
fi

# Remove the symbolic link
rm -rf $BACKUP_DIR/b.$maxInd.tar.gz


# Rotate backups from $ind to $ind+1
for ((ind = $maxInd-1; ind >= 0; ind--)); do
let nextInd=$ind+1
if [ -e $BACKUP_DIR/b.$ind.tar.gz ]; then
print "Rotating $BACKUP_DIR/b.$ind.tar.gz to $BACKUP_DIR/b.$nextInd.tar.gz"
mv $BACKUP_DIR/b.$ind.tar.gz $BACKUP_DIR/b.$nextInd.tar.gz
fi
done

# Delete the first backup (it must already be rotated, but just to be sure that the file doesn't exists)
rm -f $BACKUP_DIR/b.0.tar.gz

# Generate new timestamp for the new backup
TIME_STAMP=`date "+%F_%H-%M-%S"`

# Create the backup folder using the timestamp
mkdir $BACKUP_DIR/$TIME_STAMP

# Backup all files in the backup-list
for d in $LIST; do
print "Backuping /$d/ to $BACKUP_DIR/$TIME_STAMP/$d/"
mkdir -p $BACKUP_DIR/$TIME_STAMP/$d/
if [ $VERBOSE -eq 1 ]; then
rsync -vv -a --delete --exclude-from=$EXCLUDE_FILE /$d/ $BACKUP_DIR/$TIME_STAMP/$d/
else
rsync -a --delete --exclude-from=$EXCLUDE_FILE /$d/ $BACKUP_DIR/$TIME_STAMP/$d/
fi


done

# Go to the backup directory, so we could tar the content
pushd $BACKUP_DIR/$TIME_STAMP > /dev/null

# Archive the entire directory into single tar file
tar -c -z -f $BACKUP_DIR/$TIME_STAMP.tar.gz .

# Restore the current directory
popd > /dev/null

# Remove the backup directory
rm -rf $BACKUP_DIR/$TIME_STAMP

# Create a symbolic link to the directory
ln -s $BACKUP_DIR/$TIME_STAMP.tar.gz $BACKUP_DIR/b.0.tar.gz

FINISH_TIME=`date +%s`

FILE_SIZE=`ls -lrt $BACKUP_DIR/$TIME_STAMP.tar.gz | awk '{print $5}'`

printf "Completed! Execution time: $((FINISH_TIME - START_TIME)) seconds. Backup file size: $FILE_SIZE bytes."
printf "\n"




И така този скрипт се ползва по следния начин:
/etc/backup -s SCOPE_NAME

SCOPE_NAME се чете от конфигурационият файл /etc/backup.conf в който се описват кои директории да се копират. Ето и примерен такъв:
#
# In order to define a new scope of backup items uncomment following lines
#
#
#
# scope_name {
# LIST="etc root usr home var"
# EXCLUDE_FILE="/etc/backup/backup.excludes"
# }
#
# No leading ot trailing / in the directory names.
# The directories are sub directories of the /


weekly {
LIST="etc root usr home var"
EXCLUDE_FILE="/etc/backup/backup.excludes"
}

daily {
LIST="etc usr/red5/conf usr/red5/webapps usr/share/tomcat6/conf usr/share/tomcat6/webapps var/svn var/trac"
EXCLUDE_FILE="/etc/backup/backup.excludes"
}


Всеки scope си има списък от директории (LIST) и EXCLUDE_FILE в който се описват, коит файлове от тези директории да се пропуснат. Примерен backup.excludes:
#Excluding subdirs from /usr/

/bin/***
/java/***
/games/***
/kerberos/***
/sbin/***
/libexec/***
/lib/***
/tmp
/tmp/***
/include/***

# Excluding subdirs from /var/

/tmp/***


Така целият скрипт очаква, че ще има директория /backup (която е добре да моунтната на друг хард диск)

Хубаво е да се направят и cron task-ове които да правят дневен или седмичен бекъп.
Ето няколко примерни такива:

/etc/cron.daily/backup_daily
#!/bin/bash

TIME_STAMP=`date "+%F_%H-%M-%S"`

echo "Starting daily backup $TIME_STAMP" > /var/log/backup/$TIME_STAMP.log

/usr/bin/backup --verbose --scope daily > /var/log/backup/$TIME_STAMP.log 2> /var/log/backup/$TIME_STAMP.err



/etc/cron.weekly/backup_weekly
#!/bin/bash

TIME_STAMP=`date "+%F_%H-%M-%S"`

echo "Starting weekly backup $TIME_STAMP" > /var/log/backup/$TIME_STAMP.log

/usr/bin/backup --verbose --scope weekly > /var/log/backup/$TIME_STAMP.log 2> /var/log/backup/$TIME_STAMP.err



И така това е целият скрипт. Успех!

~Киро :-)

Many thanks to Michael Klier for his article
Parsing Simple Config Files In Bash