$ErrorActionPreference = "Stop" $BaseUrl = $env:B8GRID_RELEASE_BASE_URL if ([string]::IsNullOrWhiteSpace($BaseUrl)) { $BaseUrl = "https://source.b8grid.cloud/source-graph" } $InstallDir = $env:B8GRID_INSTALL_DIR if ([string]::IsNullOrWhiteSpace($InstallDir)) { $InstallDir = Join-Path $env:LOCALAPPDATA "B8Grid\bin" } # An offline signed release uses the same checksum and pinned signature checks. $ReleaseDirectory = $env:B8GRID_RELEASE_DIRECTORY if (![string]::IsNullOrWhiteSpace($ReleaseDirectory)) { $ReleaseDirectory = (Resolve-Path -LiteralPath $ReleaseDirectory).ProviderPath } $RawArch = $env:PROCESSOR_ARCHITECTURE if ($RawArch -eq "AMD64") { $Arch = "x64" } elseif ($RawArch -eq "ARM64") { $Arch = "arm64" } else { throw "Unsupported architecture: $RawArch" } $Platform = "windows-$Arch" $TempRoot = Join-Path ([IO.Path]::GetTempPath()) ("b8grid-install-" + [Guid]::NewGuid().ToString("N")) New-Item -ItemType Directory -Force -Path $TempRoot | Out-Null try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {} function Invoke-B8GridDownload { param( [Parameter(Mandatory=$true)][string]$Uri, [Parameter(Mandatory=$true)][string]$OutFile, [Parameter(Mandatory=$true)][string]$RelativePath ) if (![string]::IsNullOrWhiteSpace($ReleaseDirectory)) { # RelativePath is constructed below from fixed components and a checked # artifact filename; it cannot escape the selected release directory. Copy-Item -LiteralPath (Join-Path $ReleaseDirectory $RelativePath) -Destination $OutFile return } for ($Attempt = 1; $Attempt -le 3; $Attempt++) { try { Invoke-WebRequest -UseBasicParsing -TimeoutSec 60 -Uri $Uri -OutFile $OutFile return } catch { if ($Attempt -ge 3) { throw } Start-Sleep -Seconds ([Math]::Min(5 * $Attempt, 15)) } } } try { $ChecksumPath = Join-Path $TempRoot "sha256.txt" $ArtifactPath = Join-Path $TempRoot "b8grid.exe" $SignaturePath = Join-Path $TempRoot "artifact.sig" Invoke-B8GridDownload -Uri "$BaseUrl/cli/latest/$Platform/sha256.txt" -OutFile $ChecksumPath -RelativePath "cli/latest/$Platform/sha256.txt" $Parts = ((Get-Content -Raw -Path $ChecksumPath).Trim() -split "\s+", 2) if ($Parts.Length -ne 2) { throw "Invalid checksum file for $Platform" } $ExpectedSha = $Parts[0].ToLowerInvariant() $FileName = $Parts[1] if ($FileName -notmatch '^[A-Za-z0-9._-]+$') { throw "Unsafe release file name." } Invoke-B8GridDownload -Uri "$BaseUrl/cli/latest/$Platform/$FileName" -OutFile $ArtifactPath -RelativePath "cli/latest/$Platform/$FileName" Invoke-B8GridDownload -Uri "$BaseUrl/cli/latest/$Platform/$FileName.sig" -OutFile $SignaturePath -RelativePath "cli/latest/$Platform/$FileName.sig" $ActualSha = (Get-FileHash -Algorithm SHA256 -Path $ArtifactPath).Hash.ToLowerInvariant() if ($ActualSha -ne $ExpectedSha) { throw "B8Grid checksum verification failed for $Platform." } [xml]$PublicKey = @' 6uqccnYLwy9OIpq1/zxdqSAzXYsAAlipYOwWdhhhf2UCUcgsfKvFrudVfBNhyTxTbk1/2itKscJVLssr7iyOfqytCCSNSAAB4po1clT15YOaAl/M2tKv6X6CS7YhFpArtOBC7u8cRdOJ7ZfRahRG763TtBJNYUkzaZDNd9r9sOA9LAEzpnkIFQq7eEG6mLOPFfHmAtJQ+iIP+SUDntlz/wn82GGwFVGXSWpn4mAn+r45DXqLIByyRBupV2/gOq5SVA7w1ef0fH67QDYS0anLBYb5KxqsEw8higxnlaqAqtaNgFv5rWPU/svHF2PoHs/ojHQ52rjGE2Sc1UnwXiblsqIyiN02EiXxWHD1ah/QR+N6iNqvcJE425BNKzg+l2DHO6n2yjkQCCznKLr9Xn9NK/PQZo+AslvRigSYYQV2K+okY4fvJibuMbR0/7s6pjhuzvIHB6Bt32t4ys/OvOOai2v0Wv/Zh8mc2Agubifq+59u2GfNwhHy+5TEqLr/Cy89AQAB '@ $Parameters = New-Object Security.Cryptography.RSAParameters $Parameters.Modulus = [Convert]::FromBase64String($PublicKey.RSAKeyValue.Modulus) $Parameters.Exponent = [Convert]::FromBase64String($PublicKey.RSAKeyValue.Exponent) $Rsa = [Security.Cryptography.RSA]::Create() try { $Rsa.ImportParameters($Parameters) $Signature = [Convert]::FromBase64String((Get-Content -Raw -Path $SignaturePath).Trim()) $Verified = $Rsa.VerifyData( [IO.File]::ReadAllBytes($ArtifactPath), $Signature, [Security.Cryptography.HashAlgorithmName]::SHA256, [Security.Cryptography.RSASignaturePadding]::Pkcs1 ) } finally { $Rsa.Dispose() } if (!$Verified) { throw "B8Grid signature verification failed for $Platform (sha256:12cdf4cf23b853ad27083f76fc1a938899e8b3101050b4c38131f5cd7e8d15ee)." } New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null $CandidatePath = Join-Path $InstallDir (".b8grid.update." + [Guid]::NewGuid().ToString("N") + ".exe") Copy-Item -Force -Path $ArtifactPath -Destination $CandidatePath Move-Item -Force -Path $CandidatePath -Destination (Join-Path $InstallDir "b8grid.exe") Write-Host "Installed b8grid to $(Join-Path $InstallDir "b8grid.exe")" Write-Host "Next: b8grid auth login" $UserPath = [Environment]::GetEnvironmentVariable("Path", "User") if ((";${UserPath};") -notlike "*;$InstallDir;*") { Write-Host "Add $InstallDir to PATH to run b8grid from any shell." } } finally { Remove-Item -Recurse -Force -Path $TempRoot -ErrorAction SilentlyContinue }