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 получаются такими же,
как при локальной сборке. Готовые файлы выгружаются артефактами, пуш тега создаёт черновик
релиза. Как поднять раннер — [`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`) и прикрепляются к запуску
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. Релизы по тегу
Пуш тега `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`, потом сервис |
| `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`) |
+9
View File
@@ -22,6 +22,15 @@ set -uo pipefail
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=(
# 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
}