CI: автообновление прошивальщика после публикации релиза
This commit is contained in:
1 parent
e110fd87a8
commit
24e2e48e1d
4 files changed
+212
-1
No files matched your search
@@ -86,6 +86,12 @@ git merge upstream/main # правки для upstream отправляю
|
||||
набор прошивок собирается на своей машине (тот же `build.sh` через Git Bash), а готовые
|
||||
файлы прикрепляются к релизу Gitea — сервер только принимает их и не тратит CPU на сборку.
|
||||
Это основной способ выпустить прошивки: на VPS всего одно ядро.
|
||||
- **Прошивальщик на сайте** (<https://meshinfo.ru/flasher/>): список прошивок страница берёт
|
||||
из своего `index.json`, где имена файлов разобраны на плату, роль, чип и формат. После
|
||||
публикации релиза манифест пересобирается скриптом
|
||||
[`ci/update-flasher.ps1`](./ci/update-flasher.ps1): он выкладывает его на сервер, делает
|
||||
бэкап предыдущего и проверяет, что страница и файлы доступны. При `-Publish` это делается
|
||||
автоматически.
|
||||
|
||||
### Варианты плат
|
||||
|
||||
|
||||
@@ -167,6 +167,26 @@ $env:GITEA_TOKEN = '<токен с правом write:repository>'
|
||||
В CI шага публикации нет: сервер только собирает прошивки по ручному запуску и отдаёт их
|
||||
артефактом.
|
||||
|
||||
### Прошивальщик на сайте
|
||||
|
||||
Страница <https://meshinfo.ru/flasher/> берёт список прошивок из своего `index.json`
|
||||
(статика в `/var/www/flasher`, а сами файлы браузер тянет через nginx напрямую из релизов
|
||||
Gitea). Имена файлов в манифесте разобраны на плату, роль, чип и формат, поэтому после
|
||||
каждой публикации релиза его нужно пересобрать — иначе на странице останутся старые ссылки.
|
||||
|
||||
```powershell
|
||||
.\ci\update-flasher.ps1 # пересобрать и выложить на сервер
|
||||
.\ci\update-flasher.ps1 -SkipUpload # только пересобрать, без заливки
|
||||
```
|
||||
|
||||
Скрипт запускает генератор `build-flasher-index.py` из соседнего репозитория
|
||||
(`..\MeshCore-posadmesh-meshcoretel\posadmesh-tools\`; путь задаётся через `-Generator`
|
||||
или `FLASHER_GENERATOR`), делает на сервере бэкап старого манифеста, заливает новый и
|
||||
проверяет, что страница отдаёт его, а файлы доступны через `/flasher/fw/...`.
|
||||
|
||||
При публикации релиза (`ci/build-local.ps1 -Publish`) обновление выполняется автоматически,
|
||||
отключить можно флагом `-SkipFlasher`.
|
||||
|
||||
## 6. Набор прошивок по умолчанию
|
||||
|
||||
`ci/build-firmwares.sh`, массив `DEFAULT_TARGETS` — 26 env:
|
||||
|
||||
+23
-1
@@ -21,7 +21,11 @@
|
||||
FIRMWARE_VERSION — попадает в имена файлов (по умолчанию local).
|
||||
|
||||
.PARAMETER Publish
|
||||
Создать релиз в Gitea и прикрепить к нему файлы из out/.
|
||||
Создать релиз в Gitea и прикрепить к нему файлы из out/. После публикации
|
||||
автоматически обновляется манифест прошивальщика (ci/update-flasher.ps1).
|
||||
|
||||
.PARAMETER SkipFlasher
|
||||
Не обновлять манифест прошивальщика после публикации релиза.
|
||||
|
||||
.PARAMETER Tag
|
||||
Тег для релиза (обязателен с -Publish). Если тега в репозитории нет — он будет создан
|
||||
@@ -47,6 +51,7 @@ param(
|
||||
[string]$Tag = '',
|
||||
[switch]$Public,
|
||||
[switch]$SkipBuild,
|
||||
[switch]$SkipFlasher,
|
||||
[string]$Token = $env:GITEA_TOKEN,
|
||||
[string]$GiteaUrl = 'https://git.meshinfo.ru',
|
||||
[string]$Repo = 'shade/MeshCore'
|
||||
@@ -170,6 +175,23 @@ try {
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "Релиз: $GiteaUrl/$Repo/releases" -ForegroundColor Green
|
||||
|
||||
# Прошивальщик на сайте читает имена файлов из своего index.json: после публикации
|
||||
# релиза манифест нужно пересобрать, иначе на странице останутся старые ссылки.
|
||||
if (-not $SkipFlasher) {
|
||||
$updater = Join-Path $root 'ci\update-flasher.ps1'
|
||||
Write-Host ''
|
||||
Write-Host '=== обновление прошивальщика ===' -ForegroundColor Cyan
|
||||
if (Test-Path $updater) {
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $updater -Token $Token
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning 'прошивальщик не обновился — запустите ci/update-flasher.ps1 вручную'
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Warning "не найден $updater — прошивальщик не обновлён"
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host ''
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Обновление страницы-прошивальщика (https://meshinfo.ru/flasher/) после публикации релиза.
|
||||
|
||||
.DESCRIPTION
|
||||
Прошивальщик — статика в /var/www/flasher: страница читает index.json, где перечислены
|
||||
релизы и файлы прошивок (имена разбираются на плату, роль, чип и формат). Файлы прошивок
|
||||
на сервер не копируются: nginx проксирует их из релизов Gitea.
|
||||
|
||||
Скрипт пересобирает index.json генератором из posadmesh-tools и выкладывает его на сервер.
|
||||
Запускать после того, как релиз в Gitea создан (иначе новых имён в манифесте не будет).
|
||||
Из ci/build-local.ps1 вызывается автоматически при -Publish.
|
||||
|
||||
.PARAMETER Generator
|
||||
Путь к build-flasher-index.py. По умолчанию берётся из FLASHER_GENERATOR или ищется
|
||||
в соседнем репозитории ..\MeshCore-posadmesh-meshcoretel\posadmesh-tools\.
|
||||
|
||||
.PARAMETER Python
|
||||
Интерпретатор Python. По умолчанию FLASHER_PYTHON, затем python из PATH, затем
|
||||
из окружения PlatformIO.
|
||||
|
||||
.PARAMETER Token
|
||||
Токен Gitea (нужен, если репозитории приватные). По умолчанию GITEA_TOKEN.
|
||||
|
||||
.PARAMETER SshKey
|
||||
Ключ для доступа к серверу. По умолчанию ~/.ssh/meshcore_ci_ed25519.
|
||||
|
||||
.PARAMETER Server
|
||||
Адрес сервера, по умолчанию root@meshinfo.ru.
|
||||
|
||||
.PARAMETER RemotePath
|
||||
Куда положить манифест, по умолчанию /var/www/flasher/index.json.
|
||||
|
||||
.PARAMETER PublicUrl
|
||||
Базовый адрес страницы для проверки, по умолчанию https://meshinfo.ru/flasher.
|
||||
|
||||
.PARAMETER SkipUpload
|
||||
Только пересобрать index.json, ничего не заливать (для локальной проверки).
|
||||
|
||||
.EXAMPLE
|
||||
.\ci\update-flasher.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\ci\update-flasher.ps1 -SkipUpload
|
||||
#>
|
||||
param(
|
||||
[string]$Generator = '',
|
||||
[string]$Python = '',
|
||||
[string]$Token = $env:GITEA_TOKEN,
|
||||
[string]$SshKey = "$env:USERPROFILE\.ssh\meshcore_ci_ed25519",
|
||||
[string]$Server = 'root@meshinfo.ru',
|
||||
[string]$RemotePath = '/var/www/flasher/index.json',
|
||||
[string]$PublicUrl = 'https://meshinfo.ru/flasher',
|
||||
[switch]$SkipUpload
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repoRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
|
||||
function Fail($msg) { Write-Host "ошибка: $msg" -ForegroundColor Red; exit 1 }
|
||||
|
||||
# --- генератор манифеста ----------------------------------------------------
|
||||
if (-not $Generator) { $Generator = $env:FLASHER_GENERATOR }
|
||||
if (-not $Generator) {
|
||||
$candidates = @(
|
||||
(Join-Path (Split-Path -Parent $repoRoot) 'MeshCore-posadmesh-meshcoretel\posadmesh-tools\build-flasher-index.py'),
|
||||
(Join-Path $repoRoot 'posadmesh-tools\build-flasher-index.py')
|
||||
)
|
||||
$Generator = $candidates | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||
}
|
||||
if (-not $Generator -or -not (Test-Path $Generator)) {
|
||||
Fail "не найден build-flasher-index.py. Укажите путь: -Generator <путь> или переменную FLASHER_GENERATOR"
|
||||
}
|
||||
$Generator = (Resolve-Path $Generator).Path
|
||||
Write-Host "генератор: $Generator"
|
||||
|
||||
# --- python -----------------------------------------------------------------
|
||||
if (-not $Python) { $Python = $env:FLASHER_PYTHON }
|
||||
if (-not $Python) {
|
||||
$candidates = @(
|
||||
'C:\Espressif\tools\python\python.exe',
|
||||
(Join-Path $env:USERPROFILE '.platformio\penv\Scripts\python.exe')
|
||||
)
|
||||
$Python = $candidates | 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) { Fail 'не найден python.exe (укажите -Python <путь>)' }
|
||||
Write-Host "python: $Python"
|
||||
|
||||
# --- пересборка манифеста ---------------------------------------------------
|
||||
$env:PYTHONIOENCODING = 'utf-8'
|
||||
if ($Token) { $env:GITEA_TOKEN = $Token } else { Write-Warning 'GITEA_TOKEN не задан — приватные репозитории не прочитаются' }
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '=== пересборка index.json ===' -ForegroundColor Cyan
|
||||
& $Python $Generator
|
||||
if ($LASTEXITCODE -ne 0) { Fail "генератор завершился с кодом $LASTEXITCODE" }
|
||||
|
||||
$manifest = Join-Path (Split-Path -Parent $Generator) 'flasher\index.json'
|
||||
if (-not (Test-Path $manifest)) { Fail "генератор не создал $manifest" }
|
||||
|
||||
$data = Get-Content -Raw -Encoding UTF8 $manifest | ConvertFrom-Json
|
||||
$meshcore = $data.sections | Where-Object { $_.id -eq 'meshcore' } | Select-Object -First 1
|
||||
$shaList = @($meshcore.releases | ForEach-Object { $_.files } | ForEach-Object { $_.sha } | Sort-Object -Unique)
|
||||
Write-Host ''
|
||||
Write-Host "манифест: $manifest"
|
||||
Write-Host " собран: $($data.generated_at)"
|
||||
Write-Host " всего файлов: $($data.totals.files), плат: $($data.totals.boards)"
|
||||
Write-Host " MeshCore: плат $($meshcore.board_count), файлов $($meshcore.file_count)"
|
||||
Write-Host " версии кода: $($shaList -join ', ')"
|
||||
if ($meshcore.file_count -eq 0) { Fail 'в секции MeshCore нет файлов — релиз не найден или имена не разбираются' }
|
||||
|
||||
# --- выкладка на сервер -----------------------------------------------------
|
||||
if ($SkipUpload) {
|
||||
Write-Host ''
|
||||
Write-Host 'заливка пропущена (-SkipUpload)' -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
$sshOpts = @('-i', $SshKey, '-o', 'IdentitiesOnly=yes', '-o', 'BatchMode=yes', '-o', 'StrictHostKeyChecking=accept-new')
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '=== выкладка на сервер ===' -ForegroundColor Cyan
|
||||
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||
& ssh @sshOpts $Server "cp $RemotePath $RemotePath.bak-$stamp" 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { Fail "не удалось сделать бэкап на сервере ($Server)" }
|
||||
Write-Host " бэкап: $RemotePath.bak-$stamp"
|
||||
|
||||
& scp @sshOpts $manifest "${Server}:$RemotePath" 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { Fail 'scp не смог залить манифест' }
|
||||
Write-Host " залито: $RemotePath"
|
||||
|
||||
# --- проверка ---------------------------------------------------------------
|
||||
Write-Host ''
|
||||
Write-Host '=== проверка ===' -ForegroundColor Cyan
|
||||
$httpCode = & curl.exe -s -o NUL -w '%{http_code}' "$PublicUrl/index.json"
|
||||
if ($httpCode -ne '200') { Fail "страница отдаёт index.json с кодом $httpCode" }
|
||||
$remote = & curl.exe -s "$PublicUrl/index.json"
|
||||
$remoteData = $remote | ConvertFrom-Json
|
||||
Write-Host " index.json: HTTP 200, собран $($remoteData.generated_at)"
|
||||
if ($remoteData.generated_at -ne $data.generated_at) {
|
||||
Fail "на сервере старый манифест ($($remoteData.generated_at) вместо $($data.generated_at))"
|
||||
}
|
||||
|
||||
# пара ссылок на файлы: первый по списку и первый merged, если есть
|
||||
$sample = @($meshcore.releases[0].files | Select-Object -First 1)
|
||||
$merged = @($meshcore.releases[0].files | Where-Object { $_.format -eq 'merged' } | Select-Object -First 1)
|
||||
if ($merged) { $sample += $merged }
|
||||
$bad = 0
|
||||
foreach ($f in $sample) {
|
||||
$code = & curl.exe -s -o NUL -w '%{http_code}' "$PublicUrl/fw/$($f.repo)/$($f.tag)/$($f.name)"
|
||||
if ($code -eq '200') { Write-Host " $($f.name): HTTP 200" }
|
||||
else { Write-Host " $($f.name): HTTP $code" -ForegroundColor Red; $bad++ }
|
||||
}
|
||||
if ($bad -gt 0) { Fail "файлы прошивок недоступны через $PublicUrl/fw/ — проверьте, что релиз в Gitea опубликован" }
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "готово: $PublicUrl/ — $($meshcore.file_count) файлов MeshCore, версия кода $($shaList -join ', ')" -ForegroundColor Green
|
||||
exit 0
|
||||
Reference in new issue
Block a user