Зачем и как мы бэкапим github



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

    Вообще лирическое вступление было навеяно вполне конкретной историей про сломанный гитхаб. Сделанная на заре одного из проектов синхронизация домашнего репо в гитхаб решила проблему переезда. Потом про костыль забыли. Древнее зло уснуло и терпеливо ждало своего часа. В один прекрасный день скайнет новый сотрудник решил привести в порядок тот самый домашний репозиторий. И самым популярным вопросом среди программистов в тот день было «коллега, а вы не видели мою ветку 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 можно найти тут.
    Share post

    Similar posts

    Comments 24

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

        А, в целом, да, репозитории с гитхаба надо регулярно клонировать в надежное место. А то знаете ли, его и заблокировать могут.
          –3
          История в начале — иллюстрация достаточно специфичной, скучной и запутанной ситуации, итогом которой были вышеозначенные проблемы. Вообще, если она действительно вас заинтересовала, то причиной всех этих синхронизаций был переезд проекта от одной компании к другой. Соответственно, подобный механизм синхронизации был выбран для минимизации простоя. «Домашний репо» — сервис внутри компании, из которой осуществлялся переезд. Очевидная для вас мысль о необходимости бэкапа гита не является очевидной для многих проектов.
          +2
          Ещё интересно узнать, как забекапить гугл-почту, но так, чтобы из бекапа её можно было бы развернуть на альтернативном сервере? Занимался ли кто-то альтернативами Gmail, подобными GitLab?
            +3
            По IMAP разве нельзя вытащить?
              +2
              В общем да, тут лежит пример
              +3
              Попробуйте gmvault.org
              +1
              Странная проблема, конечно.
              Если предположить, что на компьтере уже есть ключи для гитхаба.битбакета, и имена репозиториев известны.
              (Было бы странно, если бы разработчики не знали, какие у них репы. Да ведь?).

              То весь бэкап делается примерно так:

              cd ~/repos/; for i in `ls`; do (cd $i; git pull) done
                0
                Как можно заметить этой команде абслютно пофиг на каком гит сервере лежит все богатство, надо только чтобы в репах ориджин был на месте.

                Если кто не в курсе, то юзера, хосты, ключи и алиасы замечательно настраиваются в ~/.ssh/config
                  0
                  Можете пояснить свое первую мысль?
                    +1
                    Мысль видимо в том, что проще не разбираться в апи разных хостеров, а сделать все в духе юникс одной строчкой на баше
                  +1
                  1) Что надо сделать, чтобы уведомить вашу систему бэкапа о добавлении новых репозиториев?
                  2) Что делает ваша команда git pull? что-то мне подсказывает, что мерджит текущую ветку с ориджином. Как быть с остальными?
                    –1
                    Добавить репо?
                    cd ~/repos; git clone ....where...new...repo...is…

                    Или у вас все репы лежат в одном экаунте в Гитхабе? Ну это вам пока повезло.
                    Практика показывает, что нужная репа внезапно может оказаться на чьем-то личном ГитЛабе или еще непойми где.

                    git pull делает что обычно.
                    Там есть еще перед ней команда cd и еще перед ними for… `ls`.

                    Но если это вызывает вопросы, то пролистайте какой-нибудь тюториал по башу —
                    сэкономите себе кучу времени.

                    С точки зрения программиста баш выглядит ужасно, но я этой одной строкой решаю практически аналогичную задачу у себя. А у вас там пара страниц кода написана, который еще как-то там настраивать надо.
                      0
                      В вашем решении есть две проблемы:
                      1. git pull не бэкапит репозиторий, есть ощущение, что вы не совсем понимаете суть этой команды. То же самое с использованием вами git clone
                      2. Ваше решение в целом короткое и элегантное. Правда оно плодит ручные операции(уведомить о создании репо, что-то склонировать), которых я стараюсь избегать

                        0
                        В моем решении нет проблемы, так как это решение для моей задачи :)

                        Но если нужны разные ветки, то вполне можно пробежаться git checkout'ом по списку
                        git branch -a | grep remotes | grep -v HEAD | grep -v master
                    0
                    for i in `ls`;

                    не надо так делать. первая директория с пробелом или другим причудливым символом сломает ваш цикл. так лучше:
                    for dir in ~/repos/*; do ( [ -d "$dir" ] && cd "$dir" && git pull ); done
                    
                      +1
                      Разумеется, баш страшен и ужасен с точки зрения здравомыслящего программиста. Можно развить тему про то, что вместо глоба со ззвездочкой еще правильнее было бы призвать xargs, так как с глобами тоже бывают курьезные ситуации. Но я как-то ни разу еще не встречал ни на Гитхабе ни на Битбакете репозитория, ломающего ls.
                    +2
                    cmd = 'git clone --mirror %s:%s@bitbucket.org/%s/%s.git %s' % (username, password, owner, slug, sub_dir_name)


                    Я бы не рекомендовал использовать пароль от аккаунта для этого.
                    И в github и bitbucket можно добавить deployment key, который работает для read-only операций с репозиторием. С его помощью можно склонировать репозитории, но нельзя пушить и делать деструктивные действия.

                    С другой стороны, если сам список репозиториев в bb нельзя получить без пароля (не знаю, есть ли там oauth и т.п.), тогда это не так важно.

                    Ещё я бы рекомендовал хранить локальный кеш репозиториев, тоесть сохранять все репозитории с предыдущего бекапа. Это позволяло бы не делать каждый раз clone --mirror, а делать git remote update. Таким образом выкачивались бы каждый раз только новые коммиты и изменения в репозиториях, а не целиком все репозитории со всеми ветками с нуля. Для крупных репозиториев это работает существевнно быстрее.

                    В остальном нормальное решение, сам так делаю.
                      0
                      Да, в бб есть OAuth, можно использовать его в запросе, это лучше. Использование git clone — это уже специфика того, что для бэкапа используется динамическая машина, которая благополучно умирает после выполнения задачи. В случае persistent-хоста — ваше решение лучше.
                        0
                        В случае persistent-хоста — ваше решение лучше.


                        Не совсем так. Вы же в один архив все репозитории складываете? Так вот суть в том, что проще тогда даже в случае динамической машины достать предыдущий архив, и разархивировать его целиком, потом обновить существующие репозитории через remote update, и добавить новые через mirror, удалить ненужные, и опять заархивировать.
                      0
                      А есть кстати решение под NodeJS для клонирования репозитариев, принадлежащих организации с GitHub. Надо туда --mirror продвинуть тоже, кстати. Важная вещь.
                        –1
                        Действительно, можно нафантазировать большое количество количество возможных проблем…

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

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

                          Only users with full accounts can post comments. Log in, please.