diff --git a/README.md b/README.md index 8bee8407..2b5927f7 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,12 @@ git merge upstream/main # правки для upstream отправляю набор прошивок собирается на своей машине (тот же `build.sh` через Git Bash), а готовые файлы прикрепляются к релизу Gitea — сервер только принимает их и не тратит CPU на сборку. Это основной способ выпустить прошивки: на VPS всего одно ядро. +- **Прошивальщик на сайте** (): список прошивок страница берёт + из своего `index.json`, где имена файлов разобраны на плату, роль, чип и формат. После + публикации релиза манифест пересобирается скриптом + [`ci/update-flasher.ps1`](./ci/update-flasher.ps1): он выкладывает его на сервер, делает + бэкап предыдущего и проверяет, что страница и файлы доступны. При `-Publish` это делается + автоматически. ### Варианты плат diff --git a/ci/README.md b/ci/README.md index 01f58ce4..f607287f 100644 --- a/ci/README.md +++ b/ci/README.md @@ -167,6 +167,26 @@ $env:GITEA_TOKEN = '<токен с правом write:repository>' В CI шага публикации нет: сервер только собирает прошивки по ручному запуску и отдаёт их артефактом. +### Прошивальщик на сайте + +Страница берёт список прошивок из своего `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: diff --git a/ci/build-local.ps1 b/ci/build-local.ps1 index 25c500cd..f5e9985a 100644 --- a/ci/build-local.ps1 +++ b/ci/build-local.ps1 @@ -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 '' diff --git a/ci/update-flasher.ps1 b/ci/update-flasher.ps1 new file mode 100644 index 00000000..b58b7da4 --- /dev/null +++ b/ci/update-flasher.ps1 @@ -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