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

Вообще лирическое вступление было навеяно вполне конкретной историей про сломанный гитхаб. Сделанная на заре одного из проектов синхронизация домашнего репо в гитхаб решила проблему переезда. Потом про костыль забыли. Древнее зло уснуло и терпеливо ждало своего часа. В один прекрасный день скайнет новый сотрудник решил привести в порядок тот самый домашний репозиторий. И самым популярным вопросом среди программистов в тот день было «коллега, а вы не видели мою ветку 0022? ну такая, с багфиксами». Руководство опс-тим было спокойно как никогда: гит — это распределенная система, версия кода хранится на персональном компьютере каждого разработчика. Давайте уже как-нибудь разберитесь между собой и не отвлекайте нас от сборки наших ядер и тюнингов сетевых стеков.

И все же зачем..?

Действительно, можно нафантазировать большое количество количество возможных проблем, например:

  • Недоступность удаленного репозитория и отсутсвие актуальной локальной копии
  • Злоумышленники с помощью украденного пароля испортили/удалили репозитории
  • Ошибки в манипуляциях с репозиториями


Итак, что и как бэкапить...

Вот вкратце наш хитрый план действий:

  1. Получаем список репозиториев для организации
  2. Клонируем репозитории поз полученного списка
  3. Архивируем
  4. Кладем в AWS S3


Немного больше конкретики в случае использования github.com


Разумно завести для процедуры бэкапа отдельного readonly-пользователя. Так же необходимо сгенерировать для него token(Settings -> Personal Access Tokens -> Generate new token).

Для начала с помощью pygithub3 получаем репозитории, которые мы впоследствии собираемся бэкапить:
from pygithub3 import Github

def get_repos(args):
    config = {'token': args.token}
    gh = Github(**config)

    return gh.repos.list_by_org(args.organization).all()


Для клонирования будем использовать консольный git:
def clone_repo(repo_list,args):

    if os.path.isdir(args.directory):
        shutil.rmtree(args.directory)

    os.mkdir(args.directory)

    if args.mirror is True:
        args.git += " --mirror"

    for repo in repo_list:
        repo_url = "https://%(token)s:x-oauth-basic@github.com/%(organization)s/%(repo)s.git" % {'token': args.token,
                                                                                                 'organization': args.organization,
                                                                                                 'repo': repo.name}

        os.system('git clone %(arguments)s %(repo_url)s %(directory)s/%(repo)s' % {'arguments': args.git,
                                                                                   'repo_url': repo_url,
                                                                                   'directory': args.directory, 'repo': repo.name})

Обратите внимание на опцию "--mirror" — с помощью нее создается зеркальная копия удаленного репозитория.

Кстати, в случае использования bitbucket.org...


Получаем список репозиториев:
def _get_repositories(owner, username, password):
    auth_value = ('%s:%s' % (username, password)).encode('base64').strip()
    headers = {'Authorization': 'Basic %s' % auth_value}
    url = 'https://bitbucket.org/api/2.0/repositories/%s?role=member' % owner
    values = []

    while url is not None:
        request = urllib2.Request(url, None, headers)
        data = json.loads(urllib2.urlopen(request).read())
        values = values + data['values']
        url = data.get('next')

    return values


И клонируем:
def _git_clone(username, password, directory, sub_dir_name, owner, slug, verbose=False):
    os.chdir(directory)
    cmd = 'git clone --mirror https://%s:%s@bitbucket.org/%s/%s.git %s' % (username, password,
                                                                  owner, slug, sub_dir_name)
    proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
    ret_value = proc.wait()
    msg = proc.stdout.read()
    sys.stdout.write('%s%s%s%s' % (sub_dir_name, os.linesep,
                                   '=' * len(sub_dir_name), os.linesep))
    sys.stdout.write("%s%s" % (msg, os.linesep))
    return ret_value

Кстати, slug — это url-friendly название вашего репо в bitbucket.

Готовый скрипт для github можно найти тут.

Комментарии (24)


  1. SyCraft
    20.01.2016 13:09
    -1

    Отличная статья! спасибо большое


  1. antirek
    20.01.2016 13:33
    +3

    Не понял: все коллеги пушили в «домашний репо», который синхронизировался в гитхаб, затем кто-то сел и почистил его, и вы приходите к выводу, что надо бэкапить гитхаб репозитории? А что такое «домашний репо»? Он на локальном компьютере разработчика стоит? Тогда зачем «новый сотрудник решил привести в порядок» на компьютере разработчика? Так принято: «я новенький — поэтому сейчас наведу порядок»?: ) Или домашний репо — это репо в стенах вашей компании на сервере репозиториев? Тогда почему вы с него не делаете бэкапов?

    А, в целом, да, репозитории с гитхаба надо регулярно клонировать в надежное место. А то знаете ли, его и заблокировать могут.


    1. yaak
      20.01.2016 13:49
      -3

      История в начале — иллюстрация достаточно специфичной, скучной и запутанной ситуации, итогом которой были вышеозначенные проблемы. Вообще, если она действительно вас заинтересовала, то причиной всех этих синхронизаций был переезд проекта от одной компании к другой. Соответственно, подобный механизм синхронизации был выбран для минимизации простоя. «Домашний репо» — сервис внутри компании, из которой осуществлялся переезд. Очевидная для вас мысль о необходимости бэкапа гита не является очевидной для многих проектов.


  1. spmbt
    20.01.2016 13:49
    +2

    Ещё интересно узнать, как забекапить гугл-почту, но так, чтобы из бекапа её можно было бы развернуть на альтернативном сервере? Занимался ли кто-то альтернативами Gmail, подобными GitLab?


    1. evilbot
      20.01.2016 16:07
      +3

      По IMAP разве нельзя вытащить?


  1. FractalizeR
    20.01.2016 23:32

    А есть кстати решение под NodeJS для клонирования репозитариев, принадлежащих организации с GitHub. Надо туда --mirror продвинуть тоже, кстати. Важная вещь.


  1. chlp
    21.01.2016 01:26
    -1

    Действительно, можно нафантазировать большое количество количество возможных проблем…

    а можно не фантазировать и написать «Роскомнадзор» geektimes.ru/post/242306


  1. AxelPAL
    21.01.2016 09:21
    +1

    Есть даже отдельная статья (https://addyosmani.com/blog/backing-up-a-github-account/) о разном импорте с GitHub.
    Там есть ссылка на проект (https://github.com/joeyh/github-backup), который позволяет сдампить всё, что у вас есть на гитхабе, включая репозитории, ветки, форки, pull-реквесты и остальное.
    Есть, конечно, https://backhub.co, который вообще все оптимизирует и делает за вас по нажатию одной кнопки. Главный минус — файлы хранятся на их сервере в Германии, соответственно, это опять облако с неизвестной доступностью. Но что самое настораживающее — ваш код будет сохранен 3 лицом где-то на его стороне.

    Как по мне, можно ведь просто использовать GitLab или аналоги на вашем сервере или на какой-нибудь виртуалке и создавать автоматический дамп сервера или каталога репозиториев по расписанию. Я бы предпочел такой способ, чем постоянно тягать данные через гит и отправлять их на другой сервер.
    П.С. извиняюсь на текстовые ссылки, карма не дает вставить html.


    1. yaak
      21.01.2016 16:48

      github.com/joeyh/github-backup — да, но в limitations написано, что github-backup does not log into GitHub, so it cannot backup private repositories. Плюс оно, как пишет автор, repository-focused. Другими словами надо опять же откуда-то вначале получить список репо.
      Использовать гитлаб или облачные решения — это вопрос. Причем не всегда только технический.