# install.ps1 — Kami CLI installer (PowerShell 5.1+ / pwsh 7+). # # Served two ways from get.leistenmacher.de: # • per-CLI (recommended): irm https://get.leistenmacher.de//install.ps1 | iex # • generic: & ([scriptblock]::Create((irm https://get.leistenmacher.de/install.ps1))) # # This is a TEMPLATE: is replaced at publish time — with the CLI # name for each per-CLI copy, and with nothing for the generic root copy. # # Asset layout expected: # ///-x86_64-pc-windows-msvc.zip (holds -/.exe) # ///SHA256SUMS [CmdletBinding()] param( [Parameter(Position = 0)][string]$Cli = $(if ($env:KAMI_CLI) { $env:KAMI_CLI } else { "" }), [Parameter(Position = 1)][string]$Version = "latest" ) $ErrorActionPreference = "Stop" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # TLS 1.2 $BaseUrl = if ($env:KAMI_BASE_URL) { $env:KAMI_BASE_URL } else { "https://get.leistenmacher.de" } $InstallDir = if ($env:KAMI_INSTALL_DIR) { $env:KAMI_INSTALL_DIR } else { "$env:LOCALAPPDATA\Kami\bin" } $ValidClis = @("excalidraw","listmonk","opencode-ctl","sish-ctl","solidtime","umami") function Die($msg) { Write-Error "kami-install: $msg"; exit 1 } function Say($msg) { Write-Host "kami-install: $msg" } if (-not $Cli) { Die "no CLI selected. Use a per-CLI installer: irm $BaseUrl//install.ps1 | iex (cli is one of: $($ValidClis -join ', '))" } if ($ValidClis -notcontains $Cli) { Die "unknown CLI '$Cli'. Valid: $($ValidClis -join ', ')" } function Get-Arch { try { $a = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() switch ($a) { "X64" { return "x86_64" } "Arm64" { return "aarch64" } default { return $a } } } catch { if ([Environment]::Is64BitOperatingSystem) { return "x86_64" } else { return "x86" } } } $arch = Get-Arch if ($arch -ne "x86_64") { Die "no Windows build for arch '$arch'. Kami ships x86_64-pc-windows-msvc only. On Windows ARM, run the x64 build under emulation or use WSL + install.sh." } $target = "x86_64-pc-windows-msvc" $asset = "$Cli-$target.zip" $rel = "$BaseUrl/$Cli/$Version" Say "installing $Cli ($Version) for $target" $tmp = Join-Path $env:TEMP ("kami-" + [guid]::NewGuid().ToString()) New-Item -ItemType Directory -Path $tmp -Force | Out-Null try { $zipPath = Join-Path $tmp $asset $sumsPath = Join-Path $tmp "SHA256SUMS" $wc = New-Object Net.WebClient if ($env:HTTPS_PROXY) { $wc.Proxy = New-Object Net.WebProxy($env:HTTPS_PROXY, $true) } try { $wc.DownloadFile("$rel/$asset", $zipPath) } catch { Die "download failed: $rel/$asset" } try { $wc.DownloadFile("$rel/SHA256SUMS", $sumsPath) } catch { Die "download failed: $rel/SHA256SUMS" } # ---- checksum verification ---- $want = (Get-Content $sumsPath | Where-Object { $_ -match "\*?$([regex]::Escape($asset))$" } | Select-Object -First 1) -split '\s+' | Select-Object -First 1 if (-not $want) { Die "no checksum for $asset in SHA256SUMS" } $got = (Get-FileHash -Algorithm SHA256 -Path $zipPath).Hash.ToLower() if ($got -ne $want.ToLower()) { Die "checksum mismatch for $asset`n want: $want`n got: $got" } Say "checksum OK ($asset)" # ---- extract (zip contains -/.exe) ---- Expand-Archive -Path $zipPath -DestinationPath $tmp -Force $exe = Get-ChildItem -Path $tmp -Recurse -Filter "$Cli.exe" | Select-Object -First 1 if (-not $exe) { Die "$Cli.exe not found in archive" } # ---- install ---- New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null Copy-Item $exe.FullName -Destination (Join-Path $InstallDir "$Cli.exe") -Force Say "installed $InstallDir\$Cli.exe" # ---- PATH (user-level, registry + broadcast) ---- if ($env:KAMI_NO_MODIFY_PATH -ne "1") { $reg = 'registry::HKEY_CURRENT_USER\Environment' $cur = (Get-Item -LiteralPath $reg).GetValue('Path', '', 'DoNotExpandEnvironmentNames') -split ';' -ne '' if ($cur -notcontains $InstallDir) { $new = (,$InstallDir + $cur) -join ';' Set-ItemProperty -Type ExpandString -LiteralPath $reg Path $new $dummy = 'kami-' + [guid]::NewGuid().ToString() [Environment]::SetEnvironmentVariable($dummy, 'x', 'User') # broadcast WM_SETTINGCHANGE [Environment]::SetEnvironmentVariable($dummy, [NullString]::value, 'User') $env:PATH = "$InstallDir;$env:PATH" # current session too Say "added $InstallDir to user PATH (new shells will see it)" } } Say "done. run: $Cli --help" } finally { Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue }