Files

202 lines
5.9 KiB
PowerShell
Raw Permalink Normal View History

2026-07-29 15:26:40 +12:00
<#
.SYNOPSIS
Builds, verifies, and installs a signed Memby release on an Android TV.
.DESCRIPTION
Uses the permanent signing configuration stored in the current Windows user's
MEMBY_KEYSTORE environment variables. The APK is installed directly with ADB;
no Gitea release or update server is required.
.EXAMPLE
.\deploy-tv.ps1
.EXAMPLE
.\deploy-tv.ps1 -Device 10.0.0.238:35343 -SkipTests
#>
#Requires -Version 7.2
[CmdletBinding()]
param(
[Parameter()]
[ValidatePattern('^[A-Za-z0-9._:-]+$')]
[string] $Device = '10.0.0.238:35343',
[Parameter()]
[switch] $SkipTests
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$root = $PSScriptRoot
$packageName = 'com.ponzischeme89.memby'
function Invoke-Checked {
param(
[Parameter(Mandatory)]
[string] $FilePath,
[Parameter()]
[string[]] $Arguments = @()
)
& $FilePath @Arguments
if ($LASTEXITCODE -ne 0) {
throw "'$FilePath' failed with exit code $LASTEXITCODE."
}
}
function Get-AndroidSdk {
if ($env:ANDROID_HOME -and (Test-Path -LiteralPath $env:ANDROID_HOME)) {
return $env:ANDROID_HOME
}
$propertiesPath = Join-Path $root 'local.properties'
if (Test-Path -LiteralPath $propertiesPath) {
$sdkLine = Get-Content -LiteralPath $propertiesPath |
Where-Object { $_ -match '^sdk\.dir=' } |
Select-Object -First 1
if ($sdkLine) {
$sdkPath = $sdkLine.Substring($sdkLine.IndexOf('=') + 1).
Replace('\:', ':').
Replace('\\', '\')
if (Test-Path -LiteralPath $sdkPath) {
return $sdkPath
}
}
}
throw 'Android SDK not found. Set ANDROID_HOME or sdk.dir in local.properties.'
}
function Import-UserSigningEnvironment {
foreach ($name in @(
'MEMBY_KEYSTORE',
'MEMBY_KEYSTORE_PASSWORD',
'MEMBY_KEY_ALIAS',
'MEMBY_KEY_PASSWORD'
)) {
$value = [Environment]::GetEnvironmentVariable($name, 'Process')
if ([string]::IsNullOrWhiteSpace($value)) {
$value = [Environment]::GetEnvironmentVariable($name, 'User')
}
if ([string]::IsNullOrWhiteSpace($value)) {
throw "Release signing variable $name is not configured."
}
[Environment]::SetEnvironmentVariable($name, $value, 'Process')
}
if (-not (Test-Path -LiteralPath $env:MEMBY_KEYSTORE -PathType Leaf)) {
throw "Release keystore not found: $env:MEMBY_KEYSTORE"
}
}
Write-Host ''
Write-Host '╭─ Memby TV deployment' -ForegroundColor Magenta
Write-Host "│ Device $Device" -ForegroundColor DarkGray
Write-Host '│ Build signed release' -ForegroundColor DarkGray
Write-Host '╰─' -ForegroundColor Magenta
Write-Host ''
Import-UserSigningEnvironment
$sdk = Get-AndroidSdk
$adb = Join-Path $sdk 'platform-tools\adb.exe'
if (-not (Test-Path -LiteralPath $adb -PathType Leaf)) {
throw "ADB not found: $adb"
}
$buildTools = Get-ChildItem -LiteralPath (Join-Path $sdk 'build-tools') -Directory |
Sort-Object { [version]$_.Name } -Descending |
Select-Object -First 1
if (-not $buildTools) {
throw 'Android SDK Build Tools are not installed.'
}
$apkSigner = Join-Path $buildTools.FullName 'apksigner.bat'
if (-not (Test-Path -LiteralPath $apkSigner -PathType Leaf)) {
throw "APK signer not found: $apkSigner"
}
$jdkHome = 'C:\Program Files\Android\Android Studio\jbr'
if (Test-Path -LiteralPath $jdkHome) {
$env:JAVA_HOME = $jdkHome
}
Write-Host '● Building release APK' -ForegroundColor Cyan
$gradleArguments = @('--console=plain')
if (-not $SkipTests) {
$gradleArguments += 'testDebugUnitTest'
}
$gradleArguments += 'assembleRelease'
Invoke-Checked -FilePath (Join-Path $root 'gradlew.bat') -Arguments $gradleArguments
$apk = Join-Path $root 'app\build\outputs\apk\release\app-release.apk'
if (-not (Test-Path -LiteralPath $apk -PathType Leaf)) {
$unsigned = Join-Path $root 'app\build\outputs\apk\release\app-release-unsigned.apk'
if (Test-Path -LiteralPath $unsigned) {
throw 'Gradle produced an unsigned APK; deployment was stopped.'
}
throw "Release APK not found: $apk"
}
Write-Host ''
Write-Host '● Verifying release signature' -ForegroundColor Cyan
Invoke-Checked -FilePath $apkSigner -Arguments @(
'verify',
'--verbose',
'--print-certs',
$apk
)
Write-Host ''
Write-Host '● Connecting to Android TV' -ForegroundColor Cyan
& $adb connect $Device
if ($LASTEXITCODE -ne 0) {
throw "Unable to connect to Android TV at $Device."
}
Invoke-Checked -FilePath $adb -Arguments @('-s', $Device, 'get-state')
Write-Host ''
Write-Host '● Installing release' -ForegroundColor Cyan
Invoke-Checked -FilePath $adb -Arguments @(
'-s', $Device,
'install',
'-r',
'--no-streaming',
$apk
)
$packageDetails = & $adb -s $Device shell dumpsys package $packageName
if ($LASTEXITCODE -ne 0) {
throw 'The APK installed, but its package details could not be read.'
}
$versionName = (
$packageDetails |
Select-String -Pattern 'versionName=([^\s]+)' |
Select-Object -First 1
).Matches.Groups[1].Value
if ([string]::IsNullOrWhiteSpace($versionName)) {
throw "Installed package $packageName was not found."
}
$resolveOutput = & $adb -s $Device shell cmd package resolve-activity --brief `
-a android.intent.action.MAIN `
-c android.intent.category.LEANBACK_LAUNCHER `
$packageName
$component = $resolveOutput |
Where-Object { $_.Trim() -match '^[A-Za-z0-9._]+/[A-Za-z0-9._$]+$' } |
Select-Object -Last 1
if ($LASTEXITCODE -eq 0 -and $component) {
$component = $component.Trim()
Invoke-Checked -FilePath $adb -Arguments @(
'-s', $Device,
'shell', 'am', 'start',
'-n', $component
)
}
Write-Host ''
Write-Host "✓ Memby $versionName installed on $Device" -ForegroundColor Green
Write-Host " Package: $packageName" -ForegroundColor DarkGray
Write-Host " APK: $apk" -ForegroundColor DarkGray