Ctrl K
ComunidadGuías…

install.ps1

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Opengrep installation script for Windows PowerShell
#
# NOTES
# ============================
#
# 1. Junctions
#    Uses directory junctions (mklink /J) which do not require admin privileges.
#    Falls back to copying files if junction creation fails.
#
# 2. Binary Naming
#    Windows: Binary is named 'opengrep.exe'
#
# 3. Architecture Support
#    Currently only x86_64 builds are available. On ARM64 Windows, the x86_64
#    build is installed and runs under emulation.
#
# 4. PATH Integration
#    Provides instructions for manually adding to PATH. No automatic
#    modification of user PATH to avoid requiring elevation.
#
# 5. Parameter Naming (PowerShell conventions)
#    -v <version>        ->  -Version <version>
#    --verify-signatures ->  -VerifySignatures
#    -l                  ->  -List
#    -h                  ->  -Help
#
# 6. Installation Path
#    %USERPROFILE%\.opengrep\cli\<version>\opengrep.exe
#
# USAGE:
# ======
#
# Local execution:
#   .\install.ps1                       # Install latest version
#   .\install.ps1 -Version v1.15.0      # Install specific version
#   .\install.ps1 -VerifySignatures     # Verify with cosign
#   .\install.ps1 -List                 # List available versions
#   .\install.ps1 -Help                 # Show help
#
# Remote execution (one-liner):
#   irm https://raw.githubusercontent.com/opengrep/opengrep/main/install.ps1 | iex
#
# Remote execution with parameters:
#   & ([scriptblock]::Create((irm https://raw.githubusercontent.com/opengrep/opengrep/main/install.ps1))) -Version v1.15.0
#
# Note: You may need to adjust your execution policy:
#   Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
#

#Requires -Version 5.1

[CmdletBinding()]
param(
    [Parameter()]
    [string]$Version,

    [Parameter()]
    [switch]$VerifySignatures,

    [Parameter()]
    [switch]$List,

    [Parameter()]
    [switch]$Help
)

# Session-global under irm | iex, like SecurityProtocol below; both are
# restored at the end of the run.
$originalErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "Stop"

# PowerShell 5.1's SystemDefault protocol negotiation can be aborted by
# github.com ("connection was closed unexpectedly"), which also masks HTTP
# status codes such as 404. The same happens when TLS 1.3 is enabled, as
# .NET Framework's TLS 1.3 support is unreliable; force TLS 1.2 only.
# The setting is process-global, so it is restored at the end of the run:
# under the documented one-liners the script executes in the caller's
# session, which must not stay pinned to TLS 1.2.
$originalSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol
if ($PSVersionTable.PSEdition -ne 'Core') {
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
}

$ScriptName = if ($MyInvocation.MyCommand.Name) { $MyInvocation.MyCommand.Name } else { "install.ps1" }

function Print-Usage {
    Write-Host "Usage:"
    Write-Host "  $ScriptName [-Version <version>] [-VerifySignatures]"
    Write-Host "      Install the latest or specified version (default: latest)"
    Write-Host ""
    Write-Host "  $ScriptName -List"
    Write-Host "      List the latest 3 available versions"
    Write-Host ""
    Write-Host "  $ScriptName -Help"
    Write-Host "      Show this help message"
    Write-Host ""
    Write-Host "Options:"
    Write-Host "  -Version <version>    Specify version to install (optional)"
    Write-Host "  -VerifySignatures     Require Cosign verification of signature"
    Write-Host "  -List                 List latest 3 versions (no install)"
    Write-Host "  -Help                 Display help (no install)"
    Write-Host ""
    Write-Host "Notes:"
    Write-Host "  - '-VerifySignatures' can be used with or without '-Version'."
    Write-Host "  - '-List' and '-Help' cannot be combined with other options."
}

# The version is interpolated into the install path and download URL, so accept
# only a release tag. Same semver shape as validate-inputs in rolling-release,
# with the suffix restricted to tag characters so no path separator gets through.
# [.] rather than \. to match the regex literal in install.sh; \z rather
# than $ because .NET $ accepts a trailing newline where bash rejects it.
$ReleaseTagRegex = '^v[0-9]+[.][0-9]+[.][0-9]+(-[A-Za-z0-9._-]+)?\z'

function Test-VersionFormat {
    param([string]$Version)

    if ($Version -cnotmatch $ReleaseTagRegex) {
        throw "Invalid version '$Version'. Expected a release tag such as v1.27.1."
    }
}

# coupling: is_2_or_later in install.sh
function Test-Is2OrLater {
    param([string]$Version)

    return ([int]($Version.TrimStart('v').Split('.')[0]) -ge 2)
}

function Throw-FetchFailed {
    param([string]$Reason)

    if ($Reason) { throw "Failed to fetch available versions from GitHub: $Reason" }
    throw "Failed to fetch available versions from GitHub."
}

# Lists the Count most recent releases, newest first, marking pre-releases.
# Throws on failure so a caller mid-install can still clean up.
function Get-VersionList {
    param([int]$Count)

    try {
        $response = Invoke-RestMethod -Uri "https://api.github.com/repos/opengrep/opengrep/releases?per_page=$Count" -UseBasicParsing
    }
    catch {
        $reason = $_.Exception.Message
        if ($_.Exception.Response) {
            $status = [int]$_.Exception.Response.StatusCode
            # GitHub API error bodies explain the failure in a "message" field.
            $detail = $null
            try { $detail = (ConvertFrom-Json $_.ErrorDetails.Message).message } catch {}
            $reason = if ($detail) { "HTTP status $status ($detail)." } else { "HTTP status $status." }
        }
        Throw-FetchFailed $reason
    }
    # Tags that are not release tags are not listed.
    $versions = @($response | Where-Object { $_.tag_name -cmatch $ReleaseTagRegex } | ForEach-Object {
        if ($_.prerelease) { "$($_.tag_name) (pre-release)" } else { $_.tag_name }
    })
    if ($versions.Count -eq 0) {
        Throw-FetchFailed
    }
    return $versions
}

function Show-VersionList {
    param([int]$Count)

    Write-Host "Available versions (latest $Count):"
    Get-VersionList -Count $Count | ForEach-Object { Write-Host "  $_" }
}

# The /releases/latest web redirect never points to a pre-release or draft,
# and does not use the rate-limited API.
function Get-LatestVersion {
    try {
        $response = Invoke-WebRequest -Uri "https://github.com/opengrep/opengrep/releases/latest" -Method Head -UseBasicParsing
        $finalUri = if ($PSVersionTable.PSEdition -eq 'Core') {
            # PowerShell 7+ (HttpResponseMessage)
            $response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri
        }
        else {
            # PowerShell 5.1 (HttpWebResponse)
            $response.BaseResponse.ResponseUri.AbsoluteUri
        }
        if ($finalUri -match '/releases/tag/([^/]+)$') {
            return $Matches[1]
        }
        throw "unexpected final URL $finalUri"
    }
    catch {
        throw "Failed to determine the latest version: $_"
    }
}

function Find-Cosign {
    # Check known binary names: official installs use 'cosign',
    # winget and manual downloads use 'cosign-windows-amd64'.
    foreach ($name in @('cosign', 'cosign-windows-amd64')) {
        $cmd = Get-Command $name -ErrorAction SilentlyContinue
        if ($null -ne $cmd) {
            return $cmd.Source
        }
    }
    return $null
}

function Test-CosignInstalled {
    return $null -ne (Find-Cosign)
}

function Get-CosignMajorVersion {
    $cosignPath = Find-Cosign
    if ($null -eq $cosignPath) { return $null }
    try {
        $versionOutput = & $cosignPath version 2>&1
        $versionLine = $versionOutput | Where-Object { $_ -match "GitVersion" }
        if ($versionLine -match "v?(\d+)") {
            return [int]$Matches[1]
        }
    }
    catch {}
    return $null
}

function Validate-Signature {
    param(
        [string]$InstallPath,
        [string]$SignedFile
    )

    if ($script:HasCosign) {
        Write-Host "Verifying signatures for $InstallPath\opengrep.cert"
        $cosignPath = Find-Cosign
        # Run cosign in a child scope with $ErrorActionPreference = "Continue"
        # so that stderr text (e.g. "Verified OK") is not treated as a
        # terminating ErrorRecord under PS 5.1's global "Stop" preference.
        $result = & {
            $ErrorActionPreference = "Continue"
            & $cosignPath verify-blob `
                --cert "$InstallPath\opengrep.cert" `
                --signature "$InstallPath\opengrep.sig" `
                --certificate-identity-regexp "https://github.com/opengrep/opengrep.+" `
                --certificate-oidc-issuer "https://token.actions.githubusercontent.com" `
                "$SignedFile" 2>&1
        } | Out-String

        if ($LASTEXITCODE -eq 0) {
            Write-Host "Signature valid."
        }
        else {
            Write-Host $result.Trim()
            throw "Signature validation error."
        }
    }
    else {
        Write-Host "Warning: cosign needed for signature validation; the package will still be installed." -ForegroundColor Yellow
        Write-Host "If this was not intended, delete and rerun with -VerifySignatures or install cosign."
    }
}

function Cleanup-OnFailure {
    param([string]$InstallPath)

    Write-Host "An error occurred during the installation. Cleaning up $InstallPath..." -ForegroundColor Yellow
    Remove-Item -Path "$InstallPath\opengrep.exe" -ErrorAction SilentlyContinue
    Remove-Item -Path "$InstallPath\opengrep.exe.download" -ErrorAction SilentlyContinue
    Remove-Item -Path "$InstallPath\opengrep.sig" -ErrorAction SilentlyContinue
    Remove-Item -Path "$InstallPath\opengrep.cert" -ErrorAction SilentlyContinue
    Remove-Item -Path "$InstallPath\*.dll" -ErrorAction SilentlyContinue
    Remove-Item -Path "$InstallPath\opengrep_windows_x86.zip*" -ErrorAction SilentlyContinue
    Remove-Item -Path $InstallPath -ErrorAction SilentlyContinue
}

function Update-LatestLink {
    param(
        [string]$LatestPath,
        [string]$TargetPath
    )

    # Remove existing latest directory/junction if it exists
    if (Test-Path $LatestPath) {
        $item = Get-Item $LatestPath -Force
        if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
            # It's a junction/symlink, remove it
            cmd /c rmdir "$LatestPath" 2>$null
        }
        else {
            Remove-Item -Path $LatestPath -Recurse -Force
        }
    }

    # Create a directory junction (does not require admin privileges)
    cmd /c mklink /J "$LatestPath" "$TargetPath" >$null 2>&1
    if ($LASTEXITCODE -ne 0) {
        # Fallback: copy the directory if junction creation fails
        Write-Host "Warning: Could not create directory junction, copying files instead." -ForegroundColor Yellow
        Copy-Item -Path $TargetPath -Destination $LatestPath -Recurse -Force
    }
}

function Main {
    param(
        [string]$VersionToInstall,
        [bool]$DoVerifySignatures
    )

    $prefix = Join-Path $env:USERPROFILE ".opengrep\cli"
    $inst = Join-Path $prefix $VersionToInstall
    $latest = Join-Path $prefix "latest"

    # Windows architecture detection
    $arch = $env:PROCESSOR_ARCHITECTURE
    if (-not $arch) {
        $arch = (Get-CimInstance Win32_Processor).Architecture
    }

    # Determine distribution name
    # Currently only x86_64 Windows builds are available
    # From 2.0.0 the asset is an archive holding opengrep.exe and its DLLs;
    # before that, a single self-extracting executable.
    $isArchive = Test-Is2OrLater -Version $VersionToInstall
    $dist = if ($isArchive) { "opengrep_windows_x86.zip" } else { "opengrep_windows_x86.exe" }
    if ($arch -eq "ARM64") {
        Write-Host "Warning: ARM64 Windows builds are not yet available. Installing x86_64 build (runs under emulation)." -ForegroundColor Yellow
    }
    elseif ($arch -ne "AMD64" -and $arch -ne "x86_64") {
        throw "Architecture '$arch' is unsupported."
    }

    $url = "https://github.com/opengrep/opengrep/releases/download/$VersionToInstall/$dist"

    # Check if binary already exists
    $binaryPath = Join-Path $inst "opengrep.exe"
    if (Test-Path $binaryPath) {
        Write-Host "Destination binary $binaryPath already exists."
        Update-LatestLink -LatestPath $latest -TargetPath $inst
        Write-Host "Updated link from $latest\opengrep.exe to point to $binaryPath."
        if ($DoVerifySignatures) {
            Write-Host "Signature verification skipped for existing installation."
        }
    }
    else {
        Write-Host ""
        Write-Host "*** Installing Opengrep $VersionToInstall for Windows ($arch) ***"

        # Create install directory
        if (-not (Test-Path $inst)) {
            New-Item -ItemType Directory -Path $inst -Force | Out-Null
        }

        try {
            # Download the asset
            Write-Host "Downloading $url..."
            $progressPreference = 'SilentlyContinue'  # Speeds up Invoke-WebRequest
            # The download also validates the version, avoiding the rate-limited
            # GitHub API: a 404 means the tag does not exist or has no asset for
            # this platform. A temporary name, renamed only on success, so an
            # interrupted download is never mistaken for an installed binary.
            # The archive is kept under its asset name: it is the file the
            # signature covers, and Expand-Archive requires a .zip extension.
            $assetPath = if ($isArchive) { Join-Path $inst $dist } else { $binaryPath }
            $downloadPath = "$assetPath.download"
            try {
                Invoke-WebRequest -Uri $url -OutFile $downloadPath -UseBasicParsing
            }
            catch {
                if ($_.Exception.Response.StatusCode -eq 404) {
                    Write-Host "Error: Version $VersionToInstall not found, or it has no $dist asset." -ForegroundColor Red
                    # A listing failure must not mask the diagnosis above.
                    try { Show-VersionList -Count 3 } catch { Write-Host "  ($_)" }
                    throw "Version not found"
                }
                throw "Failed to download $url`: $_"
            }
            Move-Item -Force -Path $downloadPath -Destination $assetPath

            $sigExists = $true

            # Try downloading .cert
            $certPath = Join-Path $inst "opengrep.cert"
            $certUrl = "$url.cert"
            try {
                Invoke-WebRequest -Uri $certUrl -OutFile $certPath -UseBasicParsing
            }
            catch {
                if ($_.Exception.Response.StatusCode -eq 404) {
                    $sigExists = $false
                    Remove-Item -Path $certPath -ErrorAction SilentlyContinue
                    Write-Host "Warning: Certificate file not found at $certUrl" -ForegroundColor Yellow
                }
                else {
                    throw "Failed to download $certUrl`: $_"
                }
            }

            if ($sigExists) {
                # Only attempt .sig if .cert was found
                $sigPath = Join-Path $inst "opengrep.sig"
                $sigUrl = "$url.sig"
                try {
                    Invoke-WebRequest -Uri $sigUrl -OutFile $sigPath -UseBasicParsing
                }
                catch {
                    if ($_.Exception.Response.StatusCode -eq 404) {
                        Write-Host "Error: Signature file not found at $sigUrl, but $certUrl was found." -ForegroundColor Red
                        throw "Signature file missing"
                    }
                    else {
                        throw "Failed to download $sigUrl`: $_"
                    }
                }
            }

            # Check signature if it exists
            if ($sigExists) {
                Validate-Signature -InstallPath $inst -SignedFile $assetPath
            }
            else {
                if ($DoVerifySignatures) {
                    Write-Host "Error: No signature / certificate found for $VersionToInstall but -VerifySignatures was requested." -ForegroundColor Red
                    Write-Host "Error: It is likely that signature verification was added after this version."
                    throw "Signature verification failed"
                }
                else {
                    Write-Host "Warning: No signature / certificate found for $VersionToInstall. Skipping signature verification." -ForegroundColor Yellow
                    Write-Host "Warning: The package will still be installed. It is likely that signature verification was added after this version."
                }
            }

            # The archive is unpacked only once its signature has been checked.
            if ($isArchive) {
                Expand-Archive -Path $assetPath -DestinationPath $inst -Force
                Remove-Item -Force -Path $assetPath
            }

            # Verify the binary exists
            if (-not (Test-Path $binaryPath)) {
                throw "Failed to download binary at $binaryPath"
            }

            Write-Host "Testing binary..."
            # Test by calling --version on the downloaded binary.
            # We route stderr through a temp file rather than using `2>&1`, so
            # that harmless runtime warnings (e.g. requests' RequestsDependency-
            # Warning) are not surfaced by PowerShell as NativeCommandError
            # records and misinterpreted as failures. We still surface them to
            # the user via Write-Host, and rely on $LASTEXITCODE to decide
            # whether the binary actually ran.
            $stderrFile = New-TemporaryFile
            try {
                $testOutput = & $binaryPath --version 2>$stderrFile
                $testExit   = $LASTEXITCODE
                $testStderr = (Get-Content -Raw -ErrorAction SilentlyContinue $stderrFile)
            }
            finally {
                Remove-Item -Force -ErrorAction SilentlyContinue $stderrFile
            }
            if ($testStderr) {
                Write-Host $testStderr
            }
            if ($testExit -ne 0 -or -not $testOutput) {
                throw "Failed to execute installed binary: $binaryPath"
            }

            Write-Host ""
            Write-Host "Successfully installed Opengrep binary at $binaryPath"

            Update-LatestLink -LatestPath $latest -TargetPath $inst
            Write-Host "with a link from $latest\opengrep.exe"
        }
        catch {
            Cleanup-OnFailure -InstallPath $inst
            throw
        }
    }

    # Add to PATH guidance
    $latestBinary = Join-Path $latest "opengrep.exe"

    Write-Host ""
    Write-Host "To launch Opengrep now, type:"
    Write-Host "  $latestBinary"
    Write-Host ""
    Write-Host "To add Opengrep to your PATH permanently, run (as Administrator):"
    Write-Host "  `$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User')"
    Write-Host "  [Environment]::SetEnvironmentVariable('Path', `"`$currentPath;$latest`", 'User')"
    Write-Host ""
    Write-Host "Or add '$latest' to your PATH manually via System Properties."
    Write-Host ""
}

# --- Main script execution ---

# Failures are thrown and reported once in the catch below; `exit` happens
# only at the very end, and only when running as a script file. Under the
# documented one-liners the script executes at session scope, where an
# early exit would close the caller's console; the failure is rethrown
# instead, so callers still observe a terminating error.
$failed = $false
try {
    # Check for cosign
    $script:HasCosign = Test-CosignInstalled

    # Validate argument combinations
    if (($Help -and ($List -or $Version -or $VerifySignatures)) -or
        ($List -and ($Version -or $VerifySignatures))) {
        Print-Usage
        throw "incorrect arguments"
    }

    if ($VerifySignatures -and -not $script:HasCosign) {
        throw "cosign is required for -VerifySignatures but is not installed.`nGo to https://github.com/sigstore/cosign to install it or run without the -VerifySignatures flag to install without signature verification."
    }
    elseif (-not $script:HasCosign) {
        Write-Host "Warning: cosign is required for -VerifySignatures but is not installed. Skipping signature validation." -ForegroundColor Yellow
        Write-Host "Go to https://github.com/sigstore/cosign to install it."
    }
    elseif ($script:HasCosign) {
        $cosignMajor = Get-CosignMajorVersion
        if ($null -eq $cosignMajor) {
            if ($VerifySignatures) {
                throw "could not determine cosign version and -VerifySignatures was requested.`nYour cosign binary may have been built without version metadata (e.g. distro packages).`nInstall cosign from https://github.com/sigstore/cosign or run without -VerifySignatures."
            }
            else {
                Write-Host "Warning: could not determine cosign version. Signature validation may not work correctly." -ForegroundColor Yellow
            }
        }
        elseif ($cosignMajor -lt 2) {
            Write-Host "Warning: cosign version is less than 2.0.0, signature validation may fail." -ForegroundColor Yellow
        }
    }

    if ($Help) {
        Print-Usage
    }
    elseif ($List) {
        Show-VersionList -Count 3
    }
    else {
        # Determine version to install; an explicit empty -Version is
        # rejected by the format check, as in install.sh
        if (-not $PSBoundParameters.ContainsKey('Version')) {
            $Version = Get-LatestVersion
        }
        Test-VersionFormat -Version $Version
        Main -VersionToInstall $Version -DoVerifySignatures $VerifySignatures.IsPresent
    }
}
catch {
    if ($MyInvocation.MyCommand.Path) {
        Write-Host "Error: $_" -ForegroundColor Red
        $failed = $true
    }
    else {
        throw
    }
}
finally {
    $ErrorActionPreference = $originalErrorActionPreference
    if ($PSVersionTable.PSEdition -ne 'Core') {
        [Net.ServicePointManager]::SecurityProtocol = $originalSecurityProtocol
    }
}

if ($failed) {
    exit 1
}