Files
klink/install.ps1
T

148 lines
6.3 KiB
PowerShell

[CmdletBinding()]
param(
[string]$Version = $env:KLINK_VERSION,
[string]$InstallDir = $env:KLINK_INSTALL_DIR
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
if ($PSVersionTable.PSEdition -eq "Desktop") {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
}
$RepositoryUrl = if ($env:KLINK_REPOSITORY_URL) { $env:KLINK_REPOSITORY_URL.TrimEnd("/") } else { "https://gitea.bytevibe.dev/gary/klink" }
$ApiUrl = if ($env:KLINK_API_URL) { $env:KLINK_API_URL } else { "https://gitea.bytevibe.dev/api/v1/repos/gary/klink/releases/latest" }
$AllowInsecure = $env:KLINK_ALLOW_INSECURE -eq "1"
function Assert-SecureUrl([string]$Url) {
if (-not $Url.StartsWith("https://", [System.StringComparison]::OrdinalIgnoreCase) -and -not $AllowInsecure) {
throw "Refusing non-HTTPS URL: $Url"
}
}
function Download-File([string]$Url, [string]$Destination) {
Assert-SecureUrl $Url
Invoke-WebRequest -UseBasicParsing -Uri $Url -OutFile $Destination
}
function Get-KlinkArchitecture {
if (-not [string]::IsNullOrWhiteSpace($env:KLINK_ARCH)) {
return $env:KLINK_ARCH.ToLowerInvariant()
}
try {
$RuntimeInformationType = [System.Runtime.InteropServices.RuntimeInformation]
$OSArchitectureProperty = $RuntimeInformationType.GetProperty("OSArchitecture")
if ($null -ne $OSArchitectureProperty) {
$RuntimeArchitecture = $OSArchitectureProperty.GetValue($null, $null)
if ($null -ne $RuntimeArchitecture) {
return $RuntimeArchitecture.ToString().ToLowerInvariant()
}
}
} catch {
# Windows PowerShell 5.1 can run on .NET versions without RuntimeInformation.
}
foreach ($EnvironmentArchitecture in @($env:PROCESSOR_ARCHITEW6432, $env:PROCESSOR_ARCHITECTURE)) {
if (-not [string]::IsNullOrWhiteSpace($EnvironmentArchitecture)) {
return $EnvironmentArchitecture.ToLowerInvariant()
}
}
throw "Could not detect the Windows architecture; set KLINK_ARCH to amd64 or arm64"
}
if ($env:KLINK_OS -and $env:KLINK_OS -ne "windows") {
throw "install.ps1 only installs Windows releases"
}
$Architecture = Get-KlinkArchitecture
switch ($Architecture) {
{ $_ -in @("x64", "amd64") } { $TargetArch = "amd64"; break }
{ $_ -in @("arm64", "aarch64") } { $TargetArch = "arm64"; break }
default { throw "Unsupported Windows architecture: $Architecture" }
}
$Release = $null
if ([string]::IsNullOrWhiteSpace($Version)) {
Assert-SecureUrl $ApiUrl
$Release = Invoke-RestMethod -Uri $ApiUrl
$Tag = [string]$Release.tag_name
if ([string]::IsNullOrWhiteSpace($Tag)) {
throw "Latest Gitea release has no tag"
}
} else {
$Tag = if ($Version.StartsWith("v")) { $Version } else { "v$Version" }
}
$ReleaseVersion = $Tag.TrimStart("v")
$AssetName = "klink_${ReleaseVersion}_windows_${TargetArch}.zip"
if ($null -ne $Release) {
$ArchiveAsset = @($Release.assets | Where-Object { $_.name -eq $AssetName }) | Select-Object -First 1
$ChecksumAsset = @($Release.assets | Where-Object { $_.name -eq "checksums.txt" }) | Select-Object -First 1
if ($null -eq $ArchiveAsset) { throw "Release $Tag has no asset named $AssetName" }
if ($null -eq $ChecksumAsset) { throw "Release $Tag has no checksums.txt" }
$ArchiveUrl = [string]$ArchiveAsset.browser_download_url
$ChecksumUrl = [string]$ChecksumAsset.browser_download_url
} else {
$DownloadBase = "$RepositoryUrl/releases/download/$Tag"
$ArchiveUrl = "$DownloadBase/$AssetName"
$ChecksumUrl = "$DownloadBase/checksums.txt"
}
$Temporary = Join-Path ([System.IO.Path]::GetTempPath()) ("klink-install-" + [System.Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $Temporary | Out-Null
try {
$Archive = Join-Path $Temporary $AssetName
$Checksums = Join-Path $Temporary "checksums.txt"
Write-Host "Downloading klink $Tag for windows/$TargetArch..."
Download-File $ArchiveUrl $Archive
Download-File $ChecksumUrl $Checksums
$Expected = $null
foreach ($Line in Get-Content -LiteralPath $Checksums) {
if ($Line -match '^\s*([0-9a-fA-F]{64})\s+\*?(.+?)\s*$' -and $Matches[2] -eq $AssetName) {
$Expected = $Matches[1].ToLowerInvariant()
break
}
}
if ($null -eq $Expected) { throw "$AssetName is not listed in checksums.txt" }
$Actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $Archive).Hash.ToLowerInvariant()
if ($Actual -ne $Expected) { throw "SHA256 checksum mismatch for $AssetName" }
$ExtractDir = Join-Path $Temporary "extract"
Expand-Archive -LiteralPath $Archive -DestinationPath $ExtractDir
$Binary = Get-ChildItem -LiteralPath $ExtractDir -Filter "klink.exe" -File -Recurse | Select-Object -First 1
if ($null -eq $Binary) { throw "Release archive does not contain klink.exe" }
if ([string]::IsNullOrWhiteSpace($InstallDir)) {
$InstallDir = if ($env:LOCALAPPDATA) {
Join-Path $env:LOCALAPPDATA "Programs\klink\bin"
} else {
Join-Path $HOME ".local\bin"
}
}
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
$Destination = Join-Path $InstallDir "klink.exe"
$Staged = Join-Path $InstallDir (".klink-install-" + $PID + ".exe")
Copy-Item -LiteralPath $Binary.FullName -Destination $Staged -Force
Move-Item -LiteralPath $Staged -Destination $Destination -Force
$UserPath = [Environment]::GetEnvironmentVariable("Path", "User")
$PathEntries = @($UserPath -split ";" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
if (-not ($PathEntries | Where-Object { $_.TrimEnd("\") -ieq $InstallDir.TrimEnd("\") })) {
$NewUserPath = (@($PathEntries) + $InstallDir) -join ";"
[Environment]::SetEnvironmentVariable("Path", $NewUserPath, "User")
Write-Host "Added $InstallDir to your user PATH. Open a new terminal to use it."
}
if (-not (($env:Path -split ";") -contains $InstallDir)) {
$env:Path = "$InstallDir;$env:Path"
}
Write-Host "Installed klink $Tag to $Destination"
if (-not (Get-Command ssh.exe -ErrorAction SilentlyContinue)) {
Write-Warning "OpenSSH was not found in PATH; install the Windows OpenSSH Client before connecting to nodes."
}
} finally {
Remove-Item -LiteralPath $Temporary -Recurse -Force -ErrorAction SilentlyContinue
}