Понеже основната ми машинка която ползвам за 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"
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>
echo " -v or --verbose switch verbose mode on" >&2
echo " -s or --scope reads the list of directories to backup for " >&2
echo " the
}
# 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"
}
# 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/***
/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
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
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