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

    Недавно появилось некоторое чувство дискомфорта когда я приступаю к работе. Чувство было не то чтобы сильным, но сосредоточиться мешало. Думал, лень. Оказалось, что все чуть сложнее :) Ноуту, за которым я работаю, уже почти 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. Сделать формат файла-списка помягче и поприличнее.


    Заключение


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

    Благодарю за внимание!
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 7

      0
      Спасибо за статью. Я использвал похожую технику, она мне тоже приятна и я стал её применять на многих хостах. Вот в этот-то момент меня и заломало настраивать взеде локальный SMTP :-) Текомендую для отправки почты msmtp. Она поддежривает все необходимы фичи, всякие там TLS и прочее. Вот конфиг для гуглового почтового сервера.
      $ cat .msmtprc 
      account default
      host smtp.gmail.com
      port 587
      from XXX@XXX.XX
      tls on
      tls_starttls on
      tls_certcheck off
      auth on
      user XXXXXXX@gmail.com
      password XXXXXXX
      logfile /var/log/msmtp.log
        0
        Спасибо за дополнение! :)
        Сам потратил много времени в попытках настроить встроенный Postfix на Gmail, пока не догадался узнать насчет SMTP-сервера провайдера.
      • НЛО прилетело и опубликовало эту надпись здесь
          0
          Ну, во-первых, возможно, при таких размерах backup-а лучше использовать уже более сложные программы. Или, скажем, синхронизировать непосредственно Git-репозиторий с находящимся на доверенных серверах.

          Во-вторых, для размеров подобного порядка можно оптимизировать примерно так:
          Делаем 2 скрипта, backup-monthly.sh и backup-daily.sh, добавляем их в crontab для выполения ежемесячно и ежедневно соответственно. Ежемесячный скрипт выглядит так же, только не удаляем .7z в конце; в ежедневном меняем команду сжатия 7-zip на инкрементальное (наверное, можно накопать в мануале 7-zip или в гугле; навскидку, из того же гугла, как-то так:

          7za u backup.7z -up0q3x2z0!backup-delta-$(<тут вычисляется какой-нибудь уникальный порядковый номер>).7z * -ms=off

          ). Шифруем и отправляем, естественно, delta-файл. Единственное «но»: я не занимался вопросами того, как потом это дело распаковывать и можно ли делать архивы действительно инкрементальными (т.е. по алгоритму: сделали дельту, отправили дельту, слили изменения с локальной копией исходного backup.7z, чтобы следующая дельта была уже от текущего состояния).
          0
          rsync?
            0
            Вот прошу прощения, нет сейчас возможности глубоко покопаться в документации, но к rsync (в сравнении с моим sn-backup) потенциально есть такие вопросы:
            • Поддерживается ли список масок при копировании, или то, что в примере, придется разбивать на 6 строк?
            • Поддерживается ли копирование по маске со сканированием всего поддерева (тут подозреваю, что возможно)?
            • Два предыдущих вопроса одновременно?..
              0
              Поддерживает и то и другое и ещё много чего.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое