Files
BrowserSkill/install.ps1
T
Qiaochu HuandClaude c75f97119d fix(install): verify release archive checksum before install (#38)
The first-run installers (install.sh, install.ps1) downloaded and
extracted the bsk release archive with no integrity check, even though
the in-app `bsk update` path treats a sha256 checksum as mandatory
(update.rs bails with "does not include a sha256 checksum; cannot
safely auto-update"). The release version.json already carries a
per-asset sha256 (render-version-json.mjs renders with --dist), so the
installers can verify the same way.

Both installers now fetch version.json once (pinned and latest), read
`assets[<platform>].sha256`, and verify the downloaded archive's sha256
before extracting:
- install.sh: POSIX sed to parse the field (no jq dependency), and
  sha256sum/shasum to compute the digest.
- install.ps1: Invoke-RestMethod + Get-FileHash.

A checksum mismatch is fatal (fail-closed, matching `bsk update`). A
missing manifest/checksum, or the absence of a sha256 tool, only skips
verification with a warning, so the first-run bootstrap still works on
minimal/offline machines.

Validation: `sh -n` / `bash -n`; functional test of the sed extraction
against a realistic version.json and of compute_sha256 against
sha256sum. The installers are not exercised by CI (no shell harness).

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-12 18:44:17 +08:00

219 lines
7.5 KiB
PowerShell

<#
.SYNOPSIS
install.ps1 — install the bsk CLI on Windows from GitHub Releases.
.DESCRIPTION
Downloads the latest (or pinned) bsk release for Windows x64,
extracts bsk.exe to a user-local directory, and adds it to PATH.
Usage:
irm https://raw.githubusercontent.com/Tencent/BrowserSkill/main/install.ps1 | iex
Environment overrides:
$env:BSK_REPO GitHub owner/repo (default: Tencent/BrowserSkill)
$env:BSK_VERSION Pin CLI version (default: latest from version.json)
$env:BSK_INSTALL_DIR Install directory (default: $HOME\.local\bin)
#>
#Requires -Version 5.1
$ErrorActionPreference = "Stop"
$Repo = if ($env:BSK_REPO) { $env:BSK_REPO } else { "Tencent/BrowserSkill" }
$InstallDir = if ($env:BSK_INSTALL_DIR) { $env:BSK_INSTALL_DIR } else { Join-Path $HOME ".local\bin" }
$GitHub = "https://github.com/${Repo}"
function Write-Log {
param([string]$Message)
Write-Host "==> $Message" -ForegroundColor Cyan
}
function Write-Die {
param([string]$Message)
Write-Host "error: $Message" -ForegroundColor Red
exit 1
}
# ── Platform / architecture detection ─────────────────────────────────────────
function Get-PlatformTriple {
$arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture
switch ($arch) {
"X64" { $archId = "x64" }
"Arm64" { $archId = "arm64" }
default { Write-Die "unsupported architecture: $arch (x64 and ARM64 only)" }
}
$windowsArch = switch ($archId) {
"x64" { "x86_64-pc-windows-msvc" }
"arm64" { "aarch64-pc-windows-msvc" }
}
return @{
ArchId = $archId
TargetTriple = $windowsArch
PlatformKey = "windows-$archId"
}
}
# ── Version resolution ────────────────────────────────────────────────────────
# ── PATH helpers ──────────────────────────────────────────────────────────────
function Add-ToUserPath {
param([string]$Dir)
$currentUserPath = [Environment]::GetEnvironmentVariable("PATH", "User") -split ";" | Where-Object { $_ }
if ($currentUserPath -contains $Dir) {
Write-Log "$Dir is already in your user PATH"
return
}
$newUserPath = ($currentUserPath + $Dir) -join ";"
[Environment]::SetEnvironmentVariable("PATH", $newUserPath, "User")
Write-Log "added ${Dir} to user PATH"
}
function Add-ToSessionPath {
param([string]$Dir)
$pathEntries = $env:PATH -split ";" | Where-Object { $_ }
if ($pathEntries -contains $Dir) {
return
}
$env:PATH = "$Dir;$env:PATH"
}
# ── Git Bash (bash environment) PATH helper ──────────────────────────────────
function Add-ToBashProfile {
param([string]$Dir)
# Convert Windows path (e.g. C:\Users\foo\.local\bin) to Git-Bash Unix-style (/c/Users/foo/.local/bin)
$unixPath = $Dir -replace '\\', '/'
if ($unixPath -match '^([A-Z]):(.*)$') {
$unixPath = '/' + $matches[1].ToLower() + $matches[2]
}
$bashRc = Join-Path $HOME ".bashrc"
$exportLine = "export PATH=""${unixPath}:`$PATH"" # bsk CLI"
if (Test-Path $bashRc) {
$content = Get-Content $bashRc -Raw -ErrorAction SilentlyContinue
if ($content -match [regex]::Escape($unixPath)) {
Write-Log "$unixPath is already in ~/.bashrc"
return
}
}
Add-Content $bashRc "`n$exportLine" -Encoding ASCII
Write-Log "added ${unixPath} to ~/.bashrc"
}
# ── Main ──────────────────────────────────────────────────────────────────────
function Main {
$platform = Get-PlatformTriple
if ($env:BSK_VERSION) {
$version = $env:BSK_VERSION -replace '^v', ''
$tag = "cli-v${version}"
$manifestUrl = "${GitHub}/releases/download/${tag}/version.json"
Write-Log "using pinned version ${version}"
# Best-effort manifest fetch for the checksum (missing manifest
# only skips verification; a mismatch is fatal below).
try { $manifest = Invoke-RestMethod -Uri $manifestUrl } catch { $manifest = $null }
}
else {
$manifestUrl = "${GitHub}/releases/latest/download/version.json"
Write-Log "fetching latest version from ${manifestUrl}"
$manifest = Invoke-RestMethod -Uri $manifestUrl
$version = $manifest.version
if (-not $version) { Write-Die "could not parse version from version.json" }
$tag = "cli-v${version}"
Write-Log "latest version is ${version}"
}
$archiveName = "bsk-v${version}-$($platform.TargetTriple).zip"
$downloadUrl = "${GitHub}/releases/download/${tag}/${archiveName}"
$expectedSha = $null
$platformKey = $platform.PlatformKey
if ($manifest -and $manifest.assets) {
$expectedSha = $manifest.assets.$platformKey.sha256
}
if (-not $expectedSha) {
if (-not $manifest) {
Write-Log "warning: could not fetch version.json; skipping checksum verification"
}
else {
Write-Log "warning: no checksum published for $($platform.PlatformKey); skipping checksum verification"
}
}
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
try {
$archivePath = Join-Path $tempDir $archiveName
Write-Log "downloading ${downloadUrl}"
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath -UseBasicParsing -ErrorAction Stop
if ($expectedSha) {
Write-Log "verifying checksum"
$actualSha = (Get-FileHash -Algorithm SHA256 -Path $archivePath).Hash
if ($actualSha -ieq $expectedSha) {
Write-Log "checksum OK"
}
else {
Write-Die "checksum mismatch: expected $expectedSha, got $actualSha"
}
}
Write-Log "extracting ${archiveName}"
Expand-Archive -Path $archivePath -DestinationPath $tempDir -Force
if (-not (Test-Path (Join-Path $tempDir "bsk.exe"))) {
Write-Die "bsk.exe not found in archive"
}
if (-not (Test-Path $InstallDir)) {
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
}
Copy-Item -Path (Join-Path $tempDir "bsk.exe") -Destination (Join-Path $InstallDir "bsk.exe") -Force
Write-Log "installed bsk to $InstallDir\bsk.exe"
# Add to session PATH (current shell)
Add-ToSessionPath $InstallDir
# Add to user PATH (persistent, for PowerShell / cmd)
Add-ToUserPath $InstallDir
# Add to Git Bash PATH (persistent, for bash-based shells / agents)
Add-ToBashProfile $InstallDir
# Verify
$bskPath = Join-Path $InstallDir "bsk.exe"
if (Get-Command bsk -ErrorAction SilentlyContinue) {
& bsk --version
}
else {
Write-Log "verify install: & ""$bskPath"" --version"
}
Write-Log "done"
Write-Host ""
Write-Host "Open a new terminal (PowerShell / Git Bash) for PATH changes to take full effect."
}
finally {
Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue
}
}
Main