Private/Tools/Install-AvmToolFromPins.ps1
|
function Install-AvmToolFromPins { <# .SYNOPSIS Install a single tool entry from a parsed avm.pins into the cache. .DESCRIPTION Internal worker invoked by the public Install-AvmTool. Performs the full install pipeline for one (tool, platform) pair: 1. Resolve cache target '<Data>/tools/<name>/<version>/'. 2. If '.verified' marker exists and -Force not set, return path. 3. Acquire cross-process lock under '<Data>/tools/<name>/.lock'. 4. Re-check '.verified' (another process may have raced ahead). 5. Stage download into '<Data>/tools/<name>/.staging/<uuid>/'. 6. Verify SHA256 (in Invoke-AvmHttp). 7. Expand archive into the staging dir. 8. Move-Item staging dir to final '<version>/' (atomic rename). If the rename loses a race, discard staging and use the existing dir. 9. Write .meta.json and touch .verified marker. 10. Release lock; return final path. #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'Noun mirrors the avm.pins.jsonc manifest, which holds many pins.')] [OutputType([pscustomobject])] param( [Parameter(Mandatory)] [hashtable] $Tool, [Parameter(Mandatory)] [string] $Platform, [switch] $Force ) Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' if ($Tool.ContainsKey('unsupportedPlatforms') -and (@($Tool.unsupportedPlatforms) -ccontains $Platform)) { throw [AvmToolException]::new( "Tool '$($Tool.name)' does not ship a release for '$Platform'.", 'AVM1012') } if (-not $Tool.sha256.ContainsKey($Platform)) { throw [AvmToolException]::new( "Tool '$($Tool.name)' has no sha256 entry for platform '$Platform'.", 'AVM1012') } $toolsRoot = Get-AvmFolder -Kind Tools $toolDir = Join-Path $toolsRoot $Tool.name $versionDir = Join-Path $toolDir $Tool.version $verified = Join-Path $versionDir '.verified' $entrypointName = if ($IsWindows) { "$($Tool.entrypoint).exe" } else { $Tool.entrypoint } $entrypointPath = Join-Path $versionDir $entrypointName Write-AvmLog ("install: target directory = {0}" -f $versionDir) -Level Verbose | Out-Null if ((Test-Path -LiteralPath $verified) -and (Test-Path -LiteralPath $entrypointPath) -and -not $Force) { Write-AvmLog ("install: cache hit for {0}/{1}" -f $Tool.name, $Tool.version) -Level Verbose | Out-Null return [pscustomobject]@{ Name = $Tool.name Version = $Tool.version Platform = $Platform Path = $entrypointPath Action = 'cache-hit' } } if ($Force -and (Test-Path -LiteralPath $versionDir)) { Write-AvmLog ("install: force removing {0}" -f $versionDir) -Level Verbose | Out-Null Remove-Item ` -LiteralPath $versionDir ` -Recurse ` -Force ` -ProgressAction SilentlyContinue } if (-not (Test-Path -LiteralPath $toolDir)) { New-Item -ItemType Directory -Path $toolDir -Force | Out-Null } $lockFile = Join-Path $toolDir '.lock' Write-AvmLog ("install: acquiring cache lock {0}" -f $lockFile) -Level Verbose | Out-Null $lock = Lock-AvmToolCache -LockFile $lockFile try { if ((Test-Path -LiteralPath $verified) -and (Test-Path -LiteralPath $entrypointPath) -and -not $Force) { Write-AvmLog ("install: post-lock cache hit for {0}/{1}" -f $Tool.name, $Tool.version) -Level Verbose | Out-Null return [pscustomobject]@{ Name = $Tool.name Version = $Tool.version Platform = $Platform Path = $entrypointPath Action = 'cache-hit' } } $osPart, $archPart = $Platform.Split('-', 2) $url = $Tool.urlTemplate $url = $url.Replace('{version}', $Tool.version) $url = $url.Replace('{os}', $osPart) $url = $url.Replace('{arch}', $archPart) if ($Tool.ContainsKey('platformAliases')) { $alias = [string]$Tool.platformAliases[$Platform] $url = $url.Replace('{platform}', $alias) } $resolvedArchive = $Tool.archive if ($Tool.ContainsKey('archives') -and $Tool.archives.ContainsKey($Platform)) { $resolvedArchive = [string]$Tool.archives[$Platform] } $extToken = switch ($resolvedArchive) { 'zip' { '.zip' } 'tar.gz' { '.tar.gz' } 'raw' { '' } } $url = $url.Replace('{ext}', $extToken) Write-AvmLog ("install: source = {0}" -f $url) -Level Verbose | Out-Null Write-AvmLog ("install: archive = {0}; expected sha256 = {1}" -f $resolvedArchive, $Tool.sha256[$Platform]) -Level Verbose | Out-Null $stagingRoot = Join-Path $toolDir '.staging' if (-not (Test-Path -LiteralPath $stagingRoot)) { New-Item -ItemType Directory -Path $stagingRoot -Force | Out-Null } $stagingDir = Join-Path $stagingRoot ([Guid]::NewGuid().ToString('N').Substring(0, 12)) New-Item -ItemType Directory -Path $stagingDir -Force | Out-Null try { $archiveSuffix = $extToken $archivePath = Join-Path $stagingDir ("download" + $archiveSuffix) Write-AvmLog ("install: downloading to {0}" -f $archivePath) -Level Verbose | Out-Null Invoke-AvmHttp -Url $url -Destination $archivePath -ExpectedSha256 $Tool.sha256[$Platform] | Out-Null Write-AvmLog ("install: expanding {0}" -f $resolvedArchive) -Level Verbose | Out-Null Expand-AvmToolArchive -ArchivePath $archivePath -Archive $resolvedArchive -TargetDir $stagingDir -EntrypointBasename $Tool.entrypoint Remove-Item -LiteralPath $archivePath -Force -ErrorAction SilentlyContinue $stagedEntrypoint = Join-Path $stagingDir $entrypointName if (-not (Test-Path -LiteralPath $stagedEntrypoint)) { throw [AvmToolException]::new( "Expected entrypoint '$entrypointName' missing after extracting $($Tool.name) $($Tool.version) for $Platform.", 'AVM1013') } $meta = [pscustomobject]@{ name = $Tool.name version = $Tool.version platform = $Platform url = $url sha256 = $Tool.sha256[$Platform] archive = $resolvedArchive installedAt = [DateTime]::UtcNow.ToString('o') } $meta | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $stagingDir '.meta.json') -Encoding utf8 try { Move-Item -LiteralPath $stagingDir -Destination $versionDir -Force } catch [System.IO.IOException] { if (Test-Path -LiteralPath $verified) { Write-AvmLog ("install: rename race lost for {0}/{1}; using completed cache entry" -f $Tool.name, $Tool.version) -Level Verbose | Out-Null Remove-Item ` -LiteralPath $stagingDir ` -Recurse ` -Force ` -ErrorAction SilentlyContinue ` -ProgressAction SilentlyContinue return [pscustomobject]@{ Name = $Tool.name Version = $Tool.version Platform = $Platform Path = $entrypointPath Action = 'race-loss' } } throw } New-Item -ItemType File -Path $verified -Force | Out-Null Write-AvmLog ("install: verified marker written to {0}" -f $verified) -Level Verbose | Out-Null return [pscustomobject]@{ Name = $Tool.name Version = $Tool.version Platform = $Platform Path = $entrypointPath Action = 'installed' } } finally { if (Test-Path -LiteralPath $stagingDir) { Remove-Item ` -LiteralPath $stagingDir ` -Recurse ` -Force ` -ErrorAction SilentlyContinue ` -ProgressAction SilentlyContinue } } } finally { $lock.Dispose() } } # SIG # Begin signature block # MIInOgYJKoZIhvcNAQcCoIInKzCCJycCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCABwO8A4M2KgyMz # A8debIcQtmMmQk3KWWxBaTXUbdvLCKCCDMkwggYEMIID7KADAgECAhMzAAACHPrN # xZvoL37EAAAAAAIcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQxWhcNMjcwNDE1MTg1 # OTQxWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDVsZfgOKmM31HPfoWOoNEiw0SlCiIxUMC0I9NMWbucKOw/e9lP # oAoehQVu6SG65V4EPzrYsnBnFPNoi4/HoOdjhz1qkrEt4I6tEcxXU6oOeY9zGveC # /3iBeuhLYxM3M/PkcUoebF+Nednm8OkdSPoDu8imViHPQq/8CQUu0WRR4rE+dMRf # rpVqfmNi2qWCX94T4MsepijGVkwE//tJg0ryAiYdHT34LSnlG/RSBZmQRGWZ5g8j # qnKjRParSqMft1gvjuUTVgtWNZfgcLFSK5Wa0myrq8OPcgTGGsRgun+tnSS+IxDT # xVsAPH1OzvPjwomguByhUe/OcvUN0D5Wmp7xAgMBAAGjggGqMIIBpjAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFNoH7a2YDjOSwpkp6DHcmUS7J+0yMFQGA1UdEQRNMEukSTBHMS0wKwYDVQQL # EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxFjAUBgNVBAUT # DTIzMDAxMis1MDc1NjkwHwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEw # YAYDVR0fBFkwVzBVoFOgUYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w # cy9jcmwvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # bDBtBggrBgEFBQcBAQRhMF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9z # b2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmcl # MjBQQ0ElMjAyMDI0LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IC # AQAUnEqhaRXe0T3hIJjvdQErEkrA/7bByjn6t5IArODkkRjzkYwtKMc2yYj2quaN # rLutWw2YZcngKPy1b71YyDJQTy4NDRwaSh9Tw5thrk3NmcPrAHia5vtcBJ1CgtKK # 7mQbIcQ22d/N3813ayCDDFewu1+jsZmX+r/aTEqaOM4TVxVtRSkuCy8nAXKuChOK # Li/zA4XuH8iEYqIsj2YoNaeSxVmeGiERXpKdo3dDmYi0kO5w2D8VS4c3+9h6gElY # BaAAg/dYErBg27qT3vv0zRDJhJufvCNylA8S7/+8H5E/PV5cng6na9VV/w9OV3qu # uND6zdGa2EX38Glp50F9AIQk3p2xXmcvorDeM4XJ7UlWYBi6g80J1SSOQnInCYFE # msfUNn3+1AaTJKSJL83quKArTac2pKhu0Yzzzrzo6HrsRiQKzpnRBb1/dMa6P3hz # 75XbMRBctNsFhZC07WCmjExdLg2eHW5uV0TY8D5+6wozJf7vF3+WHkYPO85Z+BC6 # U4FkNbYNycZ9cE4j1tXRdyDCfml6c0HWPHjNVDObrv9lKt3qUqFpX38VCqVCyNOO # 1UcXfQiVjJw32U2WUKZjt/neJKHEBsm9kFsLuWzkQ53+qcaSaytmsCnk2gOglrlD # 5d3kKyvvAw+rzm0lT8K38P6PLxfZQHhu4W8dV7Av8N2ZmDCCBr0wggSloAMCAQIC # EzMAAAA5O7Y3Gb8GHWcAAAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS # b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoX # DTM2MDMyMjIyMTMwNFowVzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQ # Q0EgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeq # lRYHNa265v4IY9fH8TKhemHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo # 0dtS/EW6I/yEL/bLSY8hKpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATv # QVL4tcf03aTycsz8QeCdM0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a # 1uv1zerOYMnsneRRwCbpyW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1 # FyQfK0fVkaya8SmVHQ/tOf23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfO # GSWHIIV4YrTJTT6PNty5REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7 # ttOu1bVnXfHaqPYl2rPs20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJ # uz2MXMCt7iw7lFPG9LXKGjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxS # CwyoGIq0PhaA7Y+VPct5pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOm # VQop36wUVUYklUy++vDWeEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3 # SkE/xIkgpfl22MM1itkZ35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8E # BAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPX # LQaUEggxMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMB # Af8wHwYDVR0jBBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBP # oE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAw # TgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMv # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOC # AgEAFJQfOChP7onn6fLIMKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D # 5W4wMwYeLystcEqfkjz4NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBY # nbu0+THSuVHTe0VTTPVhily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSI # vgn0JksVBVMYVI5QFu/qhnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6 # aR9y34aiM1qmxaxBi6OUnyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4w # PKC5OmHm1DQIt/MNokbbH3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7 # RTX8AdBPo0I6OEojf39zuFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK # /fg8B2qjW88MT/WF5V5uvZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSK # YBv0VisCzfxgeU+dquXW9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkw # YTu/9dLeH2pDqeJZAABVDWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVT # Ql0v4q8J/AUmQN5W4n101cY2L4A7GTQG1h32HHAvfQESWP0xghnHMIIZwwIBATBu # MFcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # KDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIc # +s3Fm+gvfsQAAAAAAhwwDQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwG # CisGAQQBgjcCAQQwLwYJKoZIhvcNAQkEMSIEIDgqfCqt2tK0mw6bOHzPaz634zIp # Z0Zu+SMtr15CgkcBMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBv # AGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAE # ggEApT8T0qRD8Q3LZD2XWlutpxxwFcPET0ZYWrSf90fk8+h/EkQn8MVEBjAjscc9 # YRvNWUHLCIOUyZMkmBHZ2zwn03ZgEt8CMnls3eDpuLEg0AenE0JuwK64Te0H5Lhy # shDFDwLkOqGTEWMdFQ56klIZophY+M5yxYE5FfsKsKa9ozTWBDNUrQfMMoHFgkv7 # ptnLAvKCLM4hAoGNRuCM4uxXZewdR54ilmVemAhp9I4kpttYxeMP/kNyPT/G7JZV # DnoBTBZ/G4kdeEpd0rjIxbPVAdhK8oR55RcJuUiS2YnF2o4W+V6nwoWyZha8rFMz # lCOvGjkfoK4UE4Xy7aUPypnTL6GCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCCF38G # CSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsqhkiG # 9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQC # AQUABCCPy3PiLlc09FI+iL/Jj5mOgJYvzNg5cCLMYLPZnLMMGwIGaqqI0CUOGBMy # MDI2MDkyMTEzMjUyNy4zNTFaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj # YSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RjAwMi0wNUUw # LUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WgghHt # MIIHIDCCBQigAwIBAgITMwAAAiAk4ebgF7m0jgABAAACIDANBgkqhkiG9w0BAQsF # ADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNjAyMTkxOTM5NTJa # Fw0yNzA1MTcxOTM5NTJaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScw # JQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RjAwMi0wNUUwLUQ5NDcxJTAjBgNVBAMT # HE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQDRYY7yr7ijW6CR178uKveIMufutWOicxgJwKOce/2GOQce # us6ZWfX14i3jNg3JOP7MGJMkOAucwWBwiA8URp+ZYkGjpVoVkGZsV27WjqLwpf2A # wqBsJ/TzqwE7JFFaxup3Ldxj8GjdJymDFRrdVN/pYHoBFrjD1IkIDu8b1CWn8tgo # miKRSY+STvJq99mVkdphMBIUGOegQny8qRd24VME0xi8Oomks9Zq9EjDeKHGpvAb # XUEQ6m3cROoEPhTE/miweQH9TqJt3IOsqPv3L8urojB747XBC2y0CDIHlKLcLl3Z # G8D7JXKnWTFen3msMPJpcvrQ3zUBVJrH/mI3RxHmCh9ppDP0uG1+PJwk6H/x+sfo # G9hW64xoXkpx6DEfNZNfcXdKbXF28XEXdLNnzo3SLNVymeQJhNqOSKhnU84QnKmr # jEk541JiurlDCkCWO9lUBUMb9x0nyfXUbNRPVLgP+PTMRdXOowJdYCzCQfN2ZqL0 # s4YI28F1Dbn7Bgw2E4P1E9unsvMzJHtzhS2Th3TpCfBbOGalIlF9x/DJZ/ssm/yy # zT9YtIFeqmfNxBPTE3aOuh6HxmTICzfYAATvWNhBbo19QwsjPeA9JvhqTLC2KUNg # rXroGy4eDZo0n7jFYjZkUih1Ty+8E6qEvV2Na6Z5gUyD5a+tHGDmq69CmUiHfwID # AQABo4IBSTCCAUUwHQYDVR0OBBYEFNvInOCIhxGA8mY7l1g07UHvyNgzMB8GA1Ud # IwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRp # bWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYI # KwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMv # TWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1Ud # EwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeA # MA0GCSqGSIb3DQEBCwUAA4ICAQCtKGBto1BSvm4WFI+J0NSyVhU1LHL7F3fbjZ2d # 7F5Kn/FCTBZXpzrDVl63FLRNcIFpnJy4/nlg43r7T5sJPdo4Ms8ADSHQEJnHSu3x # 9UpjCzREBPi9+nHhvDgRx/1WmBD6gQUZJLOhcN2TxW4KJyhinMtiBFtkNRZ2vmZ1 # MAdNXTm5d0Lwk3wzj+/f7VCCTWCXJSoqNa3VU/6sACHI97Evbnzg8bd3hxrfz6Cc # CVuf77egvRHinthJuwSRePP7aVmcevb1nWUIAICdBebHQOrzNIeWBIQwvcFaS3SF # c+49rqrwQOMFDR4FYBzS7b0QeBVxFuLL2iVu4KAHMNUhLLSD4iKLDFBNTOtTzTlh # GvMgG77A1cjeQrDMHa6oReMDeUDqHUrxv8g7IRdIh+h0gDLkzN0xIuzli0Bv7Jty # bGJbV6JxaDF4CzSCIMRpK59nI6iKo4LgnbQBZJW7+6akYsKG/pXPlfxNv2InpD10 # tSCkCvw9kr6W1+NRN+EuZczRgAwWlcK9XJZ3uu/v/oxHtO7/kmVIs51F9qV6Y2QN # Xd6tU46YPrK98m2QDys+lvLNimK0e1xZ7Z1GawKohKGvlLALWDlZQqgHfJ31CB0L # lIDI7iLyYTpd2iyKjqskbQiyMtICH+RmH/oCg7JOK0ZA3XIMba9aSWgBF3QZ6pG3 # EGeQqjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcN # AQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAw # BgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEw # MB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt # U3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk # 4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9c # T8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWG # UNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6Gnsz # rYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2 # LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLV # wIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTd # EonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0 # gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFph # AXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJ # YfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXb # GjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJ # KwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnP # EP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMw # UQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9z # b2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggr # BgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYw # DwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoY # xDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtp # L2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYB # BQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v # cGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0B # AQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U5 # 18JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgAD # sAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo # 32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZ # iefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZK # PmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RI # LLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgk # ujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9 # af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzba # ukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/ # OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQMIIC # OAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUG # A1UECxMeblNoaWVsZCBUU1MgRVNOOkYwMDItMDVFMC1EOTQ3MSUwIwYDVQQDExxN # aWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCTGA9v # psJ6glqCLmI0rggGx4YEEqCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD # QSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7lueHDAiGA8yMDI2MDkyMTEyMTQyMFoY # DzIwMjYwOTIyMTIxNDIwWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDuW54cAgEA # MAoCAQACAgoRAgH/MAcCAQACAhNkMAoCBQDuXO+cAgEAMDYGCisGAQQBhFkKBAIx # KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI # hvcNAQELBQADggEBACbU+GOlYMmqX69xuuwbPhmq4LP70LZiWLGPT8RKqCkzMSKf # 9grk8dUrWleZbznF/RgqO/X+7aLLIultnQUbnpbzW4WuIB6wUyMfdOFDeoDP5zxg # Ag4+mdBZE3KNFqKMrxOXoPclTBA5k+wCi1L9jUS2X7HWKn/tOfr3iSzbSVaUCbkw # 5Rk6P71bCwfWu+2Kv/gQUV5Ys2OCI7hCgGQ3358WzXerBOOJQqIh+OKuGoIm1Ilr # 72iw3k/J4yYtW4JRaBQWkY0Mr287X/TMfl5ftpIdWJnnAQbUtofTtc8GWn2wOXeC # xMPEVjC0f8UyTJVJ5qN9Far0tsgB8Ivdj++4kSkxggQNMIIECQIBATCBkzB8MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy # b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAiAk4ebgF7m0jgABAAACIDAN # BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G # CSqGSIb3DQEJBDEiBCBDcCL2sYaLKLMpRbEbGUH27XA75w+7dvVQBK24YM1YmjCB # +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EION7vyOlPA1VqlEp0QIVGlNd8S5Y # WBnKj97LuTWHSO2vMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTACEzMAAAIgJOHm4Be5tI4AAQAAAiAwIgQgQns+rvWTi1ULWi7ljop9Jfq3cnmZ # shmcTgGFGrHPrnswDQYJKoZIhvcNAQELBQAEggIAcNvCGYttZxZv49PhSUPFzUFD # dItY++LPRYulUkqADwDIS/C4gkkP+ouF1yTqRHOwynJu6EOWtiRZhLquDGWFWfM6 # OhvZrR+JyXePvYillPZVAzrs6Zjm63eUwYRCU0PKCoxSLNzRvZujOolcFCVYr/3J # 4K1adkRc0tn464WiwZUGmSDivld7REv213or1SgZMWtF1+vqUU/F67JUKR9ZQxiK # APpypLb399WvcpUafaQOQlj+W83EiJ+vlKOsEZML+SgqxHQgefSMopce8eIlomt1 # xY0BGWDDLaTjxvjHFZWq2r9ccd8iO1hfawhviXIM8dM3HeDCQnkp+Zan0cSL6HmS # TRsxpvmhUNC6DFTDFmG6Bw0SrDkqc2ytpZi4XsYM+NrvWDHqO2ovSDDAmBrwisQ+ # M0pn7D0yClFdbE7X6HMP7/ZouSZOdTBaX7lb3DNMZcny/a35mcHlfZR6TjgKyfKN # Nf6o4fTdKJFDSoJNd3O4en3+0ftu7pfU3ovhqqOvXc4C1jYjlQVwZY6X5XBO/Tuq # qZzXQ4USalG34cTPgxXpja2l3scbT9MmiS0vkQVlApmoS/ek19YCM4Ia2QIlUsU+ # ZBRWad7RCkeSrVW0q+SdRYHi8jUoehTgV3ciKAS6Xa732d1R4JlLGQ1CWiMwZBiI # hFkcKEOs+5eWY/D2KpA= # SIG # End signature block |