CI: локальная сборка прошивок с публикацией релиза в Gitea
Firmware build / release (push) Canceled after 0s
Firmware build / build (push) Canceled after 36m29s

This commit is contained in:
2026-10-11 14:15:38 +03:00
parent c2a79e49ab
commit e4b8e4e820
4 changed files with 214 additions and 0 deletions
+4
View File
@@ -71,6 +71,10 @@ git merge upstream/main # правки для upstream отправляю
`build.sh`, поэтому версия, `merge-bin` для ESP32 и `.uf2` для nRF52 получаются такими же, `build.sh`, поэтому версия, `merge-bin` для ESP32 и `.uf2` для nRF52 получаются такими же,
как при локальной сборке. Готовые файлы выгружаются артефактами, пуш тега создаёт черновик как при локальной сборке. Готовые файлы выгружаются артефактами, пуш тега создаёт черновик
релиза. Как поднять раннер — [`ci/README.md`](./ci/README.md). релиза. Как поднять раннер — [`ci/README.md`](./ci/README.md).
- **Локальная сборка с публикацией результата** ([`ci/build-local.ps1`](./ci/build-local.ps1)):
набор прошивок собирается на своей машине (тот же `build.sh` через Git Bash), а готовые
файлы прикрепляются к релизу Gitea — сервер только принимает их и не тратит CPU на сборку.
Это основной способ собрать полный набор: на VPS всего одно ядро.
### Варианты плат ### Варианты плат
+26
View File
@@ -126,6 +126,30 @@ cd ci/act_runner && cp .env.example .env && nano .env && docker compose up -d
Готовые файлы попадают в `out/` (каталог в `.gitignore`) и прикрепляются к запуску Готовые файлы попадают в `out/` (каталог в `.gitignore`) и прикрепляются к запуску
workflow как артефакт `firmware`. workflow как артефакт `firmware`.
### Локально на Windows, без нагрузки на сервер
На этом VPS всего 1 CPU, поэтому полный набор прошивок собирается там часами. Разумнее
собрать у себя, а на сервер отправить только готовые файлы — релизом Gitea:
```powershell
# быстрый набор: все env, в имени которых есть подстрока
.\ci\build-local.ps1 -Match promicro -Version v1.0.0
# полный набор из DEFAULT_TARGETS
.\ci\build-local.ps1 -Version v1.0.0
# собрать и прикрепить к релизу (черновик; -Public опубликует сразу)
$env:GITEA_TOKEN = '<токен с правом write:repository>'
.\ci\build-local.ps1 -Version v1.0.0 -Publish -Tag companion-v1.0.0
```
Скрипт находит Git Bash и PlatformIO, подставляет рабочий `python3` (в Git Bash это
заглушка Microsoft Store — из-за неё `build.sh` не может определить платформу env),
чистит `out/` перед сборкой, а с `-Publish` создаёт тег, черновик релиза и прикрепляет
к нему файлы. Токен нужен только для публикации: сама сборка работает без него.
На Linux/macOS то же самое делается напрямую: `FIRMWARE_VERSION=v1.0.0 bash ci/build-firmwares.sh`.
## 5. Релизы по тегу ## 5. Релизы по тегу
Пуш тега `v1.0.0` или `companion-v1.0.0` собирает набор и создаёт **черновик релиза** Пуш тега `v1.0.0` или `companion-v1.0.0` собирает набор и создаёт **черновик релиза**
@@ -166,3 +190,5 @@ 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` |
| `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`) |
+9
View File
@@ -22,6 +22,15 @@ set -uo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.." cd "$(dirname "${BASH_SOURCE[0]}")/.."
# На Windows в Git Bash команда python3 — это заглушка Microsoft Store, из-за которой
# build.sh не может определить платформу env. Если PYTHON3 задан, подставляем рабочий
# интерпретатор (нужен и самому скрипту, и вызываемому build.sh):
# PYTHON3=/c/Espressif/tools/python/python.exe bash ci/build-firmwares.sh
if [[ -n "${PYTHON3:-}" ]]; then
python3() { "$PYTHON3" "$@"; }
export -f python3
fi
# Набор по умолчанию — устройства этого форка. # Набор по умолчанию — устройства этого форка.
DEFAULT_TARGETS=( DEFAULT_TARGETS=(
# companion # companion
+175
View File
@@ -0,0 +1,175 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Локальная сборка прошивок MeshCore под Windows с публикацией в Gitea.
.DESCRIPTION
Запускает ci/build-firmwares.sh (тот же путь сборки, что и в CI: build.sh, merge-bin,
uf2) через Git Bash, а готовые файлы из out/ при необходимости прикрепляет к релизу
в Gitea. Сам сервер при этом не нагружается — на него уходит только загрузка файлов.
Зачем: на VPS всего 1 CPU, полный набор прошивок собирается там часами. Локально —
быстрее, а результат удобно раздать через релиз Gitea.
.PARAMETER Targets
Список env через запятую. Пусто — набор устройств форка из ci/build-firmwares.sh.
.PARAMETER Match
Подстрока имени env: собрать все подходящие (например, promicro).
.PARAMETER Version
FIRMWARE_VERSION — попадает в имена файлов (по умолчанию local).
.PARAMETER Publish
Создать релиз в Gitea и прикрепить к нему файлы из out/.
.PARAMETER Tag
Тег для релиза (обязателен с -Publish). Если тега в репозитории нет — он будет создан
от текущего коммита.
.PARAMETER Public
Опубликовать релиз сразу. Без этого флага создаётся черновик.
.PARAMETER Token
Токен Gitea с правом write:repository. По умолчанию берётся из переменной GITEA_TOKEN.
.EXAMPLE
.\ci\build-local.ps1 -Match promicro -Version v1.0.0
.EXAMPLE
.\ci\build-local.ps1 -Version v1.0.0 -Publish -Tag companion-v1.0.0
#>
param(
[string]$Targets = '',
[string]$Match = '',
[string]$Version = 'local',
[switch]$Publish,
[string]$Tag = '',
[switch]$Public,
[string]$Token = $env:GITEA_TOKEN,
[string]$GiteaUrl = 'https://git.meshinfo.ru',
[string]$Repo = 'shade/MeshCore'
)
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
Push-Location $root
try {
# --- ищем Git Bash -------------------------------------------------------
$bash = @(
"$env:ProgramFiles\Git\bin\bash.exe",
"${env:ProgramFiles(x86)}\Git\bin\bash.exe",
"$env:LOCALAPPDATA\Programs\Git\bin\bash.exe"
) | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $bash) { $bash = (Get-Command bash -ErrorAction SilentlyContinue).Source }
if (-not $bash) { throw 'Git Bash не найден — установите Git for Windows' }
Write-Host "Git Bash: $bash"
# --- ищем рабочий python3 для build.sh ----------------------------------
# В Git Bash путь должен быть в unix-виде: C:\Tools\python.exe -> /c/Tools/python.exe
$pythonCandidates = @(
"$env:USERPROFILE\.platformio\penv\Scripts\python.exe",
'C:\Espressif\tools\python\python.exe'
)
$python = $pythonCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $python) {
$cmd = Get-Command python -ErrorAction SilentlyContinue
if ($cmd) { $python = $cmd.Source }
}
if (-not $python) { throw 'Не найден python.exe (нужен для build.sh)' }
$pythonUnix = '/' + $python.Substring(0, 1).ToLower() + $python.Substring(2).Replace('\', '/')
Write-Host "python3 для build.sh: $pythonUnix"
# --- pio должен быть доступен и для bash ---------------------------------
$pioDir = Join-Path $env:USERPROFILE '.platformio\penv\Scripts'
if ((Test-Path (Join-Path $pioDir 'pio.exe')) -and ($env:PATH -notlike "*$pioDir*")) {
$env:PATH = "$pioDir;$env:PATH"
}
$pioCheck = & $bash -c 'command -v pio' 2>$null
if (-not $pioCheck) { throw 'PlatformIO (pio) не найден в PATH — установите PlatformIO Core' }
Write-Host "pio: $pioCheck"
# --- сборка --------------------------------------------------------------
$env:FIRMWARE_VERSION = $Version
$env:TARGETS = $Targets
$env:MATCH = $Match
$env:PYTHON3 = $pythonUnix
# out/ чистим, иначе в релиз попадут файлы прошлых сборок
$outDir = Join-Path $root 'out'
if (Test-Path $outDir) { Get-ChildItem $outDir -File | Remove-Item -Force }
Write-Host ''
Write-Host "=== сборка (версия $Version) ===" -ForegroundColor Cyan
& $bash 'ci/build-firmwares.sh'
$buildExit = $LASTEXITCODE
$files = @(Get-ChildItem $outDir -File -ErrorAction SilentlyContinue)
Write-Host ''
Write-Host "файлов в out/: $($files.Count)"
$files | ForEach-Object { ' {0} {1:N0} байт' -f $_.Name, $_.Length }
if ($buildExit -ne 0) { Write-Warning "часть env не собралась (код $buildExit)" }
# --- публикация релиза ---------------------------------------------------
if ($Publish) {
if (-not $Tag) { throw 'С -Publish нужно указать -Tag, например -Tag companion-v1.0.0' }
if (-not $Token) { throw 'Нужен токен Gitea: -Token <токен> или переменная окружения GITEA_TOKEN' }
if ($files.Count -eq 0) { throw 'В out/ нет файлов — публиковать нечего' }
$api = "$GiteaUrl/api/v1/repos/$Repo"
$headers = @{ Authorization = "token $Token"; 'Content-Type' = 'application/json' }
# тег создаём, если его ещё нет (на текущем коммите).
# В Gitea тег создаётся через POST /tags (у /git/refs нет метода POST).
$tagExists = $true
try { Invoke-RestMethod -Method Get -Uri "$api/tags/$Tag" -Headers $headers | Out-Null }
catch { $tagExists = $false }
if (-not $tagExists) {
$sha = (git -C $root rev-parse HEAD).Trim()
$body = @{ tag_name = $Tag; target = $sha } | ConvertTo-Json -Compress
Invoke-RestMethod -Method Post -Uri "$api/tags" -Headers $headers -Body $body | Out-Null
Write-Host "создан тег $Tag на коммите $($sha.Substring(0,8))"
}
else {
Write-Host "тег $Tag уже существует"
}
$draft = if ($Public) { 'false' } else { 'true' }
$releaseBody = @{
tag_name = $Tag
name = $Tag
draft = ($draft -eq 'true')
prerelease = $false
target_commitish = 'main'
} | ConvertTo-Json -Compress -Depth 3
try {
$release = Invoke-RestMethod -Method Post -Uri "$api/releases" -Headers $headers -Body $releaseBody
Write-Host "релиз #$($release.id) создан ($(if ($Public) { 'опубликован' } else { 'черновик' }))"
}
catch {
throw "Не удалось создать релиз: $($_.Exception.Message)"
}
foreach ($f in $files) {
& curl.exe -sS -X POST "$api/releases/$($release.id)/assets?name=$($f.Name)" `
-H "Authorization: token $Token" -F "attachment=@$($f.FullName)" | Out-Null
if ($LASTEXITCODE -ne 0) { Write-Warning "не загрузился: $($f.Name)" }
else { Write-Host " прикреплён $($f.Name)" }
}
Write-Host ''
Write-Host "Релиз: $GiteaUrl/$Repo/releases" -ForegroundColor Green
}
else {
Write-Host ''
Write-Host 'Файлы готовы в out/. Чтобы прикрепить их к релизу Gitea, добавьте -Publish -Tag <тег>' -ForegroundColor Yellow
}
exit $buildExit
}
finally {
Pop-Location
}