feat(cli): add install scripts for macOS, Linux, and Windows (#587)

* feat(cli): add install scripts for macOS, Linux, and Windows

Bash script (install.sh) for macOS/Linux and PowerShell script
(install.ps1) for Windows. Both download the correct platform binary
from GitHub Releases with checksum verification, version resolution,
and PATH setup.

* fix(cli): address PR review comments for install scripts

- Add checksum verification to install.ps1 using Get-FileHash
- Add warnings on all checksum skip paths in install.sh
- Use grep -F (fixed-string) instead of regex for filename matching
- Add ?per_page=100 to GitHub API call in install.ps1
- Use random temp directory name in install.ps1 to avoid collisions

* fix(cli): address installer review feedback
This commit is contained in:
Nikhil
2026-03-26 17:05:21 -07:00
committed by GitHub
parent aa85907212
commit 03b45013a6
2 changed files with 300 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
#
# Install browseros-cli for Windows — downloads the latest release binary.
#
# Usage (PowerShell — save and run):
# Invoke-WebRequest -Uri "https://raw.githubusercontent.com/browseros-ai/BrowserOS/main/packages/browseros-agent/apps/cli/scripts/install.ps1" -OutFile install.ps1
# .\install.ps1
# .\install.ps1 -Version "0.1.0" -Dir "C:\tools\browseros"
#
# Usage (one-liner, uses env vars for options):
# & { $env:BROWSEROS_VERSION="0.1.0"; irm https://raw.githubusercontent.com/browseros-ai/BrowserOS/main/packages/browseros-agent/apps/cli/scripts/install.ps1 | iex }
#
param(
[string]$Version = "",
[string]$Dir = ""
)
$ErrorActionPreference = "Stop"
# TLS 1.2 — required for GitHub, older PS 5.1 defaults to TLS 1.0
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$Repo = "browseros-ai/BrowserOS"
$Binary = "browseros-cli"
# When piped via irm | iex, param() is ignored — fall back to env vars
if (-not $Version) { $Version = $env:BROWSEROS_VERSION }
if (-not $Dir) { $Dir = if ($env:BROWSEROS_DIR) { $env:BROWSEROS_DIR } else { "$env:LOCALAPPDATA\browseros-cli\bin" } }
# ── Resolve latest version ───────────────────────────────────────────────────
if (-not $Version) {
Write-Host "Fetching latest version..."
$releases = Invoke-RestMethod "https://api.github.com/repos/$Repo/releases?per_page=100"
$tag = ($releases `
| Where-Object { $_.tag_name -match "^browseros-cli-v" -and $_.tag_name -notmatch "-rc" } `
| Select-Object -First 1).tag_name
if (-not $tag) {
Write-Error "Could not determine latest version. Try: -Version 0.1.0"
exit 1
}
$Version = $tag -replace "^browseros-cli-v", ""
}
Write-Host "Installing browseros-cli v$Version..."
# ── Detect architecture ──────────────────────────────────────────────────────
# $env:PROCESSOR_ARCHITECTURE lies under x64 emulation on ARM64 Windows.
# Use .NET RuntimeInformation when available, fall back to PROCESSOR_ARCHITEW6432.
$Arch = "amd64"
try {
$osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
if ($osArch -eq [System.Runtime.InteropServices.Architecture]::Arm64) { $Arch = "arm64" }
} catch {
if ($env:PROCESSOR_ARCHITEW6432 -eq "ARM64" -or $env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
$Arch = "arm64"
}
}
if (-not [Environment]::Is64BitOperatingSystem) {
Write-Error "32-bit Windows is not supported."
exit 1
}
# ── Download and extract ─────────────────────────────────────────────────────
$Tag = "browseros-cli-v$Version"
$Filename = "${Binary}_${Version}_windows_${Arch}.zip"
$Url = "https://github.com/$Repo/releases/download/$Tag/$Filename"
$ChecksumUrl = "https://github.com/$Repo/releases/download/$Tag/checksums.txt"
$TmpDir = Join-Path ([System.IO.Path]::GetTempPath()) ("browseros-cli-install-" + [System.IO.Path]::GetRandomFileName())
try {
New-Item -ItemType Directory -Path $TmpDir | Out-Null
$ZipPath = Join-Path $TmpDir $Filename
Write-Host "Downloading $Url..."
Invoke-WebRequest -Uri $Url -OutFile $ZipPath -UseBasicParsing
$ChecksumPath = Join-Path $TmpDir "checksums.txt"
$ChecksumAvailable = $true
try {
Invoke-WebRequest -Uri $ChecksumUrl -OutFile $ChecksumPath -UseBasicParsing
} catch {
$ChecksumAvailable = $false
Write-Warning "Could not fetch checksums.txt; skipping checksum verification. $($_.Exception.Message)"
}
if ($ChecksumAvailable) {
$ExpectedChecksum = $null
foreach ($line in Get-Content $ChecksumPath) {
$parts = $line -split '\s+', 2
if ($parts.Length -eq 2 -and $parts[1] -eq $Filename) {
$ExpectedChecksum = $parts[0].ToLowerInvariant()
break
}
}
if ($ExpectedChecksum) {
$ActualChecksum = (Get-FileHash -Path $ZipPath -Algorithm SHA256).Hash.ToLowerInvariant()
if ($ActualChecksum -ne $ExpectedChecksum) {
Write-Error "Checksum mismatch (expected $ExpectedChecksum, got $ActualChecksum)"
exit 1
}
Write-Host "Checksum verified."
} else {
Write-Warning "Checksum not found in checksums.txt; skipping checksum verification."
}
}
Expand-Archive -Path $ZipPath -DestinationPath $TmpDir -Force
$Exe = Get-ChildItem -Path $TmpDir -Filter "$Binary.exe" -File -Recurse | Select-Object -First 1
if (-not $Exe) {
Write-Error "Binary not found in archive."
exit 1
}
# ── Install ──────────────────────────────────────────────────────────────
if (-not (Test-Path $Dir)) {
New-Item -ItemType Directory -Path $Dir -Force | Out-Null
}
Move-Item -Force $Exe.FullName (Join-Path $Dir "$Binary.exe")
Write-Host "Installed $Binary.exe to $Dir"
} finally {
if (Test-Path $TmpDir) { Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue }
}
# ── PATH ─────────────────────────────────────────────────────────────────────
$UserPath = [Environment]::GetEnvironmentVariable("Path", "User")
$PathEntries = $UserPath -split ";" | Where-Object { $_ -ne "" }
if ($Dir -notin $PathEntries) {
Write-Host ""
Write-Host "Adding $Dir to your user PATH..."
[Environment]::SetEnvironmentVariable("Path", "$Dir;$UserPath", "User")
$env:Path = "$Dir;$env:Path"
Write-Host "Done. Restart your terminal for PATH changes to take effect."
}
Write-Host ""
Write-Host "Run 'browseros-cli --help' to get started."

View File

@@ -0,0 +1,153 @@
#!/usr/bin/env bash
#
# Install browseros-cli — downloads the latest release binary for your platform.
#
# Usage:
# curl -fsSL https://raw.githubusercontent.com/browseros-ai/BrowserOS/main/packages/browseros-agent/apps/cli/scripts/install.sh | bash
#
# # Or with options:
# curl -fsSL ... | bash -s -- --version 0.1.0 --dir /usr/local/bin
set -euo pipefail
REPO="browseros-ai/BrowserOS"
BINARY="browseros-cli"
INSTALL_DIR="${HOME}/.browseros/bin"
# ── Parse arguments ──────────────────────────────────────────────────────────
VERSION=""
CUSTOM_DIR=""
while [[ $# -gt 0 ]]; do
case "$1" in
--version)
[[ $# -lt 2 ]] && { echo "Error: --version requires a value" >&2; exit 1; }
VERSION="$2"; shift 2 ;;
--dir)
[[ $# -lt 2 ]] && { echo "Error: --dir requires a value" >&2; exit 1; }
CUSTOM_DIR="$2"; shift 2 ;;
--help)
echo "Usage: install.sh [--version VERSION] [--dir INSTALL_DIR]"
echo ""
echo " --version Install a specific version (default: latest)"
echo " --dir Install directory (default: ~/.browseros/bin)"
exit 0
;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
[[ -n "$CUSTOM_DIR" ]] && INSTALL_DIR="$CUSTOM_DIR"
# ── Resolve latest version ───────────────────────────────────────────────────
if [[ -z "$VERSION" ]]; then
# Use per_page=1 with a tag name filter via the releases endpoint.
# The tags all start with "browseros-cli-v" so we grab page 1 of those.
VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases?per_page=100" \
| grep -o '"tag_name": *"browseros-cli-v[^"]*"' \
| grep -v -- "-rc" \
| head -1 \
| sed 's/.*browseros-cli-v//; s/"//')
if [[ -z "$VERSION" ]]; then
echo "Error: could not determine latest version." >&2
echo " Try: install.sh --version 0.1.0" >&2
exit 1
fi
fi
echo "Installing browseros-cli v${VERSION}..."
# ── Detect platform ──────────────────────────────────────────────────────────
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case "$OS" in
darwin) OS="darwin" ;;
linux) OS="linux" ;;
*) echo "Error: unsupported OS: $OS" >&2; exit 1 ;;
esac
case "$ARCH" in
x86_64|amd64) ARCH="amd64" ;;
arm64|aarch64) ARCH="arm64" ;;
*) echo "Error: unsupported architecture: $ARCH" >&2; exit 1 ;;
esac
# ── Download and extract ─────────────────────────────────────────────────────
FILENAME="${BINARY}_${VERSION}_${OS}_${ARCH}.tar.gz"
TAG="browseros-cli-v${VERSION}"
URL="https://github.com/${REPO}/releases/download/${TAG}/${FILENAME}"
CHECKSUM_URL="https://github.com/${REPO}/releases/download/${TAG}/checksums.txt"
TMPDIR_DL=$(mktemp -d)
trap 'rm -rf "$TMPDIR_DL"' EXIT
echo "Downloading ${URL}..."
curl -fSL --progress-bar -o "${TMPDIR_DL}/${FILENAME}" "$URL"
# Verify checksum if sha256sum/shasum is available
if curl -fsSL -o "${TMPDIR_DL}/checksums.txt" "$CHECKSUM_URL" 2>/dev/null; then
expected=$(awk -v filename="$FILENAME" '$2 == filename { print $1; exit }' "${TMPDIR_DL}/checksums.txt")
if [[ -n "$expected" ]]; then
if command -v sha256sum >/dev/null 2>&1; then
actual=$(sha256sum "${TMPDIR_DL}/${FILENAME}" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
actual=$(shasum -a 256 "${TMPDIR_DL}/${FILENAME}" | awk '{print $1}')
else
actual=""
echo "Warning: no sha256sum/shasum found; skipping checksum verification." >&2
fi
if [[ -n "$actual" && "$actual" != "$expected" ]]; then
echo "Error: checksum mismatch (expected ${expected}, got ${actual})" >&2
exit 1
fi
[[ -n "$actual" ]] && echo "Checksum verified."
else
echo "Warning: checksum not found in checksums.txt; skipping verification." >&2
fi
else
echo "Warning: could not fetch checksums.txt; skipping checksum verification." >&2
fi
tar -xzf "${TMPDIR_DL}/${FILENAME}" -C "$TMPDIR_DL"
BINARY_PATH="${TMPDIR_DL}/${BINARY}"
if [[ ! -f "$BINARY_PATH" ]]; then
BINARY_PATH=$(find "$TMPDIR_DL" -type f -name "$BINARY" -print -quit)
fi
if [[ -z "$BINARY_PATH" || ! -f "$BINARY_PATH" ]]; then
echo "Error: binary not found in archive." >&2
exit 1
fi
# ── Install ──────────────────────────────────────────────────────────────────
mkdir -p "$INSTALL_DIR"
mv "$BINARY_PATH" "${INSTALL_DIR}/${BINARY}"
chmod +x "${INSTALL_DIR}/${BINARY}"
echo "Installed ${BINARY} to ${INSTALL_DIR}/${BINARY}"
# ── PATH hint ────────────────────────────────────────────────────────────────
if ! echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR"; then
echo ""
echo "Add browseros-cli to your PATH:"
echo ""
SHELL_NAME=$(basename "${SHELL:-/bin/bash}")
case "$SHELL_NAME" in
zsh) echo " echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ~/.zshrc && source ~/.zshrc" ;;
fish) echo " fish_add_path ${INSTALL_DIR}" ;;
*) echo " echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ~/.bashrc && source ~/.bashrc" ;;
esac
fi
echo ""
echo "Run 'browseros-cli --help' to get started."