Pull to refresh

bash: Бэкап без лишнего ПО

Reading time 7 min
Views 70K
Бэкап важной информации — каждый системный администратор сталкивается с такой задачей. Задача казалось бы тривиальная и у многих читателей интереса не вызовет. Но, например, мне бы такая статья в определенный момент помогла бы весьма сильно, поэтому считаю, что этой статье быть.

Задача: Бэкап данных в локальную директорию и на отдельный сервер, с использованием минимума стороннего ПО, логированием и оповещением администратора в jabber при сбоях. Все основные функции большинства ПО для автоматического бэкапа, но без установки оного, а следовательно без его багов (что, собственно, и привело к подобной идее).

А теперь к делу.

Для начала создадим и откроем скрипт
nano backup-script

Теперь в скрипте добавим строку
#!/bin/bash

Объявим некоторые переменные.
TN — TASKNAME — имя задания.Используется для вывода в лог и определения названия файла.
Так как заданий несколько (ежемесячное, еженедельное, ежедневное) и писать на каждый случай скрипт было лень, я создал универсальный, в котором надо просто раскомментить нужные строки. Наименование заданий писать надо без пробелов, желательно в латинице, если не хотите проблем с кодировкой и неправильными параметрами команд.
TN=docs-monthly<br />
#TN=docs-weekly<br />
#TN=docs-daily

OF — Output File — имя выходного файла. Получается из переменной TN, то есть имени задания.
OF=$TN.tar.gz

Объявляем переменную с путем к файлу лога, и далее все сообщения об ошибках и остальном будем выводить в лог.
LOGFILE=/var/log/backup.log

Сделаем запись в лог о начале бэкапа (дата, время, имя задания)
echo  >>$LOGFILE
echo "====================================================="  >>$LOGFILE
echo "$(date +'%d-%b-%Y %R')" >>$LOGFILE
echo "Задание \"$TN\" запущено..." >>$LOGFILE

Есть проблема в том что если указывать в параметрах команд (напр. tar) имена каталогов с пробелами, скрипт срабатывает с ошибкой. Решение найдено на просторах интернета — операционная система linux использует пробел в качестве стандартного разделителя параметров команды. Переопределим стандартный разделитель (хранится в переменной $IFS) отличным от пробела, например \n – знаком переноса строки.
Запоминаем старое значение стандартного разделителя
OLD_IFS=$IFS

Заменяем стандартный разделитель своим
IFS=$'\n'

SRCD — SouRCe Directory — каталог с данными для бэкапа
Теперь можно перечислять несколько каталогов, разделителем будет перенос строк как мы сами указали строкой выше
SRCD="/mnt/source/folder_1
/mnt/source/folder_2
/mnt/source/folder_N"

TGTD — TarGeT Directory — каталог в который будут складываться бэкапы
TGTD="/var/backups/"

Естественно мы понимаем что хранить важные бэкапы только на источнике как минимум легкомысленно. Поэтому оставим копию и на удаленном ресурсе, который будем отдельно монтировать с помощью mount и fstab. Сразу поясню почему я использовал mount и fstab, а не один mount — я монтирую этот каталог и в других своих скриптах, а как сказал один из знакомых программистов — хороший программист не будет писать один и тот же код дважды (как-то так, дословно не помню, но надеюсь смысл донес).
TGTD2="/mnt/archive/"

Сам процесс архивирования в варианте "Создать новый архив"
tar -czf $TGTD$OF $SRCD &>>$LOGFILE

и в варианте "Обновить файлы в старом архиве"
tar -u -f $TGTD$OF $SRCD &>>$LOGFILE

Во втором случае лучше вместо $OF использовать конктретное имя файла потому что у меня например ежедневно апдэйтится еженедельный архив, а их $TN (имена задания) не совпадают, соответственно и $OF.

В переменной "?" ханится статус выполнения последней команды. Сохраним его, чтобы воспользоваться позже.
STATUS=$?

Возвращаем стандартный разделитель к исходному значению
IFS=$OLD_IFS

Теперь добавим условие — если процесс упаковки в архив tar закончился с ошибкой, отправляем сообщение админу, удаляем неудачный файл бекапа. Иначе продолжаем дальше — монтируем сетевую шару и кидаем в нее копию архива. После каждой операции проверяем результат выполнения, пишем логи, и либо продолжаем, либо извещаем админа и прерываем процедуру.
if [[ $STATUS != 0 ]]; then
    rm $TGTD$OF &>>$LOGFILE
    echo "###########################################" >>$LOGFILE
    echo "###  Произошла ошибка! Бэкап не удался. ###" >>$LOGFILE
    echo "###########################################" >>$LOGFILE
    echo "$(date +'%d-%b-%Y %R%nФайл') бекапа $OF не создан" | sendxmpp -t -f /usr/local/etc/XMPP_settings логин_получателя@домен &>>$LOGFILE
else
    echo "Файл бэкапа сохранен как \"$TGTD$OF\"" >>$LOGFILE
    echo "Бэкап успешно завершен в $(date +'%R %d-%b-%Y')!" >>$LOGFILE
    echo "Монтирование файловой системы для резервного архива $TGTD_archive" >>$LOGFILE
    mount $TGTD2 &>>$LOGFILE
    if [[ $? != 0 ]]; then
        echo "#############################################################" >>$LOGFILE
        echo "###  Произошла ошибка при монтировании резервного ресурса ###" >>$LOGFILE
        echo "#############################################################" >>$LOGFILE
        echo "$(date +'%d-%b-%Y %R%nФайл') бекапа $OF не скопирован на резервный ресурс" | sendxmpp -t -f /usr/local/etc/XMPP_settings логин_получателя@домен &>>$LOGFILE
        exit
    fi
    echo "Начато копирование файла на резервный ресурс" >>$LOGFILE
    cp -f $TGTD$OF $TGTD_archive$OF &>>$LOGFILE
    if [[ $? != 0 ]]; then
        echo "#############################################################" >>$LOGFILE
        echo "###  Произошла ошибка при копировании на резервный ресурс ###" >>$LOGFILE
        echo "#############################################################" >>$LOGFILE
        echo "$(date +'%d-%b-%Y %R%nФайл') бекапа $OF не скопирован на резервный ресурс" | sendxmpp -t -f /usr/local/etc/XMPP_settings логин_получателя@домен &>>$LOGFILE
    else
        echo "Копирование файла успешно завершено  в $(date +'%R %d-%b-%Y')!" >>$LOGFILE
        echo "Файл скопирован как \"$TGTD_archive$OF\"" >>$LOGFILE
    fi
    echo "Размонтирование файловой системы для резервного архива $TGTD_archive" >>$LOGFILE
    umount $TGTD2 &>>$LOGFILE
    echo "Все операции завершены успешно!" >>$LOGFILE
  fi
  exit


В процессе мы копируем архив из локального хванилища в удаленное. Естественно, проверяем, что каждая операция успешно завершена, и пишем все в логи.
Для отсылки сообщения администратору я использую XMPP сообщение, так как в организации поднят Jabber-сервер, и я больше люблю получить быстрое сообщение о сбое, чем лезть в почту, вбивая пароли, тыкая на ссылки, и ожидая пока браузер мне все отобразит. В любом случае никто не мешает вам использовать sendmail вместо sendxmpp.
Файл /usr/local/etc/XMPP_settings следующего содержания:

#логин_отправителя@домен;IP_jabber_сервера:порт_jabber_сервера пароль_отправителя
login@domen;127.0.0.1:5222 password

В файле fstab строка описывающая подключение шары Windows
//192.168.0.250/arhiv	/mnt/archive	cifs	noauto,rw,iocharset=utf8,cp866,file_mod=0666,dir_mod=0777,noexec,_netdev,credentials=/root/.passwd_to_archive_directory	0	0

Теперь осталось только добавить задание в cron. Это можно сделать с помощью файла /etc/crontab, но я, в силу привычки к GUI, оставшейся в наследство от виндовс, пользую вэб-интерфейсы для таких случаев. Команда должна выполняться с правами рута, то бишь, к примеру, sudo bash backup_script. Добавляя команду в cron можно определить что она будет сразу выполняться от имени root`а

В ходе обсуждений затронули проблему разрастания логов. Пошел по простейшему (на мой взгляд) пути: будем хранить только последние N строк лога, например 300. В скрипт добавятся две строки, в которых мы сохраним последние 300 строк лога во временный файл, потом затрем им лог
tail -n 300  $LOGFILE >/tmp/unique_fantastic_filename.tmp
mv -f /tmp/unique_fantastic_filename.tmp $LOGFILE

Приведу полный текст скрипта:
#!/bin/bash

TN=docs-monthly 
#TN=docs-weekly
#TN=docs-daily

OF=$TN.tar.gz

LOGFILE=/var/log/backup.log

echo  >>$LOGFILE
echo "====================================================="  >>$LOGFILE
echo "$(date +'%d-%b-%Y %R')" >>$LOGFILE
echo "Задание \"$TN\" запущено..." >>$LOGFILE

OLD_IFS=$IFS

IFS=$'\n'

SRCD="/mnt/source/folder_1
/mnt/source/folder_2
/mnt/source/folder_N"

TGTD="/var/backups/"
TGTD2="/mnt/archive/"

tar -czf $TGTD$OF $SRCD &>>$LOGFILE
#tar -u -f $TGTD$OF $SRCD &>>$LOGFILE

STATUS=$?

IFS=$OLD_IFS

if [[ $STATUS != 0 ]]; then
    rm $TGTD$OF &>>$LOGFILE
    echo "###########################################" >>$LOGFILE
    echo "###  Произошла ошибка! Бэкап не удался. ###" >>$LOGFILE
    echo "###########################################" >>$LOGFILE
    echo "$(date +'%d-%b-%Y %R%nФайл') бекапа $OF не создан" | sendxmpp -t -f /usr/local/etc/XMPP_settings логин_получателя@домен &>>$LOGFILE
else
    echo "Файл бэкапа сохранен как \"$TGTD$OF\"" >>$LOGFILE
    echo "Бэкап успешно завершен в $(date +'%R %d-%b-%Y')!" >>$LOGFILE
    echo "Монтирование файловой системы для резервного архива $TGTD_archive" >>$LOGFILE
    mount $TGTD2 &>>$LOGFILE
    if [[ $? != 0 ]]; then
        echo "#############################################################" >>$LOGFILE
        echo "###  Произошла ошибка при монтировании резервного ресурса ###" >>$LOGFILE
        echo "#############################################################" >>$LOGFILE
        echo "$(date +'%d-%b-%Y %R%nФайл') бекапа $OF не скопирован на резервный ресурс" | sendxmpp -t -f /usr/local/etc/XMPP_settings логин_получателя@домен &>>$LOGFILE
        exit
    fi
    echo "Начато копирование файла на резервный ресурс" >>$LOGFILE
    cp -f $TGTD$OF $TGTD_archive$OF &>>$LOGFILE
    if [[ $? != 0 ]]; then
        echo "#############################################################" >>$LOGFILE
        echo "###  Произошла ошибка при копировании на резервный ресурс ###" >>$LOGFILE
        echo "#############################################################" >>$LOGFILE
        echo "$(date +'%d-%b-%Y %R%nФайл') бекапа $OF не скопирован на резервный ресурс" | sendxmpp -t -f /usr/local/etc/XMPP_settings логин_получателя@домен &>>$LOGFILE
    else
        echo "Копирование файла успешно завершено  в $(date +'%R %d-%b-%Y')!" >>$LOGFILE
        echo "Файл скопирован как \"$TGTD_archive$OF\"" >>$LOGFILE
    fi
    echo "Размонтирование файловой системы для резервного архива $TGTD_archive" >>$LOGFILE
    umount $TGTD2 &>>$LOGFILE
    echo "Все операции завершены успешно!" >>$LOGFILE
fi
tail -n 300  $LOGFILE >/tmp/unique_fantastic_filename.tmp
mv -f /tmp/unique_fantastic_filename.tmp $LOGFILE
exit


Всем спасибо за внимание!
Tags:
Hubs:
+27
Comments 38
Comments Comments 38

Articles