# Установка, настройка и сборка на Linux

Инструкция проводит от установки GSF CLI до сборки `applib` и `appsrc`. Команды рассчитаны на Bash в Debian-подобной
системе `x86_64`. Для другой ОС семейства Linux названия системных пакетов и способ их установки нужно подобрать
отдельно.

Для CI сначала подготовьте агент по [главе о раннерах](035_runner_setup.md). Дальше вся настройка сборки, включая
pipeline, выполняется по этой главе.

## Перед установкой

Подготовьте URL конфигурационного Git-проекта, его ветку и имя проекта в GSF CLI. В примерах имя — `main`, каталог
установки — `/opt/global/gsf-cli`. Git URL должен оканчиваться на `.git`.

Для сборки используются JDK **21** и SBT **1.x** начиная с 1.8.2; ниже показана установка SBT 1.10.7. Версию самого SBT,
загружаемую launcher, также определяет конфигурация прикладного проекта. IDEA нужна для работы разработчика со средой,
сборка через `config.json` обходится без неё.

Выберите, откуда будут загружаться зависимости:

| Условия               | Что подготовить                                                                                              |
|-----------------------|--------------------------------------------------------------------------------------------------------------|
| Публичные репозитории | Доступ к указанным в проекте источникам в интернете                                                          |
| Внутренний прокси     | Адреса прокси-репозиториев; недостающие зависимости прокси скачивает и кеширует из внешних источников        |
| Изолированный контур  | Все зависимости заранее загружены во внутренние репозитории; установочные файлы тоже доступны внутри контура |

На чистой машине нужны сам SBT, Scala, плагины и их транзитивные зависимости, библиотеки проекта, исходники модулей и
дистрибутив GlobalServer.

## Установка компонентов

### Пользователь и каталоги

Укажите пользователя, который будет запускать сборку. Для локальной работы это ваша учётная запись, для GitLab —
`gitlab-runner`, для Jenkins — пользователь SSH-агента:

```bash
GSF_BUILD_USER="<build_user>"
GSF_BUILD_GROUP="$(id -gn "$GSF_BUILD_USER")"
id "$GSF_BUILD_USER"
sudo mkdir -p /opt/global/{tmp,builds}
```

Установочные команды с `sudo` выполняются администратором. После установки переключитесь на пользователя сборки. Его
`$HOME` определяет расположение файлов SBT и credentials.

Используйте пути без пробелов: часть скриптов окружения GSF CLI не заключает их в кавычки.

### Системные пакеты и сертификаты для загрузки

```bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl git wget zip unzip nano python3-venv
```

Если установочные файлы или Git доступны через HTTPS с корпоративным сертификатом, до обращения к этим адресам
установите CA. Получите корневой и промежуточные сертификаты в PEM и добавьте каждый отдельным `.crt`-файлом:

```bash
sudo install -m 0644 /path/to/company-root-ca.crt \
  /usr/local/share/ca-certificates/company-root-ca.crt
sudo update-ca-certificates
```

Для промежуточных CA повторите `install`, затем `update-ca-certificates`. Если доверие уже настроено при подготовке
runner, повторять установку не нужно.

### JDK 21, SBT и GSF CLI

Установите JDK 21 из принятого в вашей организации дистрибутива. В примерах используется
`/usr/lib/jvm/bellsoft-java21-amd64`; замените этот путь на фактический во всех командах и в `config.json`.

```bash
/usr/lib/jvm/bellsoft-java21-amd64/bin/java -version
/usr/lib/jvm/bellsoft-java21-amd64/bin/javac -version
```

Обе команды должны показывать версию 21. Нужен JDK с `javac`, одного JRE недостаточно.

Скачайте и распакуйте компоненты:

```bash
sudo wget -P /opt/global/tmp \
  https://repo.global-system.ru/artifactory/common/ru/bitec/gsf-cli-linux/LATEST/gsf-cli-linux-LATEST.zip
sudo wget -P /opt/global/tmp \
  https://github.com/sbt/sbt/releases/download/v1.10.7/sbt-1.10.7.zip

sudo unzip /opt/global/tmp/sbt-1.10.7.zip -d /opt/global
sudo unzip /opt/global/tmp/gsf-cli-linux-LATEST.zip -d /opt/global/gsf-cli
```

В изолированном контуре положите заранее полученные архивы в `/opt/global/tmp` и выполните только распаковку.

### Python-окружение

Проверьте `python3 --version`: для поставляемых offline wheels нужен CPython 3.9.2–3.13 на `x86_64`.

```bash
sudo bash /opt/global/gsf-cli/bin/installpkg.sh
sudo bash /opt/global/gsf-cli/bin/initvenv.sh
sudo chown -R "$GSF_BUILD_USER:$GSF_BUILD_GROUP" /opt/global
```

`installpkg.sh` устанавливает `python3-venv` через `apt`; если пакет уже установлен, этот вызов можно пропустить. В
изолированной сети пакет должен поступать из внутреннего источника.

`initvenv.sh` создаёт окружение в `gsf-cli/python` и устанавливает зависимости из `requirements.txt`. Если в
дистрибутиве есть каталог `wheels`, установка выполняется с `--no-index`. Без него потребуется доступ к настроенному
Python package index.

Linux-дистрибутив содержит wheels для CPython 3.9.2–3.13 на `x86_64`, включая Python 3.13 из Debian 13. Эта поставка не
означает поддержку любой архитектуры или любого Linux-дистрибутива.

Проверьте права от имени пользователя сборки:

```bash
sudo -u "$GSF_BUILD_USER" -H test -x /opt/global/gsf-cli/manage.sh
sudo -u "$GSF_BUILD_USER" -H test -x /opt/global/gsf-cli/config.sh
sudo -u "$GSF_BUILD_USER" -H test -x /opt/global/sbt/bin/sbt
sudo -u "$GSF_BUILD_USER" -H test -f /opt/global/sbt/bin/sbt-launch.jar
sudo -u "$GSF_BUILD_USER" -H test -x /usr/lib/jvm/bellsoft-java21-amd64/bin/java
sudo -u "$GSF_BUILD_USER" -H test -x /usr/lib/jvm/bellsoft-java21-amd64/bin/javac
sudo -u "$GSF_BUILD_USER" -H mkdir -p /opt/global/gsf-cli/workspace
sudo -u "$GSF_BUILD_USER" -H touch /opt/global/gsf-cli/workspace/.write-test
sudo -u "$GSF_BUILD_USER" -H rm /opt/global/gsf-cli/workspace/.write-test
```

Если архив распаковался без права запуска `.sh`, восстановите его для скриптов GSF CLI и SBT:

```bash
sudo find /opt/global/gsf-cli -type f -name '*.sh' -exec chmod u+x {} +
sudo chmod u+x /opt/global/sbt/bin/sbt
```

## Окружение пользователя сборки

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

```bash
sudo -u "$GSF_BUILD_USER" -H bash
```

Все дальнейшие команды до раздела pipeline выполняются в этой консоли. Проверьте пользователя и задайте пути:

```bash
whoami
printf '%s\n' "$HOME"
export JAVA_HOME="/usr/lib/jvm/bellsoft-java21-amd64"
export PATH="/opt/global/sbt/bin:$JAVA_HOME/bin:$PATH"
cd /opt/global/gsf-cli
./manage.sh version
java -version
javac -version
```

Пути из `config.json` будут применяться к командам SBT, запущенным GSF CLI. Для ручного вызова `sbt` из консоли
переменные `JAVA_HOME` и `PATH` также должны указывать на выбранные инструменты.

### Java и Python: доверие к сертификатам

Если репозитории используют обычную доверенную TLS-цепочку, переходите к регистрации ключа. Для корпоративного CA
настройте ещё два потребителя сертификатов.

В хранилище **используемой JDK 21** импортируйте CA от пользователя с правом записи в JDK. Если у пользователя агента
нет `sudo`, эти команды выполняет администратор в отдельной консоли с тем же путём JDK:

```bash
sudo /usr/lib/jvm/bellsoft-java21-amd64/bin/keytool -import -trustcacerts \
  -keystore /usr/lib/jvm/bellsoft-java21-amd64/lib/security/cacerts \
  -storepass changeit -alias company-ca -file /path/to/company-ca.crt

/usr/lib/jvm/bellsoft-java21-amd64/bin/keytool -list \
  -keystore /usr/lib/jvm/bellsoft-java21-amd64/lib/security/cacerts \
  -storepass changeit | grep company-ca
```

`company-ca` — уникальный alias. `changeit` — стандартный пароль `cacerts`; если он изменён, используйте действующий.
Для каждого дополнительного CA выберите отдельный alias.

Часть артефактов GSF CLI скачивает через Python Requests. Java truststore не используется этими запросами. Если
корпоративных CA нет в наборе, доступном Requests, укажите читаемый пользователем сборки PEM-bundle:

```bash
export REQUESTS_CA_BUNDLE="/path/to/company-ca-bundle.pem"
test -r "$REQUESTS_CA_BUNDLE"
```

Bundle заменяет стандартный набор Requests и должен содержать все корневые и промежуточные CA для используемых
HTTPS-сервисов. Вместо файла можно указать каталог сертификатов, предварительно обработанный
`openssl rehash <path-to-ca-directory>`.

Системные сертификаты, Java `cacerts` и `REQUESTS_CA_BUNDLE` решают разные части задачи. Настройка только одного из них
не гарантирует доступ всем инструментам сборки.

## Мастер-ключ и учётные данные

### Регистрация ключа

Из `/opt/global/gsf-cli`, от пользователя сборки:

```bash
./config.sh register_private_key -c /opt/global
test -f /opt/global/.gsf-cli.priv
```

`-c` получает существующий каталог. Команда создаёт в нём `.gsf-cli.priv` или использует уже лежащий там ключ. Путь
записывается в `workspace/store.json`. Если GSF CLI уже загрузил зарегистрированный ключ, повторная команда не меняет
его расположение.

Мастер-ключ нужен для шифрования паролей. Не удаляйте его при обновлении CLI: сохранённые credentials без него не
расшифровать.

### Доступ к Git

Перед первым добавлением или обновлением проекта сохраните учётные данные для базового URL Git-сервера:

```bash
read -rp "Git URL: " GSF_GIT_URL
read -rp "Git login: " GSF_GIT_LOGIN
read -rsp "Git token: " GSF_GIT_TOKEN
printf '\n'
printf '%s' "$GSF_GIT_TOKEN" | ./credential_manager.sh set \
  --url "$GSF_GIT_URL" --login "$GSF_GIT_LOGIN" --password-stdin
unset GSF_GIT_URL GSF_GIT_LOGIN GSF_GIT_TOKEN
```

Для `https://git.example.org/group/project.git` базовый URL — `https://git.example.org`. Одна запись действует для всех
вложенных путей с теми же протоколом, хостом и портом. Для другого Git-хоста добавьте отдельную запись. Учётной записи
нужны права чтения конфигурационного проекта, Git-модулей и SBT-плагина, если он тоже загружается из Git.

В интерактивном режиме публичный Git без авторизации позволяет пропустить этот шаг. Без сохранённой записи стандартный
запрос Git также остаётся доступен.

```{attention}
В текущем headless-режиме GSF CLI требует сохранённую Git-запись даже для публичного URL. Без неё команда завершится ошибкой до обращения к серверу. Поэтому для headless добавьте действующие учётные данные всех Git-хостов. Анонимная headless-сборка публичного Git без таких записей текущим кодом не обеспечена.
```

Если для URL есть запись в хранилище GSF CLI, он автоматически подключает свой Git helper в обоих режимах. Для этого
вызова Git остальные helpers отключаются. Не записывайте пароль в `bin/credential_manager_git.sh`. После смены токена
повторите `set` для того же URL.

### Когда нужен credential.helper store

GSF CLI не выполняет `git config --global credential.helper store`. Если вы сохранили данные через
`credential_manager.sh set`, вручную включать `store` для вызовов Git из GSF CLI не нужно.

Эта настройка пригодится для обычного Git, если он каждый раз спрашивает логин и пароль. В интерактивном режиме без
подходящей записи в хранилище GSF CLI также вызывает обычный Git, и тот использует свои настройки. Чтобы включить
сохранение данных для текущего пользователя Linux, выполните:

```bash
git config --global credential.helper store
```

После этого повторите Git-операцию и введите логин и токен. Команда только включает helper; сами данные сохраняются
после успешной авторизации. `store` хранит их на диске открытым текстом, обычно в `~/.git-credentials`.
Подробнее — [документация git-credential-store](https://git-scm.com/docs/git-credential-store).

Настройка `store` не создаёт запись в GSF CLI и не заменяет её в headless. Если CLI сообщает «Не заданы учетные данные»,
используйте `credential_manager.sh set`, как показано выше.

### Доступ к артефактам

Если GlobalServer доступны только после авторизации, добавьте запись для их хоста:

```bash
read -rp "Repository URL: " GSF_REPO_URL
read -rp "Repository login: " GSF_REPO_LOGIN
read -rsp "Repository token: " GSF_REPO_TOKEN
printf '\n'
printf '%s' "$GSF_REPO_TOKEN" | ./credential_manager.sh set \
  --url "$GSF_REPO_URL" --login "$GSF_REPO_LOGIN" --password-stdin
unset GSF_REPO_URL GSF_REPO_LOGIN GSF_REPO_TOKEN

./credential_manager.sh show
stat -c '%a %U:%G %n' /opt/global/.gsf-cli.priv workspace/store.json
```

Для ключа и `store.json` ожидаются права `600` и владелец — пользователь сборки. `show` отображает пароли как
`********`. Параметр `--reveal` открывает их; в CI и логах не используйте его.

Повторите настройку для каждого хоста с отдельным доступом. После переноса установки сохраните пару «ключ + store.json»
и исправьте пути либо заново зарегистрируйте ключ и учётные данные. Сам по себе перенос runner не переносит эти файлы.

## Закрытые репозитории SBT

Для публичных источников без авторизации и без принудительного прокси этот раздел можно пропустить. Для закрытых
репозиториев создайте оба файла `.credentials`, задайте `SBT_CREDENTIALS` и **все три** параметра JVM ниже. Это
требуется и при ручном запуске, и в CI.

### Адреса репозиториев

```bash
mkdir -p "$HOME/.sbt" "$HOME/.ivy2"
nano "$HOME/.sbt/repositories"
```

Содержимое:

```text
[repositories]
local
repository-1: https://repository.example.org/path/to/repository-1/
repository-2: https://repository.example.org/path/to/repository-2/
```

`local` — локальный Ivy-репозиторий `~/.ivy2/local`. Имена `repository-1` и `repository-2` произвольные и должны быть
уникальны. Добавьте строку для каждого источника. Один групповой URL подходит, если он предоставляет все необходимые
артефакты в нужном формате.

Для источника по HTTP, если он используется в вашем контуре, строка выглядит так:

```text
repository-http: http://repository.example.org/path/to/repository/, allowInsecureProtocol
```

`allowInsecureProtocol` разрешает HTTP, но не отключает проверку HTTPS-сертификата. Для HTTPS с корпоративным CA
настройте truststore, как описано выше. Формат Ivy-репозитория при необходимости включает шаблон размещения артефактов;
его берите из конфигурации вашего менеджера репозиториев.
Подробнее — [Proxy Repositories в SBT](https://www.scala-sbt.org/1.x/docs/Proxy-Repositories.html).

Launcher читает `repositories` до загрузки прикладного `build.sbt`. В изолированном контуре убедитесь, что внутренние
источники содержат `org.scala-sbt:sbt` и все необходимые зависимости; кеш другой машины не заменяет их подготовку.

### Две копии credentials

```bash
nano "$HOME/.sbt/.credentials"
```

Запишите:

```properties
realm=repository-realm
host=repository.example.org
user=build-user
password=very-secret-password
```

Замените пример действующими данными. `realm` и `host` должны соответствовать ответу сервера. Пример рассчитан на один
hostname, realm и учётную запись для всех перечисленных SBT-репозиториев.

```bash
chmod 600 "$HOME/.sbt/.credentials"
install -m 600 "$HOME/.sbt/.credentials" "$HOME/.ivy2/.credentials"
```

Основной файл — `~/.sbt/.credentials`. Копия `~/.ivy2/.credentials` обязательна для компонентов цепочки сборки,
использующих путь Ivy. После смены пароля или токена обновите оба файла.

### Переменные запуска

```bash
export SBT_CREDENTIALS="$HOME/.sbt/.credentials"
export JAVA_TOOL_OPTIONS="${JAVA_TOOL_OPTIONS:+$JAVA_TOOL_OPTIONS }\
-Dsbt.boot.credentials=$SBT_CREDENTIALS \
-Dsbt.repository.config=$HOME/.sbt/repositories \
-Dsbt.override.build.repos=true"
```

`sbt.boot.credentials` передаёт launcher файл доступа, `sbt.repository.config` задаёт список источников, а
`sbt.override.build.repos=true` принудительно использует этот список вместо репозиториев, объявленных сборкой.

Выполняйте блок в каждой новой консоли до первого запуска GSF CLI/SBT или добавьте его в профиль пользователя сборки.
Точно так же сохраняйте нужные `JAVA_HOME`, `PATH` и `REQUESTS_CA_BUNDLE`. При запуске через CI переменные задаются
внутри job — примеры приведены ниже.

Скрипты GSF CLI автоматически подставляют только `SBT_CREDENTIALS`, если переменная не задана и основной файл
существует. Три параметра JVM нужно передать явно. Наличие файлов на диске не заменяет этот шаг.

GSF credential manager и Git helper не передают пароль launcher. При запуске от `root` или другого пользователя файлы в
домашнем каталоге пользователя сборки также не будут найдены автоматически.

## Подготовка проекта

### Через config.json: без IDEA и для CI

Создайте `/opt/global/builds/config.json` от пользователя сборки:

```bash
nano /opt/global/builds/config.json
```

```json
{
  "sbt_home": "/opt/global/sbt",
  "concurrent_module_updates": 1,
  "projects": [
    {
      "name": "main",
      "project_source": "https://git.example.org/group/configuration-project.git",
      "project_branch": "main",
      "jdk_home": "/usr/lib/jvm/bellsoft-java21-amd64",
      "build_system": "sbt",
      "publish_type": "SNAPSHOT"
    }
  ]
}
```

Замените URL и ветку реальными значениями. `concurrent_module_updates: 1` включает последовательную загрузку модулей; по
умолчанию допускается до 20 одновременных обновлений.

```{attention}
`load_config` приводит список проектов GSF CLI к списку из файла. Уже зарегистрированные проекты, которых в нём нет, удаляются вместе с исходниками, дистрибутивом, ярлыками, окружением IDEA и служебным кешем. Перед загрузкой включите в файл все проекты, которые должны остаться в этой установке.
```

```bash
python3 -m json.tool /opt/global/builds/config.json >/dev/null
./config.sh enable_headless
./config.sh load_config -f /opt/global/builds/config.json
```

`load_config` регистрирует настройки, но не клонирует исходники и не делает проект активным. Дальше используйте
`-p main`. Первое клонирование произойдёт при `refresh`.

Headless запрещает запросы пользователю. Если не хватает настройки или доступа, команда завершится ошибкой. Режим
сохраняется для всей установки GSF CLI. Для возврата к диалогам:

```bash
./config.sh disable_headless
```

Для CI оставьте headless включённым. Если вам нужны диалоги, но не нужна IDEA, можно загрузить `config.json`, отключить
headless и выполнять те же команды `refresh` и `build`.

### Через мастер: рабочее место разработчика

В этом варианте `config.json` не требуется. Установите IntelliJ IDEA с плагином Scala. Закройте открытую IDEA в общем
окружении перед добавлением проекта. Для работы с IDEA используйте скрипты из `gsf-cli/links` готового дистрибутива.
Запускайте их из терминала с настроенными выше переменными, чтобы IDEA и SBT получили доступ к закрытым репозиториям.

```bash
cd /opt/global/gsf-cli
./config.sh disable_headless
./links/add_project.sh
```

Укажите путь SBT `/opt/global/sbt`, имя проекта, Git URL и существующую ветку, систему сборки `sbt`, JDK 21 и вариант
портов. Мастер предложит подготовить проект, запросит путь IDEA и недостающие данные доступа. Для закрытых источников
файлы SBT и переменные уже должны быть настроены.

Следуйте запросам импорта проекта в IDEA. После импорта дождитесь его завершения, закройте IDEA и продолжите
конфигурацию. Подтвердите активацию проекта, чтобы следующие ярлыки работали с ним. Позже выбрать активный проект можно
через `./links/activate_project.sh`.

Результат мастера — исходники в `workspace/sources/<project_name>/application`, сервер в
`workspace/dists/<project_name>/Global3se` и ярлыки в `workspace/links/<project_name>`. Если подготовку пропустили,
позднее выполните `./manage.sh -p <project_name> prepare_project`.

Для повседневной работы используйте общие ярлыки из `/opt/global/gsf-cli/links`:

| Скрипт                             | Действие                              |
|------------------------------------|---------------------------------------|
| `active_project_refresh.sh`        | Обновить проект и зависимости         |
| `active_project_start_idea.sh`     | Открыть IDEA для активного проекта    |
| `active_project_sbt.sh`            | Открыть консоль SBT активного проекта |
| `active_project_configure_idea.sh` | Обновить конфигурацию IDEA            |

Открывайте IDEA через `./links/active_project_start_idea.sh`: скрипт задаёт окружение проекта перед запуском среды.
В общем окружении допускается одна IDEA. Если нужны несколько проектов одновременно, используйте
`./links/start_sep_idea.sh` и выберите проект в диалоге.

## Первая сборка и результат

Ниже имя проекта — `main`. Если вы выбрали другое, замените его во всех командах. Команды выполняются от того же
пользователя, с теми же переменными доступа.

При работе через мастер и IDEA обновите активный проект ярлыком из каталога GSF CLI:

```bash
cd /opt/global/gsf-cli
./links/active_project_refresh.sh
```

Для маршрута через `config.json` выполните обновление командой:

```bash
cd /opt/global/gsf-cli
./manage.sh -p main refresh
```

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

После успешного обновления соберите JAR-файлы. Общего ярлыка для `build` в поставке нет, поэтому в обоих вариантах
используется команда:

```bash
./manage.sh -p main build --build-appsrc --skip-publication
```

В `workspace/sources/main/application/build/publish` появятся:

```text
applib/
  *.jar
  metadata.yaml
appsrc/
  *-sources.jar
```

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

```bash
find workspace/sources/main/application/build/publish/applib -type f -name '*.jar'
find workspace/sources/main/application/build/publish/appsrc -type f -name '*-sources.jar'
```

`--build-appsrc` выполняет `publishSrcFolder` после `publishLibFolder` и проверяет наличие обоих видов JAR. Флаг
поддерживается только для SBT. Для заполнения результата выполняется чистая SBT-сборка.

Чтобы собрать в другой каталог, задайте общий корень:

```bash
./manage.sh -p main build --build-appsrc --skip-publication \
  --publish-path /opt/global/builds/publish/main
```

Результат будет в `<root>/applib` и `<root>/appsrc`. Не используйте один корень для нескольких проектов: выполняйте
отдельную команду с `-p` и отдельным путём. Относительный `--publish-path` считается от каталога `application`.

`--skip-publication` оставляет результат локально. Без него `build` вызывает публикацию, включая `sbt publish`. Путь
`--publish-path` не меняет адрес Maven-репозитория. При включённой публикации исходников в Maven отправляются отдельные
`*-sources.jar`, а не каталог `appsrc` одним артефактом.

GSF CLI не создаёт `applib.zip` и `appsrc.zip`. Если следующему этапу нужны ZIP, упакуйте каталоги отдельно в CI/CD или
передайте их nscli.

## Сборка в GitLab CI

Этот раздел выполняется после настройки установки GSF CLI для пользователя `gitlab-runner`. Runner использует один
постоянный workspace; параллельные GSF-сборки от этого пользователя запрещены. Для других задач выделите другую
установку и пользователя.

### Переменные проекта

В `Settings` → `CI/CD` → `Variables` для закрытых SBT-репозиториев создайте:

| Переменная                  | Содержимое                                                     | Настройки           |
|-----------------------------|----------------------------------------------------------------|---------------------|
| `GSF_SBT_REPOSITORIES_FILE` | Файл `repositories` из раздела выше                            | `File`, `Protected` |
| `GSF_SBT_CREDENTIALS_FILE`  | Файл `.credentials` с действующими realm, host, user, password | `File`, `Protected` |

Для File-переменной GitLab создаёт временный файл, а в переменную окружения передаёт путь к нему. Пример предполагает
один hostname, realm и одну учётную запись для SBT-репозиториев.

Защитите ветку или Git tag, из которых запускается сборка. Иначе Protected-переменные будут недоступны. Не храните
токены в `.gitlab-ci.yml`, не включайте `CI_DEBUG_TRACE` и не выводите `env`/`printenv` в job с секретами.
Подробнее — [переменные GitLab CI/CD](https://docs.gitlab.com/ci/variables/).

### Pipeline

Создайте `.gitlab-ci.yml` в корне проекта. Замените tag и имя GSF-проекта своими значениями; Git credentials в headless
всё равно должны быть сохранены в GSF CLI.

```yaml
stages:
  - build

variables:
  GSF_CONFIG_FILE: "/opt/global/builds/config.json"
  GSF_PROJECT_NAME: "main"

gsf_build:
  stage: build
  tags:
    - gsf-build
  timeout: 4h
  before_script:
    - |
      set -e
      : "${GSF_PROJECT_NAME:?Не задано имя проекта}"
      test -r "$GSF_CONFIG_FILE"
      test -x /opt/global/gsf-cli/config.sh
      test -x /opt/global/gsf-cli/manage.sh
      umask 077
      case "$GSF_REPOSITORY_MODE" in
        internal)
          : "${GSF_SBT_REPOSITORIES_FILE:?Не задан файл репозиториев}"
          : "${GSF_SBT_CREDENTIALS_FILE:?Не задан файл credentials}"
          test -s "$GSF_SBT_REPOSITORIES_FILE"
          test -s "$GSF_SBT_CREDENTIALS_FILE"
          install -d -m 700 "$HOME/.sbt" "$HOME/.ivy2"
          install -m 600 "$GSF_SBT_REPOSITORIES_FILE" "$HOME/.sbt/repositories"
          install -m 600 "$GSF_SBT_CREDENTIALS_FILE" "$HOME/.sbt/.credentials"
          install -m 600 "$HOME/.sbt/.credentials" "$HOME/.ivy2/.credentials"
          export SBT_CREDENTIALS="$HOME/.sbt/.credentials"
          export JAVA_TOOL_OPTIONS="${JAVA_TOOL_OPTIONS:+$JAVA_TOOL_OPTIONS }-Dsbt.boot.credentials=$SBT_CREDENTIALS -Dsbt.repository.config=$HOME/.sbt/repositories -Dsbt.override.build.repos=true"
          ;;
        public) ;;
        *) echo "Неизвестный GSF_REPOSITORY_MODE" >&2; exit 1 ;;
      esac
  script:
    - /opt/global/gsf-cli/config.sh enable_headless
    - /opt/global/gsf-cli/config.sh load_config -f "$GSF_CONFIG_FILE"
    - /opt/global/gsf-cli/manage.sh -p "$GSF_PROJECT_NAME" refresh || /opt/global/gsf-cli/manage.sh -p "$GSF_PROJECT_NAME" refresh
    - /opt/global/gsf-cli/manage.sh -p "$GSF_PROJECT_NAME" build --build-appsrc --skip-publication --publish-path "$CI_PROJECT_DIR/publish"
  after_script:
    - rm -f "$HOME/.sbt/repositories" "$HOME/.sbt/.credentials" "$HOME/.ivy2/.credentials"
  artifacts:
    paths:
      - publish/applib/
      - publish/appsrc/
```

Каждый job, использующий закрытые репозитории, должен получить обе копии credentials, `SBT_CREDENTIALS` и три параметра
JVM **до первой команды GSF CLI или SBT**. Включение headless и загрузка `config.json` повторяются при каждой сборке,
поэтому изменения конфигурации применяются автоматически. Файл должен сохранять полный список нужных проектов.

### CA для Python в pipeline

Если требуется `REQUESTS_CA_BUNDLE`, добавьте в существующий блок `variables` путь к читаемому пользователем
`gitlab-runner` bundle:

```yaml
variables:
  REQUESTS_CA_BUNDLE: "/path/to/company-ca-bundle.pem"
```

Либо создайте File-переменную `GSF_CA_BUNDLE_FILE` и добавьте в `before_script`:

```yaml
    - export REQUESTS_CA_BUNDLE="$GSF_CA_BUNDLE_FILE"
    - test -r "$REQUESTS_CA_BUNDLE"
```

Bundle должен содержать все необходимые CA. Java `cacerts` остаётся отдельной настройкой. Каталог вместо PEM-файла
допустим после `openssl rehash`.

Для временного аварийного запуска при неустранённой TLS-ошибке можно добавить `GSF_CLI_INSECURE_TLS: "true"` в
`variables`. Это отключает проверку HTTPS в запросах GSF CLI к артефактам; удалите переменную после восстановления
цепочки. Git и Java она не настраивает.

### Перед первым pipeline

Проверьте runner `online`, tag, защиту ветки и доступность Protected-переменных. У пользователя `gitlab-runner` должны
читаться ключ, `store.json`, `config.json`, оба исполняемых файла JDK 21 и SBT.

Убедитесь, что Git-записи покрывают конфигурационный проект, модули и плагин, а доступ к артефактам — SBT-репозитории и
GlobalServer. Проверьте URL и realm в `application/project/repositories/default.yaml`, источники внутри `project.yaml`.

На хосте проверьте ресурсы:

```bash
df -h /opt/global
free -h
```

Проверьте YAML через `CI/CD` → `Editor` → `Validate`, затем запустите pipeline. После успешной сборки в job artifacts
должны находиться `publish/applib` и `publish/appsrc` с JAR-файлами.

## Если сборка не проходит

Сначала посмотрите `workspace/logs/cmd_error_log.txt` и ежедневный `YYYY-MM-DD.log`. В Linux для `manage.sh` можно
временно вернуть stderr в терминал:

```bash
NO_STDERR_REDIRECT=1 ./manage.sh -p main refresh
```

| Ошибка                       | Что проверить                                                                                                             |
|------------------------------|---------------------------------------------------------------------------------------------------------------------------|
| `Не заданы учетные данные`   | Того ли пользователя запустили; есть ли ключ и запись нужного Git URL в GSF CLI                                           |
| `unauthorized`, HTTP 401/403 | Права учётной записи, realm/host, обе копии credentials и переменные JVM; для загрузок Python — запись credential manager |
| `not found`, HTTP 404        | Наличие артефакта во внутреннем репозитории, путь и доступность upstream у прокси                                         |
| Ошибка сертификата Java      | `cacerts` той JDK 21, которой выполняется сборка                                                                          |
| Ошибка сертификата Requests  | `REQUESTS_CA_BUNDLE` в том же процессе и его полный набор CA                                                              |
| Диалог запрещён в headless   | Не задана настройка, которую команда пытается спросить; интерактивный мастер в CI запускать не нужно                      |
| Нет `applib`/`appsrc`        | Система сборки SBT, параметр `--build-appsrc`, значение `isPublishSrcFolder`, журнал SBT и выбранный корень публикации    |

### Подробный лог SBT: publishDevDependencies

Если ошибка возникает при подготовке dev-зависимостей, повторите SBT-задачу `publishDevDependencies` с параметром
`-debug`. Он включает подробный вывод SBT. У команды `manage build` такого параметра нет.

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

```bash
cd /opt/global/gsf-cli/workspace/links/main
./sbt.sh -debug "publishDevDependencies"
```

`main` замените именем своего проекта. Этот `sbt.sh` находится в `workspace/links/<project_name>` и передаёт аргументы
в SBT, предварительно задав окружение проекта. Общий ярлык `links/active_project_sbt.sh` используется для обычного
открытия консоли SBT.

Если проект настроен через `config.json` и ярлыков нет, используйте созданный при подготовке сборки `set_env.sh`:

```bash
cd /opt/global/gsf-cli/workspace/sources/main/application
source build/scripts/set_env.sh
sbt -debug "publishDevDependencies"
```

В обоих случаях запуск выполняется под пользователем сборки. Для закрытых репозиториев по-прежнему нужны обе копии
`.credentials`, `SBT_CREDENTIALS` и все три JVM-параметра. Скрипты не задают эти параметры за вас.

Смотрите первую ошибку и сообщения перед ней: по подробному выводу проще понять, на каком репозитории или зависимости
останавливается задача. Это повтор только `publishDevDependencies`; он не собирает `applib` и `appsrc` целиком.
После исправления ошибки вернитесь к обычным `refresh` и `build`. Возможности подробного лога описаны в
[справке SBT](https://www.scala-sbt.org/1.x/docs/Howto-Logging.html).

### Проверка источников SBT

От пользователя сборки, с настроенными переменными, выполните:

```bash
cd /opt/global/gsf-cli/workspace/sources/main/application
source build/scripts/set_env.sh
sbt -batch sbtVersion
sbt -batch "show externalResolvers"
```

Проверьте, что зависимости загружаются из указанных внутренних источников. В изолированном режиме публичные источники не
должны использоваться вопреки `sbt.override.build.repos=true`.

[ПРОВЕРИТЬ: DOC-007 — сверить фактический вывод
`externalResolvers` с версией SBT/плагина. В исходной главе обещалось буквальное присутствие всех строк
`repositories` в этом выводе; одного отображаемого списка недостаточно для проверки сетевых обращений.]

### Проверка с пустыми кешами

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

`manage.sh clean` удаляет служебный `cache.json` GSF CLI, а `sbt clean` — результаты компиляции. Для проверки загрузки
зависимостей также очищаются кеши SBT, Ivy и Coursier. `~/.ivy2/local` содержит локально опубликованные артефакты: после
удаления они тоже должны быть восстановлены. Файлы `.credentials` и `repositories` оставьте на месте.

```bash
cd /opt/global/gsf-cli
./manage.sh -p main clean
cd workspace/sources/main/application
source build/scripts/set_env.sh
sbt -batch clean

rm -rf \
  "$HOME/.sbt/boot" \
  "$HOME/.ivy2/cache" \
  "$HOME/.ivy2/local" \
  "$HOME/.cache/coursier" \
  "$HOME/.coursier/cache"

cd /opt/global/gsf-cli
./manage.sh -p main refresh || ./manage.sh -p main refresh
```

Продолжайте только после успешного `refresh`:

```bash
./manage.sh -p main build --build-appsrc --skip-publication
cd workspace/sources/main/application
source build/scripts/set_env.sh
sbt -batch "publishDevDependencies"
```

Последняя команда сохранена из исходного сценария восстановления зависимостей. `refresh` уже вызывает эту задачу;
дополнительный запуск не заменяет проверку его результата. Повторно вызывать `publishLibFolder` и `publishSrcFolder` не
нужно — оба каталога сформированы командой `build --build-appsrc`.

### Временное отключение проверки TLS

Если корректную TLS-цепочку нельзя восстановить до аварийного запуска, для одной команды:

```bash
GSF_CLI_INSECURE_TLS=true ./manage.sh -p main build --build-appsrc --skip-publication
```

Для всех команд текущей консоли:

```bash
export GSF_CLI_INSECURE_TLS=true
```

После исправления сертификатов:

```bash
unset GSF_CLI_INSECURE_TLS
```

Только явное `true` без учёта регистра и окружающих пробелов отключает проверку. GSF CLI выводит предупреждение при
первом таком HTTPS-запросе к артефактам. Соединение становится уязвимым для MITM-атак. Переменная относится к запросам
Python за артефактами; ошибки Git или SBT решаются настройкой их сертификатов.
