Миграция self-hosted gitea в docker

Привет. Это закономерное продолжение предыдущей статьи про Gitea, когда я взял сервак и тупо вкатил её туда без особых заморочек.

Обстоятельства изменились и теперь я хочу большего. Обо всём этом далее — как обычно, в виде описания процесса и готового решения. Рекомендую почитать, потому что это может дать пищу для размышлений и идей, а также понимание происходящего.

Под хостом в тексте понимается машина, на которой производятся операции. Это может быть твой ПК или сервер.

Исходные данные

  1. Виртуальный сервер на Ubuntu 22.04 с «белым» IP
  2. На сервере установлены:
    • бинарник Gitea последней версии
    • nginx последней версии как реверс-прокси
    • MariaDB 10.6 как СУБД
  3. Docker не используется
  4. На сервере прописан ssh-ключ юзера хоста

Задача

  1. Добиться полной переносимости Gitea и воспроизводимости окружения засчёт Docker для деплоя как нового инстанса, так и его резервной копии
  2. Перенести основной инстанс Gitea на другой виртуальный сервер с «белым» IP уже в Docker-окружении
  3. Внедрить OpenGist в общее Docker-окружение
  4. Настроить домены и SSL
  5. Наладить резервное копирование на сервере / с сервера, развернуть резервные инстансы в домашней сети

Что будет использовано: git, ssh, scp, nginx, certbot, docker, docker-compose-plugin, любой текстовый редактор

Общее описание

Начал я со сборки compose.yml для докера по кускам из документации gitea rootless. Подготовил небольшой файл .env — из него будут подсасываться всякие секретики при деплое. Сложные пайплайны и волты здесь не нужны, файлика вполне хватит.

В качестве СУБД выбрана MariaDB как здоровая альтернатива гигачаду-Postgres и чимсу-SQLite. Версия для гити выбрана та, на которой крутилась исходная гити с момента первоначального разворота — уже довольно старая 10.6. Для гиста я выбрал ту же версию: его я разворачиваю впервые и бэкапа нет, так что можно было бы взять и посвежее, но зато так не придётся качать два разных докер-образа.

Главная идея в том, что я должен получить связку из двух сервисов, которые должны уметь стартовать из бэкапов. Так что сразу вписываю Opengist в тот же компоуз-файл и готовлю сервисам по директории. В этих директориях будут конфиги и директории с файлами, и могут быть файлики с дампами БД. При наличии дампов Маша их автоматически выжмет из .sql.gz и импортирует, предварительно создав нужных юзеров засчёт реквизитов из переменных окружения.

Но т. к. файлов дампов может и не быть (читай, инициализируем сервисы с нуля), то нам нужно предусмотреть файл compose.override.yml с дополнительными volume для обеих СУБД. Если прописать их в основной композ, а файлов не будет, то докер создаст директории с теми же именами и владельцем root. Мелочь, но неприятно.

Мой кейс как раз с бэкапами гити (это я решал в первую очередь, а для гиста просто применил тот же конфиг), так что иницализацию чистых сервисов я пока особо не рассматривал — просто подстелил соломку.

На прошлом сервере гити работала на уровне системы, поэтому там не было особой возни с ssh. Но когда гити в контейнере, то документация даёт такой хитрый схематоз: для хостового юзера git нужно создать скрипт-оболочку, который будет прокидывать команды в контейнер гити, а авторизовываться юзер будет по ключам из контейнера гити. Этот скрипт я ещё слегка доработал, дальше увидишь как. Мелочь, но приятно.

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

Теперь сладенькое.

Порядок действий

Склонируем репозиторий и подготовим файлы:

git clone https://git.axenov.dev/anthony/gitea-opengist.git
cd gitea-opengist
cp .env.example .env

Резервирование старой среды

Зайдём на сервер Gitea и потушим её. Выполним команды, учитывая корректные пути:

cp compose.override.yml.example compose.override.yml
cd ./gitea
scp user@example.com:/etc/gitea/app.ini ./
scp -r user@example.com:/var/lib/gitea ./data
ssh user@example.com \
    mysqldump -u$DB_USER -p$DB_PASSWORD $DB_NAME \
    | gzip -9 - \
    > ./dump.sql.gz

где:

  • user@example.com — пользователь user на сервере example.com, где развёрнута Gitea (можно обращаться просто по тому имени, которое задано в ~/.ssh/config);
  • $DB_USER, $DB_PASSWORD и $DB_NAME — переменные, которые должны содержать соответствующие параметры для подключения к БД и снятия её дампа.

Для сервиса Opengist, при необходимости, повтори аналогичные шаги. Я этого не делал, но отличий будет минимум.

Снова запускать Gitea на сервере особого смысла нет. Теперь ты работаешь с тем, что выгрузил.

Файл compose.override.yml нужен будет один раз, после успеха его можно будет удалить.

Подготовка к запуску

Когда я настраивал прошлый сервер, я выполнял такую команду:

adduser \
   --system \
   --shell /bin/bash \
   --gecos 'Gitea user' \
   --group \
   --disabled-password \
   --home /home/git \
   git

Если сейчас такого юзера нет, то надо выполнить эту команду. Обрати внимание на --disabled-password — да, пользователь будет входить без пароля, но по ключам. Об этом ниже.

UID/GID юзера git должны быть 1000/1000, если не — исправляем:

usermod -u 1000 git
usermod -g 1000 git

Директории {gitea/opengist}/data должны принадлежать юзеру UID=1000 и группе GID=1000. Если не, то выполняй:

chown git: -R {gitea/opengist}/data

Но вообще я бы рекомендовал сделать это для всего репозитория ./gitea-opengist, от греха.

Права на всю файловую структуру должны сохраниться со старого сервера.

В файле .env подготовь все необходимые настройки и реквизиты. Некоторые из них обязательны, а некоторые — наеборот.

В конфиге gitea/app.ini скорректируй настройки, чтобы они хоть немного соответствовали новому окружению. Как минимум, это реквизиты подключения к БД, порты и адреса самой гити.

Конфиг opengist/opengist.yml может отсутствовать, т. е. сервис может быть настроен только переменными окружения в compose.override.yml, а главная из них для подключения к БД уже прописана в основном композе. Так что это на твоё усмотрение.

Максимальная комплектация файлов может выглядеть так (но всё зависит от твоих обстоятельств и необходимостей):

./gitea-opengist
├── gitea
│   ├── app.ini
│   ├── data
│   ├── dump.sql.gz
│   └── gitea.sh
├── opengist
│   ├── data
│   ├── dump.sql.gz
│   └── opengist.yml
├── .env
├── .env.example
├── compose.override.yml
├── compose.override.yml.example
└── compose.yml

Запуск

Пробуем запустить стек:

docker compose up -d --build

Смотрим логи контейнеров:

docker logs -f gitea
docker logs -f gitea-db
docker logs -f opengist
docker logs -f opengist-db

Логи контейнеров *-db первичны, т.к. там можно будет найти записи об импорте дампов (при наличии) и готовности Маши к подключениям после.

Логи контейнеров сервисов тоже нужно посмотреть, потому что они должны успешно подключиться к своим БД. Если что-то пойдёт не так, они об этом сообщат. Скорее всего, если проблемы и будут, то на уровне реквизитов или прав на ФС.

Дальше проверяем веб-морды.

Заходим на localhost:8080 — это гити. Если дампа не было, то она предложит установку. Идеальный результат таков: ты видишь список репозиториев, логинишься и навигируешься как ни в чём не бывало, в URL для клонирования корректные адреса и порты.

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

Другой способ — через curl -I ...

Gitea + ssh

Вот ещё мякотка. Я в начале упомянул про хитрость и ты уже видел gitea/gitea.sh. Это как раз та оболочка, в которую будет входить юзер git по ssh.

На хосте назначаем ему этот скрипт как оболочку:

usermod -s /home/user/gitea/gitea/gitea.sh git

где путь /home/user/gitea/gitea/gitea.sh должен быть корректным абсолютным адресом файла в твоей ФС.

Обрати внимание на if внутри скрипта. Если gitea и/или gitea-db будут лежать, при git push (и не только) в stderr будет выводиться сообщение о недоступности хранилища:

$ git push
============================================
Gitea is currently offline. Try again later.
============================================
fatal: Не удалось прочитать из внешнего репозитория.

Удостоверьтесь, что у вас есть необходимые права доступа
и репозиторий существует.

Чтобы юзер git мог пройти авторизацию, нужны ключи, которые существуют только в гити, а хостовый sshd должен их как-то получить. Так что создаём конфиг /etc/ssh/sshd_config.d/gitea.conf и пишем в него это:

Match User git
    AuthorizedKeysCommandUser git
    AuthorizedKeysCommand /usr/bin/docker exec -i gitea /usr/local/bin/gitea keys -e git -u %u -t %t -k %k

Чтобы всё заработало, перечитываем конфиги:

systemctl reload sshd

Через это мы обезопасиваем… обезопаши… обезопасим беспарольный вход.

Opengist + ssh

Если ты не знал, каждый гист есть репозиторий, даже в гитхабе. Разница только в гуйне.

Но я, как и многие другие, привык с гистами работать как с гуёвым заметочником. Поэтому я не задавался целью наладить ssh в Opengist.

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

Настройка доменов

Первично надо в DNS своих (под)доменов настроить A/AAAA-записи на новый сервер. Пока идёт обновление, займёмся реверс-прокси.

Для примера мы разместим всё на доменах git.example.com и gist.example.com. У тебя будет что-то своё.

Устанавливаем nginx и готовим конфиги:

/etc/nginx/sites-available/gitea.conf

# /etc/nginx/sites-available/gitea.conf
server {
    listen 80;
    listen [::]:80;
    server_name git.example.com

    access_log /var/log/nginx/gitea-access.log;
    error_log /var/log/nginx/gitea-error.log;
    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}

# /etc/nginx/sites-available/opengist.conf
server {
    listen 80;
    listen [::]:80;
    server_name gist.example.com

    access_log /var/log/nginx/opengist-access.log;
    error_log /var/log/nginx/opengist-error.log;
    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:8081;
    }
}

Несложно догадаться, что nginx будет слушать порт 80 хоста и просто прокидывать все запросы на http-серверы, которые, в свою очередь, прокинуты на хост в порта 8080/8081 из контейнеров с дефолтных портов сервисов.

Чтобы это заработало, нужно сделать симлинки конфигов в /etc/nginx/sites-enabled (тем «включив» конфиги) и перезапустить веб-сервер:

ln -s /etc/nginx/sites-available/gitea.conf \
      /etc/nginx/sites-enabled/gitea.conf

ln -s /etc/nginx/sites-available/opengist.conf \
      /etc/nginx/sites-enabled/opengist.conf

unlink /etc/nginx/sites-enabled/default
systemctl restart nginx

Это необходимый и достаточный минимум для старта.

За это время наверняка обновились глобальные DNS-записи. Это можно проверить так:

nslookup git.example.com
nslookup gist.example.com

В ответе должно быть эдакое:

$ nslookup git.example.com
Server:         127.0.0.53
Address:        127.0.0.53#53

Non-authoritative answer:
Name:   git.example.com
Address: %твой новый IP-адрес%

Проще всего проверить через curl -i http://git.example.com

Для гиста всё проверяем также. В ответах должны быть успехи и html соответствующих сервисов. Их легко отличить от минималистичных дефолтных заглушек nginx.

Потерпи немношк, скоро финиш.

Настраиваем SSL

Устанавливаем свет-наш-ясно-солнышко certbot и плагин к нему python3-certbot-nginx.

Настройка SSL на доменах сводится к короткой процедуре:

certbot --nginx

Указываем свой email, (не)соглашаемся на рассылку. Будет показан список доменов, который считан согласно конфигов nginx. Выбираем любой один, немного ждём, видим успех, проверяем в браузере и радуемся. Повторяем для второго домена.

Автопродление сертификатов будет происходить автоматически по крону.

Резервирование новой среды

Для этого можно использовать те же команды, которые использовались в начале. Но теперь у нас СУБД внутри контейнеров, поэтому команду снятия дампа мы не выполняем на хосте, а отправляем в контейнер:

ssh user@example.com \
    "docker exec gitea-db mysqldump -u$DB_USER -p$DB_PASSWORD $DB_NAME" \
    | gzip -9 - > ./dump.sql.gz

Должно работать. Аналогично для гиста.

Директории можно просто качать по scp как в начале. Но я покажу лайфхак, похожий на выкачку дампа:

ssh user@example.com \
    "tar -zOc /home/git/gitea/gitea/data" \
    > backup.tar.gz

Это сохранит .tar.gz архив с директорией сразу на твою тачку. Как это применить придумай сам.


Пфух, всё, вроде ничего не забыл. Работа в целом на уровне типичного вебмастера нулевых, только ещё с докером. Просто текста получилось многовато.

Изучай, пробуй, предлагай доработки.

Вероятно, как обычно это бывает, я буду сюда возвращаться и потихоньку корректировать статью и реп.

Использованные материалы

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *