Перейти к содержимому
GitRiverGitRiver
EN
Навигация

Установка и настройка GitRiver

Установка GitRiver: быстрый запуск через Docker Compose, пакеты deb и rpm, чарт Helm, обратный посредник, обновление и проверка поставки

Ставится тремя способами: образ Docker (compose), системный пакет deb/rpm, Helm-чарт для Kubernetes. Возможности одинаковы во всех трёх — различаются только способ доставки и место конфигурации:

Способ Конфигурация Обновление Кому
Docker Compose ./data/gitriver/gitriver.toml, переменные в compose docker compose pull && up -d стенд, небольшая установка, знакомство
Пакет deb/rpm /etc/gitriver/gitriver.toml, дополнение службы systemd apt upgrade / dnf upgrade машина дистрибутива, эксплуатация без Docker
Helm-чарт values.yaml helm upgrade кластер Kubernetes

Сборочные машины ставятся отдельно и своим пакетом — см. «Установка внешнего исполнителя».

Подлинность поставки

К каждому выпуску приложены SHA256SUMS со суммами всех файлов и SHA256SUMS.asc — подпись сопровождающего. Неподписанный выпуск не публикуется: без подписи отличить поставку издателя от подменённой по дороге нечем.

# ключ издателя приложен к выпуску; отпечаток сверьте с опубликованным на сайте
gpg --import gitriver-release-key.asc
gpg --show-keys --with-fingerprint gitriver-release-key.asc   # он же — в gitriver-release-key.fingerprint
gpg --verify SHA256SUMS.asc SHA256SUMS
sha256sum -c SHA256SUMS

Отпечаток лежит рядом отдельной строкой — gitriver-release-key.fingerprint, — и входит в подписанный перечень сумм. Сверять его всё равно следует с опубликованным на сайте издателя: файл рядом с ключом подтверждает только то, что они друг другу соответствуют.

Образы отдельной подписи не имеют — их дайджесты входят в тот же подписанный перечень: image-digest.txt для сервера, runner-image-digest.txt для внешнего исполнителя. Проверка после загрузки образа:

docker pull gitriver/gitriver:1.1.0
docker image inspect --format '{{index .RepoDigests 0}}' gitriver/gitriver:1.1.0
cat image-digest.txt      # должно совпасть

Сверяется дайджест списка манифестов — то, что отдаёт реестр по тегу. При классическом хранилище Docker его покажет и docker image inspect (RepoDigests); при хранилище containerd там может оказаться дайджест одной архитектуры — тогда берите его у реестра: docker buildx imagetools inspect gitriver/gitriver:1.1.0.

Проверяйте подпись до установки, а не после: сумма, взятая оттуда же, откуда и файл, ничего не подтверждает.


Требования

  • PostgreSQL 15+ с доступным расширением pgcrypto (docker-compose.prod.yml из поставки поднимает 18; на RHEL-семействе расширение — в пакете postgresql-contrib)
  • 2+ CPU, 4+ GB RAM (рекомендуется)
  • Docker 20.10+ с BuildKit — для установки образом и для встроенного исполнителя CI (доступ к сокету демона)

Со встроенным исполнителем сервер обслуживает кеш сборки на всей машине. Раз в сутки (и один раз при запуске) он оставляет не более 10 ГБ кеша у КАЖДОГО сборщика BuildKit на хосте — включая сборщики, заведённые не им и не для CI. Иначе тома сборщиков растут без предела. Если на этой машине собирается что-то ещё, учитывайте это при выборе хоста.

Docker нужен серверу только для встроенного исполнителя CI. Если задания выполняют внешние исполнители, сервер обходится без него — см. «Лёгкая установка без Docker».


Быстрый старт (Docker Compose)

Понадобятся два файла из выпуска — docker-compose.prod.yml и gitriver.env.example:

mkdir gitriver && cd gitriver
# положите сюда docker-compose.prod.yml и gitriver.env.example
cp gitriver.env.example .env
${EDITOR:-nano} .env    # GITRIVER_DB_PASS — любой пароль, GITRIVER_BASE_URL=http://localhost:3000
docker compose -f docker-compose.prod.yml up -d

Сервер доступен на http://localhost:3000. При первом входе открывается мастер настройки — он создаст администратора.

Так знакомятся с продуктом на своей машине. Для установки, доступной из сети, берите рабочую установку: мастер настройки не защищён ничем, и открытый в сеть он отдаёт установку первому, кто до него дошёл.


Установка в прод

1. Подготовка

mkdir -p /opt/gitriver/data/{gitriver,postgres,registry}
cd /opt/gitriver

Положите сюда docker-compose.prod.yml и образец окружения gitriver.env.example — оба приложены к выпуску.

2. Файл .env

Все значения рабочей установки задаются в .env рядом с compose-файлом — сам файл править не нужно:

cp gitriver.env.example .env
${EDITOR:-nano} .env              # пароль базы, внешний адрес

Обязательны GITRIVER_DB_HOST, GITRIVER_DB_USER, GITRIVER_DB_PASS, GITRIVER_DB_NAME и GITRIVER_BASE_URL; остальное описано в самом образце.

Запуск без .env останавливается с перечислением недостающих переменных, и это защита, а не помеха. Сервер без подключения к базе открывает мастер первичной настройки, а порт 3000 проброшен наружу: кто первым откроет страницу, тот и задаст базу и создаст администратора. То же предупреждение — для установки из пакета, см. «Настройка».

3. Запуск

docker compose -f docker-compose.prod.yml up -d

Проверить, что окружение прочитано и подставлено, можно не поднимая службы:

docker compose -f docker-compose.prod.yml config

Из поставки приходит образ издателя с тегом версии. Своё зеркало — GITRIVER_IMAGE в .env. Тег закреплён за версией (не latest): установка меняется только тогда, когда вы сами меняете тег.

Служба registry в этом файле — реестр образов для заданий CI. Пароля у него нет, а порт 5000 публикуется на все адреса, потому что образы забирают контейнеры заданий: закройте этот порт снаружи правилами сетевого экрана. Если 5000 на машине уже занят, задайте другой порт хоста переменной GITRIVER_REGISTRY_PORT в .env — внутри контейнера порт остаётся 5000, и адрес для заданий задаётся отдельно (GITRIVER_CI_REGISTRY, ниже).

Задания с docker push в реестр инстанса требуют GITRIVER_CI_REGISTRY в .env. Без него адрес реестра (CI_REGISTRY в заданиях) строится из GITRIVER_BASE_URL, а там порт сервера — реестра на нём нет: docker login отказывает, а docker push проваливает задание. Задайте адресом, который достанет демон Docker хоста: и исполнитель, и контейнеры заданий ходят к нему через его сокет, поэтому alias контейнеров (host.docker.internal) не годится — внешний адрес хоста с портом 5000, например 192.0.2.1:5000. Реестр из этого файла TLS не имеет и отдаёт только http, а демону нужен явный список: впишите адрес реестра в insecure-registries в конфигурации Docker.

4. Обратный прокси (nginx)

server {
    listen 443 ssl http2;
    server_name git.example.com;

    ssl_certificate     /etc/ssl/certs/git.example.com.pem;
    ssl_certificate_key /etc/ssl/private/git.example.com.key;

    client_max_body_size 512m;  # Для LFS и больших push

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # SSE (CI журналы, события pipeline)
        proxy_buffering off;
        proxy_read_timeout 3600s;
    }
}

Установка из пакета (deb/rpm)

Пакет ставит бинарник (веб-интерфейс встроен в него), unit-файл systemd, заводит пользователя gitriver и каталоги данных. Docker при этом не требуется — если задания CI выполняют внешние исполнители (см. «Лёгкая установка без Docker»).

Пакету нужна glibc 2.34 или новее: он ставится на RHEL, Rocky и AlmaLinux 9+, Debian 12+, Ubuntu 22.04+, Astra Linux 1.8+ и другие дистрибутивы не старше их. Для более старых остаётся установка образом.

Архитектуры две — x86-64 и arm64: к выпуску приложены оба набора пакетов (_amd64.deb / .x86_64.rpm и _arm64.deb / .aarch64.rpm), а образы собраны списком манифестов, и docker pull берёт из них подходящий сам.

1. Установка

# Debian, Ubuntu, Astra
sudo apt install ./gitriver_1.1.0-1_amd64.deb

# RHEL, Rocky, AlmaLinux, РЕД ОС
sudo dnf install ./gitriver-1.1.0-1.x86_64.rpm

Пакеты приложены к выпуску: скачайте их со страницы выпуска вместе с файлом SHA256SUMS и его подписью и сверьте перед установкой — как это сделать, описано в разделе «Подлинность поставки».

2. База данных

RHEL, Rocky, AlmaLinux, РЕД ОС: сначала включите поток PostgreSQL 15 или новее. Поток по умолчанию в этих системах — 13, а пакет рекомендует postgresql-server >= 15: невыполнимую рекомендацию dnf молча пропускает, и база не поставится вовсе.

sudo dnf module enable -y postgresql:16
sudo dnf install -y postgresql-server postgresql-contrib   # contrib — расширение pgcrypto
sudo postgresql-setup --initdb
# вход по паролю: по умолчанию здесь ident, и сервер получит «Ident authentication failed»
sudo sed -i -E 's/^(host\s.*)\bident$/\1scram-sha-256/' /var/lib/pgsql/data/pg_hba.conf
sudo systemctl enable --now postgresql

На старой базе сервер работать не откажется, но при запуске предупредит: версии ниже 15 не проверяются.

sudo -u postgres createuser gitriver --pwprompt
sudo -u postgres createdb  gitriver --owner gitriver

3. Настройка

Строка подключения вписывается в /etc/gitriver/gitriver.toml:

database_url = "postgres://gitriver:пароль@localhost:5432/gitriver"
base_url = "https://git.example.com"

Оставить database_url пустым тоже можно: тогда сервер поднимет мастер первичной настройки и запишет строку подключения сам. Мастер доступен любому, кто дотянется до порта, — на машине с внешним адресом закройте порт до конца настройки.

Что не должно попадать в файл, который правит мастер (пароли, токены, переопределения окружения), задаётся дополнением службы systemd:

sudo systemctl edit gitriver
[Service]
Environment=GITRIVER_BASE_URL=https://git.example.com
Environment=GITRIVER_METRICS_TOKEN=длинная-случайная-строка

Переменные окружения перекрывают TOML.

4. Запуск

sudo systemctl enable --now gitriver
sudo systemctl status gitriver
journalctl -u gitriver -f

Служба не запускается сразу при установке пакета намеренно: до того, как задана база или закрыт доступ к порту, открывать мастер настройки в сеть не следует.

Что и где лежит

Путь Что это
/usr/bin/gitriver сервер с встроенным веб-интерфейсом и консольные команды (run, backup, restore, serv, notices, init-config)
/etc/gitriver/gitriver.toml настройки; при обновлении пакета не перезаписывается
/etc/gitriver/.jwt_secret ключ подписи токенов, создаётся при установке
/var/lib/gitriver/ репозитории, данные CI, Pages, реестр
/usr/lib/systemd/system/gitriver.service unit-файл
/usr/lib/systemd/system/gitriver.slice срез ресурсов CI (пределы ему задаются отдельно, см. «Пределы ресурсов заданий CI»)
/usr/share/doc/gitriver/ полный пример конфигурации, документация, лицензионное соглашение и перечень сторонних лицензий

Каталог /etc/gitriver открыт службе и на запись — мастер настройки вписывает в файл строку подключения. Ключи, которые сервер создаёт сам без явного пути (подпись коммитов, ключ узла SSH), по умолчанию лежат рядом с файлом настроек, в /etc/gitriver. Ключ подписи токенов создаётся при установке пакета и переживает обновления; его смена закрыла бы сеансы всем пользователям.

Лицензии

Условия на сам GitRiver — в /usr/share/doc/gitriver/LICENSE.ru.md (и LICENSE.en.md). Сторонний код с открытым исходным текстом, входящий в поставку, перечислен вместе с условиями в /usr/share/doc/gitriver/THIRD-PARTY-NOTICES.md: около пятисот библиотек Rust (crates.io) и почти столько же пакетов npm с текстами их лицензий. Перечень соответствует составу именно этой поставки.

Тот же файл печатает сам сервер — gitriver notices — и показывает интерфейс на странице /legal, вкладка «Сторонние компоненты»: условия читаются и в закрытом контуре, без выхода в интернет. В образе контейнера файл лежит там же: /usr/share/doc/gitriver/THIRD-PARTY-NOTICES.md.

SSH

Встроенный SSH-сервер включается секцией [ssh_server] в конфиге. Unit-файл разрешает службе привязку к портам ниже 1024 (CAP_NET_BIND_SERVICE), поэтому занять 22 она может — но обычно он занят системным sshd, и тогда берут свой порт:

ssh_port = 2222

[ssh_server]
listen = "0.0.0.0:2222"
host_key_path = "/var/lib/gitriver/ssh_host_ed25519_key"

Одновременно сервер обслуживает не более 256 соединений SSH. Соединение сверх предела закрывается сразу, клиент видит обрыв и повторяет; отказ пишется в журнал строкой «встроенный SSH: соединение отклонено — предел одновременных исчерпан».

Вариант с системным sshd: ключи пользователей сервер поддерживает сам, домашний каталог учётной записи службы — /var/lib/gitriver:

authorized_keys_path = "/var/lib/gitriver/.ssh/authorized_keys"

Пределы ресурсов заданий CI

Строка Delegate=cpu pids memory в unit-файле уже стоит и нужна: без неё задания без image: выполняются без пределов памяти и числа процессов. Там же стоит Slice=gitriver.slice: срез, в котором считается весь расход CI. Предел ему задаётся отдельно — он не берётся ни из пакета, ни из gitriver.toml:

systemctl set-property gitriver.slice CPUQuota=600% MemoryMax=12G

Подробнее — «Пределы ресурсов задания».

Обновление

sudo apt install ./gitriver_НОВАЯ-ВЕРСИЯ_amd64.deb   # или dnf upgrade

Пакет перезапускает службу сам, если она была запущена; схему базы сервер обновляет сам при старте. Настройки, данные и ключ подписи токенов остаются на месте.

Удаление

apt remove / dnf remove останавливает службу и снимает файлы поставки. Репозитории в /var/lib/gitriver и база данных не трогаются ни при удалении, ни при apt purge — purge убирает только настройки и созданный установкой ключ подписи токенов. Снос данных остаётся отдельным решением администратора.

dnf remove понятия purge не знает: правленый конфиг он сохраняет как /etc/gitriver/gitriver.toml.rpmsave, а ключ подписи токенов остаётся на месте — после повторной установки выданные токены продолжают приниматься.


Установка внешнего исполнителя (deb/rpm)

Внешний исполнитель — отдельный пакет gitriver-runner и ставится на другие машины: на те, где выполняются сборки. Серверу он не нужен, а сборочной машине не нужен сервер, поэтому и пакеты разные. На одной машине они уживаются: пути, учётные записи и службы у них не пересекаются.

Тот же выпуск даёт и образ исполнителя — тем же репозиторием реестра, что и образ сервера, отдельным тегом (<образ сервера>:<версия>-runner); им поднимается исполнитель в контейнере и им же пользуется автомасштабирование в Kubernetes.

1. Установка

# Debian, Ubuntu, Astra
sudo apt install ./gitriver-runner_1.1.0-1_amd64.deb

# RHEL, Rocky, AlmaLinux, РЕД ОС
sudo dnf install ./gitriver-runner-1.1.0-1.x86_64.rpm

Для arm64 — файлы gitriver-runner_1.1.0-1_arm64.deb и gitriver-runner-1.1.0-1.aarch64.rpm из того же выпуска.

Пакеты исполнителя приложены к выпуску рядом с серверными и проверяются тем же файлом SHA256SUMS.

Git, Git LFS и клиент SSH пакет ставит зависимостями. Docker не обязателен и ставится отдельно, из любого источника (docker.io, docker-ce, moby-engine): он нужен только заданиям с image:.

Служба при установке не запускается: без адреса сервера и токена ей нечего делать. Что делать дальше, пакет напоминает в конце установки — это шаги 2–5 ниже.

2. Токен исполнителя

Исполнитель заводится на сервере — «Администрирование» → «Исполнители» → «Добавить». Токен (grr_…) показывается один раз; там же задаются метки, по которым задания попадут именно на эту машину (runs-on:), и область — сервер, группа или репозиторий.

3. Настройка

Адрес сервера и токен вписываются в /etc/gitriver-runner/runner.env:

GITRIVER_URL=https://git.example.com
GITRIVER_RUNNER_TOKEN=grr_...

Адрес указывайте с https://. По http:// исполнитель работает, но предупреждает при старте: токен и секреты заданий пойдут по сети открытым текстом.

Остальные параметры в том же файле закомментированы вместе с пояснениями и умолчаниями (рабочий каталог, интервал опроса сервера, пределы артефактов, сроки операций git, образ для уборки рабочих копий, срез ресурсов); подробнее — в руководстве администратора. Файл читает служба, и он закрыт остальным (0640, группа gitriver-runner): токен открывает очередь заданий, а с ней и секреты сборок. Не делайте файл доступным для чтения всем.

Правка файла вступает в силу после перезапуска службы: sudo systemctl restart gitriver-runner.

4. Доступ к Docker

Задания с image: исполнитель выполняет в контейнерах и обращается к демону Docker на своей машине. Пакет доступа не выдаёт: членство в группе docker равносильно root на машине, и это решение администратора, а не установки.

sudo systemctl edit gitriver-runner
[Service]
SupplementaryGroups=docker

Если служба уже запущена, перезапустите её: sudo systemctl restart gitriver-runner. Без этого доступа выполняются только задания без image: — прямо на машине, правами учётной записи gitriver-runner.

5. Запуск

sudo systemctl enable --now gitriver-runner
journalctl -u gitriver-runner -f

В журнале при старте видно, к какому серверу исполнитель подключился и какой каталог занял. Появился он и в админке сервера («Исполнители») — со своими метками и временем последнего обращения.

Если исполнитель в админке не появился, причина — в журнале службы: чаще всего это неверный адрес сервера, сервер недоступен с этой машины или токен неверен (например, исполнитель удалён на сервере). Исправьте runner.env и перезапустите службу.

Что и где лежит

Путь Что это
/usr/bin/gitriver-runner исполнитель
/etc/gitriver-runner/runner.env адрес сервера, токен и параметры; при обновлении пакета не перезаписывается
/var/lib/gitriver-runner/ рабочие копии заданий, архивы артефактов и кеша
/usr/lib/systemd/system/gitriver-runner.service unit-файл
/usr/share/doc/gitriver-runner/ документация, лицензионное соглашение и перечень сторонних лицензий

Рабочий каталог заводит systemd (StateDirectory) и отдаёт его исполнителю целиком, правами 0700: внутри лежат рабочие копии заданий вместе с их учётными данными. Каталог, принадлежащий другому пользователю, исполнитель отвергает и не стартует — см. «Рабочий каталог исполнителя».

Исполнитель в контейнере

docker run -d --name gitriver-runner \
  -e GITRIVER_URL=https://git.example.com \
  -e GITRIVER_RUNNER_TOKEN=grr_... \
  -v /var/run/docker.sock:/var/run/docker.sock \
  gitriver/gitriver:1.1.0-runner

Сокет демона пробрасывается ради заданий с image: — контейнеры задания поднимаются на хосте, рядом с самим исполнителем, а не внутри него. Рабочий каталог, проброшенный с хоста (-v), должен принадлежать пользователю, от которого исполнитель работает внутри контейнера (root), иначе он не стартует.

Обновление и удаление

sudo apt install ./gitriver-runner_НОВАЯ-ВЕРСИЯ_amd64.deb   # или dnf upgrade

Пакет перезапускает службу сам, если она была запущена. Настройки и рабочий каталог остаются на месте; apt remove останавливает службу и снимает файлы поставки, а purge убирает ещё и настройки. Рабочий каталог не трогает ни то, ни другое: в нём могут лежать рабочие копии незаконченных заданий.

dnf remove понятия purge не знает: правленый runner.env он сохраняет как /etc/gitriver-runner/runner.env.rpmsave — токен в нём остаётся на месте, и после повторной установки он принимается снова.


Установка в Kubernetes (Helm)

Чарт ставит Deployment, Service, при желании Ingress, постоянный том под репозитории и Secret с настройками. База данных по умолчанию внешняя.

Установка

Чарт приложен к выпуску архивом gitriver-1.1.0.tgz:

helm install gitriver gitriver-1.1.0.tgz \
  --namespace gitriver --create-namespace \
  --set database.host=postgres.internal \
  --set database.password=<ПАРОЛЬ> \
  --set config.baseUrl=https://git.example.com \
  --set ingress.enabled=true \
  --set ingress.className=nginx \
  --set ingress.hosts[0].host=git.example.com

Те же значения удобнее держать в своём файле:

helm install gitriver gitriver-1.1.0.tgz -f values.yaml

Пароль лучше держать в своём Secret, а не в командной строке:

kubectl -n gitriver create secret generic gitriver-db --from-literal=password=<ПАРОЛЬ>
helm install gitriver gitriver-1.1.0.tgz --set database.existingSecret=gitriver-db

Ключ подписи токенов

Ключом подписываются выданные токены, и от него же зависит часть сохранённых секретов: одноразовые коды двухфакторной проверки, секреты приложений OAuth, токены посредника реестра. Сменился ключ — все вышли из системы, а эти секреты перестали читаться (подробнее — «Ошибки токенов после перезапуска»).

Чарт передаёт ключ серверу переменной GITRIVER_JWT_SECRET из Secret; в gitriver.toml он не пишется. Откуда берётся значение:

  • config.existingJwtSecret.name — из готового Secret (ключ в нём по умолчанию jwt-secret). Единственный путь, пригодный для GitOps;
  • config.jwtSecret — из values как есть (попадёт в Secret <выпуск>-gitriver-signing-key открытым текстом values);
  • ничего не задано — чарт генерирует ключ при первой установке, кладёт в Secret <выпуск>-gitriver-signing-key и при helm upgrade переносит прежнее значение. Secret помечен helm.sh/resource-policy: keep, как и тома данных: он переживает helm uninstall, и повторная установка под тем же именем подхватывает его.

Имена объектов чарта строятся так: <выпуск>-gitriver, но если имя выпуска уже содержит gitriver, приставка не повторяется. При установке как в примерах этого раздела (helm install gitriver …) Secret называется gitriver-signing-key, а не gitriver-gitriver-signing-key; то же касается томов ниже.

Задать оба сразу нельзя: установка остановится с объяснением.

Третий путь под GitOps не работает: Argo CD и Flux рендерят чарт без кластера (helm template), прежнего ключа там не видно, и каждая синхронизация выпускала бы новый. Такой рендер чарт останавливает с объяснением. Под GitOps создайте Secret один раз и укажите его:

kubectl -n gitriver create secret generic gitriver-jwt \
  --from-literal=jwt-secret="$(openssl rand -base64 48)"
helm install gitriver gitriver-1.1.0.tgz --set config.existingJwtSecret.name=gitriver-jwt

Смена значения в этом Secret доходит до сервера только с перезапуском подов: переменные окружения читаются один раз при старте.

Основные значения

Значение По умолчанию Что задаёт
image.repository, image.tag gitriver/gitriver, версия чарта образ выпуска; своё зеркало — здесь же
database.host, .port, .user, .name — , 5432, gitriver, gitriver внешняя база
database.password / .existingSecret — пароль напрямую либо готовый Secret
postgresql.enabled false поднять базу вместе с релизом (стенд)
config.baseUrl — внешний адрес установки
config.ciLocalExecutor false встроенный исполнитель CI (нужен сокет Docker узла)
config.ssh.enabled false встроенный SSH-сервер и порт ssh в Service
service.ssh.port, .nodePort 22, — порт SSH на Service; наружу решает service.type — LoadBalancer, NodePort (узловой порт, без него установка остановится — см. «Что чарт проверяет»), ClusterIP — только изнутри кластера
config.ssh.containerPort, .publicPort, .publicHost 2222, — , — порт SSH внутри пода; публичный порт и публичный хост в адресе для клонирования (порт пусто — из Service, хост — из config.baseUrl)
config.existingJwtSecret.name, .key — , jwt-secret готовый Secret с ключом подписи токенов (обязателен под GitOps)
config.jwtSecret — ключ подписи значением; пусто — чарт сгенерирует при установке
config.extraToml — остальные параметры TOML: SMTP, LDAP, S3
config.extraEnv [] переменные GITRIVER_*, в том числе из Secret
persistence.size, .storageClass, .accessModes 50Gi, — , ReadWriteOnce том под данные
ingress.* выключен публикация наружу
ingress.nginxDefaults true четыре nginx-аннотации чарта; свои annotations перекрывают те же ключи
resources 500m/1Gi запрос, 4Gi предел памяти доля узла под сервер (предела процессора нет намеренно)
podSecurityContext, securityContext RuntimeDefault, снят весь набор возможностей кроме четырёх права пода
tests.image — (образ сервера) образ для helm test
replicaCount 1 число реплик (см. ниже)

Всё остальное — helm show values gitriver-1.1.0.tgz.

С nginx-классом Ingress (в className есть “nginx”) чарт сам добавляет четыре аннотации, без которых git через ingress-nginx не работает: контроллер отбрасывает тело запроса больше 1 МБ — 413 Request Entity Too Large на push — и закрывает проксируемый запрос через 60 секунд — клонирование большого репозитория обрывается. Чарт снимает предел тела (proxy-body-size: "0": пределы загрузки сервер держит сам), отключает буферизацию запроса (proxy-request-buffering: "off": большой push уходит к серверу потоком, не заполняя диск контроллера) и ставит таймауты чтения и записи по часу (proxy-read-timeout, proxy-send-timeout). Свои аннотации (ingress.annotations) перекрывают те же ключи. С другим контроллером или с пустым className при nginx как классе по умолчанию — задайте эквиваленты в annotations сами.

Что чарт проверяет до установки

Установка останавливается с объяснением, а не оставляет под в цикле перезапусков, если: не задано подключение к базе; не задан пароль (ни у внешней базы, ни у базы релиза); запрошено больше одной реплики на томе, который не ReadWriteMany или которого нет вовсе; ключ подписи токенов задан дважды — и значением, и готовым Secret; встроенный SSH включён, а Service — NodePort без узлового порта service.ssh.nodePort (30000–32767; и без config.ssh.publicPort) — порт, выбранный наугад, не назвать в адресе для клонирования; ключ подписи токенов не задан, а рендер идёт без кластера — прежнее значение оттуда не прочитать, и каждая синхронизация GitOps выпускала бы новый ключ.

Ресурсы и права пода

Запросы ресурсов заданы по требованиям к установке: cpu: 500m, memory: 1Gi. Предел задан только памяти (4Gi) — он защищает узел от разросшегося пода. Предела процессора нет: с ним сервер замедлялся бы на всплесках нагрузки (обход большого репозитория, обновление схемы при первом запуске), а долю процессора на занятом узле обеспечивает запрос. Свои значения — resources в values.yaml.

Под запускается от root и сам переходит к пользователю службы: он выравнивает владельца тома и подключается к группе сокета Docker для встроенного исполнителя CI. Поэтому runAsNonRoot для него невозможен, и права сужены иначе — снят весь набор возможностей Linux, кроме CHOWN, DAC_OVERRIDE, SETGID и SETUID, добавлен seccompProfile: RuntimeDefault и запрещено повышение прав. Кластер с политикой restricted такой под отклонит: там задайте своё исключение для этого пространства имён.

Проверка релиза (helm test) запускается на образе самого сервера — в закрытом контуре он уже на месте. Свой образ (нужен только wget) — tests.image.

Несколько реплик

Сервер рассчитан на несколько реплик, но им нужно общее хранилище: том с accessModes: [ReadWriteMany] и strategy.type: RollingUpdate. Подробнее — «Несколько реплик». На ReadWriteOnce оставьте одну реплику: стратегия по умолчанию (Recreate) как раз для этого случая — том не отдаётся двум подам сразу.

CI в кластере

Встроенный исполнитель CI запускает задания в Docker на машине сервера, поэтому в кластере он выключен: очередь забирают внешние исполнители. Нужен хотя бы один исполнитель с меткой default — иначе задания ждут до ci_runner_max_wait_secs и завершаются ошибкой. Установка исполнителей — в руководстве по CI.

База данных вместе с релизом

Для стенда чарт поднимает базу сам — одиночный StatefulSet на официальном образе postgres:

helm install gitriver gitriver-1.1.0.tgz \
  --set postgresql.enabled=true \
  --set postgresql.auth.password=<ПАРОЛЬ>

Внешних зависимостей у чарта нет: helm dependency update не нужен, и в контуре без интернета достаточно образов gitriver и postgres во внутреннем реестре.

Для эксплуатации базу держат снаружи (database.host) или под оператором PostgreSQL. База релиза не реплицируется, не резервируется и не переходит между мажорными версиями PostgreSQL: смена postgresql.image на следующий мажорный выпуск оставит том, который новый сервер не прочитает.

Обновление

helm upgrade gitriver gitriver-НОВАЯ-ВЕРСИЯ.tgz --reuse-values --set image.tag=НОВАЯ-ВЕРСИЯ

Схему базы сервер обновляет сам при старте. Ключ подписи токенов чарт переносит из прежнего Secret — выпущенные токены остаются действительными.

Удаление выпуска и данные

helm uninstall снимает объекты выпуска, но ТОМА ОСТАВЛЯЕТ — это защита от случайной потери данных. После удаления в пространстве имён остаются:

Том Что в нём
<выпуск>-gitriver репозитории, реестр, артефакты сборок, страницы
data-<выпуск>-gitriver-postgresql-0 база, если она развёрнута вместе с выпуском (postgresql.enabled=true)

Кроме томов, остаётся Secret <выпуск>-gitriver-signing-key — ключ подписи токенов; он помечен helm.sh/resource-policy: keep, как и тома, и повторная установка под тем же именем подхватывает его.

Отсюда два следствия, и оба стоит знать заранее.

Место НЕ освобождается: пока тома живы, занятое ими остаётся занятым, и на общем кластере это заметят не сразу. Посмотреть, что осталось:

kubectl -n ПРОСТРАНСТВО get pvc

Удаление пространства имён УНОСИТ ДАННЫЕ. Вместе с томами уходит база, а с ней — сведения об установке, к которым привязана активация лицензии: восстановить её будет нечем, потребуется новая активация у издателя. Прежде чем чистить «для порядка», сделайте резервную копию (gitriver backup) и убедитесь, что она восстанавливается.

Осознанная полная очистка — после копии:

helm uninstall ВЫПУСК -n ПРОСТРАНСТВО
kubectl -n ПРОСТРАНСТВО delete pvc <выпуск>-gitriver data-<выпуск>-gitriver-postgresql-0
kubectl -n ПРОСТРАНСТВО delete secret <выпуск>-gitriver-signing-key

Для выпуска с именем gitriver имена без повтора приставки: тома gitriver и data-gitriver-postgresql-0, Secret gitriver-signing-key.


Конфигурация

GitRiver настраивается через TOML-файл и/или переменные окружения GITRIVER_*. Переменные окружения имеют приоритет над TOML. Где лежит файл, зависит от способа установки: в образе — /var/lib/gitriver/gitriver.toml, из пакета — /etc/gitriver/gitriver.toml, в Kubernetes его собирает чарт из values.yaml (дописать своё — config.extraToml).

Генерация конфигурации

gitriver init-config --output gitriver.toml

Образец пишется на языке терминала (LANG, например LANG=en_US.UTF-8 — по-английски); тот же образец на каждом языке лежит в пакете — /usr/share/doc/gitriver/<язык>/gitriver.toml.example.

Образец называет все настройки установки: те, что не нужны, оставлены в нём закомментированными вместе с пояснением и значением по умолчанию. Строки, помеченные в образце словом «БЕЗОПАСНОСТЬ», меняют границы доступа — их читают целиком, прежде чем задавать значение. Образец полон: настройки, которой в нём нет, не существует и в продукте.

Основные параметры

Параметр Env По умолчанию Описание
host GITRIVER_HOST 0.0.0.0 Адрес привязки
port GITRIVER_PORT 8080 HTTP-порт
database_url GITRIVER_DATABASE_URL — PostgreSQL URL
jwt_secret GITRIVER_JWT_SECRET авто Ключ подписи токенов (32+ байт)
git_repos_path GITRIVER_GIT_REPOS_PATH /var/lib/gitriver/repos Каталог репозиториев
web_dist_path GITRIVER_WEB_DIST_PATH — Каталог с другой сборкой интерфейса. Не задан — отдаётся интерфейс, встроенный в бинарник
base_url GITRIVER_BASE_URL http://{host}:{port} Внешний URL
language GITRIVER_LANGUAGE ru Язык установки: на нём сервер отвечает, когда язык читателя неизвестен — запрос без заголовка Accept-Language, SSH, журнал задания CI, сохраняемые тексты. Запрос с Accept-Language получает свой язык, если он доступен (ru, en). Недоступный язык — отказ при старте
trusted_proxies — приватные сети Адреса обратных посредников, чьим X-Real-IP/X-Forwarded-For можно доверять. Пустой список и список без единой разобранной записи означают то же, что незаданный: доверие приватным сетям (loopback, RFC1918, fc00::/7). «Не верить никому» этой настройкой не выражается
rate_limit_exempt_cidrs GITRIVER_RATE_LIMIT_EXEMPT_CIDRS пусто Адреса, освобождённые от пределов частоты (например, система мониторинга или собственный нагрузочный стенд). Снимает для названных адресов все пределы частоты, включая предел попыток входа, — список держат узким. Счёт неудачных попыток пароля (пять на пару «имя-адрес») это НЕ снимает: он считается отдельно. Подробнее: admin-guide
pages_data_path GITRIVER_PAGES_DATA_PATH {repos}/../pages-data Хранение статических сайтов Pages

Доступ и видимость

Параметр Env По умолчанию Описание
public_profiles GITRIVER_PUBLIC_PROFILES false Видны ли профили пользователей без входа. По умолчанию нет: анонимно читаемый список сотрудников с адресами почты — сведения о компании, которые она не собиралась публиковать. Действует одинаково на /api/v1 и на слои совместимости с чужими интерфейсами
cors_origins — только base_url Источники, которым разрешены запросы из браузера (CORS). Не задано или пустой список — разрешён ровно один источник, base_url. Задают, когда интерфейс живёт на другом адресе, чем API. Пишутся со схемой (https://git.example.com) — так, как браузер шлёт заголовок Origin; значение без схемы примется, но не совпадёт ни с одним запросом
metrics_token GITRIVER_METRICS_TOKEN не задан Токен доступа к /metrics. Задан — точка требует заголовка Authorization: Bearer <токен>; не задан — открыта. Метрики не называют репозиториев и людей, но сводные счётчики установки (сколько пользователей, репозиториев, находок безопасности) — сведения о жизни организации. На установке, доступной из интернета, токен задать следует

Подключение к БД (альтернативное)

Вместо database_url можно указать отдельные параметры:

GITRIVER_DB_HOST=localhost
GITRIVER_DB_PORT=5432
GITRIVER_DB_USER=gitriver
GITRIVER_DB_PASS=secret
GITRIVER_DB_NAME=gitriver

Пределы загрузки файлов

Сколько сервер принимает за один запрос:

Что загружается Предел Отказ при превышении
Файл пакета — PyPI, Cargo, Maven, NuGet, Generic 500 МиБ 413, текст называет заявленный размер и предел
Публикация npm (npm publish) 64 МиБ на весь запрос 413
Вложение релиза 500 МиБ 413; сверх квоты владельца — 403, см. квоту вложений релизов
ZIP статического сайта Pages 500 МиБ сжатого, 1 ГиБ распакованного 413
Объект Git LFS 5 ГиБ 422; сверх квоты владельца — 403
Слой образа в реестре образов 10 ГиБ на слой целиком (не на запрос) 413; сверх квоты — 403, см. квоту пакетного хранилища
Архив артефактов и кеша задания CI 500 МиБ 413; сверх квоты владельца — 403
git push по HTTP 500 МиБ на запрос (сжатое тело) 413; тело, распакованное сверх 500 МиБ, — 400

Предел npm ниже остальных: публикация npm целиком проходит через память сервера. 64 МиБ запроса — это примерно 48 МиБ архива пакета. Остальные загрузки идут потоком на диск, и память сервера от размера файла не зависит.

Предел слоя образа выше остальных: слои базовых образов (окружения машинного обучения, CUDA) исчисляются гигабайтами. Считается он по слою целиком, сколько бы запросов ни потребовалось на его передачу. Сколько места слой получит на самом деле, решает квота; предел действует и там, где квота не задана.

Значения заданы в самом продукте и настройками не меняются. Настраивается срок загрузки:

Параметр Env По умолчанию Описание
upload_http_timeout_secs GITRIVER_UPLOAD_HTTP_TIMEOUT_SECS 3600 Срок запроса, загружающего файл (публикация пакета, вложение релиза, ZIP сайта, архив артефактов и кеша задания CI). Отдельный от общего срока API в 30 секунд: большой файл в него не укладывается
ai_http_timeout_secs GITRIVER_AI_HTTP_TIMEOUT_SECS 660 Срок запроса, ожидающего ответа модели (разбор упавшего задания, разбор запроса на слияние, проверка связи с поставщиком). Отдельный от общего срока API в 30 секунд: рассуждающая модель думает дольше, а помощнику разрешено ждать до 600 секунд

Этим же значением ограничен срок, на который загрузка занимает место в квоте: дольше запроса резерв не живёт, поэтому оборванное соединение и перезапуск сервера не держат место навсегда (см. место занимается на время приёма).

Обратный прокси обязан пропускать столько же. У nginx client_max_body_size по умолчанию 1 МиБ, и загрузка отвалится на нём, не дойдя до сервера (в примере конфигурации выше — 512m).

Помимо предела на запрос действует квота владельца — пакеты, Pages, LFS, CI-артефакты и CI-кеш считаются в ней вместе по всем репозиториям; см. руководство администратора.

CI/CD

Параметр Env По умолчанию Описание
ci_data_path GITRIVER_CI_DATA_PATH {repos}/../ci-data Журналы и рабочие каталоги заданий
ci_max_concurrent_jobs GITRIVER_CI_MAX_CONCURRENT_JOBS доступный ЦП / ci_docker_cpus, не больше 4 Предел параллельных заданий. Умолчание считается от доступного ЦП — числа ядер, ограниченного квотой cgroup: при ci_docker_cpus = 2 четырёхъядерный сервер берёт два задания, восьмиъядерный — четыре (см. Пределы ресурсов задания)
ci_local_executor_enabled GITRIVER_CI_LOCAL_EXECUTOR true Встроенный исполнитель (задания в Docker на сервере). false — сервер задания не исполняет и Docker ему не нужен: вся очередь, включая runs-on: default и задания без runs-on, уходит внешним исполнителям
ci_job_timeout_secs GITRIVER_CI_JOB_TIMEOUT_SECS 3600 Срок задания (секунды)
ci_runner_offline_after_secs GITRIVER_CI_RUNNER_OFFLINE_AFTER_SECS 180 Через сколько секунд молчания внешний исполнитель считается вышедшим из сети. Исполнитель отмечается при каждом опросе очереди (--poll-interval, по умолчанию 5 с); поднимайте значение, если опрос у исполнителей реже
ci_pipeline_retention_days GITRIVER_CI_PIPELINE_RETENTION_DAYS 90 Хранение конвейеров (0 — навсегда)
ci_cache_retention_days GITRIVER_CI_CACHE_RETENTION_DAYS 14 Хранение архива cache: без обращений (0 — не вытеснять по сроку)
ci_docker_memory GITRIVER_CI_DOCKER_MEMORY 2g Предел памяти задания: docker --memory у задания с image:, memory.max cgroup у задания без образа
ci_docker_cpus GITRIVER_CI_DOCKER_CPUS 2 Предел CPU контейнера задания. Сборка через docker build его не соблюдает — см. Пределы ресурсов задания
ci_job_pids_limit GITRIVER_CI_JOB_PIDS_LIMIT 512 Предел числа процессов и потоков задания: docker --pids-limit у задания с image:, pids.max cgroup у задания без образа. 0 — не ограничивать
ci_cgroup_parent GITRIVER_CI_CGROUP_PARENT gitriver.slice Срез ресурсов CI: cgroup, в которую идут ВСЕ контейнеры заданий. Пустая строка — без среза. Предел самому срезу задаёт systemd, а не этот файл — см. Пределы ресурсов задания
ci_eraser_image GITRIVER_CI_ERASER_IMAGE busybox Образ, которым снимается рабочий каталог задания, если его контейнер оставил в нём файлы от root (см. Уборка каталогов заданий)
allowed_external_action_domains GITRIVER_ALLOWED_EXTERNAL_ACTION_DOMAINS не задано Откуда разрешено брать внешние действия (uses: host/owner/repo@ref). Не задано — разрешён любой домен; [] — запрет всех. Записи: git.example.com (весь домен), git.example.com/actions (один владелец), git.example.com/actions/checkout (репозиторий)
ci_docker_runtime GITRIVER_CI_DOCKER_RUNTIME default Среда запуска контейнеров задания: default — обычный Docker, sysbox — DinD без прав root через sysbox-runc, rootless — Docker без прав root, privileged — полный DinD. Последнее означает выход задания на хост и включается только там, где заданиям доверяют как администратору сервера
ci_host_data_path GITRIVER_CI_HOST_DATA_PATH автоопределение Хостовый путь ci_data_path для Docker-in-Docker. Обычно не нужен: путь определяется через docker inspect
ci_git_clone_timeout_secs GITRIVER_CI_GIT_CLONE_TIMEOUT_SECS 120 Срок git clone и checkout в задании
ci_lfs_fetch_timeout_secs GITRIVER_CI_LFS_FETCH_TIMEOUT_SECS 600 Срок git lfs pull в задании. Отдельный от клонирования: объекты LFS весят сотни мегабайт
ci_after_script_timeout_secs GITRIVER_CI_AFTER_SCRIPT_TIMEOUT_SECS 300 Срок after_script задания
ci_runner_poll_secs GITRIVER_CI_RUNNER_POLL_SECS 3 Как часто сервер проверяет, какие задания конвейера готовы к запуску, секунды
ci_runner_max_wait_secs GITRIVER_CI_RUNNER_MAX_WAIT_SECS 3600 Сколько задание ждёт свободного исполнителя
ci_job_token_ttl_secs GITRIVER_CI_JOB_TOKEN_TTL_SECS 28800 Срок жизни CI_JOB_TOKEN. Должен быть не меньше самого долгого срока задания в конвейере

Контейнерный реестр и сканирование образов

Параметр Env По умолчанию Описание
ci_registry_host GITRIVER_CI_REGISTRY из base_url Адрес реестра для переменной CI_REGISTRY. Указывайте 127.0.0.1, если Docker BuildKit не может подключиться к localhost (IPv6 против IPv4)
registry_scan_enabled — false Автоматическое сканирование образов на уязвимости (Trivy) при публикации манифеста. Сканированием по требованию не управляет: его просит человек с правом записи в реестр
trivy_path — trivy Путь к исполняемому файлу Trivy
registry_token_expiry_secs GITRIVER_REGISTRY_TOKEN_EXPIRY_SECS 7200 Срок жизни токена реестра. Заметно длиннее прочих: BuildKit держит токен на всю сборку

Хранилище реестра задаётся секцией [s3]; без неё образы лежат на файловой системе.

Лицензирование

Параметр Env По умолчанию Описание
license_server_url GITRIVER_LICENSE_SERVER_URL https://gitriver.ru Адрес сервера лицензий: туда уходит отметка установки, оттуда приходит подписанный ответ о продлении и отзыве
update_check_url GITRIVER_UPDATE_CHECK_URL https://gitriver.com/releases/latest Откуда установка узнаёт о вышедших версиях. Закрытому контуру сюда ставят своё зеркало, установке за посредником — адрес посредника
update_check_enabled GITRIVER_UPDATE_CHECK_ENABLED true Спрашивать ли о новых версиях. Для бесплатной установки это единственное обращение наружу; false выключает его целиком

Адрес без https:// принимается — закрытый контур ставит свой сервер, — но сервер называет это в журнале при каждом старте: в отметке идут сведения об установке. Что именно передаётся — licensing.

Сроки запросов и сроки жизни токенов

Параметр Env По умолчанию Описание
http_request_timeout_secs GITRIVER_HTTP_REQUEST_TIMEOUT_SECS 30 Обычный запрос /api/v1
git_http_timeout_secs GITRIVER_GIT_HTTP_TIMEOUT_SECS 3600 git clone и git push по HTTP: короткий срок разорвал бы клонирование большого репозитория
lfs_http_timeout_secs GITRIVER_LFS_HTTP_TIMEOUT_SECS 3600 Выгрузка и загрузка объектов LFS
registry_http_timeout_secs GITRIVER_REGISTRY_HTTP_TIMEOUT_SECS 3600 Операции реестра образов
oauth_provider_timeout_secs GITRIVER_OAUTH_PROVIDER_TIMEOUT_SECS 30 Обращения к поставщику OAuth2
webhook_timeout_secs GITRIVER_WEBHOOK_TIMEOUT_SECS 10 Запрос к получателю webhook
webhook_connect_timeout_secs GITRIVER_WEBHOOK_CONNECT_TIMEOUT_SECS 5 Установление соединения с получателем webhook
sse_keepalive_secs GITRIVER_SSE_KEEPALIVE_SECS 15 Интервал контрольных сообщений потока событий (SSE)
lfs_token_ttl_secs GITRIVER_LFS_TOKEN_TTL_SECS 900 Срок жизни токена LFS
oauth_token_expiry_secs GITRIVER_OAUTH_TOKEN_EXPIRY_SECS 3600 Срок жизни токенов OAuth и JWT

Сроки загрузки файлов и запросов к помощнику — в разделе Пределы загрузки файлов; сроки заданий CI — в разделе CI/CD.

Пределы ресурсов задания

Задание встроенного исполнителя выполняется под пределами памяти и числа процессов. Значения и поведение при их исчерпании описаны в руководстве по CI.

Сколько заданий идёт разом. ci_max_concurrent_jobs по умолчанию считается от того, сколько ЦП установке ДОСТУПНО: сколько заданий умещается при пределе ci_docker_cpus на каждое (доступное делится на предел), но не больше четырёх. Четырёхъядерный сервер с умолчаниями берёт два задания, восьмиъядерный — четыре.

Доступное — это не только число процессоров: квота cgroup тоже потолок. Шестнадцатиядерная машина со срезом CPUQuota=600% (см. ниже) даёт заданиям шесть ядер, и умолчание считается от шести. Квота читается при старте, поэтому после systemctl set-property службу нужно перезапустить.

Заданное руками число сервер не поправит, но скажет при старте, если обещанного ЦП больше, чем доступно:

ci_max_concurrent_jobs = 4 при ci_docker_cpus = 2 обещает заданиям 8 ядер,
а доступно 4

Переобещание стоит дороже, чем кажется: машина, перегруженная сборками встроенного исполнителя, перестаёт отвечать и по API, и по git — снаружи это выглядит как зависание всей установки, а не как медленный CI.

Предел по заданию считает не весь расход. ci_docker_memory и ci_docker_cpus получает КОНТЕЙНЕР задания. Шаг docker build внутри задания исполняет не он, а демон BuildKit в отдельном контейнере, и пределы задания на него не распространяются. Пока задания собирают образы, предел на задание ограничивает только меньшую часть расхода.

Срез ресурсов CI

Поверх пределов задания стоит срез — общая cgroup, в которую попадает всё, что запускает сам исполнитель:

Что Как попадает в срез
Сам сервер и задания без image: (идут в его процессе) Slice=gitriver.slice в unit-файле службы
Контейнер задания, after_script, сервисы services: --cgroup-parent от настройки ci_cgroup_parent
Контейнеры действий (uses:), которые поднимает скрипт задания то же, через переменную CI_CGROUP_PARENT
Служебные контейнеры уборки рабочей копии то же
Внешний исполнитель на этой же машине Slice=gitriver.slice и --cgroup-parent

Имя среза по умолчанию — gitriver.slice, и оно должно быть одним у всех перечисленных: иначе сервер окажется в одном срезе, а контейнеры его заданий — в другом. Первая строка таблицы выполняется только там, где службу поднимает unit-файл поставки: у сервера, запущенного из терминала или иным способом, в срез попадут контейнеры заданий, но не он сам и не задания без image:.

Имя записывается в виде *.slice — такое значение принимают оба драйвера cgroup демона docker. Абсолютный путь cgroup тоже принимается, но работает только при драйвере cgroupfs: драйвер systemd (умолчание современных дистрибутивов) его отвергает. Непригодное значение сервер называет в журнале при старте и работает БЕЗ среза, а задания выполняются как обычно.

При старте сервер сверяет срез с драйвером cgroup демона docker и называет в журнале, что вышло:

срез ресурсов CI: gitriver.slice; отведено ему: ЦП — 6 ядер, память — 12288 МиБ

Если демон cgroup не управляет вовсе (Cgroup Driver: none — Docker без прав root и без делегирования, cgroup v1), срез снимается с предупреждением в журнале. Строка «его cgroup серверу недоступна» означает другое — контейнеры в срез попадут, а вот пределы среза, метрики и строки о его срабатываниях в журналах заданий читаться не будут. У сервера в контейнере это лечится монтированием /sys/fs/cgroup на чтение (см. «Установка в Docker» ниже).

Предел срезу задаёт systemd, а не gitriver.toml. Такой настройки в gitriver.toml нет; предел задаётся командой:

systemctl set-property gitriver.slice CPUQuota=600% MemoryMax=12G

Оставьте машине запас сверх среза — ОС, базе и обратному посреднику; пример выше рассчитан на машину о восьми ядрах и 16 ГиБ. Правка через set-property переживает обновление пакета.

Без предела срез остаётся местом учёта — и это уже полезно:

systemd-cgtop gitriver.slice

То же самое, не заходя на машину, показывает админка: «Система» → «Срез ресурсов CI» — имя, пределы, занятая память и счётчики срабатываний, а во времени — метрики.

Сервер получает свою долю внутри среза. Чтобы API и git отвечали и на машине, перегруженной сборками, unit-файл поставки даёт службе вес ЦП выше, чем у контейнеров-соседей по срезу (CPUWeight=10000 против умолчания 100), и защиту страниц от вытеснения (MemoryLow=512M). Это не резервирование: пока ресурсов хватает, задания берут всё свободное.

Установка в Docker — две строки, и обе обязательны. Служба gitriver в docker-compose.prod.yml объявляет cgroup_parent: gitriver.slice — тем же именем, что уходит серверу в GITRIVER_CI_CGROUP_PARENT, — и монтирует иерархию cgroup на чтение:

    cgroup_parent: ${GITRIVER_CI_CGROUP_PARENT-gitriver.slice}
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro

Первая кладёт сервер в срез. Вторая даёт ему срез УВИДЕТЬ. Забыть вторую — не отказ, а тихая потеря половины: контейнеры заданий в срез попадут, а пределы среза, метрики по нему и строки о его срабатываниях в журналах заданий пропадут. Сервер говорит об этом при старте прямо:

срез ресурсов CI: gitriver.slice; его cgroup серверу недоступна — пределы среза
и метрики по нему не читаются, строк о его срабатываниях в журнале заданий не будет

Монтирование только на чтение. Новых прав оно не даёт: рядом уже смонтирован docker.sock, а он равносилен root на машине.

Предел задаётся срезу НА ХОСТЕ той же командой systemctl set-property: внутри контейнера systemd нет.

Установка обновляется — compose обновите тоже. Файл на вашей машине — копия, сделанная при установке: docker compose pull && up -d берёт её, а не ту, что приехала с новым выпуском. Обе строки выше нужно перенести в свой файл руками.

Сборка образов в срез не попадает, и это не настраивается на сервере. Сборщик образов, который заводит сам конвейер (docker buildx create), работает вне среза и вне пределов задания; параметр --driver-opt cgroup-parent=… его в срез не переносит. Шаг docker build без buildx исполняет сам демон docker, и пределы ни среза, ни службы docker.service на него не действуют.

Что остаётся администратору:

  • потребовать от авторов конвейеров пределов самому сборщику — --driver-opt memory=6g --driver-opt cpu-quota=300000 (см. руководство по CI);
  • либо задать умолчание демону: ключ cgroup-parent в /etc/docker/daemon.json. Оно подействует на ВСЕ контейнеры машины, поэтому годится там, где машина отдана GitRiver целиком;
  • либо вынести сборку образов на внешний исполнитель отдельной машины — тогда её расход ограничен этой машиной, а не соседством с сервером.

Задание с image: получает пределы от Docker — ничего настраивать не нужно.

Задание без image: выполняется скриптом на самом сервере, и пределы ему накладывает cgroup v2. Для этого службе нужно делегированное поддерево cgroup — строка в unit-файле:

[Service]
Delegate=cpu pids memory

В пакете эта строка уже стоит — при установке из deb/rpm настраивать нечего.

Сервер в Docker-контейнере получает поддерево, если /sys/fs/cgroup смонтирован на запись (--cgroupns=private и rw-монтирование cgroup2).

Проверить, вышло ли, можно по журналу сервера: если поддерево недоступно, при первом же задании появляется предупреждение

cgroup v2 серверу недоступна: пределы числа процессов и памяти задач БЕЗ `image:`
не накладываются. Задачам с `image:` пределы задаёт Docker. Как включить —
руководство по установке (/usr/share/doc/gitriver/<язык>/installation.md),
раздел «Пределы ресурсов задания»

Задание без image: в этом случае выполняется без пределов памяти и процессов. Число одновременных заданий при этом не понижается: умолчание ci_max_concurrent_jobs считается от ci_docker_cpus, а этот предел накладывает Docker. Установке, где пределы обязательны для всех заданий, следует требовать image: у заданий либо выключить встроенный исполнитель и отдать очередь внешним исполнителям.

Уборка каталогов заданий

Скрипт задания с image: выполняется в контейнере от root, и файлы, созданные им в рабочем каталоге, принадлежат root или вовсе чужому uid (так бывает у docker buildx). У образов с umask 0077 (например redis:7) закрытыми оказываются и каталоги — пользователь сервера удалить их не может.

Поэтому сервер убирает такие каталоги контейнером: сначала образом самого задания, а если этого не хватило (демон с userns-remap, файлы buildx) или образ задания уже неизвестен (уборка завершённых заданий раз в час) — служебным образом-«ластиком», который удаляет файлы от root. Так же убирает каталоги и внешний исполнитель (руководство администратора).

Образ по умолчанию — busybox (около 4 МБ). Если его на машине нет, сервер загружает его при первой надобности; в закрытом контуре укажите свой образ из доступного реестра — подойдёт любой с sh, find и rm:

ci_eraser_image = "registry.example.com/base/busybox:1.36"

Недоступность образа не молчит: каталог остаётся на диске, а в журнал уходит строка с причиной и путём.

Лёгкая установка без Docker (только внешние исполнители)

По умолчанию задания CI выполняет сам сервер — встроенным исполнителем, в контейнерах Docker. Настройка ci_local_executor_enabled = false выключает его и отдаёт всю очередь внешним исполнителям:

GITRIVER_CI_LOCAL_EXECUTOR=false
ci_local_executor_enabled = false

Что это даёт. Серверу больше не нужен ни Docker, ни доступ к его сокету: GitRiver ставится на лёгкую машину и в окружение, где Docker запрещён политикой. Освобождаются процессор и память, которые иначе уходили бы на сборки — сервер занят только git, базой и HTTP, а пики нагрузки от сборок переносятся на машины исполнителей. Сервер не обращается к Docker вовсе: уборка контейнеров сборщиков и обслуживание кеша сборки не запускаются, пределы ci_docker_memory, ci_docker_cpus и ci_job_pids_limit не действуют, а локальный предел параллельности ci_max_concurrent_jobs не занимается.

Что всё равно требуется. Диск: журналы и артефакты заданий исполнители загружают на сервер, в ci_data_path — их объём от режима не зависит. Рассчитывайте место так же, как для встроенного исполнителя, и сохраняйте ci_pipeline_retention_days (срок хранения конвейеров) осмысленным. Кеш между запусками (cache:) работает и на внешних исполнителях — архивом обменивается сам сервер, — поэтому он тоже занимает ci_data_path: его объём ограничивает квота владельца (max_ci_cache), а забытые ключи снимает срок ci_cache_retention_days.

Обязательное условие — исполнитель с меткой default. В этом режиме метки заданий не рассматриваются: исполнитель нужен всей очереди, включая runs-on: default и задания без runs-on. Задание без runs-on заберёт любой исполнитель, а runs-on: default — только тот, у которого метка default объявлена. Зарегистрируйте хотя бы одного такого исполнителя (админка → «Исполнители»), иначе задания будут ждать до ci_runner_max_wait_secs (по умолчанию час) и завершаться ошибкой: запасного варианта «выполнить на сервере» в этом режиме нет. Пока подходящего исполнителя нет, админка и мастер настройки показывают предупреждение. Как поставить исполнителя на сборочную машину — «Установка внешнего исполнителя».

Установка самих исполнителей и поведение заданий описаны в руководстве по CI.

SMTP (уведомления по почте)

[smtp]
host = "smtp.example.com"
port = 587
username = "gitriver@example.com"
password = "пароль"
from = "gitriver@example.com"
starttls = true

Шифрование задаётся одним полем и имеет три состояния:

starttls Порт Соединение
true (умолчание) любой StartTLS — соединение поднимается до TLS после приветствия
false 465 неявный TLS (SMTPS): шифрование до приветствия
false любой другой без шифрования — для внутреннего релея в закрытом контуре

Последний случай сервер называет в журнале при каждом старте: пароль учётной записи релея и тела писем идут по сети открытым текстом.

Почта настраивается и в панели администратора; её значения имеют приоритет, а секция читается, когда в базе настроек ещё нет.

LDAP (корпоративная авторизация)

[ldap]
url = "ldaps://ldap.example.com:636"
# Для url = "ldap://..." соединение поднимается до TLS через StartTLS.
# Значение по умолчанию — true; false оставляет пароли открытым текстом.
# starttls = true
bind_dn = "cn=service,dc=example,dc=com"
bind_password = "пароль"
search_base = "ou=users,dc=example,dc=com"
user_filter = "(uid={login})"
email_attr = "mail"
display_name_attr = "displayName"
admin_group_dn = "cn=admins,ou=groups,dc=example,dc=com"
# link_existing_users = false

display_name_attr — атрибут отображаемого имени (displayName, cn). Не задан — отображаемого имени у записи не будет, и везде показывается имя входа.

link_existing_users решает, вправе ли вход через каталог занять существующую локальную учётную запись с тем же именем. По умолчанию нет: имя в каталоге задаёт сторона каталога, и совпадение с локальным admin открывало бы его учётную запись мимо её пароля. Записи, заведённые через каталог, это не затрагивает. Включают при переезде, когда локальные имена намеренно совпадают с именами в каталоге.

Настройки каталога из панели администратора хранятся в базе и имеют приоритет над этой секцией: она читается, когда в базе настроек ещё нет.

S3 (реестр образов)

[s3]
endpoint = "https://s3.example.com"
bucket = "gitriver-registry"
access_key = "ACCESS_KEY"
secret_key = "SECRET_KEY"
region = "us-east-1"
# part_size_mb = 5

part_size_mb — размер части многочастной загрузки, МиБ (по умолчанию 5, допустимо от 5 до 100). Больше — меньше запросов к хранилищу на тот же объём; меньше — быстрее повтор части при обрыве.

Хранилище реестра настраивается и в панели администратора. Как только оно задано там, секция [s3] не читается вовсе — сервер говорит об этом строкой в журнале при старте. Правка секции в такой установке не даёт ничего.


SSH-доступ

GitRiver поддерживает SSH для клонирования и отправки. Настройка:

  1. В профиле пользователя: Настройки / SSH-ключи — добавить публичный ключ
  2. Дальше — по способу установки:

Встроенный SSH-сервер (системный sshd не нужен):

ssh_port = 2222

[ssh_server]
listen = "0.0.0.0:2222"
host_key_path = "/var/lib/gitriver/ssh_host_ed25519_key"

В Docker порт публикуется наружу (GITRIVER_SSH_PORT в compose), в Kubernetes — значением config.ssh.enabled=true.

Системный sshd: сервер сам поддерживает authorized_keys учётной записи службы. Путь зависит от способа установки — из пакета домашний каталог gitriver это /var/lib/gitriver:

authorized_keys_path = "/var/lib/gitriver/.ssh/authorized_keys"

Клонирование: git clone ssh://git@git.example.com/owner/repo.git

Сеанс через системный sshd ведёт команда gitriver serv. Её журнал пишется в системный журнал (syslog, под именем gitriver-serv) — рядом со строками входа самого sshd: journalctl -t gitriver-serv. Клиенту в ответ уходит только причина отказа.

Параметр Env По умолчанию Описание
authorized_keys_path — не задан Файл authorized_keys, в который сервер записывает ключи пользователей. Не задан — управление authorized_keys отключено
ssh_host GITRIVER_SSH_HOST из base_url Публичный хост в адресе для клонирования
ssh_port GITRIVER_SSH_PORT порт [ssh_server], иначе 22 Публичный порт в адресе для клонирования. Не задан — берётся порт встроенного сервера из listen, а без него 22. Для нестандартного порта адрес принимает вид ssh://git@host:port/owner/repo.git
ssh_user GITRIVER_SSH_USER git Имя пользователя в адресе для клонирования
ssh_enabled GITRIVER_SSH_ENABLED автоматически Показывать ли вкладку SSH в окне «Клонировать». По умолчанию видна, если задан authorized_keys_path или секция [ssh_server]; явное значение решает за оба случая — например, когда SSH обслуживает отдельный внешний демон. Настройка вкладки в панели администратора старше этого значения
commit_signing_key_path GITRIVER_COMMIT_SIGNING_KEY_PATH рядом с файлом настроек Ключ Ed25519, которым сервер подписывает коммиты слияния, сделанные им самим (слияние в интерфейсе или через API). Создаётся при первом запуске. Нужен, чтобы при require_signed_commits вершина защищённой ветки оставалась подписанной после слияния; без ключа коммиты слияния идут без подписи
listen (в [ssh_server]) GITRIVER_SSH_SERVER_LISTEN обязателен Адрес и порт встроенного SSH-сервера. Переносится в базу при первом старте с секцией; дальше значением владеет панель администратора
host_key_path (в [ssh_server]) GITRIVER_SSH_SERVER_HOST_KEY_PATH рядом с файлом настроек Ключ Ed25519 самого SSH-сервера. Создаётся при первом запуске. Переменная действует только при включённом встроенном сервере: одна она, без секции [ssh_server] и без GITRIVER_SSH_SERVER_LISTEN, не включает ничего

Панель администратора старше файла. Публичные ssh_host, ssh_port, ssh_user и видимость вкладки задаются ещё и в разделе SSH панели администратора; заданное там значение вытесняет значение из файла и применяется без перезапуска. Значения из файла работают, пока в панели ничего не задано.

С секцией [ssh_server] строже: она не управление, а разовый посев. При первом старте с этой секцией её значения переносятся в базу (в журнале — ssh bootstrap: TOML [ssh_server] перенесён в БД), и дальше встроенным сервером владеет панель: правка секции в файле уже ничего не меняет. То же относится к GITRIVER_SSH_SERVER_LISTEN и GITRIVER_SSH_SERVER_HOST_KEY_PATH — они задают ту же секцию.


Несколько реплик

Сервер рассчитан на запуск в нескольких экземплярах за балансировщиком (в Kubernetes — несколько подов одного Deployment). Требования к окружению:

  • Общая БД — все реплики подключены к одному PostgreSQL.
  • Общее хранилище для репозиториев, данных CI, реестра образов и Pages: сетевой том с одновременным доступом на запись (в Kubernetes — ReadWriteMany) либо S3 для реестра. Локальный диск пода не подойдёт: запрос на скачивание артефакта попадёт на любую реплику.

Фоновые задачи выполняются в одной реплике. Планировщик CI, очередь на слияние, очереди групп одновременности CI, очистки артефактов, конвейеров и реестра, опрос GitOps, контроллер исполнителей в Kubernetes, запросы подтверждения развёртываний в окружения, резервные копии по расписанию, отметка лицензии и последствия истечения лицензий (снятие мест Pro, запись в ленту) каждая выполняется только в одной реплике, и долгая работа одной задачи не задерживает остальные. Реплика, потерявшая связь с базой или остановленная, освобождает свои задачи, и со следующего прохода их подхватывает другая — вмешательства администратора не требуется, отдельного «главного» пода в конфигурации нет.

Отправка почты из очереди и повторные доставки вебхуков работают во всех репликах и делят нагрузку. Чистка мусора Docker (docker volume/image/network prune) идёт на каждой машине со встроенным исполнителем CI — демон у каждой свой.

Выкатка новой версии. Остановленный под сразу отдаёт свои фоновые задачи, поэтому скользящее обновление не оставляет их без присмотра дольше, чем на один их интервал.


Резервное копирование

Путь один — подкоманды сервера. Они делают полную копию (база, репозитории, реестр, CI, Pages), проверяют целостность архива при восстановлении и возвращают ненулевой код, если восстановить не удалось. Отдельных сценариев копирования в поставке нет; копируйте и восстанавливайте только этими подкомандами — своя обёртка может пропустить проверку целостности архива.

Настройки

Параметр Env По умолчанию Описание
backup_encryption_key GITRIVER_BACKUP_ENCRYPTION_KEY не задан Ключ шифрования копий (base64, 32 байта). Задан — архив шифруется AES-256-GCM
backup_token_ttl_secs GITRIVER_BACKUP_TOKEN_TTL_SECS 60 Срок жизни одноразовой ссылки на скачивание копии

Ключ шифрования хранят отдельно от копий: вместе они не защищают ни от чего, а порознь потеря ключа означает потерю копий. Смена ключа не перешифровывает уже сделанные архивы — восстановить их прежним ключом можно, новым нельзя.

Создание резервной копии

gitriver backup --config gitriver.toml --output backup.tar.gz

Включает: базу, репозитории, реестр, данные CI, Pages.

Флаги:

  • --repos false — исключить репозитории
  • --registry — включить реестр образов
  • --ci — включить данные CI
  • --pages — включить Pages

Восстановление

gitriver restore --config gitriver.toml backup.tar.gz

Автоматическое расписание

Настраивается в интерфейсе: Администрирование / Резервное копирование / Расписание.


Обновление

# образ
cd /opt/gitriver
docker compose pull gitriver
docker compose up -d gitriver

# пакет
sudo apt install ./gitriver_НОВАЯ-ВЕРСИЯ_amd64.deb    # или dnf upgrade

# Helm
helm upgrade gitriver gitriver-НОВАЯ-ВЕРСИЯ.tgz --reuse-values --set image.tag=НОВАЯ-ВЕРСИЯ

Схему базы сервер обновляет сам при старте — отдельного шага обновления нет ни в одном из трёх способов. Если сервер остановился с сообщением об обновлении схемы — см. «Решение проблем» ниже.

Переменная GITRIVER_RATE_LIMIT_DISABLED не действует. Пределы частоты снимаются настройкой rate_limit_exempt_cidrs (GITRIVER_RATE_LIMIT_EXEMPT_CIDRS), которая перечисляет освобождённые адреса. Если переменная у вас задана, после обновления пределы частоты действуют для всех — сервер скажет об этом в журнале при старте; перенесите нужные адреса в rate_limit_exempt_cidrs.


Решение проблем

Мастер настройки не появляется

Убедитесь, что database_url не указан в TOML и env. Мастер запускается только при отсутствии подключения к БД.

Ошибки токенов после перезапуска

Ключ подписи токенов хранится в /var/lib/gitriver/.jwt_secret (из пакета — /etc/gitriver/.jwt_secret). При необходимости укажите jwt_secret явно в конфиге. В Kubernetes файла нет: ключ приходит переменной GITRIVER_JWT_SECRET из Secret — см. «Ключ подписи токенов». Первый подозреваемый там — синхронизация GitOps с ключом, который чарт генерирует сам.

Ключ теряется — теряются не только сеансы. Вместе с ним становится нечитаемым всё, что зашифровано в базе на его основе:

Что зашифровано Что будет
TOTP-секреты пользователей вторая ступень входа перестанет работать у всех, кто её включил; сбрасывать придётся администратору по одному
client_secret приложений OAuth (GitRiver как поставщик) подключённые приложения перестанут получать токены, секреты нужно выпустить заново
Токены доступа к внешним реестрам в кеше посредника выдача приватных источников через посредника остановится до повторного ввода токенов
Ключ доступа к поставщику языковой модели помощник перестанет отвечать до повторного ввода ключа
Ключ подписи id_token (GitRiver как поставщик OIDC) установка заведёт новый и опубликует его в JWKS; выданные ранее id_token перестанут проверяться, получатели переживут это как разовый повторный вход

Поэтому .jwt_secret — часть резервной копии, а не одноразовый файл, и менять его «на всякий случай» не следует. Замена оправдана при утечке ключа; тогда перечисленное выше нужно вводить заново осознанно, а не обнаруживать по жалобам пользователей.

Задания CI не запускаются

  1. Сокет Docker доступен: -v /var/run/docker.sock:/var/run/docker.sock
  2. Пользователь gitriver в группе docker (в образе это делается при запуске само)
  3. Проверьте ci_max_concurrent_jobs — возможно предел достигнут
  4. Если ci_local_executor_enabled = false — задания ждут внешнего исполнителя. Откройте админку → «Исполнители»: там показано, есть ли исполнитель в сети и объявлена ли у него метка default (без неё задания с runs-on: default брать некому). См. «Лёгкая установка без Docker»

Отправка в git отклоняется

  • Проверьте защиту веток (настройки репозитория → «Защита веток»)
  • Для LFS: установите client_max_body_size в nginx

Сервер не стартует: «Найдены пакеты NuGet, различающиеся только регистром»

Идентификаторы NuGet регистронезависимы: Newtonsoft.Json и newtonsoft.json — один и тот же пакет. Если в одном репозитории нашлись две записи такого пакета, сервер не сливает их сам — у одинаковых версий могут различаться файлы и метаданные — и останавливает обновление, пока вы не сведёте их вручную.

Найдите конфликтующие записи:

SELECT r.name AS repo, LOWER(p.name) AS package_id,
       ARRAY_AGG(p.name ORDER BY p.created_at) AS variants
FROM packages p
JOIN repositories r ON r.id = p.repo_id
WHERE p.type = 'nuget'
GROUP BY r.name, p.repo_id, LOWER(p.name)
HAVING COUNT(*) > 1;

Для каждой группы выберите написание, которое остаётся (обычно — опубликованное первым), и перенесите версии из остальных записей:

BEGIN;
UPDATE package_versions SET package_id = '<id остающегося пакета>'
 WHERE package_id = '<id лишнего пакета>';
DELETE FROM packages WHERE id = '<id лишнего пакета>';
COMMIT;

Если одна и та же версия опубликована в обеих записях, UPDATE завершится ошибкой package_versions_package_id_version_key: это две разные публикации одного номера версии. Решите, какая из них верна, удалите вторую (DELETE FROM package_versions WHERE id = ...) и повторите перенос.

После слияния перезапустите сервер — обновление продолжится.

Сервер не стартует: «содержит несколько эквивалентных версий NuGet»

Номера версий NuGet нормализуются (1.0, 1.0.0 и 1.0.0.0 — одна версия), и две публикации одной версии сервер в одну не сводит: у них разные файлы. Родственные отказы того же обновления — «не соответствует формату NuGet» (версия, которую нормализовать нечем) и «содержит несколько файлов .nupkg» (неясно, какой файл считать файлом версии).

Найдите эквивалентные версии:

SELECT p.name AS package, pv.package_id,
       ARRAY_AGG(pv.version ORDER BY pv.created_at) AS variants
FROM package_versions pv
JOIN packages p ON p.id = pv.package_id
WHERE p.type = 'nuget'
GROUP BY p.name, pv.package_id,
         regexp_replace(pv.version, '(\.0)+$', '')
HAVING COUNT(*) > 1;

Решите, какая публикация верна, удалите лишнюю (DELETE FROM package_versions WHERE id = ...) и перезапустите сервер. Запрос выше приблизителен; точную запись называет сам текст отказа, по одной за запуск.

Сервер не стартует: «имя занято одновременно пользователем и группой»

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

SELECT u.username FROM users u JOIN groups g ON g.path = u.username;

Переименуйте одну из сторон — пользователя в админке, группу на её странице — и перезапустите сервер.

Сервер не стартует: «активации лицензий ссылаются на разные инстансы»

Признак того, что база была скопирована и обе копии активировали лицензию самостоятельно. Своими силами это не исправляется, и править базу вручную не следует: напишите издателю (info@gitriver.ru), приложите полный текст отказа и укажите, какая из копий — рабочая установка. Издатель перевыпустит лицензию, ответ вводится вручную на странице лицензии.

Если обе копии должны работать как отдельные установки, каждой нужна своя лицензия. Порядок после переезда базы или восстановления из копии — в разделе «Переезд базы и восстановление из копии».