#requires -version 5 <# .SYNOPSIS Install sptool on Windows. .DESCRIPTION The SupervisorTool repository is private, so its release assets need credentials. The same binaries are also published as a single-layer scratch image on GHCR, and that package is public — this script pulls the layer with nothing but Invoke-WebRequest and the tar that ships with Windows. After the first install, `sptool update` does the same thing without this script. .EXAMPLE .\install.ps1 $env:SPTOOL_BINDIR = "C:\tools"; .\install.ps1 $env:SPTOOL_VERSION = "0.0.1"; .\install.ps1 #> $ErrorActionPreference = 'Stop' # Invoke-WebRequest's progress bar makes downloads crawl on Windows PowerShell 5.1. $ProgressPreference = 'SilentlyContinue' # 5.1 defaults to TLS 1.0, which ghcr.io refuses. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $registry = 'ghcr.io' $image = 'andrewsav/supervisor-tool-dist' $version = if ($env:SPTOOL_VERSION) { $env:SPTOOL_VERSION } else { 'latest' } # Per-user, already on PATH, and needs no administrator. $dest = if ($env:SPTOOL_BINDIR) { $env:SPTOOL_BINDIR } else { "$env:LOCALAPPDATA\Microsoft\WindowsApps" } $tag = switch -Regex ($version) { '^latest$' { 'latest'; break } '^v' { $version; break } default { "v$version" } } $arch = switch ($env:PROCESSOR_ARCHITECTURE) { 'AMD64' { 'amd64' } 'ARM64' { 'arm64' } default { throw "unsupported architecture $env:PROCESSOR_ARCHITECTURE (amd64 and arm64 only)" } } $member = "sptool-windows-$arch.exe" $tmp = Join-Path ([IO.Path]::GetTempPath()) ("sptool-" + [Guid]::NewGuid()) New-Item -ItemType Directory -Path $tmp | Out-Null try { Write-Host "fetching sptool $tag for windows/$arch from $registry/$image" # Anonymous pull token. Works only because the package is public. # ${image} braces are required — "$image:" would parse as a scope qualifier. $token = (Invoke-RestMethod "https://$registry/token?scope=repository:${image}:pull&service=$registry").token if (-not $token) { throw "could not get a pull token - is $registry/$image public?" } $headers = @{ Authorization = "Bearer $token" Accept = 'application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json' } $manifest = Invoke-RestMethod -Headers $headers "https://$registry/v2/${image}/manifests/$tag" if (-not $manifest.layers) { throw "no layers in the manifest for tag $tag - does that release exist?" } # Single-layer scratch image: the one (last) layer holds the binaries. $digest = $manifest.layers[-1].digest $tgz = Join-Path $tmp 'layer.tgz' Invoke-WebRequest -Headers @{ Authorization = "Bearer $token" } ` "https://$registry/v2/${image}/blobs/$digest" -OutFile $tgz # Verify the download against the digest the manifest named, before unpacking. $want = $digest -replace '^sha256:', '' $got = (Get-FileHash -Algorithm SHA256 -Path $tgz).Hash.ToLower() if ($want -ne $got) { throw "layer digest mismatch (manifest $want, downloaded $got)" } # The bundled bsdtar detects gzip on its own. tar -xf $tgz -C $tmp if ($LASTEXITCODE -ne 0) { throw "tar failed with exit code $LASTEXITCODE" } $src = Join-Path $tmp $member if (-not (Test-Path $src)) { throw "$member is not in the published image" } if (-not (Test-Path $dest)) { New-Item -ItemType Directory -Path $dest -Force | Out-Null } Copy-Item -Force $src (Join-Path $dest 'sptool.exe') $reported = & (Join-Path $dest 'sptool.exe') version Write-Host "installed $reported to $dest\sptool.exe" if (($env:Path -split ';') -notcontains $dest.TrimEnd('\')) { Write-Warning "$dest is not on your PATH" } } finally { Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue }