diff --git a/install.ps1 b/install.ps1 index 0635459..ccfacec 100644 --- a/install.ps1 +++ b/install.ps1 @@ -51,27 +51,14 @@ function Get-PlatformTriple { } return @{ - ArchId = $archId + ArchId = $archId TargetTriple = $windowsArch + PlatformKey = "windows-$archId" } } # ── Version resolution ──────────────────────────────────────────────────────── -function Get-LatestVersion { - $versionJsonUrl = "${GitHub}/releases/latest/download/version.json" - Write-Log "fetching latest version from ${versionJsonUrl}" - - try { - $version = (Invoke-RestMethod -Uri $versionJsonUrl).version - if (-not $version) { Write-Die "could not parse version from version.json" } - return $version - } - catch { - Write-Die "failed to fetch version.json: $_" - } -} - # ── PATH helpers ────────────────────────────────────────────────────────────── function Add-ToUserPath { @@ -132,17 +119,40 @@ function Main { 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 { - $version = Get-LatestVersion + $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}" } - $tag = "cli-v${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 @@ -152,6 +162,17 @@ function Main { 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 diff --git a/install.sh b/install.sh index dc562d2..0cd25f5 100755 --- a/install.sh +++ b/install.sh @@ -56,13 +56,25 @@ detect_platform() { esac } -fetch_latest_version() { - version_json="${GITHUB}/releases/latest/download/version.json" - log "fetching latest version from ${version_json}" - json="$(curl -fsSL "$version_json")" - version="$(printf '%s' "$json" | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)" - [ -n "$version" ] || die "could not parse version.json" - printf '%s' "$version" +# Extract the per-platform `assets[].sha256` hex from a +# version.json manifest. POSIX sed only (no jq dependency) — matches the +# platform's object block then the sha256 field inside it. +extract_asset_sha256() { + json="$1" + key="$2" + printf '%s\n' "$json" | sed -n '/"'"$key"'":[[:space:]]*{/,/}/ s/.*"sha256"[[:space:]]*:[[:space:]]*"\([0-9a-fA-F]\{1,\}\)".*/\1/p' | head -n 1 +} + +# Print the lowercase sha256 of a file. Returns non-zero (and prints +# nothing) when neither sha256sum nor shasum is available. +compute_sha256() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | sed 's/[[:space:]].*//' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$1" | sed 's/[[:space:]].*//' + else + return 1 + fi } path_contains_dir() { @@ -115,24 +127,61 @@ main() { detect_platform + # Resolve the version + manifest once. The manifest also carries the + # per-asset sha256 we use to verify the download (mirrors `bsk update`, + # which refuses to auto-update a release without a checksum). + manifest_json="" if [ -n "${BSK_VERSION:-}" ]; then version="${BSK_VERSION#v}" + tag="cli-v${version}" + manifest_url="${GITHUB}/releases/download/${tag}/version.json" log "using pinned version ${version}" + manifest_json="$(curl -fsSL "$manifest_url" 2>/dev/null || true)" else - version="$(fetch_latest_version)" + manifest_url="${GITHUB}/releases/latest/download/version.json" + log "fetching latest version from ${manifest_url}" + manifest_json="$(curl -fsSL "$manifest_url")" || die "could not fetch version.json" + version="$(printf '%s' "$manifest_json" | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)" + [ -n "$version" ] || die "could not parse version from version.json" + tag="cli-v${version}" log "latest version is ${version}" fi - tag="cli-v${version}" archive="bsk-v${version}-${triple}.tar.gz" download_url="${GITHUB}/releases/download/${tag}/${archive}" + # Best-effort checksum: a missing manifest/checksum only skips + # verification (does not block the install), but a *mismatch* is fatal. + expected_sha="" + if [ -n "$manifest_json" ]; then + expected_sha="$(extract_asset_sha256 "$manifest_json" "$platform_key")" + else + log "warning: could not fetch version.json; skipping checksum verification" + fi + if [ -z "$expected_sha" ] && [ -n "$manifest_json" ]; then + log "warning: no checksum published for ${platform_key}; skipping checksum verification" + fi + tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT INT TERM log "downloading ${download_url}" curl -fsSL "$download_url" -o "${tmp_dir}/${archive}" + if [ -n "$expected_sha" ]; then + log "verifying checksum" + if actual_sha="$(compute_sha256 "${tmp_dir}/${archive}")"; then + expected_lower="$(printf '%s' "$expected_sha" | tr 'A-F' 'a-z')" + if [ "$actual_sha" = "$expected_lower" ]; then + log "checksum OK" + else + die "checksum mismatch: expected ${expected_lower}, got ${actual_sha}" + fi + else + log "warning: no sha256 tool (sha256sum/shasum) found; skipping checksum verification" + fi + fi + log "extracting ${archive}" tar -xzf "${tmp_dir}/${archive}" -C "$tmp_dir"