8 November 2009

Вариант простой backup-системы на Python, Bash и Git

Lumber room
Недавно появилось некоторое чувство дискомфорта когда я приступаю к работе. Чувство было не то чтобы сильным, но сосредоточиться мешало. Думал, лень. Оказалось, что все чуть сложнее :) Ноуту, за которым я работаю, уже почти 3 года; стоит на нем Mac OS X 10.6.1, но яблок на нем нигде не нарисовано, и система периодического резервного копирования на нем отсутствует как класс. В общем, не было ощущения стабильности и надежности, так что я занялся этим вопросом вплотную. Собственно, далее я опишу результат, который мое подсознание удовлетворил :) Может быть, кому-то что-нибудь будет полезно.


Задача


Что бекапим


  • Каталоги с разнообразными исходниками. Некоторые проекты являются Git-репозиториями, соответственно, хочется сохранить репозиторий, а не просто последнюю версию исходников. NB: выполнять make clean или аналоги в конце дня я как-то не привык, так что добавляем требование, что бинарники не копируются.
  • Каталоги с конфигами и параметрами.
  • Каталоги с TeX-исходниками. На самом деле, можно было бы добавить к первому пункту.

У меня, конечно, временами и фотографии появляются, которые хотелось бы сохранить, и прочие бинарники, но вопроса их резервного копирования мы касаться не будем, потому что надежного места, куда можно скриптами запихнуть больше 10 Гб у меня нет, да и чаще чем раз в месяц они не изменяются обычно.

Куда бекапим


  • Собственно, файлохранилища. Был выбран бесплатный аккаунт на Dropbox, ибо копировать командой cp – просто и удобно.
  • Почта на Gmail. Все равно 7 гигов не используются, да и в надежности пока не было поводов сомневаться.

Возможно, в ближайшем будущем этот список расширится, а пока, как мне кажется, этого более-менее достаточно.

Какие фичи


  • Простота. Основная фича :) Не нужно сложных и больших программ, я сам в состоянии решить какие файлы откуда мне нужно копировать. Проще быть уверенным в корректности работы небольшой программы.
  • История изменений. Первая фича, которую хочется – это история изменений, чтобы не ругаться на тему «чем же этот файл мне мешал!»
  • Сжатие. Git, конечно, жмет, но т.к. хранить бекапы все равно удобнее в одном файле, так что пускай его дожмет кто-нибудь еще.
  • Шифрование Я не сомневаюсь в честности и секъюрности провайдеров интернета и провайдеров хранилищ. Тем не менее, не хочу, чтобы то, что я делаю, передавалось и лежало непонятно где в открытом виде.


Решение


Гугл по моим запросам ничего путного и удовлетворяющего моим запросам не выдал, поэтому пришлось писать самому. Заодно потрогал что есть Python, давно искал подходящий повод.

Копирование


Вариант «копировать все» не проходит из-за нехватки места. К тому же, как-то кажется глупым держать кучу резервных копий ненужных файлов (объектников, логов сборки и т.п.). В итоге, так сказать, техзадание получилось следующим:
  • Список каталогов и файлов для копирования хранится в отдельном файле.
  • Для каждого каталога можно указать маски файлов, которые из него нужно копировать. Если маска не указана, копировать все.
  • Сохраняется исходная структура каталогов.
  • Скрипт копирует (и только копирует) файлы в указанную папку.
  • Если в целевой папке файл уже существует, копировать только в случае, если время модификации исходного файла больше.


Скрипт копирования написан на Python и занимает чуть меньше 200 строк (с учетом отступов, комментариев и т.п.). Дополнительно использует один нагугленный модуль для преобразования абсолютных путей в относительные и наоборот.
Использование:
sn-backup.py <файл со списком> <базовый каталог> <каталог для резервного копирования>

Ограничения:
  1. Строгий формат файла со списком. Формат строки:
    <путь относительно базового каталога>[\t<маски через запятую>]

    Более одного знака табуляции не допускается. Маски идут через запятую без пробелов.
  2. Практически не реализована защита от дурака.


Пример списка (с базовым каталогом ~):
.emacs
Documents/Programming	*.c,*.h,*.cpp,*.pro,*.py,.git
Scripts


Исходники (наверное, удобнее когда на файлохостинге лежат, чем когда те самые 200 строк засоряют текст):
sn-backup.py (посмотреть на dumpz.org)
relpath.py (посмотреть на dumpz.org)

relpath.py был взят отсюда, из первого коммента, и чуть подправлен.

Сжатие и шифрование


Сжатие и шифрование требуют по одной строчке, так что как-то странно было бы их писать на Python. Чем сжимать – дело вкуса. Я предпочитаю 7-zip. Командная строка тут, соответственно, будет простой (имя архива – backup.7z, имя папки – .backup):
7za a -mx7 backup.7z .backup

Для шифрования выбрал openssl, так как, в теории, почти везде есть и работает из коробки, без необходимости генерации чего бы то ни было. Команда выглядит так:
openssl enc -aes-256-cbc -salt -in backup.7z -out backup.aes256cbc -pass file:<pass path>

Соответственно, имя исходного файла backup.7z, имя выходного файла backup.aes256cbc (на случай, если вдруг забуду как зовется алгоритм шифрования). Пароль хранится в файле и может иметь произвольную длину. Лучше, все же, не менее 32 символов.

Отправка


Отправку, из-за потенциального разнообразия способов, сначала хотелось сделать тоже через очень настраиваемый Python-скрипт, но потом я решил, что копировать на небольшое число хостов гораздо проще вручную, а копировать на большое число хостов попахивает паранойей. Поэтому отправка реализована в sh-скрипте. Команда копирования на Dropbox при установленном клиенте интереса не представляет, так что рассмотрим только команду отправки на почту (она хоть чуть реже встречается):
uuencode <путь к зашифрованному архиву> $(basename <путь к зашифрованному архиву>) | mail -s "[BACKUP] $(date)" <адрес>

Внимание: чтобы эта команда работала, необходимо настроить на локальной машине SMTP-сервер. У меня провайдер его предоставляет Команда прикрепляет зашифрованный архив к сообщению с темой «[BACKUP] <текущая дата и время>» и отправляет сообщение на <адрес>. У меня эта команда лежит в скрипте sn-upload.sh, который в качестве единственного аргумента принимает имя файла зашифрованного архива.

Все вместе


Все вместе собирается скриптом backup.sh, который выглядит примерно так:
# ~/.backup - каталог, в который и происходит резервное копирование.
# Предварительно в нем должен быть инициализирован .git-репозиторий.
cd ~/.backup

# Список удобно держать там же.
~/Scripts/sn-backup.py ./list ~ .

# Скопировали, теперь коммитим в git.
git add .
git commit -m "Backup at $(date)"

# Возвращаемся назад и архивируем.
cd ..
7za a -mx7 backup.7z .backup > /dev/null

# Шифруем.
openssl enc -aes-256-cbc -salt -in backup.7z -out backup.aes256cbc -pass file:///${SECRET_PATH}/.password

# Отправляем.
~/Scripts/sn-upload.sh ./backup.aes256cbc

# ???
rm backup.7z
# PROFIT!


Направления для дальнейшего развития


На досуге, возможно, сделаю такие доработки:
  1. Перевести все bash-скрипты на Python.
  2. Возможно, переключиться на pylzma и встроенную криптографию Python (для кроссплатформенности).
  3. Сделать формат файла-списка помягче и поприличнее.


Заключение


Я не исключаю возможности, что я изобрел велосипед. Но я потратил на это не так много времени, и получил немного опыта. Хочу отметить, что прежде всего в, кхм, дизайн этой системы закладывалась простота. Я знаю какие файлы и откуда мне надо скопировать. Я знаю что с ними потом делать. Мне нужен скрипт только для того, чтобы все это делалось на автомате. На мой взгляд, свои задачи он выполняет полностью.

Благодарю за внимание!
Tags:backuppythonbashsourcesgitopensslencryption7zipmail
Hubs: Lumber room
+3
1.4k 20
Comments 7