правки по лаунчеру
This commit is contained in:
@@ -0,0 +1,669 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Builds, signs, verifies, and deploys MoonWell Launcher to production.
|
||||
|
||||
.DESCRIPTION
|
||||
Builds the Windows application and Inno Setup installer, verifies the DSA
|
||||
signing key pair, signs the installer, generates appcast.xml, uploads the
|
||||
installer to Yandex Object Storage, and publishes AppCast through the MoonWell
|
||||
service API. Secrets are read only from environment variables.
|
||||
|
||||
.PARAMETER ExpectedVersion
|
||||
Fails unless the X.Y.Z part of pubspec.yaml matches this value.
|
||||
|
||||
.PARAMETER ReleaseNotes
|
||||
Text written to the AppCast item description.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Builds, signs, and verifies local artifacts without changing production.
|
||||
|
||||
.PARAMETER Force
|
||||
Allows redeploying a version that is not newer than the public AppCast.
|
||||
|
||||
.PARAMETER SkipChecks
|
||||
Skips pub get, formatting, static analysis, and tests.
|
||||
|
||||
.EXAMPLE
|
||||
.\tool\deploy_launcher.ps1 -DryRun -ExpectedVersion 1.0.2 `
|
||||
-ReleaseNotes "MoonWell Launcher improvements."
|
||||
|
||||
.EXAMPLE
|
||||
.\tool\deploy_launcher.ps1 -ExpectedVersion 1.0.2 `
|
||||
-ReleaseNotes "MoonWell Launcher improvements."
|
||||
#>
|
||||
#requires -Version 5.1
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$ExpectedVersion,
|
||||
[string]$ReleaseNotes = 'MoonWell Launcher update.',
|
||||
[string]$ApiBaseUrl = 'https://moon-well.online',
|
||||
[string]$AppcastUrl = 'https://moon-well.online/appcast.xml',
|
||||
[string]$S3Endpoint,
|
||||
[string]$S3Bucket,
|
||||
[string]$S3Key = 'moonwell_launcher_setup.exe',
|
||||
[string]$PrivateKeyPath = '.moonwell_signing\dsa_priv.pem',
|
||||
[string]$PublicKeyPath = 'windows\runner\resources\dsa_pub.pem',
|
||||
[string]$FlutterCommand = 'flutter',
|
||||
[string]$DartCommand = 'dart',
|
||||
[string]$InnoSetupCommand,
|
||||
[string]$OpenSslCommand,
|
||||
[switch]$SkipChecks,
|
||||
[switch]$DryRun,
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$script:SparkleNamespace =
|
||||
'http://www.andymatuschak.org/xml-namespaces/sparkle'
|
||||
|
||||
function Write-Step {
|
||||
param([Parameter(Mandatory = $true)][string]$Message)
|
||||
|
||||
Write-Host "`n==> $Message" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
function Resolve-RequiredCommand {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Command,
|
||||
[Parameter(Mandatory = $true)][string]$Description
|
||||
)
|
||||
|
||||
if (Test-Path -LiteralPath $Command -PathType Leaf) {
|
||||
return (Resolve-Path -LiteralPath $Command).Path
|
||||
}
|
||||
|
||||
$resolved = Get-Command $Command -ErrorAction SilentlyContinue
|
||||
if ($null -eq $resolved) {
|
||||
throw "$Description was not found: $Command"
|
||||
}
|
||||
|
||||
return $resolved.Source
|
||||
}
|
||||
|
||||
function Invoke-RequiredCommand {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Command,
|
||||
[Parameter(Mandatory = $true)][string[]]$Arguments
|
||||
)
|
||||
|
||||
& $Command @Arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Command failed with exit code ${LASTEXITCODE}: $Command"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-FileSha256 {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
|
||||
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
}
|
||||
|
||||
function Get-AppcastVersion {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
|
||||
[xml]$xml = Get-Content -LiteralPath $Path -Raw -Encoding UTF8
|
||||
$enclosure = $xml.rss.channel.item.enclosure
|
||||
if ($null -eq $enclosure) {
|
||||
throw "AppCast does not contain channel/item/enclosure: $Path"
|
||||
}
|
||||
|
||||
return $enclosure.GetAttribute('version', $script:SparkleNamespace)
|
||||
}
|
||||
|
||||
function Write-Appcast {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path,
|
||||
[Parameter(Mandatory = $true)][string]$Version,
|
||||
[Parameter(Mandatory = $true)][string]$Notes,
|
||||
[Parameter(Mandatory = $true)][string]$InstallerUrl,
|
||||
[Parameter(Mandatory = $true)][string]$Signature,
|
||||
[Parameter(Mandatory = $true)][long]$Length
|
||||
)
|
||||
|
||||
$settings = New-Object System.Xml.XmlWriterSettings
|
||||
$settings.Encoding = New-Object System.Text.UTF8Encoding($false)
|
||||
$settings.Indent = $true
|
||||
$settings.IndentChars = ' '
|
||||
$settings.NewLineChars = "`n"
|
||||
$settings.NewLineHandling = [System.Xml.NewLineHandling]::Replace
|
||||
|
||||
$writer = [System.Xml.XmlWriter]::Create($Path, $settings)
|
||||
try {
|
||||
$writer.WriteStartDocument()
|
||||
$writer.WriteStartElement('rss')
|
||||
$writer.WriteAttributeString('version', '2.0')
|
||||
$writer.WriteAttributeString(
|
||||
'xmlns',
|
||||
'sparkle',
|
||||
'http://www.w3.org/2000/xmlns/',
|
||||
$script:SparkleNamespace
|
||||
)
|
||||
$writer.WriteStartElement('channel')
|
||||
$writer.WriteElementString('title', 'MoonWell Launcher')
|
||||
$writer.WriteElementString(
|
||||
'description',
|
||||
'MoonWell Launcher updates'
|
||||
)
|
||||
$writer.WriteElementString('language', 'ru')
|
||||
$writer.WriteStartElement('item')
|
||||
$writer.WriteElementString(
|
||||
'title',
|
||||
"MoonWell Launcher $Version"
|
||||
)
|
||||
$writer.WriteElementString('description', $Notes)
|
||||
$writer.WriteElementString(
|
||||
'pubDate',
|
||||
[DateTimeOffset]::Now.ToString(
|
||||
'ddd, dd MMM yyyy HH:mm:ss zzz',
|
||||
[Globalization.CultureInfo]::InvariantCulture
|
||||
)
|
||||
)
|
||||
$writer.WriteStartElement('enclosure')
|
||||
$writer.WriteAttributeString('url', $InstallerUrl)
|
||||
$writer.WriteAttributeString(
|
||||
'sparkle',
|
||||
'version',
|
||||
$script:SparkleNamespace,
|
||||
$Version
|
||||
)
|
||||
$writer.WriteAttributeString(
|
||||
'sparkle',
|
||||
'shortVersionString',
|
||||
$script:SparkleNamespace,
|
||||
$Version
|
||||
)
|
||||
$writer.WriteAttributeString(
|
||||
'sparkle',
|
||||
'os',
|
||||
$script:SparkleNamespace,
|
||||
'windows'
|
||||
)
|
||||
$writer.WriteAttributeString(
|
||||
'sparkle',
|
||||
'dsaSignature',
|
||||
$script:SparkleNamespace,
|
||||
$Signature
|
||||
)
|
||||
$writer.WriteAttributeString('length', $Length.ToString())
|
||||
$writer.WriteAttributeString(
|
||||
'type',
|
||||
'application/octet-stream'
|
||||
)
|
||||
$writer.WriteEndElement()
|
||||
$writer.WriteEndElement()
|
||||
$writer.WriteEndElement()
|
||||
$writer.WriteEndElement()
|
||||
$writer.WriteEndDocument()
|
||||
}
|
||||
finally {
|
||||
$writer.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-SigningKeyPair {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$OpenSsl,
|
||||
[Parameter(Mandatory = $true)][string]$PrivateKey,
|
||||
[Parameter(Mandatory = $true)][string]$PublicKey,
|
||||
[Parameter(Mandatory = $true)][string]$TemporaryPublicKey
|
||||
)
|
||||
|
||||
Invoke-RequiredCommand -Command $OpenSsl -Arguments @(
|
||||
'dsa',
|
||||
'-in',
|
||||
$PrivateKey,
|
||||
'-pubout',
|
||||
'-out',
|
||||
$TemporaryPublicKey
|
||||
)
|
||||
|
||||
$expected = (
|
||||
Get-Content -LiteralPath $PublicKey -Raw
|
||||
) -replace '\s', ''
|
||||
$actual = (
|
||||
Get-Content -LiteralPath $TemporaryPublicKey -Raw
|
||||
) -replace '\s', ''
|
||||
|
||||
if ($expected -cne $actual) {
|
||||
throw 'The private DSA key does not match the launcher public key.'
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-InstallerSignature {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$OpenSsl,
|
||||
[Parameter(Mandatory = $true)][string]$InstallerPath,
|
||||
[Parameter(Mandatory = $true)][string]$PublicKey,
|
||||
[Parameter(Mandatory = $true)][string]$Signature,
|
||||
[Parameter(Mandatory = $true)][string]$TemporaryDirectory
|
||||
)
|
||||
|
||||
$signaturePath = Join-Path $TemporaryDirectory 'installer.sig'
|
||||
$firstDigestPath = Join-Path $TemporaryDirectory 'installer.sha1'
|
||||
[IO.File]::WriteAllBytes(
|
||||
$signaturePath,
|
||||
[Convert]::FromBase64String($Signature)
|
||||
)
|
||||
|
||||
$stream = [IO.File]::OpenRead($InstallerPath)
|
||||
try {
|
||||
$sha1 = [Security.Cryptography.SHA1]::Create()
|
||||
try {
|
||||
[IO.File]::WriteAllBytes(
|
||||
$firstDigestPath,
|
||||
$sha1.ComputeHash($stream)
|
||||
)
|
||||
}
|
||||
finally {
|
||||
$sha1.Dispose()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$stream.Dispose()
|
||||
}
|
||||
|
||||
Invoke-RequiredCommand -Command $OpenSsl -Arguments @(
|
||||
'dgst',
|
||||
'-sha1',
|
||||
'-verify',
|
||||
$PublicKey,
|
||||
'-signature',
|
||||
$signaturePath,
|
||||
$firstDigestPath
|
||||
)
|
||||
}
|
||||
|
||||
$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
$previousLocation = Get-Location
|
||||
|
||||
try {
|
||||
Set-Location $repositoryRoot
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($S3Endpoint)) {
|
||||
$S3Endpoint = $env:AWS_ENDPOINT
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($S3Endpoint)) {
|
||||
$S3Endpoint = 'https://storage.yandexcloud.net'
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($S3Bucket)) {
|
||||
$S3Bucket = $env:AWS_BUCKET
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($S3Bucket)) {
|
||||
$S3Bucket = 'warcraft-client'
|
||||
}
|
||||
|
||||
foreach ($url in @($ApiBaseUrl, $AppcastUrl, $S3Endpoint)) {
|
||||
$uri = $null
|
||||
if (
|
||||
-not [Uri]::TryCreate(
|
||||
$url,
|
||||
[UriKind]::Absolute,
|
||||
[ref]$uri
|
||||
) -or
|
||||
$uri.Scheme -ne 'https'
|
||||
) {
|
||||
throw "Production URL must be absolute HTTPS: $url"
|
||||
}
|
||||
}
|
||||
|
||||
$python = $null
|
||||
if (-not $DryRun) {
|
||||
foreach ($name in @(
|
||||
'AWS_ACCESS_KEY_ID',
|
||||
'AWS_SECRET_ACCESS_KEY',
|
||||
'LAUNCHER_AUTH_KEY'
|
||||
)) {
|
||||
$value = [Environment]::GetEnvironmentVariable($name)
|
||||
if ([string]::IsNullOrWhiteSpace($value)) {
|
||||
throw "Required environment variable $name is not set."
|
||||
}
|
||||
}
|
||||
|
||||
$python = Resolve-RequiredCommand `
|
||||
-Command 'python' `
|
||||
-Description 'Python'
|
||||
Invoke-RequiredCommand -Command $python -Arguments @(
|
||||
'-c',
|
||||
'import boto3'
|
||||
)
|
||||
}
|
||||
|
||||
$versionMatch = [regex]::Match(
|
||||
(Get-Content -LiteralPath 'pubspec.yaml' -Raw),
|
||||
'(?m)^version:\s*(\d+\.\d+\.\d+)\+(\d+)\s*$'
|
||||
)
|
||||
if (-not $versionMatch.Success) {
|
||||
throw 'The pubspec.yaml version must use the X.Y.Z+N format.'
|
||||
}
|
||||
|
||||
$version = $versionMatch.Groups[1].Value
|
||||
$buildNumber = $versionMatch.Groups[2].Value
|
||||
if (
|
||||
-not [string]::IsNullOrWhiteSpace($ExpectedVersion) -and
|
||||
$ExpectedVersion -ne $version
|
||||
) {
|
||||
throw "Expected version $ExpectedVersion, pubspec.yaml contains $version."
|
||||
}
|
||||
|
||||
$privateKey = (Resolve-Path -LiteralPath $PrivateKeyPath).Path
|
||||
$publicKey = (Resolve-Path -LiteralPath $PublicKeyPath).Path
|
||||
$flutter = Resolve-RequiredCommand `
|
||||
-Command $FlutterCommand `
|
||||
-Description 'Flutter SDK'
|
||||
$dart = Resolve-RequiredCommand `
|
||||
-Command $DartCommand `
|
||||
-Description 'Dart SDK'
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($InnoSetupCommand)) {
|
||||
$InnoSetupCommand =
|
||||
'C:\Program Files (x86)\Inno Setup 6\ISCC.exe'
|
||||
}
|
||||
$innoSetup = Resolve-RequiredCommand `
|
||||
-Command $InnoSetupCommand `
|
||||
-Description 'Inno Setup Compiler'
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($OpenSslCommand)) {
|
||||
$gitOpenSsl = 'C:\Program Files\Git\usr\bin\openssl.exe'
|
||||
$OpenSslCommand = if (Test-Path -LiteralPath $gitOpenSsl) {
|
||||
$gitOpenSsl
|
||||
}
|
||||
else {
|
||||
'openssl'
|
||||
}
|
||||
}
|
||||
$openSsl = Resolve-RequiredCommand `
|
||||
-Command $OpenSslCommand `
|
||||
-Description 'OpenSSL'
|
||||
|
||||
$temporaryDirectory = Join-Path $repositoryRoot 'build\launcher_release'
|
||||
New-Item -ItemType Directory -Force -Path $temporaryDirectory | Out-Null
|
||||
|
||||
Write-Step "Checking DSA keys for version $version+$buildNumber"
|
||||
Assert-SigningKeyPair `
|
||||
-OpenSsl $openSsl `
|
||||
-PrivateKey $privateKey `
|
||||
-PublicKey $publicKey `
|
||||
-TemporaryPublicKey (
|
||||
Join-Path $temporaryDirectory 'derived_dsa_pub.pem'
|
||||
)
|
||||
|
||||
if (-not $DryRun -and -not $Force) {
|
||||
Write-Step 'Checking the published version'
|
||||
try {
|
||||
$publishedPath = Join-Path $temporaryDirectory 'current_appcast.xml'
|
||||
Invoke-WebRequest `
|
||||
-Uri $AppcastUrl `
|
||||
-UseBasicParsing `
|
||||
-OutFile $publishedPath
|
||||
$publishedVersion = Get-AppcastVersion -Path $publishedPath
|
||||
if (
|
||||
-not [string]::IsNullOrWhiteSpace($publishedVersion) -and
|
||||
[version]$version -le [version]$publishedVersion
|
||||
) {
|
||||
throw (
|
||||
"Version $version is not newer than published version " +
|
||||
"$publishedVersion. Increase X.Y.Z or use -Force."
|
||||
)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
if ($_.Exception.Message -like 'Version *') {
|
||||
throw
|
||||
}
|
||||
Write-Warning (
|
||||
'Could not determine the currently published version: ' +
|
||||
$_.Exception.Message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $SkipChecks) {
|
||||
Write-Step 'Dependencies, formatting, analysis, and tests'
|
||||
Invoke-RequiredCommand -Command $flutter -Arguments @('pub', 'get')
|
||||
Invoke-RequiredCommand -Command $dart -Arguments @(
|
||||
'format',
|
||||
'--output=none',
|
||||
'--set-exit-if-changed',
|
||||
'lib',
|
||||
'test'
|
||||
)
|
||||
Invoke-RequiredCommand -Command $flutter -Arguments @(
|
||||
'analyze',
|
||||
'lib',
|
||||
'test'
|
||||
)
|
||||
Invoke-RequiredCommand -Command $flutter -Arguments @('test')
|
||||
}
|
||||
|
||||
Write-Step 'Building the Windows production application'
|
||||
Invoke-RequiredCommand -Command $flutter -Arguments @(
|
||||
'build',
|
||||
'windows',
|
||||
'--release',
|
||||
"--dart-define=MOONWELL_API_BASE_URL=$ApiBaseUrl",
|
||||
"--dart-define=MOONWELL_APPCAST_URL=$AppcastUrl"
|
||||
)
|
||||
|
||||
Write-Step 'Building the Inno Setup installer'
|
||||
Invoke-RequiredCommand -Command $innoSetup -Arguments @(
|
||||
"/DMyAppVersion=$version",
|
||||
'installer\moonwell_launcher.iss'
|
||||
)
|
||||
|
||||
$installerPath = Join-Path (
|
||||
Join-Path $repositoryRoot 'build\installer'
|
||||
) "moonwell_launcher_${version}_windows_setup.exe"
|
||||
if (-not (Test-Path -LiteralPath $installerPath -PathType Leaf)) {
|
||||
throw "The installer was not created: $installerPath"
|
||||
}
|
||||
|
||||
Write-Step 'Signing the installer with DSA'
|
||||
$gitOpenSslDirectory = Split-Path $openSsl -Parent
|
||||
$previousPath = $env:PATH
|
||||
try {
|
||||
$env:PATH = "$gitOpenSslDirectory;$env:PATH"
|
||||
$signOutput = & $dart run auto_updater:sign_update `
|
||||
$installerPath `
|
||||
$privateKey 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Could not sign the installer: $signOutput"
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$env:PATH = $previousPath
|
||||
}
|
||||
|
||||
$signatureMatch = [regex]::Match(
|
||||
($signOutput -join "`n"),
|
||||
'sparkle:dsaSignature="([^"]+)"',
|
||||
[Text.RegularExpressions.RegexOptions]::Singleline
|
||||
)
|
||||
if (-not $signatureMatch.Success) {
|
||||
throw 'The signing tool did not return sparkle:dsaSignature.'
|
||||
}
|
||||
$signature = $signatureMatch.Groups[1].Value -replace '\s', ''
|
||||
[void][Convert]::FromBase64String($signature)
|
||||
|
||||
$installer = Get-Item -LiteralPath $installerPath
|
||||
$installerSha256 = Get-FileSha256 -Path $installerPath
|
||||
Assert-InstallerSignature `
|
||||
-OpenSsl $openSsl `
|
||||
-InstallerPath $installerPath `
|
||||
-PublicKey $publicKey `
|
||||
-Signature $signature `
|
||||
-TemporaryDirectory $temporaryDirectory
|
||||
|
||||
$installerUrl = (
|
||||
$S3Endpoint.TrimEnd('/') +
|
||||
'/' +
|
||||
$S3Bucket +
|
||||
'/' +
|
||||
$S3Key
|
||||
)
|
||||
$appcastPath = if ($DryRun) {
|
||||
Join-Path $temporaryDirectory 'appcast.xml'
|
||||
}
|
||||
else {
|
||||
Join-Path $repositoryRoot 'appcast.xml'
|
||||
}
|
||||
Write-Step 'Generating appcast.xml'
|
||||
Write-Appcast `
|
||||
-Path $appcastPath `
|
||||
-Version $version `
|
||||
-Notes $ReleaseNotes `
|
||||
-InstallerUrl $installerUrl `
|
||||
-Signature $signature `
|
||||
-Length $installer.Length
|
||||
|
||||
[xml]$generatedAppcast = Get-Content `
|
||||
-LiteralPath $appcastPath `
|
||||
-Raw `
|
||||
-Encoding UTF8
|
||||
if ((Get-AppcastVersion -Path $appcastPath) -ne $version) {
|
||||
throw 'The generated AppCast contains the wrong version.'
|
||||
}
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Step 'Dry run complete - production was not changed'
|
||||
Write-Host "Version: $version+$buildNumber"
|
||||
Write-Host "Installer: $installerPath"
|
||||
Write-Host "Size: $($installer.Length)"
|
||||
Write-Host "SHA-256: $installerSha256"
|
||||
Write-Host "AppCast: $appcastPath"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Step "Uploading $S3Bucket/$S3Key"
|
||||
$env:MOONWELL_RELEASE_FILE = $installerPath
|
||||
$env:MOONWELL_RELEASE_ENDPOINT = $S3Endpoint
|
||||
$env:MOONWELL_RELEASE_BUCKET = $S3Bucket
|
||||
$env:MOONWELL_RELEASE_KEY = $S3Key
|
||||
$env:MOONWELL_RELEASE_VERSION = $version
|
||||
$env:MOONWELL_RELEASE_SHA256 = $installerSha256
|
||||
$uploadScript = @'
|
||||
import os
|
||||
import boto3
|
||||
from botocore.config import Config
|
||||
|
||||
path_style = os.getenv("AWS_USE_PATH_STYLE_ENDPOINT", "true").lower()
|
||||
addressing_style = "path" if path_style in ("1", "true", "yes") else "virtual"
|
||||
s3 = boto3.client(
|
||||
"s3",
|
||||
endpoint_url=os.environ["MOONWELL_RELEASE_ENDPOINT"],
|
||||
region_name=os.getenv("AWS_DEFAULT_REGION", "ru-central1"),
|
||||
config=Config(s3={"addressing_style": addressing_style}),
|
||||
)
|
||||
s3.upload_file(
|
||||
os.environ["MOONWELL_RELEASE_FILE"],
|
||||
os.environ["MOONWELL_RELEASE_BUCKET"],
|
||||
os.environ["MOONWELL_RELEASE_KEY"],
|
||||
ExtraArgs={
|
||||
"ACL": "public-read",
|
||||
"ContentType": "application/x-msdownload",
|
||||
"CacheControl": "no-cache, max-age=0",
|
||||
"Metadata": {
|
||||
"launcher-version": os.environ["MOONWELL_RELEASE_VERSION"],
|
||||
"sha256": os.environ["MOONWELL_RELEASE_SHA256"],
|
||||
},
|
||||
},
|
||||
)
|
||||
response = s3.head_object(
|
||||
Bucket=os.environ["MOONWELL_RELEASE_BUCKET"],
|
||||
Key=os.environ["MOONWELL_RELEASE_KEY"],
|
||||
)
|
||||
print("S3_VERSION_ID=" + str(response.get("VersionId", "")))
|
||||
print("S3_CONTENT_LENGTH=" + str(response["ContentLength"]))
|
||||
'@
|
||||
try {
|
||||
Invoke-RequiredCommand -Command $python -Arguments @(
|
||||
'-c',
|
||||
$uploadScript
|
||||
)
|
||||
}
|
||||
finally {
|
||||
Remove-Item Env:MOONWELL_RELEASE_FILE -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:MOONWELL_RELEASE_ENDPOINT -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:MOONWELL_RELEASE_BUCKET -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:MOONWELL_RELEASE_KEY -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:MOONWELL_RELEASE_VERSION -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:MOONWELL_RELEASE_SHA256 -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Step 'Verifying the uploaded installer'
|
||||
$downloadedInstaller = Join-Path `
|
||||
$temporaryDirectory `
|
||||
'published_installer.exe'
|
||||
Invoke-WebRequest `
|
||||
-Uri $installerUrl `
|
||||
-UseBasicParsing `
|
||||
-OutFile $downloadedInstaller
|
||||
$publishedSha256 = Get-FileSha256 -Path $downloadedInstaller
|
||||
if ($publishedSha256 -ne $installerSha256) {
|
||||
throw (
|
||||
'The uploaded installer SHA-256 does not match the local file: ' +
|
||||
"$publishedSha256"
|
||||
)
|
||||
}
|
||||
|
||||
Write-Step 'Publishing AppCast'
|
||||
$publishResponse = Invoke-WebRequest `
|
||||
-Uri "$($ApiBaseUrl.TrimEnd('/'))/api/service/update-appcast" `
|
||||
-Method Post `
|
||||
-Headers @{
|
||||
Auth = $env:LAUNCHER_AUTH_KEY
|
||||
Accept = 'application/json'
|
||||
} `
|
||||
-ContentType 'application/xml' `
|
||||
-Body ([IO.File]::ReadAllBytes($appcastPath)) `
|
||||
-UseBasicParsing
|
||||
if ($publishResponse.StatusCode -lt 200 -or $publishResponse.StatusCode -ge 300) {
|
||||
throw "AppCast publication returned HTTP $($publishResponse.StatusCode)."
|
||||
}
|
||||
|
||||
Write-Step 'Verifying the published AppCast'
|
||||
$publishedAppcastPath = Join-Path `
|
||||
$temporaryDirectory `
|
||||
'published_appcast.xml'
|
||||
$publicResponse = Invoke-WebRequest `
|
||||
-Uri $AppcastUrl `
|
||||
-UseBasicParsing `
|
||||
-OutFile $publishedAppcastPath `
|
||||
-PassThru
|
||||
if ($publicResponse.StatusCode -ne 200) {
|
||||
throw "The public AppCast returned HTTP $($publicResponse.StatusCode)."
|
||||
}
|
||||
if (
|
||||
(Get-FileSha256 -Path $publishedAppcastPath) -ne
|
||||
(Get-FileSha256 -Path $appcastPath)
|
||||
) {
|
||||
throw 'The published AppCast does not match the local file.'
|
||||
}
|
||||
|
||||
[xml]$publishedAppcast = Get-Content `
|
||||
-LiteralPath $publishedAppcastPath `
|
||||
-Raw `
|
||||
-Encoding UTF8
|
||||
$publishedEnclosure = $publishedAppcast.rss.channel.item.enclosure
|
||||
if (
|
||||
$publishedEnclosure.GetAttribute(
|
||||
'version',
|
||||
$script:SparkleNamespace
|
||||
) -ne $version -or
|
||||
[long]$publishedEnclosure.length -ne $installer.Length -or
|
||||
$publishedEnclosure.url -ne $installerUrl
|
||||
) {
|
||||
throw 'The published AppCast contains incorrect release data.'
|
||||
}
|
||||
|
||||
Write-Step 'Production deployment complete'
|
||||
Write-Host "Version: $version+$buildNumber"
|
||||
Write-Host "Installer: $installerUrl"
|
||||
Write-Host "SHA-256: $installerSha256"
|
||||
Write-Host "AppCast: $AppcastUrl"
|
||||
}
|
||||
finally {
|
||||
Set-Location $previousLocation
|
||||
}
|
||||
Reference in New Issue
Block a user