#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