Align Android Argus publication with SSH contract
This commit is contained in:
+353
-122
@@ -1,141 +1,372 @@
|
||||
param(
|
||||
[string]$BaseUrl = 'https://argus.kusoft.xyz',
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Username,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Password,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Slug,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Name,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Summary,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Description,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$AndroidPackageName,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Version,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateRange(1, [int]::MaxValue)]
|
||||
[int]$AndroidVersionCode,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$PackagePath,
|
||||
[string]$Channel = 'stable',
|
||||
[string]$Platform = 'android',
|
||||
[string]$PackageKind = 'apk',
|
||||
[string]$Notes,
|
||||
[string]$RepositoryUrl,
|
||||
[string]$HomepageUrl = 'https://qsfera.kusoft.xyz',
|
||||
[switch]$Unlisted
|
||||
[string]$MetadataPath,
|
||||
[string]$PublicBaseUrl = 'https://argus.kusoft.xyz',
|
||||
[string]$RemoteHost = '192.168.0.185',
|
||||
[string]$RemoteUser = 'sevenhill',
|
||||
[int]$RemotePort = 22,
|
||||
[string]$RemoteTempDirectory = '/tmp',
|
||||
[string]$ArgusData = '/srv/argus-data',
|
||||
[string]$GeneratedScriptPath,
|
||||
[switch]$PrepareOnly,
|
||||
[switch]$RestartArgus
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Add-Type -AssemblyName System.Net.Http
|
||||
|
||||
function Read-HttpBody([System.Net.Http.HttpResponseMessage]$Response) {
|
||||
if ($null -eq $Response.Content) {
|
||||
return ''
|
||||
function Fail([string]$Message) {
|
||||
Write-Error $Message
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Get-LatestMetadataPath {
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$publicationRoot = Join-Path $repoRoot 'build\argus-publication'
|
||||
if (-not (Test-Path -LiteralPath $publicationRoot)) {
|
||||
Fail "Argus publication directory was not found: $publicationRoot"
|
||||
}
|
||||
|
||||
return $Response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
|
||||
}
|
||||
|
||||
function Ensure-Success(
|
||||
[System.Net.Http.HttpResponseMessage]$Response,
|
||||
[string]$Operation
|
||||
) {
|
||||
if ($Response.IsSuccessStatusCode) {
|
||||
return
|
||||
$metadata = Get-ChildItem -LiteralPath $publicationRoot -Recurse -File -Filter 'argus-publication.json' |
|
||||
Sort-Object LastWriteTimeUtc -Descending |
|
||||
Select-Object -First 1
|
||||
if (-not $metadata) {
|
||||
Fail "No argus-publication.json file was found under $publicationRoot"
|
||||
}
|
||||
|
||||
$body = Read-HttpBody $Response
|
||||
throw "$Operation failed with status $([int]$Response.StatusCode) ($($Response.ReasonPhrase)). Body: $body"
|
||||
return $metadata.FullName
|
||||
}
|
||||
|
||||
function New-JsonContent([string]$Json) {
|
||||
return [System.Net.Http.StringContent]::new($Json, [System.Text.Encoding]::UTF8, 'application/json')
|
||||
function Get-RequiredString($Object, [string]$Name) {
|
||||
$property = $Object.PSObject.Properties[$Name]
|
||||
if ($null -eq $property -or [string]::IsNullOrWhiteSpace([string]$property.Value)) {
|
||||
Fail "Publication metadata is missing '$Name'."
|
||||
}
|
||||
|
||||
return [string]$property.Value
|
||||
}
|
||||
|
||||
$resolvedPackagePath = (Resolve-Path -LiteralPath $PackagePath).Path
|
||||
if (-not (Test-Path $resolvedPackagePath)) {
|
||||
throw "Package file was not found: $resolvedPackagePath"
|
||||
function Quote-BashSingle([string]$Value) {
|
||||
if ($null -eq $Value) {
|
||||
$Value = ''
|
||||
}
|
||||
|
||||
return "'" + ($Value -replace "'", "'\''") + "'"
|
||||
}
|
||||
|
||||
$baseUri = $BaseUrl.TrimEnd('/')
|
||||
$cookieContainer = [System.Net.CookieContainer]::new()
|
||||
$handler = [System.Net.Http.HttpClientHandler]::new()
|
||||
$handler.CookieContainer = $cookieContainer
|
||||
$handler.UseCookies = $true
|
||||
$client = [System.Net.Http.HttpClient]::new($handler)
|
||||
$client.Timeout = [TimeSpan]::FromMinutes(15)
|
||||
function Join-RemotePath([string]$Directory, [string]$Leaf) {
|
||||
return $Directory.TrimEnd('/') + '/' + $Leaf
|
||||
}
|
||||
|
||||
function Invoke-CheckedNative([string]$Program, [string[]]$Arguments) {
|
||||
& $Program @Arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Fail "$Program failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-Slug([string]$Slug) {
|
||||
if ($Slug -notmatch '^[a-z0-9][a-z0-9-]{0,99}$') {
|
||||
Fail "Argus slug must contain only lowercase letters, digits, and hyphens, max 100 chars: $Slug"
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-ManifestValue($Manifest, [string]$Name, $Actual, $Expected) {
|
||||
if ([string]$Actual -ne [string]$Expected) {
|
||||
Fail "Published manifest $Name expected '$Expected' but got '$Actual'."
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($MetadataPath)) {
|
||||
$MetadataPath = Get-LatestMetadataPath
|
||||
}
|
||||
|
||||
$resolvedMetadataPath = (Resolve-Path -LiteralPath $MetadataPath).Path
|
||||
$metadata = Get-Content -LiteralPath $resolvedMetadataPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
|
||||
$packagePath = (Resolve-Path -LiteralPath (Get-RequiredString $metadata 'packagePath')).Path
|
||||
$slug = Get-RequiredString $metadata 'slug'
|
||||
$name = Get-RequiredString $metadata 'name'
|
||||
$summary = Get-RequiredString $metadata 'summary'
|
||||
$description = Get-RequiredString $metadata 'description'
|
||||
$version = Get-RequiredString $metadata 'version'
|
||||
$channel = (Get-RequiredString $metadata 'channel').ToLowerInvariant()
|
||||
$platform = (Get-RequiredString $metadata 'platform').ToLowerInvariant()
|
||||
$packageKind = (Get-RequiredString $metadata 'packageKind').ToLowerInvariant()
|
||||
$notes = if ($metadata.PSObject.Properties['notes']) { [string]$metadata.notes } else { '' }
|
||||
$repositoryUrl = if ($metadata.PSObject.Properties['repositoryUrl']) { [string]$metadata.repositoryUrl } else { '' }
|
||||
$homepageUrl = if ($metadata.PSObject.Properties['homepageUrl']) { [string]$metadata.homepageUrl } else { '' }
|
||||
$isListed = if ($metadata.PSObject.Properties['isListed'] -and [bool]$metadata.isListed) { '1' } else { '0' }
|
||||
|
||||
Assert-Slug $slug
|
||||
if ($platform -ne 'android') {
|
||||
Fail "QSfera Android publication requires platform=android. Actual platform: $platform"
|
||||
}
|
||||
if ($packageKind -ne 'apk') {
|
||||
Fail "QSfera Android publication requires packageKind=apk. Actual packageKind: $packageKind"
|
||||
}
|
||||
if (-not $packagePath.EndsWith('.apk', [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
Fail "QSfera Android publication requires an APK package: $packagePath"
|
||||
}
|
||||
|
||||
$scp = Get-Command scp -ErrorAction SilentlyContinue
|
||||
$ssh = Get-Command ssh -ErrorAction SilentlyContinue
|
||||
if (-not $scp -or -not $ssh) {
|
||||
Fail 'Both scp and ssh must be available for SSH-only Argus publication.'
|
||||
}
|
||||
|
||||
$localSize = (Get-Item -LiteralPath $packagePath).Length
|
||||
$localSha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $packagePath).Hash.ToLowerInvariant()
|
||||
$packageFileName = Split-Path -Leaf $packagePath
|
||||
$remotePackagePath = Join-RemotePath $RemoteTempDirectory $packageFileName
|
||||
$token = [guid]::NewGuid().ToString('N')
|
||||
$remoteScriptPath = Join-RemotePath $RemoteTempDirectory "$slug-publish-$token.sh"
|
||||
$localScriptPath = if ([string]::IsNullOrWhiteSpace($GeneratedScriptPath)) {
|
||||
Join-Path ([System.IO.Path]::GetTempPath()) "$slug-publish-$token.sh"
|
||||
} else {
|
||||
$GeneratedScriptPath
|
||||
}
|
||||
$remote = "$RemoteUser@$RemoteHost"
|
||||
$sshPort = [string]$RemotePort
|
||||
|
||||
$restartCommand = if ($RestartArgus) {
|
||||
'docker restart argus >/dev/null'
|
||||
} else {
|
||||
':'
|
||||
}
|
||||
|
||||
$scriptTemplate = @'
|
||||
set -euo pipefail
|
||||
|
||||
cleanup() {
|
||||
rm -f "$SOURCE_FILE" "$0"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
export ARGUS_DATA=__ARGUS_DATA__
|
||||
export SOURCE_FILE=__SOURCE_FILE__
|
||||
|
||||
export ARGUS_SLUG=__ARGUS_SLUG__
|
||||
export ARGUS_NAME=__ARGUS_NAME__
|
||||
export ARGUS_SUMMARY=__ARGUS_SUMMARY__
|
||||
export ARGUS_DESCRIPTION=__ARGUS_DESCRIPTION__
|
||||
export ARGUS_REPOSITORY_URL=__ARGUS_REPOSITORY_URL__
|
||||
export ARGUS_HOMEPAGE_URL=__ARGUS_HOMEPAGE_URL__
|
||||
export ARGUS_IS_LISTED=__ARGUS_IS_LISTED__
|
||||
|
||||
export ARGUS_VERSION=__ARGUS_VERSION__
|
||||
export ARGUS_CHANNEL=__ARGUS_CHANNEL__
|
||||
export ARGUS_PLATFORM=__ARGUS_PLATFORM__
|
||||
export ARGUS_PACKAGE_KIND=__ARGUS_PACKAGE_KIND__
|
||||
export ARGUS_NOTES=__ARGUS_NOTES__
|
||||
|
||||
python3 - "$SOURCE_FILE" <<'PY'
|
||||
import hashlib
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sqlite3
|
||||
import sys
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(sys.argv[1]).resolve()
|
||||
data_root = Path(os.environ.get("ARGUS_DATA", "/srv/argus-data")).resolve()
|
||||
db_path = data_root / "argus.db"
|
||||
packages_root = data_root / "Packages"
|
||||
|
||||
slug = os.environ["ARGUS_SLUG"].strip()
|
||||
name = os.environ["ARGUS_NAME"].strip()
|
||||
summary = os.environ["ARGUS_SUMMARY"].strip()
|
||||
description = os.environ["ARGUS_DESCRIPTION"].strip()
|
||||
repository_url = os.environ.get("ARGUS_REPOSITORY_URL", "").strip() or None
|
||||
homepage_url = os.environ.get("ARGUS_HOMEPAGE_URL", "").strip() or None
|
||||
is_listed = 1 if os.environ.get("ARGUS_IS_LISTED", "1").strip() != "0" else 0
|
||||
|
||||
version = os.environ["ARGUS_VERSION"].strip()
|
||||
channel = os.environ.get("ARGUS_CHANNEL", "stable").strip().lower()
|
||||
platform = os.environ.get("ARGUS_PLATFORM", "generic").strip().lower()
|
||||
package_kind = os.environ.get("ARGUS_PACKAGE_KIND", "binary").strip().lower()
|
||||
notes = os.environ.get("ARGUS_NOTES", "").strip() or None
|
||||
|
||||
if not re.fullmatch(r"[a-z0-9][a-z0-9-]{0,99}", slug):
|
||||
raise SystemExit("ARGUS_SLUG must contain only lowercase letters, digits, and hyphens, max 100 chars.")
|
||||
if not source.is_file():
|
||||
raise SystemExit(f"SOURCE_FILE does not exist: {source}")
|
||||
if not db_path.is_file():
|
||||
raise SystemExit(f"Argus database does not exist: {db_path}")
|
||||
for key, value in {
|
||||
"ARGUS_NAME": name,
|
||||
"ARGUS_SUMMARY": summary,
|
||||
"ARGUS_DESCRIPTION": description,
|
||||
"ARGUS_VERSION": version,
|
||||
}.items():
|
||||
if not value:
|
||||
raise SystemExit(f"{key} is required.")
|
||||
|
||||
release_id = str(uuid.uuid4())
|
||||
now = datetime.now(timezone.utc).isoformat(timespec="microseconds")
|
||||
safe_version = re.sub(r"[^a-zA-Z0-9._-]+", "-", version).strip("-._") or "release"
|
||||
extension = source.suffix or ".bin"
|
||||
stored_name = f"{datetime.now(timezone.utc):%Y%m%d%H%M%S}-{safe_version}-{release_id.replace('-', '')}{extension}"
|
||||
stored_relative_path = f"{slug}/{stored_name}"
|
||||
target_dir = packages_root / slug
|
||||
target_path = target_dir / stored_name
|
||||
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(source, target_path)
|
||||
|
||||
size_bytes = target_path.stat().st_size
|
||||
sha256 = hashlib.sha256(target_path.read_bytes()).hexdigest()
|
||||
content_type = mimetypes.guess_type(source.name)[0] or "application/octet-stream"
|
||||
if source.suffix.lower() == ".apk":
|
||||
content_type = "application/vnd.android.package-archive"
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
try:
|
||||
conn.execute("PRAGMA foreign_keys = ON")
|
||||
conn.execute("BEGIN")
|
||||
|
||||
row = conn.execute('SELECT "Id" FROM "Apps" WHERE "Slug" = ?', (slug,)).fetchone()
|
||||
if row is None:
|
||||
app_id = str(uuid.uuid4())
|
||||
conn.execute(
|
||||
'''
|
||||
INSERT INTO "Apps"
|
||||
("Id", "Slug", "Name", "Summary", "Description", "RepositoryUrl", "HomepageUrl", "IsListed", "CreatedAt", "UpdatedAt")
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''',
|
||||
(app_id, slug, name, summary, description, repository_url, homepage_url, is_listed, now, now),
|
||||
)
|
||||
else:
|
||||
app_id = row[0]
|
||||
conn.execute(
|
||||
'''
|
||||
UPDATE "Apps"
|
||||
SET "Name" = ?,
|
||||
"Summary" = ?,
|
||||
"Description" = ?,
|
||||
"RepositoryUrl" = ?,
|
||||
"HomepageUrl" = ?,
|
||||
"IsListed" = ?,
|
||||
"UpdatedAt" = ?
|
||||
WHERE "Id" = ?
|
||||
''',
|
||||
(name, summary, description, repository_url, homepage_url, is_listed, now, app_id),
|
||||
)
|
||||
|
||||
duplicate = conn.execute(
|
||||
'''
|
||||
SELECT "Id"
|
||||
FROM "Releases"
|
||||
WHERE "CatalogAppId" = ? AND "Version" = ? AND "Channel" = ? AND "Platform" = ?
|
||||
''',
|
||||
(app_id, version, channel, platform),
|
||||
).fetchone()
|
||||
if duplicate is not None:
|
||||
raise RuntimeError(f"Release already exists for {slug} {version} {channel} {platform}.")
|
||||
|
||||
conn.execute(
|
||||
'''
|
||||
INSERT INTO "Releases"
|
||||
("Id", "CatalogAppId", "Version", "Channel", "Platform", "PackageKind",
|
||||
"OriginalFileName", "StoredRelativePath", "ContentType", "PackageSizeBytes",
|
||||
"Sha256", "Notes", "PublishedAt")
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''',
|
||||
(
|
||||
release_id,
|
||||
app_id,
|
||||
version,
|
||||
channel,
|
||||
platform,
|
||||
package_kind,
|
||||
source.name,
|
||||
stored_relative_path,
|
||||
content_type,
|
||||
size_bytes,
|
||||
sha256,
|
||||
notes,
|
||||
now,
|
||||
),
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
except Exception:
|
||||
conn.rollback()
|
||||
try:
|
||||
target_path.unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
print(f"Published app={slug} version={version} channel={channel} platform={platform}")
|
||||
print(f"StoredRelativePath={stored_relative_path}")
|
||||
print(f"Size={size_bytes}")
|
||||
print(f"Sha256={sha256}")
|
||||
print(f"Manifest=https://argus.kusoft.xyz/api/apps/{slug}/manifest?platform={platform}&channel={channel}")
|
||||
PY
|
||||
|
||||
curl -fsS "http://127.0.0.1:5105/api/apps/$ARGUS_SLUG/manifest?platform=$ARGUS_PLATFORM&channel=$ARGUS_CHANNEL"
|
||||
__RESTART_COMMAND__
|
||||
'@
|
||||
|
||||
$publicationScript = $scriptTemplate.
|
||||
Replace('__ARGUS_DATA__', (Quote-BashSingle $ArgusData)).
|
||||
Replace('__SOURCE_FILE__', (Quote-BashSingle $remotePackagePath)).
|
||||
Replace('__ARGUS_SLUG__', (Quote-BashSingle $slug)).
|
||||
Replace('__ARGUS_NAME__', (Quote-BashSingle $name)).
|
||||
Replace('__ARGUS_SUMMARY__', (Quote-BashSingle $summary)).
|
||||
Replace('__ARGUS_DESCRIPTION__', (Quote-BashSingle $description)).
|
||||
Replace('__ARGUS_REPOSITORY_URL__', (Quote-BashSingle $repositoryUrl)).
|
||||
Replace('__ARGUS_HOMEPAGE_URL__', (Quote-BashSingle $homepageUrl)).
|
||||
Replace('__ARGUS_IS_LISTED__', (Quote-BashSingle $isListed)).
|
||||
Replace('__ARGUS_VERSION__', (Quote-BashSingle $version)).
|
||||
Replace('__ARGUS_CHANNEL__', (Quote-BashSingle $channel)).
|
||||
Replace('__ARGUS_PLATFORM__', (Quote-BashSingle $platform)).
|
||||
Replace('__ARGUS_PACKAGE_KIND__', (Quote-BashSingle $packageKind)).
|
||||
Replace('__ARGUS_NOTES__', (Quote-BashSingle $notes)).
|
||||
Replace('__RESTART_COMMAND__', $restartCommand)
|
||||
|
||||
[System.IO.File]::WriteAllText($localScriptPath, $publicationScript, [System.Text.UTF8Encoding]::new($false))
|
||||
|
||||
if ($PrepareOnly) {
|
||||
Write-Output 'QSfera Android Argus SSH publication script prepared.'
|
||||
Write-Output "Script: $localScriptPath"
|
||||
Write-Output "APK: $packagePath"
|
||||
Write-Output "Remote APK: $remotePackagePath"
|
||||
Write-Output "Size: $localSize"
|
||||
Write-Output "SHA-256: $localSha256"
|
||||
exit 0
|
||||
}
|
||||
|
||||
try {
|
||||
$loginPayload = @{
|
||||
username = $Username
|
||||
password = $Password
|
||||
} | ConvertTo-Json -Compress
|
||||
|
||||
$loginResponse = $client.PostAsync(
|
||||
"$baseUri/api/admin/session/login",
|
||||
(New-JsonContent $loginPayload)
|
||||
).GetAwaiter().GetResult()
|
||||
Ensure-Success $loginResponse 'Argus login'
|
||||
|
||||
$appPayload = @{
|
||||
name = $Name
|
||||
summary = $Summary
|
||||
description = $Description
|
||||
androidPackageName = $AndroidPackageName
|
||||
repositoryUrl = if ([string]::IsNullOrWhiteSpace($RepositoryUrl)) { $null } else { $RepositoryUrl }
|
||||
homepageUrl = if ([string]::IsNullOrWhiteSpace($HomepageUrl)) { $null } else { $HomepageUrl }
|
||||
isListed = (-not $Unlisted)
|
||||
} | ConvertTo-Json -Compress
|
||||
|
||||
$appResponse = $client.PutAsync(
|
||||
"$baseUri/api/admin/apps/$Slug",
|
||||
(New-JsonContent $appPayload)
|
||||
).GetAwaiter().GetResult()
|
||||
Ensure-Success $appResponse 'Argus app metadata update'
|
||||
|
||||
$form = [System.Net.Http.MultipartFormDataContent]::new()
|
||||
$fields = [ordered]@{
|
||||
Version = $Version
|
||||
Channel = $Channel
|
||||
Platform = $Platform
|
||||
PackageKind = $PackageKind
|
||||
AndroidVersionCode = [string]$AndroidVersionCode
|
||||
}
|
||||
|
||||
foreach ($entry in $fields.GetEnumerator()) {
|
||||
$form.Add([System.Net.Http.StringContent]::new($entry.Value, [System.Text.Encoding]::UTF8), $entry.Key)
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($Notes)) {
|
||||
$form.Add([System.Net.Http.StringContent]::new($Notes, [System.Text.Encoding]::UTF8), 'Notes')
|
||||
}
|
||||
|
||||
$fileStream = [System.IO.File]::OpenRead($resolvedPackagePath)
|
||||
try {
|
||||
$fileContent = [System.Net.Http.StreamContent]::new($fileStream)
|
||||
$fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse('application/vnd.android.package-archive')
|
||||
$form.Add($fileContent, 'Package', [System.IO.Path]::GetFileName($resolvedPackagePath))
|
||||
|
||||
$releaseResponse = $client.PostAsync(
|
||||
"$baseUri/api/admin/apps/$Slug/releases",
|
||||
$form
|
||||
).GetAwaiter().GetResult()
|
||||
Ensure-Success $releaseResponse 'Argus release upload'
|
||||
|
||||
$releaseBody = Read-HttpBody $releaseResponse
|
||||
if (-not [string]::IsNullOrWhiteSpace($releaseBody)) {
|
||||
Write-Output $releaseBody
|
||||
}
|
||||
} finally {
|
||||
$fileStream.Dispose()
|
||||
$form.Dispose()
|
||||
}
|
||||
Invoke-CheckedNative $scp.Source @('-P', $sshPort, $packagePath, "${remote}:$remotePackagePath")
|
||||
Invoke-CheckedNative $scp.Source @('-P', $sshPort, $localScriptPath, "${remote}:$remoteScriptPath")
|
||||
Invoke-CheckedNative $ssh.Source @('-p', $sshPort, $remote, "bash $(Quote-BashSingle $remoteScriptPath)")
|
||||
} finally {
|
||||
$client.Dispose()
|
||||
$handler.Dispose()
|
||||
Remove-Item -LiteralPath $localScriptPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$manifestUrl = "$($PublicBaseUrl.TrimEnd('/'))/api/apps/$slug/manifest?platform=$platform&channel=$channel"
|
||||
$manifest = Invoke-RestMethod -Uri $manifestUrl -TimeoutSec 30
|
||||
$release = $manifest.release
|
||||
if ($null -eq $release) {
|
||||
Fail "Published manifest did not return a release: $manifestUrl"
|
||||
}
|
||||
|
||||
Assert-ManifestValue $manifest 'slug' $manifest.slug $slug
|
||||
Assert-ManifestValue $release 'version' $release.version $version
|
||||
Assert-ManifestValue $release 'channel' $release.channel $channel
|
||||
Assert-ManifestValue $release 'platform' $release.platform $platform
|
||||
Assert-ManifestValue $release 'packageKind' $release.packageKind $packageKind
|
||||
Assert-ManifestValue $release 'originalFileName' $release.originalFileName $packageFileName
|
||||
Assert-ManifestValue $release 'packageSizeBytes' $release.packageSizeBytes $localSize
|
||||
Assert-ManifestValue $release 'sha256' $release.sha256 $localSha256
|
||||
|
||||
Write-Output 'QSfera Android Argus SSH publication completed.'
|
||||
Write-Output "Manifest: $manifestUrl"
|
||||
Write-Output "Size: $localSize"
|
||||
Write-Output "SHA-256: $localSha256"
|
||||
|
||||
Reference in New Issue
Block a user