12 KiB
CI: автоматическая сборка прошивок на своём Gitea
Схема: Gitea (systemd) → Gitea Runner на том же сервере → PlatformIO → артефакты out/.
Сборка идёт через build.sh из репозитория, то есть тем же путём, что и локально
(версия прошивки, merge-bin для ESP32, .uf2 для nRF52), — логика не дублируется.
Что где лежит
| Файл | Назначение |
|---|---|
.gitea/workflows/firmware.yml |
workflow: клон, кэш, сборка, артефакты (запуск только вручную) |
ci/build-firmwares.sh |
сборка выбранного набора env (годится и для запуска руками) |
ci/build-local.ps1 |
сборка на своей машине под Windows и публикация релиза в Gitea |
ci/act_runner/gitea-runner.service |
systemd-юнит раннера |
ci/act_runner/config.yaml |
конфиг раннера: ёмкость, таймаут, кэш |
ci/act_runner/docker-compose.yml |
альтернатива: раннер в Docker |
1. Что должно быть на сервере
apt-get update
apt-get install -y git python3 python3-venv python3-pip curl ca-certificates build-essential
python3-pipобязателен. Без негоpython3 -m venvсоздаёт окружение без pip (вbin/только симлинки python), и шаг установки PlatformIO падает сNo such file or directory. Если venv уже создан без pip, workflow пересоздаст его сам.- Свободного места нужно 5 ГБ: тулчейны PlatformIO занимают ~800 МБ на одну платформу, а при сборке и ESP32, и nRF52 — заметно больше.
- Swap желателен. На VPS с 2 ГБ RAM линковка ESP32 упирается в память:
fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile echo '/swapfile none swap sw 0 0' >> /etc/fstab
2. Включение Actions в Gitea
В /etc/gitea/app.ini (владелец git:git, права 600):
[actions]
ENABLED = true
# Gitea 1.27 принимает значение github или gitea, но НЕ ссылку:
# "https://github.com" даёт ошибку DEFAULT_ACTIONS_URL does not support ...
DEFAULT_ACTIONS_URL = github
# Искать workflow только в .gitea/workflows: иначе Gitea подхватит upstream-файлы
# из .github/workflows, а у них триггер push — на каждый push запустится сборка
# сотен env и деплой Pages.
WORKFLOW_DIRS = .gitea/workflows
После правки: systemctl restart gitea. Перед изменением делайте копию конфига —
cp -a /etc/gitea/app.ini /etc/gitea/app.ini.bak.
3. Установка раннера (systemd)
useradd --create-home --home-dir /var/lib/act-runner --shell /bin/bash act-runner
# актуальный релиз: https://gitea.com/gitea/runner/releases
# (проект переименован: раньше act_runner, теперь Gitea Runner; на момент настройки — v5.0.0)
curl -fsSL -o /usr/local/bin/gitea-runner \
https://gitea.com/gitea/runner/releases/download/v5.0.0/gitea-runner-5.0.0-linux-amd64
chmod +x /usr/local/bin/gitea-runner
gitea-runner --version
install -d -o act-runner -g act-runner /var/lib/act-runner
cp ci/act_runner/config.yaml /var/lib/act-runner/config.yaml
chown act-runner:act-runner /var/lib/act-runner/config.yaml
Токен — обязательно со scope репозитория. Без --scope раннер становится глобальным
и начинает брать задачи всех репозиториев инстанса (в том числе чужие stale-bot):
# от имени пользователя Gitea (CLI не работает под root)
su - git -c "gitea actions generate-runner-token -c /etc/gitea/app.ini --scope shade/MeshCore" \
> /var/lib/act-runner/reg-token
chown act-runner:act-runner /var/lib/act-runner/reg-token && chmod 600 /var/lib/act-runner/reg-token
su - act-runner -c "/usr/local/bin/gitea-runner register --no-interactive \
--instance https://git.meshinfo.ru \
--token-file /var/lib/act-runner/reg-token \
--name meshcore-builder \
--labels ubuntu-latest:host \
-c /var/lib/act-runner/config.yaml"
rm -f /var/lib/act-runner/reg-token # токен больше не нужен
Метка ubuntu-latest:host означает, что шаги выполняются прямо на сервере — без Docker,
но зависимости ставятся в систему. Метка должна совпадать с runs-on в workflow.
cp ci/act_runner/gitea-runner.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now gitea-runner
systemctl status gitea-runner
journalctl -u gitea-runner -f # ждём "declare successfully"
Альтернатива: раннер в Docker
cd ci/act_runner && cp .env.example .env && nano .env && docker compose up -d
В свежих версиях образ называется gitea/runner (раньше gitea/act_runner).
4. Запуск сборки
Workflow запускается только вручную — автоматических сборок по push и тегам нет (прошивки собираются локально и публикуются релизом, см. следующий раздел).
- Вручную в Gitea — Actions → Firmware build → Run workflow (
targets,match,version), либо через API:curl -X POST -H "Authorization: token $GITEA_TOKEN" -H 'Content-Type: application/json' \ -d '{"ref":"main","inputs":{"targets":"Heltec_t096_companion_radio_ble","version":"ci"}}' \ https://git.meshinfo.ru/api/v1/repos/shade/MeshCore/actions/workflows/firmware.yml/dispatches - Локально — тем же скриптом, что запускает CI:
FIRMWARE_VERSION=v1.0.0 bash ci/build-firmwares.sh MATCH=promicro bash ci/build-firmwares.sh TARGETS="Heltec_t096_companion_radio_ble" bash ci/build-firmwares.sh
Готовые файлы попадают в out/ (каталог в .gitignore); при запуске в Gitea они
выгружаются артефактом firmware.
Локально на Windows, без нагрузки на сервер
На этом VPS всего 1 CPU, поэтому полный набор прошивок собирается там часами. Разумнее собрать у себя, а на сервер отправить только готовые файлы — релизом Gitea:
# быстрый набор: все env, в имени которых есть подстрока
.\ci\build-local.ps1 -Match promicro -Version v1.0.0
# полный набор из DEFAULT_TARGETS
.\ci\build-local.ps1 -Version v1.0.0
# собрать и опубликовать релиз
$env:GITEA_TOKEN = '<токен с правом write:repository>'
.\ci\build-local.ps1 -Version v1.0.0 -Publish -Public -Tag v1.0.0
# опубликовать уже собранное, без пересборки
.\ci\build-local.ps1 -Version v1.0.0 -SkipBuild -Publish -Public -Tag v1.0.0
Скрипт находит Git Bash и PlatformIO, подставляет рабочий python3 (в Git Bash это
заглушка Microsoft Store — из-за неё build.sh не может определить платформу env),
чистит out/ перед сборкой, а с -Publish создаёт тег и релиз, прикрепляя к нему файлы
(-Public публикует сразу, без флага создаётся черновик). Токен нужен только для
публикации: сама сборка работает без него.
На Linux/macOS то же самое делается напрямую: FIRMWARE_VERSION=v1.0.0 bash ci/build-firmwares.sh.
5. Релизы
Релиз собирается и публикуется локально: ci/build-local.ps1 -Publish -Public -Tag <тег>
(см. выше). Скрипт создаёт тег на текущем коммите, релиз в Gitea и прикрепляет к нему
.bin / .uf2 / .zip; версия -Version попадает в имена файлов
(<env>-v1.0.0-<sha>.uf2). Если тег уже существует, он переиспользуется.
В CI шага публикации нет: сервер только собирает прошивки по ручному запуску и отдаёт их артефактом.
6. Набор прошивок по умолчанию
ci/build-firmwares.sh, массив DEFAULT_TARGETS: T096 (companion, repeater, room server,
sensor), ProMicro (SSD1306, SH1106, repeater, room server), Heltec v3, heltec_v4 TFT,
tracker_v2, T-Deck, T-Echo Card, T114, T190, M9 и три варианта Meshadventurer.
7. Время и ресурсы
- Сервер с 1 CPU / 2 ГБ RAM полный набор собирает долго (первый прогон — часы),
и на время сборки Gitea с Postgres на той же машине заметно тормозят. Поэтому рабочий
вариант — собирать полный набор локально (
ci/build-local.ps1), а в Gitea запускать вручную только те env, которые нужно проверить. - Первая сборка качает тулчейны (nRF52: framework + CMSIS + gcc-arm ≈ 660 МБ;
ESP32 — ещё столько же), дальше работает кэш
~/.platformio. capacity: 1вconfig.yaml— одна сборка за раз.
8. Грабли, на которые уже наступали
| Симптом | Причина и решение |
|---|---|
.../bin/pip: No such file or directory |
venv создан без pip — нет python3-pip; поставить пакет, venv пересоздать |
DEFAULT_ACTIONS_URL does not support "https://github.com" |
в Gitea 1.27 значение — github, не ссылка |
| раннер берёт задачи чужих репозиториев | токен выдан без --scope owner/repo |
registration file not found в цикле рестартов |
раннер запущен до регистрации: сначала register, потом сервис |
E: Could not get lock /var/lib/dpkg/lock-frontend |
работает unattended-upgrades; подождать либо остановить apt-daily.timer |
| на push запускается сборка сотен env | не выставлен WORKFLOW_DIRS = .gitea/workflows |
405 Method Not Allowed при создании тега |
в Gitea тег создаётся через POST /repos/{owner}/{repo}/tags, у /git/refs метода POST нет |
python3: command not found или пустой вывод в Git Bash |
это заглушка Microsoft Store; задайте PYTHON3=/c/.../python.exe (это делает ci/build-local.ps1) |
в out/ оказались файлы только последней платы |
build.sh при каждом запуске делает rm -rf out; ci/build-firmwares.sh копит результат в dist/ и в конце складывает всё в out/ |
| нужно опубликовать уже собранное, без пересборки | ci/build-local.ps1 -SkipBuild -Publish -Tag <тег> |