308 lines
12 KiB
PowerShell
308 lines
12 KiB
PowerShell
|
|
# 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 "要更新:再執行一次同一行指令。"
|