Gitch. Сервис для зеркалирования репозиториев

15 minute read

Жил-был github. А потом появилась куча сервисов для исходников кроме github. Казалось бы, зачем нужно что-то еще? Мой github выглядит вот так с 2017 года:

Я не могу заводить приватные репозитории и пользоваться рядом функций, которые доступны остальным пользователям. Да и хер с ними. Уже довольно давно я перешел на gitflic.ru как основной репозиторий. На остальные сервисы гита я зеркалирую свои публичные репозитории.

Где держать исходники

Сейчас появляется все больше сервисов на замену github. Их вполне удобно использовать для приватных репозиториев. Несколько, на которые стоит обратить внимание, перечислил ниже:

Наши

  • gitflic.ru - один из первых гитхабозаменителей. Жирный функционал, есть CI, реджестри для контейнеров, возможность публиковать самые разные пакеты. Топ за свои деньги. Разве что дизайн не идеальный
  • gitverse.ru - сервис хранения исходников от сбера, пока есть базовый функционал
  • hub.mos.ru - больше похоже на замену битбакета(помните что такое битбакет?)

Забугорные

Селфхостед

  • gogs.io - самый лучший, потому что на Go
  • gitea.com - так себе

Самый крутой

  • radicle.xyz - ммм, одноранговая распределенщина, мое любимое

Что с чем синхронизировать

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

Конечно, несмотря на большое количество аналогов, от github просто так не отказаться. Как минимум - это бесплатный поисковый трафик для вашего проекта. Поэтому, даже если я использую gitflic как основной репозиторий, то зеркалирую репы на github.

Gitverse подает надежды, поэтому сюда тоже буду зеркалировать репозитории

Пишем минисервис для синхронизации репозиториев

Сервис будет крутиться на любимом PocketBase. В этом фреймворке есть инструменты для запуска простых фоновых задач. С их помощью будем запускать периодическую синхронизацию репозиториев

Структура

Определимся какая структура данных должна быть у нашего сервиса. Нам нужны две коллекции. Первая будет описывать сервисы, куда синхронизируются репозитории. Из каких полей будет состоять записи этой коллекции:

  • name - название сервиса
  • period - как часто синхронизировать репоизитории для этого сервиса
  • key - приватный ssh ключ для авторизации(рекомендую завести отдельные ключи)
  • enabled - включение/выключение синхронизации для этого сервиса

Вторя коллекция - это проекты, которые описывают зеракалируемые репозитории. Записи будут состоять из полей:

  • name - название проекта
  • from - url репозитория источника
  • to - url репозитория зеркала
  • service - связь на модельку сервиса с описанием настроек

Синхронизация репозиториев

У нас есть все настройки и мы можем приступать к синхронизации репозиториев. Первым делом, нужны ssh ключи, чтобы получить авторизацию

1keys, err := ssh.NewPublicKeys("git", []byte(s.key), "")
2if err != nil {
3    return fmt.Errorf("error on create key: %w", err)
4}
5
6// https://github.com/src-d/go-git/issues/637
7keys.HostKeyCallbackHelper = ssh.HostKeyCallbackHelper{
8    HostKeyCallback: ss.InsecureIgnoreHostKey(),
9}

Тут есть один нюанс: HostKeyCallback: ss.InsecureIgnoreHostKey() нужен чтобы не получать ошибку ssh: handshake failed: knownhosts: key is unknown. Весь сервис будет крутиться в Docker и не хочется заморачиваться с настройкой ssh в докер

Теперь клонируем исходный репозиторий вместе со всеми тегами

 1r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
 2    Auth:     keys,
 3    URL:      s.from,
 4    Progress: os.Stdout,
 5    Mirror:   true,
 6    Tags:     git.AllTags,
 7})
 8
 9if err != nil {
10    return fmt.Errorf("error on clone: %w", err)
11}

Обратите внимание на memory.NewStorage(). Репозиторий хранится в памяти. Это накладывает некоторое ограничение на размер репы и в будущем будет переделано на хранение на диске

Дальше создаем репозиторий зеркало. Название может быть любым

1remote, err := r.CreateRemote(&config.RemoteConfig{
2    Name:   "sync",
3    URLs:   []string{s.to},
4    Mirror: true,
5})
6if err != nil {
7    return fmt.Errorf("error on create remote: %w", err)
8}

Финально пушим все изменения из исходного репозитория в зеркало.

1if err := remote.Push(&git.PushOptions{
2    FollowTags: true,
3    Auth:       keys,
4    RemoteName: "sync",
5    Force:      true,
6    Progress:   os.Stdout,
7}); err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
8    return fmt.Errorf("error on push: %w", err)
9}

Тут важно указать имя репозитория с прошлого шага. Не забываем передать все теги FollowTags: true и пушим форсом Force: true

Как пользоваться сервисом

Сервис завернут в Docker. Все миграции упакованы внутрь. Чтобы запустить сервис, достаточно выполнить одну команду:

1docker run -v /home/user/data:/data  -p 8080:8080 -d registry.gitflic.ru/project/kovardin/gitch/gitch:latest --dir /data  --http :8080 serve 

registry.gitflic.ru/project/kovardin/gitch/gitch:latest - готовый образ на gitflic.ru

/home/user/data:/data - указываем где будут лежать все данные сервиса

Останется только указать настройки для сервисов и сами репозитории. Настроек всего две - ssh ключ и период синхронизации

Периодичность синхронизации указываем в формате cron:

Например, для запуска синхронизации раз в минуту указываем */1 * * * *

И на этом все. Добавляем репозитории, включаем синхронизацию и радуемся. При необходимости можно включать/выключать синхронизацию отдельных проектов

Ссылки