1. 清除旧版 "agent" 废弃键 — 保存 channel 配置时自动删除旧的 agent 键, 防止 OpenClaw 报 "agent.* was moved" 错误(前端 + 服务端双重防御) 2. Dashboard URL 动态端口探测 — 将硬编码 18789 改为扫描 18789-18799, 解决端口被占用时打不开 Dashboard 的问题 3. 安装脚本配置格式升级 — install.sh / install.ps1 / install-silent.ps1 生成的 openclaw.json 从旧的 "agent" 格式迁移到 "agents.defaults.model.primary" 4. 文档示例同步更新 — CLAUDE.md / cloud.html / guide.html 中的配置示例 影响文件: 14 个(portable/, install/, website/, u-claw-app/, usb-release/) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
294 lines
10 KiB
PowerShell
294 lines
10 KiB
PowerShell
# ============================================================
|
|
# U-Claw Silent Installer (Windows PowerShell)
|
|
# Non-interactive version for remote exec deployment
|
|
#
|
|
# Usage:
|
|
# install-silent.ps1 -Model deepseek-chat -ApiKey sk-xxx
|
|
# install-silent.ps1 -Model deepseek-chat -ApiKey sk-xxx -BaseUrl https://api.deepseek.com/v1
|
|
# install-silent.ps1 -Model ollama -NoKey
|
|
# install-silent.ps1 -SkipConfig # install only, configure later
|
|
# ============================================================
|
|
param(
|
|
[string]$Model = "deepseek-chat",
|
|
[string]$ApiKey = "",
|
|
[string]$BaseUrl = "",
|
|
[switch]$NoKey,
|
|
[switch]$SkipConfig,
|
|
[switch]$Force
|
|
)
|
|
|
|
Set-ExecutionPolicy Bypass -Scope Process -Force -ErrorAction SilentlyContinue
|
|
try {
|
|
$null = cmd /c chcp 65001
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
} catch {}
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# ---- Constants ----
|
|
$UCLAW_DIR = "$env:USERPROFILE\.uclaw"
|
|
$RUNTIME_DIR = "$UCLAW_DIR\runtime"
|
|
$CORE_DIR = "$UCLAW_DIR\core"
|
|
$DATA_DIR = "$UCLAW_DIR\data"
|
|
$CONFIG_PATH = "$DATA_DIR\.openclaw\openclaw.json"
|
|
$NODE_VERSION = "v22.16.0"
|
|
$NODE_MIRROR = "https://npmmirror.com/mirrors/node"
|
|
|
|
# ---- Known model presets ----
|
|
$presets = @{
|
|
"deepseek-chat" = @{ baseUrl="https://api.deepseek.com/v1"; provider="custom" }
|
|
"moonshot-v1-auto" = @{ baseUrl="https://api.moonshot.cn/v1"; provider="custom" }
|
|
"qwen-plus" = @{ baseUrl="https://dashscope.aliyuncs.com/compatible-mode/v1"; provider="custom" }
|
|
"glm-4-plus" = @{ baseUrl="https://open.bigmodel.cn/api/paas/v4"; provider="custom" }
|
|
"abab6.5s-chat" = @{ baseUrl="https://api.minimax.chat/v1"; provider="custom" }
|
|
"claude-sonnet-4-20250514" = @{ baseUrl=""; provider="anthropic" }
|
|
"gpt-4o" = @{ baseUrl=""; provider="openai" }
|
|
"ollama" = @{ baseUrl="http://127.0.0.1:11434/v1"; provider="custom"; model="llama3.2" }
|
|
}
|
|
|
|
function Log($msg) { Write-Host "[uclaw-silent] $msg" }
|
|
function LogOK($msg) { Write-Host "[uclaw-silent] OK: $msg" -ForegroundColor Green }
|
|
function LogFail($msg) { Write-Host "[uclaw-silent] FAIL: $msg" -ForegroundColor Red }
|
|
|
|
# ============================================================
|
|
# Check existing install
|
|
# ============================================================
|
|
if ((Test-Path "$CORE_DIR\node_modules\openclaw") -and -not $Force) {
|
|
LogOK "Already installed at $UCLAW_DIR. Use -Force to overwrite."
|
|
exit 0
|
|
}
|
|
|
|
# Create directories
|
|
foreach ($d in @($RUNTIME_DIR, $CORE_DIR, "$DATA_DIR\.openclaw", "$DATA_DIR\memory", "$DATA_DIR\backups")) {
|
|
New-Item -ItemType Directory -Force -Path $d | Out-Null
|
|
}
|
|
|
|
# ============================================================
|
|
# Step 1: Node.js
|
|
# ============================================================
|
|
Log "Installing Node.js $NODE_VERSION ..."
|
|
|
|
$ARCH = $env:PROCESSOR_ARCHITECTURE
|
|
if ($ARCH -eq "AMD64" -or $ARCH -eq "x86_64") { $PLATFORM = "win-x64" }
|
|
elseif ($ARCH -eq "ARM64") { $PLATFORM = "win-arm64" }
|
|
else { LogFail "Unsupported arch: $ARCH"; exit 1 }
|
|
|
|
$NODE_INSTALL_DIR = "$RUNTIME_DIR\node-$PLATFORM"
|
|
$INSTALL_NODE = ""
|
|
$NPM_CLI = ""
|
|
|
|
# Check system Node.js
|
|
$sysNode = Get-Command node -ErrorAction SilentlyContinue
|
|
if ($sysNode) {
|
|
$sysVer = & node --version 2>$null
|
|
$major = [int]($sysVer -replace 'v','').Split('.')[0]
|
|
if ($major -ge 20) {
|
|
LogOK "System Node.js $sysVer"
|
|
$INSTALL_NODE = "node"
|
|
}
|
|
}
|
|
|
|
if (-not $INSTALL_NODE) {
|
|
if (Test-Path "$NODE_INSTALL_DIR\node.exe") {
|
|
LogOK "Node.js already downloaded"
|
|
$INSTALL_NODE = "$NODE_INSTALL_DIR\node.exe"
|
|
} else {
|
|
$zipName = "node-$NODE_VERSION-$PLATFORM.zip"
|
|
$url = "$NODE_MIRROR/$NODE_VERSION/$zipName"
|
|
$tempZip = "$env:TEMP\$zipName"
|
|
$tempExtract = "$env:TEMP\node-extract-uclaw"
|
|
|
|
Log "Downloading $url ..."
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
$ProgressPreference = 'SilentlyContinue'
|
|
try {
|
|
Invoke-WebRequest -Uri $url -OutFile $tempZip -UseBasicParsing
|
|
} catch {
|
|
try { & curl.exe -sL $url -o $tempZip } catch { LogFail "Node.js download failed"; exit 1 }
|
|
}
|
|
|
|
if (Test-Path $tempExtract) { Remove-Item -Recurse -Force $tempExtract }
|
|
Expand-Archive -Path $tempZip -DestinationPath $tempExtract -Force
|
|
$extractedDir = Get-ChildItem $tempExtract | Select-Object -First 1
|
|
New-Item -ItemType Directory -Force -Path $NODE_INSTALL_DIR | Out-Null
|
|
Copy-Item -Recurse -Force "$($extractedDir.FullName)\*" $NODE_INSTALL_DIR
|
|
Remove-Item -Force $tempZip -ErrorAction SilentlyContinue
|
|
Remove-Item -Recurse -Force $tempExtract -ErrorAction SilentlyContinue
|
|
|
|
if (Test-Path "$NODE_INSTALL_DIR\node.exe") {
|
|
LogOK "Node.js installed"
|
|
$INSTALL_NODE = "$NODE_INSTALL_DIR\node.exe"
|
|
} else {
|
|
LogFail "Node.js install failed"; exit 1
|
|
}
|
|
}
|
|
$env:PATH = "$NODE_INSTALL_DIR;$env:PATH"
|
|
}
|
|
|
|
# ============================================================
|
|
# Step 2: OpenClaw bundle
|
|
# ============================================================
|
|
Log "Installing OpenClaw ..."
|
|
|
|
if (Test-Path "$CORE_DIR\node_modules\openclaw\openclaw.mjs") {
|
|
LogOK "OpenClaw already installed"
|
|
} else {
|
|
$BUNDLE_URL = "https://github.com/dongsheng123132/u-claw/releases/download/v1.0.0-bundle/openclaw-bundle.zip"
|
|
$BUNDLE_MIRRORS = @(
|
|
"https://ghfast.top/$BUNDLE_URL",
|
|
"https://gh-proxy.com/$BUNDLE_URL",
|
|
"https://mirror.ghproxy.com/$BUNDLE_URL",
|
|
$BUNDLE_URL
|
|
)
|
|
$bundleZip = "$env:TEMP\openclaw-bundle.zip"
|
|
$downloaded = $false
|
|
|
|
foreach ($mirrorUrl in $BUNDLE_MIRRORS) {
|
|
Log "Trying: $mirrorUrl"
|
|
try {
|
|
$ProgressPreference = 'SilentlyContinue'
|
|
Invoke-WebRequest -Uri $mirrorUrl -OutFile $bundleZip -UseBasicParsing -TimeoutSec 60
|
|
if ((Get-Item $bundleZip).Length -gt 1MB) { $downloaded = $true; break }
|
|
} catch {}
|
|
}
|
|
|
|
if (-not $downloaded) {
|
|
try {
|
|
& curl.exe -sL "https://ghfast.top/$BUNDLE_URL" -o $bundleZip
|
|
if ((Get-Item $bundleZip).Length -gt 1MB) { $downloaded = $true }
|
|
} catch {}
|
|
}
|
|
|
|
if (-not $downloaded) { LogFail "OpenClaw download failed"; exit 1 }
|
|
|
|
$tempExtract = "$env:TEMP\openclaw-bundle-extract"
|
|
if (Test-Path $tempExtract) { Remove-Item -Recurse -Force $tempExtract }
|
|
Expand-Archive -Path $bundleZip -DestinationPath $tempExtract -Force
|
|
New-Item -ItemType Directory -Force -Path $CORE_DIR | Out-Null
|
|
Copy-Item -Recurse -Force "$tempExtract\*" $CORE_DIR
|
|
Remove-Item -Force $bundleZip -ErrorAction SilentlyContinue
|
|
Remove-Item -Recurse -Force $tempExtract -ErrorAction SilentlyContinue
|
|
|
|
if (Test-Path "$CORE_DIR\node_modules\openclaw\openclaw.mjs") {
|
|
LogOK "OpenClaw installed"
|
|
} else {
|
|
LogFail "OpenClaw install failed"; exit 1
|
|
}
|
|
}
|
|
|
|
# ============================================================
|
|
# Step 3: Config
|
|
# ============================================================
|
|
if ($SkipConfig) {
|
|
Log "Skipping config (-SkipConfig)"
|
|
} else {
|
|
Log "Configuring model: $Model ..."
|
|
|
|
# Resolve model preset
|
|
$resolvedModel = $Model
|
|
$resolvedBaseUrl = $BaseUrl
|
|
$resolvedProvider = "custom"
|
|
|
|
if ($presets.ContainsKey($Model)) {
|
|
$p = $presets[$Model]
|
|
if ($p.model) { $resolvedModel = $p.model }
|
|
if (-not $resolvedBaseUrl -and $p.baseUrl) { $resolvedBaseUrl = $p.baseUrl }
|
|
$resolvedProvider = $p.provider
|
|
}
|
|
|
|
if ($resolvedProvider -eq "custom" -and $resolvedBaseUrl) {
|
|
$pName = "custom"
|
|
$configJson = @"
|
|
{
|
|
"gateway": {
|
|
"mode": "local",
|
|
"auth": { "token": "uclaw" }
|
|
},
|
|
"models": {
|
|
"mode": "merge",
|
|
"providers": {
|
|
"$pName": {
|
|
"baseUrl": "$resolvedBaseUrl",
|
|
"apiKey": "$ApiKey",
|
|
"api": "openai-completions",
|
|
"models": [{ "id": "$resolvedModel" }]
|
|
}
|
|
}
|
|
},
|
|
"agents": { "defaults": { "model": { "primary": "$pName/$resolvedModel" } } }
|
|
}
|
|
"@
|
|
} else {
|
|
$pName = $resolvedProvider
|
|
$configJson = @"
|
|
{
|
|
"gateway": {
|
|
"mode": "local",
|
|
"auth": { "token": "uclaw" }
|
|
},
|
|
"models": {
|
|
"mode": "merge",
|
|
"providers": {
|
|
"$pName": {
|
|
"apiKey": "$ApiKey",
|
|
"api": "$(if ($pName -eq 'anthropic') {'anthropic'} else {'openai-completions'})",
|
|
"models": [{ "id": "$resolvedModel" }]
|
|
}
|
|
}
|
|
},
|
|
"agents": { "defaults": { "model": { "primary": "$pName/$resolvedModel" } } }
|
|
}
|
|
"@
|
|
}
|
|
|
|
[IO.File]::WriteAllText($CONFIG_PATH, $configJson, (New-Object System.Text.UTF8Encoding $false))
|
|
LogOK "Config saved: model=$resolvedModel"
|
|
}
|
|
|
|
# ============================================================
|
|
# Step 4: Generate start script
|
|
# ============================================================
|
|
Log "Generating start.bat ..."
|
|
|
|
$startBat = @'
|
|
@echo off
|
|
setlocal EnableDelayedExpansion
|
|
chcp 65001 >nul 2>&1
|
|
title U-Claw
|
|
|
|
set "DIR=%~dp0"
|
|
set "NODE_BIN=%DIR%runtime\node-win-x64\node.exe"
|
|
if not exist "%NODE_BIN%" set "NODE_BIN=node"
|
|
|
|
set "OPENCLAW_MJS=%DIR%core\node_modules\openclaw\openclaw.mjs"
|
|
set "OPENCLAW_HOME=%DIR%data"
|
|
set "OPENCLAW_STATE_DIR=%DIR%data\.openclaw"
|
|
set "OPENCLAW_CONFIG_PATH=%DIR%data\.openclaw\openclaw.json"
|
|
|
|
set PORT=18789
|
|
:check_port
|
|
netstat -an | findstr ":%PORT% " | findstr "LISTENING" >nul 2>&1
|
|
if %errorlevel%==0 (
|
|
set /a PORT+=1
|
|
if !PORT! gtr 18799 (echo No available port & pause & exit /b 1)
|
|
goto :check_port
|
|
)
|
|
|
|
cd /d "%DIR%core"
|
|
start /B "" cmd /c "timeout /t 3 /nobreak >nul && start http://127.0.0.1:!PORT!/#token=uclaw"
|
|
"%NODE_BIN%" "%OPENCLAW_MJS%" gateway run --allow-unconfigured --force --port !PORT!
|
|
pause
|
|
'@
|
|
|
|
$startBat | Out-File -Encoding ascii "$UCLAW_DIR\start.bat"
|
|
LogOK "start.bat generated"
|
|
|
|
# ============================================================
|
|
# Done
|
|
# ============================================================
|
|
Log ""
|
|
LogOK "Installation complete!"
|
|
Log " Location: $UCLAW_DIR"
|
|
Log " Start: $UCLAW_DIR\start.bat"
|