Files
Button/winDeployScripts/make-adb-bundle.ps1
T
GrigoandClaude Opus 5.5 2e554a56b1 Editions and push-to-talk voice buttons
- Editions (buttons / full): edition.json decides whether the sensor
  collector and voice service are installed and run; bt-update sync-system
  re-enables the collector when an edition with sensors appears.
- Voice button type: hold to record, release to send to the demo server's
  /api/voice; the recognized category lights up, holding again clears it.
  buttontask-voice daemon (voice/) records via PulseAudio, panel settings
  for server URL and key with a connectivity check in the web UI.
- Packaging: full edition bundle, device-install and adb bundle updates.
- Stop bt-update from re-disabling PulseAudio on every sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 12:54:26 +00:00

210 lines
9.4 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# make-adb-bundle.ps1 — собирает offline-бандл (Windows-хост, без WSL).
#
# powershell -ExecutionPolicy Bypass -File make-adb-bundle.ps1 -Version 2.0
# powershell ... make-adb-bundle.ps1 -Version 2.0 -Edition full # + BLE-датчики, голос
#
# -Edition (docs/editions.md): buttons (по умолчанию) — без collector/ и voice/
# (и без их юнитов); full — со всем. Редакция пишется в бандл (файл EDITION) и при установке
# в /opt/buttontask/config/edition.json; OTA её не меняет.
#
# По умолчанию берёт софт из .\software (бинарник ButtonTask + webconfig\),
# пакеты из .\debs. Все shell-скрипты/юниты нормализуются в LF (CRLF-фикс).
param(
[string]$SoftwareDir = '.\software',
[Parameter(Mandatory = $true)][string]$Version,
[string]$BinPath,
[string]$DebsDir,
[string]$MasterPassword,
[ValidateSet('buttons', 'full')][string]$Edition = 'buttons'
)
$ErrorActionPreference = 'Stop'
$Here = Split-Path -Parent $MyInvocation.MyCommand.Path
function Log($m) { Write-Host "[bundle] $m" -ForegroundColor Green }
function Warn($m) { Write-Host "[warn] $m" -ForegroundColor Yellow }
function Die($m) { Write-Host "[error] $m" -ForegroundColor Red; exit 1 }
function Write-Bytes($path, [string]$text) {
[System.IO.File]::WriteAllText($path, $text, (New-Object System.Text.UTF8Encoding($false)))
}
function ConvertTo-Lf($file) {
$t = [System.IO.File]::ReadAllText($file)
$t = ($t -replace "`r`n", "`n") -replace "`r", "`n"
Write-Bytes $file $t
}
if (-not (Test-Path $SoftwareDir)) { Die "нет каталога: $SoftwareDir" }
$SoftwareDir = (Resolve-Path $SoftwareDir).Path
if (-not $BinPath) { $BinPath = Join-Path $SoftwareDir 'ButtonTask' }
if (-not $DebsDir) { $DebsDir = Join-Path $Here 'debs' }
$web = Join-Path $SoftwareDir 'webconfig'
if (-not (Test-Path $BinPath)) { Die "нет бинарника: $BinPath" }
if (-not (Test-Path $web)) { Die "нет webconfig: $web" }
if (-not (Get-Command tar.exe -ErrorAction SilentlyContinue)) {
Die "не найден tar.exe (есть в Windows 10 1803+)"
}
# юниты systemd — все *.service из webconfig (в buttons — без модулей)
$moduleUnits = @('buttontask-collector.service', 'buttontask-voice.service')
$svc = @(Get-ChildItem $web -Filter '*.service' -File |
Where-Object { $Edition -ne 'buttons' -or $moduleUnits -notcontains $_.Name })
if ($svc.Count -eq 0) { Die "в webconfig нет ни одного *.service" }
# хелперы (должны совпадать с device-install.sh)
$scriptsSrc = Join-Path $web 'scripts'
$needScripts = @(
'bt-netconfig', 'bt-netconfig-boot', 'bt-update', 'bt-service',
'bt-settime', 'bt-reboot', 'bt-access', 'bt-brightness', 'bt-set-master',
'bt-displaywatch',
'buttontask.sudoers'
)
foreach ($s in $needScripts) {
if (-not (Test-Path (Join-Path $scriptsSrc $s))) { Die "нет webconfig\scripts\$s" }
}
$stage = Join-Path $Here "stage-$Version"
if (Test-Path $stage) { Remove-Item -Recurse -Force $stage }
foreach ($d in 'app', 'debs', 'scripts', 'config', 'icons', 'systemd') {
New-Item -ItemType Directory -Force -Path (Join-Path $stage $d) | Out-Null
}
Log "копирую бинарник"
Copy-Item $BinPath (Join-Path $stage 'app\ButtonTask')
Log "копирую webconfig"
Copy-Item $web (Join-Path $stage 'webconfig') -Recurse
# Необязательные модули (repo root или software\<модуль>): BLE collector, голос
foreach ($mod in 'collector', 'voice') {
$modCandidates = @(
(Join-Path $SoftwareDir $mod),
(Join-Path (Split-Path -Parent $Here) $mod)
)
$modSrc = $modCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if ($Edition -eq 'buttons') {
Log "редакция buttons — $mod в бандл не кладу"
Remove-Item (Join-Path $stage "webconfig\buttontask-$mod.service") -Force -ErrorAction SilentlyContinue
} elseif ($modSrc) {
Log "копирую ${mod}: $modSrc"
Copy-Item $modSrc (Join-Path $stage $mod) -Recurse
foreach ($junk in '__pycache__', 'tests') {
Get-ChildItem (Join-Path $stage $mod) -Recurse -Directory -Filter $junk -ErrorAction SilentlyContinue |
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
}
} else {
Warn "$mod/ не найден — модуль не попадёт в бандл"
}
}
Log "копирую хелперы и sudoers"
foreach ($s in $needScripts) { Copy-Item (Join-Path $scriptsSrc $s) (Join-Path $stage "scripts\$s") }
Log "копирую юниты: $($svc.Name -join ', ')"
foreach ($u in $svc) { Copy-Item $u.FullName (Join-Path $stage "systemd\$($u.Name)") }
$ovr = Join-Path $web 'buttontask.service.d\override.conf.example'
if (Test-Path $ovr) { Copy-Item $ovr (Join-Path $stage 'systemd\override.conf.example') }
else { Warn "override.conf.example не найден — авто-настройка тача будет пропущена" }
# config.json — ищем; если нигде нет, кладём дефолтный
$cfg = @(
(Join-Path $SoftwareDir 'config\config.json'),
(Join-Path $web 'config\config.json'),
(Join-Path $Here 'config\config.json')
) | Where-Object { Test-Path $_ } | Select-Object -First 1
if ($cfg) {
Copy-Item $cfg (Join-Path $stage 'config\config.json')
Log "config.json: $cfg"
}
else {
Warn "config.json нигде не найден — кладу дефолтный"
$default = @'
{
"version": 2,
"layout": { "mode": "grid", "columns": 2, "spacing": 10, "showLabels": true },
"background": { "type": "gradient", "color1": "#00007b", "color2": "#7b007b", "animated": true, "imagePath": "" },
"feedback": { "okColor": "#00cc44", "errorColor": "#cc2200", "okWidth": 6, "errorWidth": 6, "glowRadius": 20 },
"settings": { "darkMode": true, "password": "admin_26", "kioskMode": false, "iconsDir": "/opt/buttontask/icons", "webPort": 8080, "brightness": 60 },
"buttons": [
{
"id": "cleaning-call",
"label": "Вызов клининга",
"iconPath": "cleaner1Crop.png",
"action": { "type": "http_get", "url": "http://192.168.30.147:80/api/click/btn?bid=01*04*01", "headers": {}, "body": "" },
"latchReset": { "enabled": true, "resetUrl": "http://192.168.30.147:80/api/click/btn?bid=01*04*00", "successMatch": "OK" },
"feedback": { "successText": "Вызов отправлен", "errorText": "Ошибка", "pendingText": "...", "fadeMs": 5000 },
"trigger": { "mode": "hold", "holdMs": 800 },
"color": "#7b007b"
}
]
}
'@
Write-Bytes (Join-Path $stage 'config\config.json') ($default -replace "`r`n", "`n")
}
$netCfg = Join-Path $SoftwareDir 'config\network.json'
if (Test-Path $netCfg) {
Copy-Item $netCfg (Join-Path $stage 'config\network.json')
Log "network.json: $netCfg"
}
else {
Warn 'network.json не найден в .\software\config\ — сеть останется на DHCP'
}
$iconExts = '.png', '.jpg', '.jpeg', '.bmp', '.svg', '.gif'
$iconSources = @()
$iconSources += Get-ChildItem $SoftwareDir -File -ErrorAction SilentlyContinue |
Where-Object { $iconExts -contains $_.Extension.ToLowerInvariant() }
$iconsDir = Join-Path $SoftwareDir 'icons'
if (Test-Path $iconsDir) {
$iconSources += Get-ChildItem $iconsDir -File -ErrorAction SilentlyContinue |
Where-Object { $iconExts -contains $_.Extension.ToLowerInvariant() }
}
$iconSources = @($iconSources | Sort-Object FullName -Unique)
foreach ($ic in $iconSources) {
Copy-Item $ic.FullName (Join-Path $stage "icons\$($ic.Name)")
}
if ($iconSources.Count -gt 0) {
Log "иконок в бандле: $($iconSources.Count)"
}
else {
Warn "иконки в .\software или .\software\icons не найдены"
}
if ($MasterPassword) {
Write-Bytes (Join-Path $stage 'config\.master') $MasterPassword
Log 'мастер-пароль записан в бандл (config/.master)'
}
else {
Warn 'мастер-пароль не задан (-MasterPassword) — на устройстве задайте вручную: bt-set-master'
}
# .deb
if ((Test-Path $DebsDir) -and (Get-ChildItem $DebsDir -Filter *.deb -ErrorAction SilentlyContinue)) {
Copy-Item (Join-Path $DebsDir '*.deb') (Join-Path $stage 'debs')
Log "пакетов в бандле: $((Get-ChildItem (Join-Path $stage 'debs') -Filter *.deb).Count)"
}
else { Warn "каталог debs пуст ($DebsDir) — сначала прогоните collect-debs.ps1" }
Log "кладу device-install.sh и VERSION"
Copy-Item (Join-Path $Here 'device-install.sh') (Join-Path $stage 'device-install.sh')
Write-Bytes (Join-Path $stage 'VERSION') ($Version + "`n")
Write-Bytes (Join-Path $stage 'EDITION') ($Edition + "`n")
Log "редакция: $Edition"
Log "нормализую переводы строк в LF (CRLF-фикс)"
$fix = @()
$fix += Get-ChildItem (Join-Path $stage 'scripts') -File
$fix += Get-ChildItem (Join-Path $stage 'systemd') -File
$fix += Get-Item (Join-Path $stage 'device-install.sh')
$fix += @(Get-ChildItem (Join-Path $stage 'webconfig') -Recurse -File -Include 'bt-*', '*.sh', '*.service', '*.sudoers', 'override.conf*', '*.conf')
$fix | Sort-Object FullName -Unique | ForEach-Object { ConvertTo-Lf $_.FullName }
Log "пакую архив"
$tgz = Join-Path $Here "buttontask-deploy-$Version.tgz"
if (Test-Path $tgz) { Remove-Item $tgz }
& tar.exe -C $stage -czf $tgz .
if ($LASTEXITCODE -ne 0) { Die "tar завершился с ошибкой" }
Remove-Item -Recurse -Force $stage
Log "готово: $tgz"