Gitch. Сервис для зеркалирования репозиториев
Жил-был github. А потом появилась куча сервисов для исходников кроме github. Казалось бы, зачем нужно что-то еще? Мой github выглядит вот так с 2017 года:
Я не могу заводить приватные репозитории и пользоваться рядом функций, которые доступны остальным пользователям. Да и хер с ними. Уже довольно давно я перешел на gitflic.ru как основной репозиторий. На остальные сервисы гита я зеркалирую свои публичные репозитории.
Где держать исходники
Сейчас появляется все больше сервисов на замену github. Их вполне удобно использовать для приватных репозиториев. Несколько, на которые стоит обратить внимание, перечислил ниже:
Наши
- gitflic.ru - один из первых гитхабозаменителей. Жирный функционал, есть CI, реджестри для контейнеров, возможность публиковать самые разные пакеты. Топ за свои деньги. Разве что дизайн не идеальный
- gitverse.ru - сервис хранения исходников от сбера, пока есть базовый функционал
- hub.mos.ru - больше похоже на замену битбакета(помните что такое битбакет?)
Забугорные
- codeberg.org - немецкий
- gitee.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 * * * *
И на этом все. Добавляем репозитории, включаем синхронизацию и радуемся. При необходимости можно включать/выключать синхронизацию отдельных проектов
Ссылки
- Код сервиса на gitflic
- Код сервиса на gitverse
- Код сервиса на github