Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e182038c78 | ||
|
|
d55129160f | ||
|
|
819b140a90 | ||
|
|
bce6eaf59c |
@@ -2,21 +2,11 @@ name: Firmware build
|
|||||||
|
|
||||||
# Сборка прошивок на своём Gitea-раннере (метка ubuntu-latest, см. ci/README.md).
|
# Сборка прошивок на своём Gitea-раннере (метка ubuntu-latest, см. ci/README.md).
|
||||||
#
|
#
|
||||||
# push в main — собрать набор прошивок и выгрузить артефакты
|
# Запуск ТОЛЬКО вручную: Actions → Firmware build → Run workflow, либо через API
|
||||||
# push тега — то же плюс черновик релиза с прикреплёнными файлами
|
# (пример запроса — в ci/README.md). Автоматических сборок по push и тегам нет:
|
||||||
# вручную — можно задать targets / match / version
|
# прошивки собираются локально (ci/build-local.ps1) и публикуются релизом в Gitea.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
- '*-v*'
|
|
||||||
paths-ignore:
|
|
||||||
- '**/*.md'
|
|
||||||
- '.preview/**'
|
|
||||||
- 'ci/**'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
targets:
|
targets:
|
||||||
@@ -32,9 +22,6 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -72,14 +59,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Версия прошивки
|
- name: Версия прошивки
|
||||||
run: |
|
run: |
|
||||||
# тег вида companion-v1.2.3 даёт версию v1.2.3; иначе — значение из запуска
|
VERSION="${{ github.event.inputs.version }}"
|
||||||
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
[[ -z "$VERSION" ]] && VERSION="ci"
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
|
||||||
VERSION="${TAG##*-}"
|
|
||||||
else
|
|
||||||
VERSION="${{ github.event.inputs.version }}"
|
|
||||||
[[ -z "$VERSION" ]] && VERSION="ci"
|
|
||||||
fi
|
|
||||||
echo "FIRMWARE_VERSION=$VERSION" >> "$GITHUB_ENV"
|
echo "FIRMWARE_VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||||
echo "версия: $VERSION"
|
echo "версия: $VERSION"
|
||||||
|
|
||||||
@@ -96,57 +77,3 @@ jobs:
|
|||||||
name: firmware
|
name: firmware
|
||||||
path: out/
|
path: out/
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
|
||||||
release:
|
|
||||||
needs: build
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
steps:
|
|
||||||
- name: Скачивание артефактов
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: firmware
|
|
||||||
path: out
|
|
||||||
|
|
||||||
- name: Черновик релиза
|
|
||||||
env:
|
|
||||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
|
||||||
API="${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
|
||||||
|
|
||||||
if [[ -z "${TOKEN:-}" ]]; then
|
|
||||||
echo "Нет токена для Gitea API (secrets.GITHUB_TOKEN пуст)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "файлы к релизу:"
|
|
||||||
ls -1 out
|
|
||||||
|
|
||||||
# черновик: релиз создаётся, но не публикуется — проверить и нажать Publish
|
|
||||||
RESPONSE=$(curl -fsS -X POST "$API/releases" \
|
|
||||||
-H "Authorization: token $TOKEN" \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"draft\":true,\"prerelease\":false,\"target_commitish\":\"$GITHUB_SHA\"}")
|
|
||||||
|
|
||||||
# id разбираем питоном: в ответе есть и другие поля id (автор, вложения)
|
|
||||||
RELEASE_ID=$(printf '%s' "$RESPONSE" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
|
|
||||||
|
|
||||||
if [[ -z "$RELEASE_ID" ]]; then
|
|
||||||
echo "Не удалось создать релиз для тега $TAG" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "релиз #$RELEASE_ID создан (черновик)"
|
|
||||||
|
|
||||||
for f in out/*; do
|
|
||||||
[[ -f "$f" ]] || continue
|
|
||||||
name="$(basename "$f")"
|
|
||||||
echo " прикрепляю $name"
|
|
||||||
curl -fsS -X POST "$API/releases/$RELEASE_ID/assets?name=$name" \
|
|
||||||
-H "Authorization: token $TOKEN" \
|
|
||||||
-F "attachment=@$f" > /dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "готово: ${{ github.server_url }}/${{ github.repository }}/releases"
|
|
||||||
|
|||||||
@@ -67,14 +67,14 @@ git merge upstream/main # правки для upstream отправляю
|
|||||||
### Сборка и CI
|
### Сборка и CI
|
||||||
|
|
||||||
- **Сборка прошивок на своём Gitea** ([`.gitea/workflows/firmware.yml`](./.gitea/workflows/firmware.yml),
|
- **Сборка прошивок на своём Gitea** ([`.gitea/workflows/firmware.yml`](./.gitea/workflows/firmware.yml),
|
||||||
[`ci/build-firmwares.sh`](./ci/build-firmwares.sh)): workflow собирает набор устройств через
|
[`ci/build-firmwares.sh`](./ci/build-firmwares.sh)): workflow собирает выбранный набор устройств
|
||||||
`build.sh`, поэтому версия, `merge-bin` для ESP32 и `.uf2` для nRF52 получаются такими же,
|
через `build.sh`, поэтому версия, `merge-bin` для ESP32 и `.uf2` для nRF52 получаются такими же,
|
||||||
как при локальной сборке. Готовые файлы выгружаются артефактами, пуш тега создаёт черновик
|
как при локальной сборке. Запускается **только вручную** (Actions → Firmware build → Run workflow);
|
||||||
релиза. Как поднять раннер — [`ci/README.md`](./ci/README.md).
|
автоматических сборок по push и тегам нет. Как поднять раннер — [`ci/README.md`](./ci/README.md).
|
||||||
- **Локальная сборка с публикацией результата** ([`ci/build-local.ps1`](./ci/build-local.ps1)):
|
- **Локальная сборка с публикацией релиза** ([`ci/build-local.ps1`](./ci/build-local.ps1)):
|
||||||
набор прошивок собирается на своей машине (тот же `build.sh` через Git Bash), а готовые
|
набор прошивок собирается на своей машине (тот же `build.sh` через Git Bash), а готовые
|
||||||
файлы прикрепляются к релизу Gitea — сервер только принимает их и не тратит CPU на сборку.
|
файлы прикрепляются к релизу Gitea — сервер только принимает их и не тратит CPU на сборку.
|
||||||
Это основной способ собрать полный набор: на VPS всего одно ядро.
|
Это основной способ выпустить прошивки: на VPS всего одно ядро.
|
||||||
|
|
||||||
### Варианты плат
|
### Варианты плат
|
||||||
|
|
||||||
|
|||||||
+29
-23
@@ -8,8 +8,9 @@
|
|||||||
|
|
||||||
| Файл | Назначение |
|
| Файл | Назначение |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `.gitea/workflows/firmware.yml` | workflow: клон, кэш, сборка, артефакты, черновик релиза по тегу |
|
| `.gitea/workflows/firmware.yml` | workflow: клон, кэш, сборка, артефакты (запуск только вручную) |
|
||||||
| `ci/build-firmwares.sh` | сборка выбранного набора env (годится и для запуска руками) |
|
| `ci/build-firmwares.sh` | сборка выбранного набора env (годится и для запуска руками) |
|
||||||
|
| `ci/build-local.ps1` | сборка на своей машине под Windows и публикация релиза в Gitea |
|
||||||
| `ci/act_runner/gitea-runner.service` | systemd-юнит раннера |
|
| `ci/act_runner/gitea-runner.service` | systemd-юнит раннера |
|
||||||
| `ci/act_runner/config.yaml` | конфиг раннера: ёмкость, таймаут, кэш |
|
| `ci/act_runner/config.yaml` | конфиг раннера: ёмкость, таймаут, кэш |
|
||||||
| `ci/act_runner/docker-compose.yml` | альтернатива: раннер в Docker |
|
| `ci/act_runner/docker-compose.yml` | альтернатива: раннер в Docker |
|
||||||
@@ -108,8 +109,10 @@ cd ci/act_runner && cp .env.example .env && nano .env && docker compose up -d
|
|||||||
|
|
||||||
## 4. Запуск сборки
|
## 4. Запуск сборки
|
||||||
|
|
||||||
- **Автоматически** — push в `main` (кроме изменений в `*.md`, `ci/**`, `.preview/**`).
|
Workflow запускается **только вручную** — автоматических сборок по push и тегам нет
|
||||||
- **Вручную** — Actions → Firmware build → Run workflow (`targets`, `match`, `version`),
|
(прошивки собираются локально и публикуются релизом, см. следующий раздел).
|
||||||
|
|
||||||
|
- **Вручную в Gitea** — Actions → Firmware build → Run workflow (`targets`, `match`, `version`),
|
||||||
либо через API:
|
либо через API:
|
||||||
```bash
|
```bash
|
||||||
curl -X POST -H "Authorization: token $GITEA_TOKEN" -H 'Content-Type: application/json' \
|
curl -X POST -H "Authorization: token $GITEA_TOKEN" -H 'Content-Type: application/json' \
|
||||||
@@ -123,8 +126,8 @@ cd ci/act_runner && cp .env.example .env && nano .env && docker compose up -d
|
|||||||
TARGETS="Heltec_t096_companion_radio_ble" bash ci/build-firmwares.sh
|
TARGETS="Heltec_t096_companion_radio_ble" bash ci/build-firmwares.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Готовые файлы попадают в `out/` (каталог в `.gitignore`) и прикрепляются к запуску
|
Готовые файлы попадают в `out/` (каталог в `.gitignore`); при запуске в Gitea они
|
||||||
workflow как артефакт `firmware`.
|
выгружаются артефактом `firmware`.
|
||||||
|
|
||||||
### Локально на Windows, без нагрузки на сервер
|
### Локально на Windows, без нагрузки на сервер
|
||||||
|
|
||||||
@@ -138,32 +141,31 @@ workflow как артефакт `firmware`.
|
|||||||
# полный набор из DEFAULT_TARGETS
|
# полный набор из DEFAULT_TARGETS
|
||||||
.\ci\build-local.ps1 -Version v1.0.0
|
.\ci\build-local.ps1 -Version v1.0.0
|
||||||
|
|
||||||
# собрать и прикрепить к релизу (черновик; -Public опубликует сразу)
|
# собрать и опубликовать релиз
|
||||||
$env:GITEA_TOKEN = '<токен с правом write:repository>'
|
$env:GITEA_TOKEN = '<токен с правом write:repository>'
|
||||||
.\ci\build-local.ps1 -Version v1.0.0 -Publish -Tag companion-v1.0.0
|
.\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 это
|
Скрипт находит Git Bash и PlatformIO, подставляет рабочий `python3` (в Git Bash это
|
||||||
заглушка Microsoft Store — из-за неё `build.sh` не может определить платформу env),
|
заглушка Microsoft Store — из-за неё `build.sh` не может определить платформу env),
|
||||||
чистит `out/` перед сборкой, а с `-Publish` создаёт тег, черновик релиза и прикрепляет
|
чистит `out/` перед сборкой, а с `-Publish` создаёт тег и релиз, прикрепляя к нему файлы
|
||||||
к нему файлы. Токен нужен только для публикации: сама сборка работает без него.
|
(`-Public` публикует сразу, без флага создаётся черновик). Токен нужен только для
|
||||||
|
публикации: сама сборка работает без него.
|
||||||
|
|
||||||
На Linux/macOS то же самое делается напрямую: `FIRMWARE_VERSION=v1.0.0 bash ci/build-firmwares.sh`.
|
На Linux/macOS то же самое делается напрямую: `FIRMWARE_VERSION=v1.0.0 bash ci/build-firmwares.sh`.
|
||||||
|
|
||||||
## 5. Релизы по тегу
|
## 5. Релизы
|
||||||
|
|
||||||
Пуш тега `v1.0.0` или `companion-v1.0.0` собирает набор и создаёт **черновик релиза**
|
Релиз собирается и публикуется **локально**: `ci/build-local.ps1 -Publish -Public -Tag <тег>`
|
||||||
с прикреплёнными `.bin` / `.uf2` / `.zip`. Версия берётся из тега
|
(см. выше). Скрипт создаёт тег на текущем коммите, релиз в Gitea и прикрепляет к нему
|
||||||
(`companion-v1.2.3` → `v1.2.3`) и попадает в имена файлов (`<env>-v1.2.3-<sha>.uf2`).
|
`.bin` / `.uf2` / `.zip`; версия `-Version` попадает в имена файлов
|
||||||
|
(`<env>-v1.0.0-<sha>.uf2`). Если тег уже существует, он переиспользуется.
|
||||||
|
|
||||||
```bash
|
В CI шага публикации нет: сервер только собирает прошивки по ручному запуску и отдаёт их
|
||||||
git tag companion-v1.0.0 && git push origin companion-v1.0.0
|
артефактом.
|
||||||
# затем: репозиторий → Releases → черновик → Publish
|
|
||||||
```
|
|
||||||
|
|
||||||
Публикация идёт автоматическим `secrets.GITHUB_TOKEN` (в workflow стоит
|
|
||||||
`permissions: contents: write`). Если релизы не создаются — заведите секрет
|
|
||||||
`RELEASE_TOKEN` с правом `write:repository` и подставьте его в шаге «Черновик релиза».
|
|
||||||
|
|
||||||
## 6. Набор прошивок по умолчанию
|
## 6. Набор прошивок по умолчанию
|
||||||
|
|
||||||
@@ -174,8 +176,9 @@ tracker_v2, T-Deck, T-Echo Card, T114, T190, M9 и три варианта Mesha
|
|||||||
## 7. Время и ресурсы
|
## 7. Время и ресурсы
|
||||||
|
|
||||||
- Сервер с **1 CPU / 2 ГБ RAM** полный набор собирает долго (первый прогон — часы),
|
- Сервер с **1 CPU / 2 ГБ RAM** полный набор собирает долго (первый прогон — часы),
|
||||||
и на время сборки Gitea с Postgres на той же машине заметно тормозят.
|
и на время сборки Gitea с Postgres на той же машине заметно тормозят. Поэтому рабочий
|
||||||
Разумно запускать полный набор вручную, а на push держать 2–3 env.
|
вариант — собирать полный набор локально (`ci/build-local.ps1`), а в Gitea запускать
|
||||||
|
вручную только те env, которые нужно проверить.
|
||||||
- Первая сборка качает тулчейны (nRF52: framework + CMSIS + gcc-arm ≈ 660 МБ;
|
- Первая сборка качает тулчейны (nRF52: framework + CMSIS + gcc-arm ≈ 660 МБ;
|
||||||
ESP32 — ещё столько же), дальше работает кэш `~/.platformio`.
|
ESP32 — ещё столько же), дальше работает кэш `~/.platformio`.
|
||||||
- `capacity: 1` в `config.yaml` — одна сборка за раз.
|
- `capacity: 1` в `config.yaml` — одна сборка за раз.
|
||||||
@@ -190,5 +193,8 @@ tracker_v2, T-Deck, T-Echo Card, T114, T190, M9 и три варианта Mesha
|
|||||||
| `registration file not found` в цикле рестартов | раннер запущен до регистрации: сначала `register`, потом сервис |
|
| `registration file not found` в цикле рестартов | раннер запущен до регистрации: сначала `register`, потом сервис |
|
||||||
| `E: Could not get lock /var/lib/dpkg/lock-frontend` | работает unattended-upgrades; подождать либо остановить `apt-daily.timer` |
|
| `E: Could not get lock /var/lib/dpkg/lock-frontend` | работает unattended-upgrades; подождать либо остановить `apt-daily.timer` |
|
||||||
| на push запускается сборка сотен env | не выставлен `WORKFLOW_DIRS = .gitea/workflows` |
|
| на push запускается сборка сотен env | не выставлен `WORKFLOW_DIRS = .gitea/workflows` |
|
||||||
|
| сборка запускается на каждый push, хотя в workflow стоит `paths-ignore` | Gitea этот фильтр не применяет; автосборок нет — workflow запускается только вручную |
|
||||||
| `405 Method Not Allowed` при создании тега | в Gitea тег создаётся через `POST /repos/{owner}/{repo}/tags`, у `/git/refs` метода POST нет |
|
| `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`) |
|
| `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 <тег>` |
|
||||||
|
|||||||
@@ -58,7 +58,10 @@ DEFAULT_TARGETS=(
|
|||||||
|
|
||||||
export FIRMWARE_VERSION="${FIRMWARE_VERSION:-ci}"
|
export FIRMWARE_VERSION="${FIRMWARE_VERSION:-ci}"
|
||||||
OUT_DIR="${OUT_DIR:-out}"
|
OUT_DIR="${OUT_DIR:-out}"
|
||||||
|
DIST_DIR="${DIST_DIR:-dist}" # накопитель: build.sh чистит out/ на каждом запуске
|
||||||
mkdir -p "$OUT_DIR"
|
mkdir -p "$OUT_DIR"
|
||||||
|
rm -rf "$DIST_DIR"
|
||||||
|
mkdir -p "$DIST_DIR"
|
||||||
|
|
||||||
targets=()
|
targets=()
|
||||||
if [[ -n "${MATCH:-}" ]]; then
|
if [[ -n "${MATCH:-}" ]]; then
|
||||||
@@ -91,12 +94,19 @@ for env in "${targets[@]}"; do
|
|||||||
echo "===== $env ====="
|
echo "===== $env ====="
|
||||||
if bash build.sh build-firmware "$env"; then
|
if bash build.sh build-firmware "$env"; then
|
||||||
echo "----- $env: OK"
|
echo "----- $env: OK"
|
||||||
|
# build.sh в начале своей работы делает `rm -rf out`, поэтому файлы каждой
|
||||||
|
# следующей платы затирают предыдущие: копим их в отдельном каталоге
|
||||||
|
cp -f "$OUT_DIR"/* "$DIST_DIR"/ 2>/dev/null || true
|
||||||
else
|
else
|
||||||
echo "----- $env: ОШИБКА"
|
echo "----- $env: ОШИБКА"
|
||||||
failed+=("$env")
|
failed+=("$env")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# возвращаем накопленное в привычный out/ (его читают CI-артефакты и скрипты)
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
cp -f "$DIST_DIR"/* "$OUT_DIR"/ 2>/dev/null || true
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "===== итог ====="
|
echo "===== итог ====="
|
||||||
echo "собрано ${#targets[@]} - ${#failed[@]} ошибок"
|
echo "собрано ${#targets[@]} - ${#failed[@]} ошибок"
|
||||||
|
|||||||
+13
-5
@@ -46,6 +46,7 @@ param(
|
|||||||
[switch]$Publish,
|
[switch]$Publish,
|
||||||
[string]$Tag = '',
|
[string]$Tag = '',
|
||||||
[switch]$Public,
|
[switch]$Public,
|
||||||
|
[switch]$SkipBuild,
|
||||||
[string]$Token = $env:GITEA_TOKEN,
|
[string]$Token = $env:GITEA_TOKEN,
|
||||||
[string]$GiteaUrl = 'https://git.meshinfo.ru',
|
[string]$GiteaUrl = 'https://git.meshinfo.ru',
|
||||||
[string]$Repo = 'shade/MeshCore'
|
[string]$Repo = 'shade/MeshCore'
|
||||||
@@ -98,12 +99,19 @@ try {
|
|||||||
|
|
||||||
# out/ чистим, иначе в релиз попадут файлы прошлых сборок
|
# out/ чистим, иначе в релиз попадут файлы прошлых сборок
|
||||||
$outDir = Join-Path $root 'out'
|
$outDir = Join-Path $root 'out'
|
||||||
if (Test-Path $outDir) { Get-ChildItem $outDir -File | Remove-Item -Force }
|
$buildExit = 0
|
||||||
|
if ($SkipBuild) {
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host 'сборка пропущена (-SkipBuild): публикую то, что уже лежит в out/' -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (Test-Path $outDir) { Get-ChildItem $outDir -File | Remove-Item -Force }
|
||||||
|
|
||||||
Write-Host ''
|
Write-Host ''
|
||||||
Write-Host "=== сборка (версия $Version) ===" -ForegroundColor Cyan
|
Write-Host "=== сборка (версия $Version) ===" -ForegroundColor Cyan
|
||||||
& $bash 'ci/build-firmwares.sh'
|
& $bash 'ci/build-firmwares.sh'
|
||||||
$buildExit = $LASTEXITCODE
|
$buildExit = $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
$files = @(Get-ChildItem $outDir -File -ErrorAction SilentlyContinue)
|
$files = @(Get-ChildItem $outDir -File -ErrorAction SilentlyContinue)
|
||||||
Write-Host ''
|
Write-Host ''
|
||||||
|
|||||||
Reference in New Issue
Block a user