<# .SYNOPSIS Installs or updates the Ghaymah CLI (gy) on Windows. .DESCRIPTION Downloads the appropriate pre-built Windows binary, verifies its SHA-256 checksum (when published), installs it to %USERPROFILE%\ghaymah\bin\gy.exe, and adds the directory to your user PATH. .PARAMETER Update Update an existing installation in place. .PARAMETER ServerUrl Override the download server (defaults to https://cliv2.ghaymah.systems). .EXAMPLE # One-liner install (PowerShell 5.1+) irm https://cli.ghaymah.systems/install.ps1 | iex # One-liner install (PowerShell 3/4 compatible) iex ((New-Object System.Net.WebClient).DownloadString('https://cli.ghaymah.systems/install.ps1')) # Update an existing install .\install.ps1 -Update #> [CmdletBinding()] param( [switch]$Update, [string]$ServerUrl = "https://cliv2.ghaymah.systems" ) $ErrorActionPreference = "Stop" # === Configuration ==================================================== $CliName = "gy" $GhaymahDir = "ghaymah" $BinDir = "bin" # === Output helpers =================================================== function Write-Info { Write-Host $args -ForegroundColor Cyan } function Write-Success { Write-Host $args -ForegroundColor Green } function Write-Warning { Write-Host $args -ForegroundColor Yellow } function Write-Error { Write-Host $args -ForegroundColor Red } # === Platform detection =============================================== function Get-GoArch { $arch = $env:PROCESSOR_ARCHITECTURE if (-not $arch) { Write-Error "Unable to determine processor architecture." return $null } # A 32-bit PowerShell running on 64-bit Windows reports "x86"; the real # architecture lives in PROCESSOR_ARCHITEW6432. if ($arch -ieq "x86") { $real = $env:PROCESSOR_ARCHITEW6432 if ($real) { $arch = $real } } switch -Regex ($arch.ToUpperInvariant()) { "^(AMD64|X86_64)$" { return "amd64" } "^(ARM64|AARCH64)$" { return "arm64" } "^(X86|I386|I686)$" { return "386" } default { Write-Error "Unsupported architecture: $arch" return $null } } } # === Download helper ================================================== # Prefer curl.exe (bundled with Windows 10 1803+) for speed and reliability, # and fall back to Invoke-WebRequest for older systems. function Invoke-Download { param([string]$Url, [string]$OutFile) if (Get-Command curl.exe -ErrorAction SilentlyContinue) { & curl.exe -sSL --fail --connect-timeout 30 --max-time 300 -o $OutFile $Url if ($LASTEXITCODE -eq 0) { return $true } # Fall through to Invoke-WebRequest on failure } try { Invoke-WebRequest -Uri $Url -OutFile $OutFile -UseBasicParsing return $true } catch { return $false } } # === Checksum verification ============================================ function Test-Checksum { param([string]$BinaryPath, [string]$BinaryName) $checksumUrl = "$ServerUrl/$BinaryName.sha256" $checksumFile = [System.IO.Path]::GetTempFileName() try { if (-not (Invoke-Download $checksumUrl $checksumFile)) { Write-Warning "No checksum published for $BinaryName - skipping verification" return $true } $expected = (Get-Content $checksumFile -Raw).Trim().Split(' ')[0] if (-not $expected) { Write-Warning "Checksum file is empty - skipping verification" return $true } $actual = (Get-FileHash -Path $BinaryPath -Algorithm SHA256).Hash.ToLowerInvariant() if ($actual -ne $expected.ToLowerInvariant()) { Write-Error "Checksum verification FAILED for $BinaryName" Write-Error "Expected: $expected" Write-Error "Actual: $actual" return $false } Write-Success "Checksum verified" return $true } finally { Remove-Item $checksumFile -Force -ErrorAction SilentlyContinue } } # === Installation directory =========================================== function Get-InstallDir { $home = $env:USERPROFILE if (-not $home) { $home = $env:HOMEDRIVE + $env:HOMEPATH } return Join-Path (Join-Path $home $GhaymahDir) $BinDir } # === PATH management ================================================== function Add-ToUserPath { param([string]$Dir) $userPath = [Environment]::GetEnvironmentVariable("Path", "User") if (-not $userPath) { $userPath = "" } if ($userPath.Split(';') -icontains $Dir) { Write-Info "PATH already contains $Dir" return } $newPath = if ($userPath.Trim().Length -eq 0) { $Dir } else { "$Dir;$userPath" } [Environment]::SetEnvironmentVariable("Path", $newPath, "User") # Also update the current session so `gy` works without reopening the shell $env:Path = "$Dir;$env:Path" Write-Success "Added $Dir to your user PATH" } # === Main ============================================================= function Main { $arch = Get-GoArch if (-not $arch) { exit 1 } $binaryName = "$CliName-windows-$arch.exe" $installDir = Get-InstallDir $binaryPath = Join-Path $installDir "$CliName.exe" Write-Info "Detected platform: windows-$arch" # == Update mode == if ($Update) { if (Test-Path $binaryPath) { Write-Info "Updating existing installation at $binaryPath" } else { Write-Info "No existing installation found - performing a fresh install" } $temp = [System.IO.Path]::GetTempFileName() try { $url = "$ServerUrl/$binaryName" Write-Info "Downloading $CliName from $url" if (-not (Invoke-Download $url $temp)) { Write-Error "Failed to download binary from $url" exit 1 } if (-not (Test-Checksum $temp $binaryName)) { exit 1 } # Back up the current binary, then swap (Windows allows replacing # a non-running exe; if gy.exe is currently executing, this fails # gracefully and we report it). if (Test-Path $binaryPath) { Copy-Item $binaryPath "$binaryPath.bak" -Force } Copy-Item $temp $binaryPath -Force Remove-Item "$binaryPath.bak" -Force -ErrorAction SilentlyContinue Write-Success "Successfully updated $CliName" } finally { Remove-Item $temp -Force -ErrorAction SilentlyContinue } exit 0 } # == Fresh install == if (-not (Test-Path $installDir)) { Write-Info "Creating installation directory: $installDir" New-Item -ItemType Directory -Path $installDir -Force | Out-Null } $temp = [System.IO.Path]::GetTempFileName() try { $url = "$ServerUrl/$binaryName" Write-Info "Downloading $CliName from $url" if (-not (Invoke-Download $url $temp)) { Write-Error "Failed to download binary from $url" exit 1 } if (-not (Test-Checksum $temp $binaryName)) { exit 1 } Copy-Item $temp $binaryPath -Force Write-Success "Binary downloaded successfully to $binaryPath" } finally { Remove-Item $temp -Force -ErrorAction SilentlyContinue } Add-ToUserPath $installDir Write-Success "Installation completed successfully!" Write-Info "Binary installed to: $binaryPath" Write-Info "Open a new terminal and run 'gy --version' to verify." } Main