202 lines
8.2 KiB
PowerShell
202 lines
8.2 KiB
PowerShell
param(
|
|
[string]$ServerStatusUrl = "https://qsfera.kusoft.xyz/status.php",
|
|
[string]$ExpectedServerVersion = "7.0.0",
|
|
[string]$ExpectedServerEdition = "stable",
|
|
|
|
[string]$AndroidManifestUrl = "https://argus.kusoft.xyz/api/apps/qsfera-mobile/manifest?platform=android&channel=stable",
|
|
[string]$ExpectedAndroidSlug = "qsfera-mobile",
|
|
[string]$ExpectedAndroidPackageName = "eu.qsfera.android",
|
|
[string]$ExpectedAndroidVersion = "1.3.12",
|
|
[int]$ExpectedAndroidVersionCode = 40,
|
|
[string]$ExpectedAndroidChannel = "stable",
|
|
[string]$ExpectedAndroidPlatform = "android",
|
|
[string]$ExpectedAndroidPackageKind = "apk",
|
|
[string]$ExpectedAndroidSha256 = "279d00c6a43072caa0b26269f547fb0dc3268088aae5cd9f4ccfbbb485c35ded",
|
|
[string]$AndroidDownloadUrl = "https://argus.kusoft.xyz/api/apps/qsfera-mobile/download/latest?platform=android&channel=stable",
|
|
[switch]$VerifyAndroidDownload,
|
|
|
|
[string]$DesktopUpdateFeedUrl = "https://qsfera.kusoft.xyz/desktop/updates/stable.xml",
|
|
[switch]$RequireDesktopUpdate
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function Fail($Message) {
|
|
Write-Error $Message
|
|
exit 1
|
|
}
|
|
|
|
function Assert-AbsoluteHttpsUrl($Name, $Url) {
|
|
$parsedUrl = $null
|
|
if (-not [System.Uri]::TryCreate($Url, [System.UriKind]::Absolute, [ref]$parsedUrl)) {
|
|
Fail "$Name must be an absolute URL: $Url"
|
|
}
|
|
|
|
if ($parsedUrl.Scheme -ne "https") {
|
|
Fail "$Name must use HTTPS: $Url"
|
|
}
|
|
}
|
|
|
|
function Assert-Equal($Name, $Actual, $Expected) {
|
|
if ([string]$Actual -ne [string]$Expected) {
|
|
Fail "$Name expected '$Expected' but got '$Actual'."
|
|
}
|
|
|
|
Write-Host "OK: $Name = $Actual"
|
|
}
|
|
|
|
function Assert-True($Name, [bool]$Value) {
|
|
if (-not $Value) {
|
|
Fail "$Name expected True but got False."
|
|
}
|
|
|
|
Write-Host "OK: $Name = True"
|
|
}
|
|
|
|
function Invoke-CheckedWebRequest($Name, $Url) {
|
|
Assert-AbsoluteHttpsUrl $Name $Url
|
|
|
|
try {
|
|
$response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 30
|
|
} catch {
|
|
Fail "$Name request failed for ${Url}: $($_.Exception.Message)"
|
|
}
|
|
|
|
if ($response.StatusCode -ne 200) {
|
|
Fail "$Name must return HTTP 200. Actual status: $($response.StatusCode)."
|
|
}
|
|
|
|
Write-Host "OK: $Name returned HTTP 200"
|
|
return $response
|
|
}
|
|
|
|
function Convert-JsonContent($Name, $Content) {
|
|
try {
|
|
return $Content | ConvertFrom-Json -ErrorAction Stop
|
|
} catch {
|
|
Fail "$Name returned invalid JSON: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
function Get-RequiredProperty($Object, $Name, $Context) {
|
|
$property = $Object.PSObject.Properties[$Name]
|
|
if ($null -eq $property) {
|
|
Fail "$Context is missing '$Name'."
|
|
}
|
|
|
|
return $property.Value
|
|
}
|
|
|
|
function Test-Sha256String($Value) {
|
|
return [regex]::IsMatch($Value, "^[0-9a-f]{64}$")
|
|
}
|
|
|
|
Write-Host "Checking QSfera production server status..."
|
|
$serverResponse = Invoke-CheckedWebRequest "Server status endpoint" $ServerStatusUrl
|
|
$serverStatus = Convert-JsonContent "Server status endpoint" $serverResponse.Content
|
|
Assert-True "Server installed" ([bool](Get-RequiredProperty $serverStatus "installed" "Server status"))
|
|
Assert-Equal "Server maintenance" (Get-RequiredProperty $serverStatus "maintenance" "Server status") "False"
|
|
Assert-Equal "Server needsDbUpgrade" (Get-RequiredProperty $serverStatus "needsDbUpgrade" "Server status") "False"
|
|
Assert-Equal "Server edition" (Get-RequiredProperty $serverStatus "edition" "Server status") $ExpectedServerEdition
|
|
Assert-Equal "Server productversion" (Get-RequiredProperty $serverStatus "productversion" "Server status") $ExpectedServerVersion
|
|
|
|
Write-Host "Checking QSfera Android Argus manifest..."
|
|
$manifestResponse = Invoke-CheckedWebRequest "Android Argus manifest" $AndroidManifestUrl
|
|
$manifest = Convert-JsonContent "Android Argus manifest" $manifestResponse.Content
|
|
Assert-Equal "Android app slug" (Get-RequiredProperty $manifest "slug" "Android manifest") $ExpectedAndroidSlug
|
|
Assert-Equal "Android package name" (Get-RequiredProperty $manifest "androidPackageName" "Android manifest") $ExpectedAndroidPackageName
|
|
|
|
$release = Get-RequiredProperty $manifest "release" "Android manifest"
|
|
if ($null -eq $release) {
|
|
Fail "Android manifest release must not be null."
|
|
}
|
|
|
|
Assert-Equal "Android release version" (Get-RequiredProperty $release "version" "Android release") $ExpectedAndroidVersion
|
|
Assert-Equal "Android versionCode" (Get-RequiredProperty $release "androidVersionCode" "Android release") $ExpectedAndroidVersionCode
|
|
Assert-Equal "Android release channel" (Get-RequiredProperty $release "channel" "Android release") $ExpectedAndroidChannel
|
|
Assert-Equal "Android release platform" (Get-RequiredProperty $release "platform" "Android release") $ExpectedAndroidPlatform
|
|
Assert-Equal "Android package kind" (Get-RequiredProperty $release "packageKind" "Android release") $ExpectedAndroidPackageKind
|
|
$releaseSha256 = Get-RequiredProperty $release "sha256" "Android release"
|
|
Assert-True "Android SHA-256 shape" (Test-Sha256String $releaseSha256)
|
|
Assert-Equal "Android SHA-256" $releaseSha256 $ExpectedAndroidSha256
|
|
|
|
if ($VerifyAndroidDownload) {
|
|
Write-Host "Checking QSfera Android latest APK download..."
|
|
Assert-AbsoluteHttpsUrl "Android download URL" $AndroidDownloadUrl
|
|
$tempFile = [System.IO.Path]::GetTempFileName()
|
|
try {
|
|
try {
|
|
Invoke-WebRequest -Uri $AndroidDownloadUrl -UseBasicParsing -TimeoutSec 120 -OutFile $tempFile | Out-Null
|
|
} catch {
|
|
Fail "Android download request failed for ${AndroidDownloadUrl}: $($_.Exception.Message)"
|
|
}
|
|
|
|
$downloadSize = (Get-Item -LiteralPath $tempFile).Length
|
|
$expectedSize = Get-RequiredProperty $release "packageSizeBytes" "Android release"
|
|
Assert-Equal "Android APK size" $downloadSize $expectedSize
|
|
|
|
$downloadSha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $tempFile).Hash.ToLowerInvariant()
|
|
Assert-Equal "Android APK download SHA-256" $downloadSha256 $ExpectedAndroidSha256
|
|
} finally {
|
|
Remove-Item -LiteralPath $tempFile -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Write-Host "Checking QSfera Desktop update feed..."
|
|
$desktopResponse = Invoke-CheckedWebRequest "Desktop update feed" $DesktopUpdateFeedUrl
|
|
try {
|
|
[xml]$desktopFeed = $desktopResponse.Content
|
|
} catch {
|
|
Fail "Desktop update feed returned invalid XML: $($_.Exception.Message)"
|
|
}
|
|
|
|
if ($desktopFeed.DocumentElement.Name -ne "QSfera") {
|
|
Fail "Desktop update feed root element must be <QSfera>."
|
|
}
|
|
|
|
foreach ($elementName in @("version", "versionstring", "web", "downloadurl")) {
|
|
if ($null -eq $desktopFeed.QSfera.$elementName) {
|
|
Fail "Desktop update feed is missing <$elementName>."
|
|
}
|
|
}
|
|
|
|
$desktopWeb = [string]$desktopFeed.QSfera.web
|
|
if (-not [string]::IsNullOrWhiteSpace($desktopWeb)) {
|
|
Assert-AbsoluteHttpsUrl "Desktop update feed <web>" $desktopWeb
|
|
}
|
|
|
|
$desktopVersion = [string]$desktopFeed.QSfera.version
|
|
$desktopVersionString = [string]$desktopFeed.QSfera.versionstring
|
|
$desktopDownloadUrl = [string]$desktopFeed.QSfera.downloadurl
|
|
|
|
if ([string]::IsNullOrWhiteSpace($desktopVersion)) {
|
|
if ($RequireDesktopUpdate) {
|
|
Fail "Desktop update feed must advertise a concrete version when -RequireDesktopUpdate is set."
|
|
}
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($desktopDownloadUrl)) {
|
|
Fail "Desktop no-update feed must leave <downloadurl> empty when <version> is empty."
|
|
}
|
|
|
|
Write-Host "OK: Desktop update feed is a valid no-update feed"
|
|
} else {
|
|
$parsedVersion = $null
|
|
if (-not [System.Version]::TryParse($desktopVersion, [ref]$parsedVersion)) {
|
|
Fail "Desktop update feed <version> must be parseable as a version number."
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($desktopVersionString)) {
|
|
Fail "Desktop update feed must set <versionstring> when <version> is present."
|
|
}
|
|
|
|
Assert-AbsoluteHttpsUrl "Desktop update feed <downloadurl>" $desktopDownloadUrl
|
|
$downloadUri = [System.Uri]$desktopDownloadUrl
|
|
if (-not $downloadUri.AbsolutePath.EndsWith(".msi", [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
Fail "Desktop Windows updater expects an MSI download URL."
|
|
}
|
|
|
|
Write-Host "OK: Desktop update feed advertises version $desktopVersionString"
|
|
}
|
|
|
|
Write-Host "QSfera production endpoint verification passed."
|