AnyBot/install.ps1

308 lines
12 KiB
PowerShell
Raw Normal View History

# AnyBot Windows 一鍵安裝(x64、arm64)。桌面、手腳、第一次設定。
#
# irm https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.ps1 | iex
# powershell -NoProfile -ExecutionPolicy Bypass -File install.ps1 app
# powershell -File install.ps1 hands https://你的網域 482913
# powershell -File install.ps1 -Print
#
# 代理程式不要問問題:
# $env:ANYBOT_NONINTERACTIVE='1'
# $env:ANYBOT_MODE='hands'
# $env:ANYBOT_PAIR_URL='https://…'
# $env:ANYBOT_PAIR_CODE='482913'
# irm https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.ps1 | iex
#
# 伺服器(帳號+大腦+HTTPS)請在 Linux 上用 install.sh server。Windows 可以當大腦或手腳。
#Requires -Version 5.1
param(
[Parameter(Position = 0)][string]$Mode = "",
[Parameter(Position = 1)][string]$PairUrl = "",
[Parameter(Position = 2)][string]$PairCode = "",
[switch]$Print
)
$ErrorActionPreference = "Stop"
$Repo = if ($env:ANYBOT_REPO) { $env:ANYBOT_REPO } else { "https://code.30cm.net/daniel.w/AnyBot.git" }
$Branch = if ($env:ANYBOT_BRANCH) { $env:ANYBOT_BRANCH } else { "main" }
$Src = if ($env:ANYBOT_DIR) { $env:ANYBOT_DIR } else { Join-Path $env:USERPROFILE "AnyBot" }
$RustMin = 95
if (-not $Mode -and $env:ANYBOT_MODE) { $Mode = $env:ANYBOT_MODE }
if (-not $PairUrl -and $env:ANYBOT_PAIR_URL) { $PairUrl = $env:ANYBOT_PAIR_URL }
if (-not $PairCode -and $env:ANYBOT_PAIR_CODE) { $PairCode = $env:ANYBOT_PAIR_CODE }
if ($env:ANYBOT_PRINT -eq "1") { $Print = $true }
if ($Mode -eq "--print" -or $Mode -eq "print" -or $Mode -eq "doctor") { $Print = $true; if ($Mode -eq "doctor") { $script:Doctor = $true } }
function Say($msg) { Write-Host " $msg" }
function Ok($msg) { Write-Host " ✓ $msg" -ForegroundColor Green }
function Warn($msg) { Write-Host " ! $msg" -ForegroundColor Yellow }
function Die($msg, $hint) { Write-Host "`n ✗ $msg" -ForegroundColor Red; if ($hint) { Write-Host " $hint" }; exit 1 }
$ArchRaw = $env:PROCESSOR_ARCHITECTURE
if ($env:PROCESSOR_ARCHITEW6432) { $ArchRaw = $env:PROCESSOR_ARCHITEW6432 }
$ArchId = "x86_64"
$RustHost = "x86_64-pc-windows-msvc"
if ($ArchRaw -eq "ARM64") { $ArchId = "arm64"; $RustHost = "aarch64-pc-windows-msvc" }
elseif ($ArchRaw -ne "AMD64") { $ArchId = $ArchRaw; $RustHost = "" }
$Interactive = (-not $env:ANYBOT_NONINTERACTIVE) -and (-not $env:CI) -and [Environment]::UserInteractive -and -not [Console]::IsInputRedirected
function Report {
$ok = [bool]$RustHost
if ($script:Doctor) {
Write-Host "AnyBot 安裝檢查 Windows $ArchRaw"
Write-Host " Rust 目標 $(if ($RustHost) { $RustHost } else { '不支援' })"
Write-Host " 建議 app(有桌面)或 hands(讓大腦操控這台登入中的桌面)"
Write-Host " 伺服器 請在 Linux 上架(install.sh server)。這台當大腦:裝完 anybot setup --brain"
return
}
$obj = [ordered]@{
ok = $ok
os = "Windows"
arch = "$ArchRaw"
arch_id = "$ArchId"
rust_host = "$RustHost"
distro = "Windows"
libc = "msvc"
pm = "winget"
desktop = $true
headless = $false
wsl = $false
systemd = $false
recommended = "app"
server_ok = $false
modes = @("app", "hands", "deps")
notes = @(
"操控的是登入中的 Windows 桌面(工作排程器只在使用者登入時執行)。",
"伺服器請用 Linux 的 install.sh server。"
)
one_click = @{
app = "irm https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.ps1 | iex"
hands = "`$env:ANYBOT_MODE='hands'; `$env:ANYBOT_PAIR_URL='網址'; `$env:ANYBOT_PAIR_CODE='配對碼'; irm https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.ps1 | iex"
server = "curl -fsSL https://code.30cm.net/daniel.w/AnyBot/raw/branch/main/install.sh | sh -s -- server"
print = "powershell -File install.ps1 -Print"
}
}
$obj | ConvertTo-Json -Compress
if (-not $ok) { exit 1 }
}
if ($Print) { Report; exit 0 }
if (-not $RustHost) { Die "不支援的 Windows 架構:$ArchRaw" "支援 x64 與 arm64。" }
if (-not $Mode -or $Mode -eq "auto" -or $Mode -eq "guide") {
if ($Interactive) {
Write-Host ""
Write-Host " ◆ AnyBot Windows $ArchRaw" -ForegroundColor Cyan
Write-Host " 1 桌面 App"
Write-Host " 2 手腳(讓另一台的 Bot 操控這台登入中的桌面)"
Write-Host " 3 只裝編譯需要的東西"
Write-Host " 伺服器請在 Linux 上安裝。選 1/2/3 [1]: " -NoNewline
$c = Read-Host
switch ($c) {
"2" { $Mode = "hands" }
"3" { $Mode = "deps" }
default { $Mode = "app" }
}
} else { $Mode = "app" }
}
if ($Mode -eq "server") { Die "Windows 不架帳號伺服器。" "請在任何有 Docker 的 Linux 執行 install.sh server。這台當大腦或手腳即可。" }
if ($Mode -notin @("app", "hands", "deps")) { Die "不認得「$Mode」。" "可以用 app、hands、deps、-Print。" }
Write-Host ""
Write-Host " ◆ AnyBot 安裝 $Mode · Windows $ArchRaw" -ForegroundColor Cyan
function Have($name) { [bool](Get-Command $name -ErrorAction SilentlyContinue) }
function Ensure-WingetPackage($id, $override) {
if (-not (Have "winget")) { return $false }
$args = @("install", "--id", $id, "-e", "--accept-package-agreements", "--accept-source-agreements")
if ($override) { $args += @("--override", $override) }
& winget @args
return $LASTEXITCODE -eq 0
}
function Ensure-Git {
if (Have "git") { Ok "git"; return }
Say "安裝 Git…"
if (-not (Ensure-WingetPackage "Git.Git" $null)) {
Die "沒有 git。" "請安裝 https://git-scm.com/download/win 後再開一個 PowerShell。"
}
$git = "C:\Program Files\Git\cmd"
if (Test-Path $git) { $env:Path = "$git;" + $env:Path }
if (-not (Have "git")) { Die "Git 裝了,但這個視窗還沒看到。" "請關掉 PowerShell 再開一次,然後重跑同一行。" }
Ok "git"
}
function Rust-Ok {
if (-not (Have "rustc")) { return $false }
$v = & rustc --version
if ($v -match "rustc (\d+)\.(\d+)") {
$maj = [int]$Matches[1]; $min = [int]$Matches[2]
return ($maj -gt 1) -or ($maj -eq 1 -and $min -ge $RustMin)
}
return $false
}
function Ensure-Msvc {
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
if (Test-Path $vswhere) {
$found = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null
if ($found) { Ok "Visual Studio C++ 工具"; return }
}
if (Have "cl") { Ok "cl.exe"; return }
Say "安裝 Visual Studio Build Tools(C++,編譯 Rust 需要,約幾個 GB)…"
$override = "--wait --passive --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"
if (-not (Ensure-WingetPackage "Microsoft.VisualStudio.2022.BuildTools" $override)) {
Die "沒有 C++ 編譯器。" "請安裝 Visual Studio Build Tools 的「使用 C++ 的桌面開發」,再開一個 PowerShell:https://visualstudio.microsoft.com/visual-cpp-build-tools/"
}
Ok "已安裝 Build Tools。若接下來 rustc 仍找不到 link.exe,請關掉 PowerShell 再開一次。"
}
function Ensure-Rust {
$cargoBin = Join-Path $env:USERPROFILE ".cargo\bin"
if (Test-Path $cargoBin) { $env:Path = "$cargoBin;" + $env:Path }
if (Rust-Ok) { Ok "Rust $((rustc --version).Split(' ')[1])"; return }
Ensure-Msvc
if (Have "rustup") {
Say "更新 Rust…"
& rustup update stable
& rustup default stable
} else {
Say "安裝 Rust(rustup,目標 $RustHost)…"
$init = Join-Path $env:TEMP "rustup-init.exe"
Invoke-WebRequest -Uri "https://win.rustup.rs/x86_64" -OutFile $init
if ($ArchId -eq "arm64") {
Invoke-WebRequest -Uri "https://win.rustup.rs/aarch64" -OutFile $init
}
& $init -y --profile minimal --default-host $RustHost
if ($LASTEXITCODE -ne 0) { Die "安裝 Rust 失敗。" "手動:https://rustup.rs" }
}
$env:Path = "$cargoBin;" + $env:Path
if (-not (Rust-Ok)) { Die "Rust 還是太舊或找不到。" "關掉視窗再開,執行 rustup update stable。" }
if (-not ((& rustup target list --installed) -contains $RustHost)) {
& rustup target add $RustHost
}
Ok "Rust $((rustc --version).Split(' ')[1])"
}
function Find-Source {
$here = $PSScriptRoot
if ($here -and (Test-Path (Join-Path $here "Cargo.toml"))) {
$toml = Get-Content (Join-Path $here "Cargo.toml") -Raw
if ($toml -match 'name = "anybot"') { $script:Src = $here; Ok "用這份原始碼:$here"; return }
}
Ensure-Git
if (Test-Path (Join-Path $Src ".git")) {
Say "更新 $Src …"
& git -C $Src pull --ff-only
if ($LASTEXITCODE -ne 0) { Warn "沒辦法更新,用現有的版本" }
} else {
if (Test-Path $Src) { Die "$Src 已經存在,但不是 AnyBot 的原始碼。" "設 ANYBOT_DIR 換一個資料夾。" }
Say "下載原始碼到 $Src …"
& git clone --depth 1 -b $Branch $Repo $Src
if ($LASTEXITCODE -ne 0) { Die "下載原始碼失敗。" "網路連得到 $Repo 嗎?" }
}
$script:Src = $Src
Ok "原始碼:$Src"
}
function Build-Anybot {
Say "編譯中,第一次約 5–15 分鐘…"
Push-Location $script:Src
& cargo build --release --bin anybot
$code = $LASTEXITCODE
Pop-Location
if ($code -ne 0) { Die "編譯失敗。" "把最後幾行錯誤貼給維護的人。常見原因:沒有 C++ 建置工具,或記憶體不夠。" }
Ok "編譯完成"
}
function Install-Bin {
$from = Join-Path $script:Src "target\release\anybot.exe"
if (-not (Test-Path $from)) { Die "找不到 $from" "" }
$destDir = Join-Path $env:LOCALAPPDATA "AnyBot"
New-Item -ItemType Directory -Force -Path $destDir | Out-Null
$dest = Join-Path $destDir "anybot.exe"
Copy-Item $from $dest -Force
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
if ($userPath -notlike "*$destDir*") {
[Environment]::SetEnvironmentVariable("Path", "$userPath;$destDir", "User")
}
$env:Path = "$destDir;" + $env:Path
$script:Bin = $dest
Ok "已安裝 $dest"
}
function First-Config {
if ($PairUrl -and $PairCode) {
Say "用配對碼接上大腦,不另外詢問。"
& $script:Bin setup $PairUrl $PairCode
if ($LASTEXITCODE -ne 0) { Die "配對失敗。" "配對碼 10 分鐘、一次。到大腦按「加一台電腦」再拿一組。" }
return
}
if ($env:ANYBOT_PROVIDER) {
Say "寫入第一次的模型設定。"
& $script:Bin setup --seed
if ($LASTEXITCODE -ne 0) { Die "寫入第一次設定失敗。" "" }
return
}
if (-not $Interactive) {
Say "尚未做第一次設定。請設 ANYBOT_PAIR_URL 與 ANYBOT_PAIR_CODE,或 ANYBOT_PROVIDER 與 ANYBOT_API_KEY。"
return
}
Write-Host ""
Write-Host " 第一次設定(可略過)"
Write-Host " 1 稍後在 App 裡設定"
Write-Host " 2 貼上 API 金鑰,建立第一個 Bot"
Write-Host " 3 接到大腦,這台當手腳"
Write-Host " 選 1/2/3 [1]: " -NoNewline
switch (Read-Host) {
"2" {
$env:ANYBOT_PROVIDER = Read-Host " 服務 openai / anthropic / xai / gemini / openrouter / opencode / ollama / custom"
$sec = Read-Host " API 金鑰(ollama 可直接 Enter)" -AsSecureString
$env:ANYBOT_API_KEY = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec))
& $script:Bin setup --seed
}
"3" {
$u = Read-Host " 網址"
$c = Read-Host " 配對碼"
if ($u -and $c) { & $script:Bin setup $u $c }
}
}
}
if ($Mode -eq "deps") {
Ensure-Git
Ensure-Rust
Write-Host "`n 好了。在原始碼資料夾:cargo build --release --bin anybot" -ForegroundColor Green
exit 0
}
Ensure-Git
Ensure-Rust
Find-Source
Build-Anybot
Install-Bin
if ($Mode -eq "hands") {
if (-not $PairUrl -and $Interactive) {
Write-Host ""
Write-Host " 把這台接到大腦(AnyBot 按「加一台電腦」)"
$PairUrl = Read-Host " 網址(直接 Enter 先跳過)"
if ($PairUrl) { $PairCode = Read-Host " 配對碼" }
}
if ($PairUrl -and $PairCode) {
& $script:Bin setup $PairUrl $PairCode
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
} else {
Write-Host "`n 裝好了。接著:anybot setup 網址 配對碼" -ForegroundColor Green
Say "配對後會在登入時自動執行。Bot 操控的是這個已登入的桌面,不是鎖定畫面。"
}
} else {
First-Config
Write-Host "`n 裝好了。從開始功能表找不到時,直接執行:" -ForegroundColor Green
Write-Host " $($script:Bin)"
Say "要讓 Bot 操控這台:在大腦按「加一台電腦」,用裡面的 Windows 那一行。"
}
Say "要更新:再執行一次同一行指令。"