param( [Parameter(Mandatory = $true)] [string]$VersionName, [Parameter(Mandatory = $true)] [int]$VersionCode ) $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $env:JAVA_HOME = "C:\Program Files\Android\Android Studio1\jbr" $env:PATH = "$env:JAVA_HOME\bin;$env:PATH" $Server = "root@5.188.21.226" $BaseUrl = "https://5.188.21.226/updates" $RemoteDir = "/var/www/html/updates" $SshKey = Join-Path $env:USERPROFILE ".ssh\durakometr_release" if ($VersionName -notmatch '^[0-9A-Za-z._-]+$') { throw "Unsafe versionName: $VersionName" } if ($VersionCode -le 0) { throw "Invalid versionCode: $VersionCode" } $ProjectRoot = $PSScriptRoot $Apk = Join-Path $ProjectRoot "app\build\outputs\apk\release\app-release.apk" $NotesFile = Join-Path $ProjectRoot "release-notes.txt" if (-not (Test-Path $Apk)) { throw "APK not found: $Apk" } $ApkInfo = Get-Item $Apk if ($ApkInfo.Length -lt 1MB) { throw "APK is suspiciously small: $($ApkInfo.Length) bytes" } if (-not (Test-Path $SshKey)) { throw "SSH key not found: $SshKey" } $BuildToolsRoot = Join-Path $env:LOCALAPPDATA "Android\Sdk\build-tools" if (-not (Test-Path $BuildToolsRoot)) { throw "Android build-tools not found: $BuildToolsRoot" } $ApkSigner = Get-ChildItem $BuildToolsRoot -Directory | Sort-Object Name -Descending | ForEach-Object { $Candidate = Join-Path $_.FullName "apksigner.bat" if (Test-Path $Candidate) { $Candidate } } | Select-Object -First 1 if (-not $ApkSigner) { throw "apksigner.bat not found" } & $ApkSigner verify --verbose $Apk if ($LASTEXITCODE -ne 0) { throw "APK signature verification failed" } $Sha256 = (Get-FileHash -Algorithm SHA256 $Apk).Hash.ToLowerInvariant() $Notes = "Application update" if (Test-Path $NotesFile) { $LoadedNotes = (Get-Content $NotesFile -Raw -Encoding UTF8).Trim() if (-not [string]::IsNullOrWhiteSpace($LoadedNotes)) { $Notes = $LoadedNotes } } $RemoteName = "durakometr-$VersionName.apk" $RemoteTemp = "$RemoteDir/.$RemoteName.uploading" $RemoteFinal = "$RemoteDir/$RemoteName" $LocalJson = Join-Path $env:TEMP "durakometr-latest-$VersionCode.json" $RemoteJsonTemp = "$RemoteDir/.latest.json.uploading" $RemoteJsonFinal = "$RemoteDir/latest.json" $Metadata = [ordered]@{ versionCode = $VersionCode versionName = $VersionName apkUrl = "$BaseUrl/$RemoteName" sha256 = $Sha256 mandatory = $false notes = $Notes } $Json = $Metadata | ConvertTo-Json -Depth 5 [System.IO.File]::WriteAllText( $LocalJson, $Json, [System.Text.UTF8Encoding]::new($false) ) $SshArgs = @( "-i", $SshKey, "-o", "BatchMode=yes", "-o", "ConnectTimeout=10" ) Write-Host "" Write-Host "=== Durakometr release $VersionName ($VersionCode) ===" Write-Host "APK: $([math]::Round($ApkInfo.Length / 1MB, 2)) MB" Write-Host "SHA256: $Sha256" Write-Host "" & scp.exe @SshArgs $Apk "${Server}:$RemoteTemp" if ($LASTEXITCODE -ne 0) { throw "APK upload failed" } $RemoteSizeLines = & ssh.exe @SshArgs $Server "stat -c%s '$RemoteTemp'" if ($LASTEXITCODE -ne 0) { throw "Failed to read remote APK size" } $RemoteSizeText = ($RemoteSizeLines | Select-Object -Last 1).ToString().Trim() [long]$RemoteSize = 0 if (-not [long]::TryParse($RemoteSizeText, [ref]$RemoteSize)) { throw "Invalid remote APK size: $RemoteSizeText" } if ($RemoteSize -ne $ApkInfo.Length) { & ssh.exe @SshArgs $Server "rm -f '$RemoteTemp'" throw "Size mismatch. Local=$($ApkInfo.Length), remote=$RemoteSize" } & scp.exe @SshArgs $LocalJson "${Server}:$RemoteJsonTemp" if ($LASTEXITCODE -ne 0) { & ssh.exe @SshArgs $Server "rm -f '$RemoteTemp'" throw "latest.json upload failed" } $PublishCommand = "set -e; " + "test -s '$RemoteTemp'; " + "test -s '$RemoteJsonTemp'; " + "mv -f '$RemoteTemp' '$RemoteFinal'; " + "chmod 644 '$RemoteFinal'; " + "mv -f '$RemoteJsonTemp' '$RemoteJsonFinal'; " + "chmod 644 '$RemoteJsonFinal'; " + "stat -c '%n %s bytes' '$RemoteFinal'; " + "cat '$RemoteJsonFinal'" & ssh.exe @SshArgs $Server $PublishCommand if ($LASTEXITCODE -ne 0) { throw "Server publish step failed" } Remove-Item $LocalJson -Force -ErrorAction SilentlyContinue Write-Host "" Write-Host "==============================================" Write-Host "RELEASE PUBLISHED" Write-Host "Version: $VersionName ($VersionCode)" Write-Host "APK: $([math]::Round($ApkInfo.Length / 1MB, 2)) MB" Write-Host "SHA256: $Sha256" Write-Host "$BaseUrl/$RemoteName" Write-Host "=============================================="