148 lines
6.3 KiB
PowerShell
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
|
|
}
|