Files
openclaw/scripts/install.ps1
Onur 900e291f31 CI: expand native release validation coverage (#67144)
* Actions: grant reusable release checks actions read

* Actions: use read-all for reusable release checks

* CI: add native cross-OS release checks

* CI: wire Discord smoke secrets for cross-OS checks

* CI: fix native cross-OS installer compatibility

* CI: skip empty pnpm cache saves in matrix jobs

* CI: honor workflow runner override envs

* CI: finish native cross-OS update checks

* CI: fix native cross-OS workflow regressions

* Installer: capture Windows npm stderr safely

* CI: harden cross-OS release checks

* CI: resolve reusable workflow harness ref

* CI: stabilize cross-OS dev update lanes

* CI: tighten release-check workflow semantics

* CI: repoint repaired git CLI on POSIX

* CI: repair native dev-update shell handoff

* CI: preserve real updater semantics

* CI: harden supported release-check refs

* CI: harden release-check refs and fresh mode

* CI: skip dev-update for immutable tag refs

* CI: repair fresh installer release checks

* CI: fix native release check installer lanes

* CI: install release checks from candidate artifacts

* CI: use Windows cmd shims in release checks

* Installer: run Windows npm shim via PowerShell

* CI: pin dev update verification to candidate sha

* CI: pin reusable harness and published installers

* CI: isolate Windows dev-update PATH validation

* CI: align Windows dev-update bootstrap validation

* CI: avoid Windows installer gateway flake

* CI: run cross-OS release checks via TypeScript

* CI: bootstrap tsx for release-check workflow

* CI: fix native release-check follow-ups

* CI: tighten dev-update release checks

* CI: peel annotated workflow refs

* CI: harden native release checks

* CI: fix release-check verifier drift

* CI: fix release-check workflow drift

* CI: fix release-check ref resolution

* CI: harden Windows release-check gateway startup

* CI: fix release-check fallback validation

* CI: harden cross-os release checks

* CI: pin dev-update release checks to candidate SHA

* CI: resolve remote dev target refs

* CI: detect cloned dev-update checkouts

* CI: harden Windows release-check launcher

* Windows: harden task fallback and runner overrides

* Release checks: preserve Windows PATH and baseline version reads

* CI: add release validation live lanes

* CI: expand live and e2e release coverage

* CI: add branch dispatch for live and e2e checks
2026-04-16 19:58:19 +02:00

452 lines
14 KiB
PowerShell

# OpenClaw Installer for Windows (PowerShell)
# Usage: iwr -useb https://openclaw.ai/install.ps1 | iex
# Or: & ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -NoOnboard
param(
[string]$InstallMethod = "npm",
[string]$Tag = "latest",
[string]$GitDir = "$env:USERPROFILE\openclaw",
[switch]$NoOnboard,
[switch]$NoGitUpdate,
[switch]$DryRun
)
$ErrorActionPreference = "Stop"
# Colors
$ACCENT = "`e[38;2;255;77;77m" # coral-bright
$SUCCESS = "`e[38;2;0;229;204m" # cyan-bright
$WARN = "`e[38;2;255;176;32m" # amber
$ERROR = "`e[38;2;230;57;70m" # coral-mid
$MUTED = "`e[38;2;90;100;128m" # text-muted
$NC = "`e[0m" # No Color
function Write-Host {
param([string]$Message, [string]$Level = "info")
$msg = switch ($Level) {
"success" { "$SUCCESS$NC $Message" }
"warn" { "$WARN!$NC $Message" }
"error" { "$ERROR$NC $Message" }
default { "$MUTED·$NC $Message" }
}
Microsoft.PowerShell.Utility\Write-Host $msg
}
function Write-Banner {
Write-Host ""
Write-Host "${ACCENT} 🦞 OpenClaw Installer$NC" -Level info
Write-Host "${MUTED} All your chats, one OpenClaw.$NC" -Level info
Write-Host ""
}
function Get-ExecutionPolicyStatus {
$policy = Get-ExecutionPolicy
if ($policy -eq "Restricted" -or $policy -eq "AllSigned") {
return @{ Blocked = $true; Policy = $policy }
}
return @{ Blocked = $false; Policy = $policy }
}
function Test-Admin {
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Ensure-ExecutionPolicy {
$status = Get-ExecutionPolicyStatus
if ($status.Blocked) {
Write-Host "PowerShell execution policy is set to: $($status.Policy)" -Level warn
Write-Host "This prevents scripts like npm.ps1 from running." -Level warn
Write-Host ""
# Try to set execution policy for current process
try {
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -ErrorAction Stop
Write-Host "Set execution policy to RemoteSigned for current process" -Level success
return $true
} catch {
Write-Host "Could not automatically set execution policy" -Level error
Write-Host ""
Write-Host "To fix this, run:" -Level info
Write-Host " Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process" -Level info
Write-Host ""
Write-Host "Or run PowerShell as Administrator and execute:" -Level info
Write-Host " Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine" -Level info
return $false
}
}
return $true
}
function Get-NodeVersion {
try {
$version = node --version 2>$null
if ($version) {
return $version -replace '^v', ''
}
} catch { }
return $null
}
function Get-NpmVersion {
try {
$version = npm --version 2>$null
if ($version) {
return $version
}
} catch { }
return $null
}
function Install-Node {
Write-Host "Node.js not found" -Level info
Write-Host "Installing Node.js..." -Level info
# Try winget first
if (Get-Command winget -ErrorAction SilentlyContinue) {
Write-Host " Using winget..." -Level info
try {
winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements 2>&1 | Out-Null
# Refresh PATH
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
Write-Host " Node.js installed via winget" -Level success
return $true
} catch {
Write-Host " Winget install failed: $_" -Level warn
}
}
# Try chocolatey
if (Get-Command choco -ErrorAction SilentlyContinue) {
Write-Host " Using chocolatey..." -Level info
try {
choco install nodejs-lts -y 2>&1 | Out-Null
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
Write-Host " Node.js installed via chocolatey" -Level success
return $true
} catch {
Write-Host " Chocolatey install failed: $_" -Level warn
}
}
# Try scoop
if (Get-Command scoop -ErrorAction SilentlyContinue) {
Write-Host " Using scoop..." -Level info
try {
scoop install nodejs-lts 2>&1 | Out-Null
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
Write-Host " Node.js installed via scoop" -Level success
return $true
} catch {
Write-Host " Scoop install failed: $_" -Level warn
}
}
Write-Host "Could not install Node.js automatically" -Level error
Write-Host "Please install Node.js 22+ manually from: https://nodejs.org" -Level info
return $false
}
function Ensure-Node {
$nodeVersion = Get-NodeVersion
if ($nodeVersion) {
$major = [int]($nodeVersion -split '\.')[0]
if ($major -ge 22) {
Write-Host "Node.js v$nodeVersion found" -Level success
return $true
}
Write-Host "Node.js v$nodeVersion found, but need v22+" -Level warn
}
return Install-Node
}
function Get-GitVersion {
try {
$version = git --version 2>$null
if ($version) {
return $version
}
} catch { }
return $null
}
function Install-Git {
Write-Host "Git not found" -Level info
if (Get-Command winget -ErrorAction SilentlyContinue) {
Write-Host " Installing Git via winget..." -Level info
try {
winget install Git.Git --accept-package-agreements --accept-source-agreements 2>&1 | Out-Null
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
Write-Host " Git installed" -Level success
return $true
} catch {
Write-Host " Winget install failed" -Level warn
}
}
Write-Host "Please install Git for Windows from: https://git-scm.com" -Level error
return $false
}
function Ensure-Git {
$gitVersion = Get-GitVersion
if ($gitVersion) {
Write-Host "$gitVersion found" -Level success
return $true
}
return Install-Git
}
function Read-TrimmedFileText {
param([string]$Path)
if (!(Test-Path -LiteralPath $Path)) {
return ""
}
return ((Get-Content -LiteralPath $Path -Raw) -replace "(\r?\n)+$", "")
}
function ConvertTo-PowerShellSingleQuotedLiteral {
param([string]$Value)
return "'" + ($Value -replace "'", "''") + "'"
}
function Invoke-NativeCommandCapture {
param(
[Parameter(Mandatory = $true)]
[string]$FilePath,
[string[]]$Arguments = @()
)
$stdoutPath = [System.IO.Path]::GetTempFileName()
$stderrPath = [System.IO.Path]::GetTempFileName()
try {
$startFilePath = $FilePath
$startArguments = $Arguments
if ($FilePath -match '(?i)\.(cmd|bat)$') {
# Start-Process cannot directly redirect stdio for command shims like
# npm.cmd. Run them inside a nested PowerShell so the shim executes
# normally while stdout/stderr still flow back to these temp files.
$commandParts = @(
ConvertTo-PowerShellSingleQuotedLiteral -Value $FilePath
)
foreach ($argument in $Arguments) {
$commandParts += ConvertTo-PowerShellSingleQuotedLiteral -Value $argument
}
$commandScript = "& " + ($commandParts -join " ") + "`nexit `$LASTEXITCODE"
$startFilePath = "powershell.exe"
$startArguments = @(
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-Command",
$commandScript
)
}
$process = Start-Process -FilePath $startFilePath `
-ArgumentList $startArguments `
-Wait `
-PassThru `
-RedirectStandardOutput $stdoutPath `
-RedirectStandardError $stderrPath
return @{
ExitCode = $process.ExitCode
Stdout = Read-TrimmedFileText -Path $stdoutPath
Stderr = Read-TrimmedFileText -Path $stderrPath
}
} finally {
Remove-Item -LiteralPath $stdoutPath, $stderrPath -Force -ErrorAction SilentlyContinue
}
}
function Install-OpenClawNpm {
param([string]$Target = "latest")
$installSpec = Resolve-PackageInstallSpec -Target $Target
Write-Host "Installing OpenClaw ($installSpec)..." -Level info
try {
# Run npm out-of-process so warning chatter on stderr does not get
# promoted into a terminating PowerShell error while the install succeeds.
$installResult = Invoke-NativeCommandCapture -FilePath "npm.cmd" -Arguments @(
"install",
"-g",
$installSpec,
"--no-fund",
"--no-audit"
)
if ($installResult.Stdout) {
Microsoft.PowerShell.Utility\Write-Output $installResult.Stdout
}
if ($installResult.Stderr) {
Microsoft.PowerShell.Utility\Write-Output $installResult.Stderr
}
if ($installResult.ExitCode -ne 0) {
Write-Host "npm install failed with exit code $($installResult.ExitCode)" -Level error
return $false
}
Write-Host "OpenClaw installed" -Level success
return $true
} catch {
Write-Host "npm install failed: $_" -Level error
return $false
}
}
function Install-OpenClawGit {
param([string]$RepoDir, [switch]$Update)
Write-Host "Installing OpenClaw from git..." -Level info
if (!(Test-Path $RepoDir)) {
Write-Host " Cloning repository..." -Level info
git clone https://github.com/openclaw/openclaw.git $RepoDir 2>&1
} elseif ($Update) {
Write-Host " Updating repository..." -Level info
git -C $RepoDir pull --rebase 2>&1
}
# Install pnpm if not present
if (!(Get-Command pnpm -ErrorAction SilentlyContinue)) {
Write-Host " Installing pnpm..." -Level info
npm install -g pnpm 2>&1
}
# Install dependencies
Write-Host " Installing dependencies..." -Level info
pnpm install --dir $RepoDir 2>&1
# Build
Write-Host " Building..." -Level info
pnpm --dir $RepoDir build 2>&1
# Create wrapper
$wrapperDir = "$env:USERPROFILE\.local\bin"
if (!(Test-Path $wrapperDir)) {
New-Item -ItemType Directory -Path $wrapperDir -Force | Out-Null
}
@"
@echo off
node "%~dp0..\openclaw\dist\entry.js" %*
"@ | Out-File -FilePath "$wrapperDir\openclaw.cmd" -Encoding ASCII -Force
Write-Host "OpenClaw installed" -Level success
return $true
}
function Test-ExplicitPackageInstallSpec {
param([string]$Target)
if ([string]::IsNullOrWhiteSpace($Target)) {
return $false
}
return $Target.Contains("://") -or
$Target.Contains("#") -or
$Target -match '^(file|github|git\+ssh|git\+https|git\+http|git\+file|npm):'
}
function Resolve-PackageInstallSpec {
param([string]$Target = "latest")
$trimmed = $Target.Trim()
if ([string]::IsNullOrWhiteSpace($trimmed)) {
return "openclaw@latest"
}
if ($trimmed.ToLowerInvariant() -eq "main") {
return "github:openclaw/openclaw#main"
}
if (Test-ExplicitPackageInstallSpec -Target $trimmed) {
return $trimmed
}
return "openclaw@$trimmed"
}
function Add-ToPath {
param([string]$Path)
$currentPath = [Environment]::GetEnvironmentVariable("Path", "User")
if ($currentPath -notlike "*$Path*") {
[Environment]::SetEnvironmentVariable("Path", "$currentPath;$Path", "User")
Write-Host "Added $Path to user PATH" -Level info
}
}
# Main
function Main {
Write-Banner
Write-Host "Windows detected" -Level success
# Check and handle execution policy FIRST, before any npm calls
if (!(Ensure-ExecutionPolicy)) {
Write-Host ""
Write-Host "Installation cannot continue due to execution policy restrictions" -Level error
exit 1
}
if (!(Ensure-Node)) {
exit 1
}
if ($InstallMethod -eq "git") {
if (!(Ensure-Git)) {
exit 1
}
if ($DryRun) {
Write-Host "[DRY RUN] Would install OpenClaw from git to $GitDir" -Level info
} else {
Install-OpenClawGit -RepoDir $GitDir -Update:(-not $NoGitUpdate)
}
} else {
# npm method
if (!(Ensure-Git)) {
Write-Host "Git is required for npm installs. Please install Git and try again." -Level warn
}
if ($DryRun) {
Write-Host "[DRY RUN] Would install OpenClaw via npm ($((Resolve-PackageInstallSpec -Target $Tag)))" -Level info
} else {
if (!(Install-OpenClawNpm -Target $Tag)) {
exit 1
}
}
}
# Try to add npm global bin to PATH
try {
$prefixResult = Invoke-NativeCommandCapture -FilePath "npm.cmd" -Arguments @(
"config",
"get",
"prefix"
)
$npmPrefix = $prefixResult.Stdout
if ($prefixResult.ExitCode -eq 0 -and $npmPrefix) {
Add-ToPath -Path "$npmPrefix"
}
} catch { }
if (!$NoOnboard -and !$DryRun) {
Write-Host ""
Write-Host "Run 'openclaw onboard' to complete setup" -Level info
}
Write-Host ""
Write-Host "🦞 OpenClaw installed successfully!" -Level success
}
Main