#Requires -Version 5.1 # # 从 Git 克隆后首次部署(推荐分行复制执行,避免参数被误传给 git;浅克隆加 --depth 1): # git clone --depth 1 "https://git.51easyai.com/wangbo/easyai.git" "easyai" # Set-Location ".\easyai" # powershell.exe -NoProfile -ExecutionPolicy Bypass -File ".\start.ps1" # # 若必须一行,请用分号分隔整条链,且 -File 后必须是带引号的脚本路径(不要用 Istart.ps1,应为 .\start.ps1): # git clone --depth 1 "https://git.51easyai.com/wangbo/easyai.git" "easyai"; Set-Location "easyai"; powershell.exe -NoProfile -ExecutionPolicy Bypass -File ".\start.ps1" # # 若出现 error: unknown switch 'E':说明 -ExecutionPolicy 被交给了 git(可复现:git clone URL -ExecutionPolicy)。 # 常见原因:整段命令未用分号分隔、或把 powershell 与 git 写在同一行且参数被 git 解析。 # "Istart.ps1" 多为把 ".\start.ps1" 复制错(点反斜杠被看成字母 I)。 # [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8 $ErrorActionPreference = "Stop" $script:Root = if ($PSScriptRoot) { $PSScriptRoot } else { (Get-Location).Path } $script:LogFile = Join-Path $script:Root "start.ps1.log" $script:DeployDryRun = ($env:DEPLOY_DRY_RUN -eq "1") $script:DeployIP = "" $script:SkipDeployQuestions = $false $script:DockerDesktopUrl = "https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe" function Write-Log { param([string]$Message) try { Add-Content -Path $script:LogFile -Encoding UTF8 -Value ("[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message) } catch { } } function Wait-ForExit { if ($env:CI -eq "true") { return } if ($env:DEPLOY_NO_WAIT -eq "1") { return } Write-Host "" Read-Host "Press Enter to exit" } trap { $msg = $_.Exception.Message Write-Log "FATAL: $msg" Write-Host "" Write-Host "========================================" -ForegroundColor Red Write-Host "Script failed. Log file: $script:LogFile" -ForegroundColor Red Write-Host "========================================" -ForegroundColor Red Write-Host $msg -ForegroundColor Red Wait-ForExit exit 1 } function Step { param([string]$Message) Write-Host $Message } function Ok { param([string]$Message) Write-Host "[OK] $Message" -ForegroundColor Green } function Warn { param([string]$Message) Write-Host "[WARN] $Message" -ForegroundColor Yellow } function Fail { param([string]$Message) Write-Host "[ERR] $Message" -ForegroundColor Red; throw $Message } function Init-ProjectDir { $composePath = Join-Path $script:Root "docker-compose.yml" if (-not (Test-Path $composePath)) { Fail "docker-compose.yml not found. Please run this script in easyai directory." } Set-Location $script:Root Step "Project directory: $script:Root" } function Ensure-FileFromSample { param([string]$Target,[string]$Sample) if (Test-Path $Target) { return } if (-not (Test-Path $Sample)) { Fail "Missing sample file: $Sample" } Copy-Item $Sample $Target Ok "$Target created" } function Get-DetectedLanIp { try { $ip = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress -notmatch "^127\." -and $_.IPAddress -notmatch "^169\.254\." -and $_.InterfaceAlias -notmatch "Loopback" } | Sort-Object InterfaceIndex | Select-Object -First 1 -ExpandProperty IPAddress return $ip } catch { return $null } } function Run-DeployQuestions { Write-Host "" Write-Host "================================" Write-Host " EasyAI Deployment Config" Write-Host "================================" Write-Host "" if ($env:DEPLOY_IP) { $script:DeployIP = $env:DEPLOY_IP.Trim() Step "Using DEPLOY_IP=$($script:DeployIP)" return } Write-Host "Choose mode:" Write-Host " [1] Local only (127.0.0.1)" Write-Host " [2] LAN access" $choice = Read-Host "Select [1/2]" switch ($choice) { "1" { $script:DeployIP = "127.0.0.1"; Step "Selected local mode" } "2" { $detected = Get-DetectedLanIp if ($detected) { Write-Host "Detected LAN IP: $detected" $inputIp = (Read-Host "Input LAN IP (Enter to use detected)").Trim() $script:DeployIP = if ([string]::IsNullOrWhiteSpace($inputIp)) { $detected } else { $inputIp } } else { $inputIp = (Read-Host "Input LAN IP").Trim() if ([string]::IsNullOrWhiteSpace($inputIp)) { Fail "IP cannot be empty." } $script:DeployIP = $inputIp } Warn "Allow firewall ports: 3001, 3002, 3003" } default { Fail "Invalid selection." } } } function Upsert-Env { param([string]$Content,[string]$Key,[string]$Value) $line = "$Key=$Value" $pattern = "(?m)^$Key=.*$" if ($Content -match $pattern) { return ($Content -replace $pattern, $line) } if ($Content -and -not $Content.EndsWith("`n")) { $Content += "`n" } return ($Content + $line + "`n") } function Setup-EnvFiles { Write-Host "" Step "Configuring env files..." Ensure-FileFromSample ".env.tools" ".env.tools.sample" Ensure-FileFromSample ".env.ASG" ".env.ASG.sample" Ensure-FileFromSample ".env.AMS" ".env.AMS.sample" Ensure-FileFromSample ".env" ".env.sample" $content = Get-Content ".env" -Raw -Encoding UTF8 if (-not $content) { $content = "" } $content = Upsert-Env $content "NUXT_PUBLIC_BASE_APIURL" "http://$($script:DeployIP):3001" $content = Upsert-Env $content "NUXT_PUBLIC_BASE_SOCKETURL" "ws://$($script:DeployIP):3002" $content = Upsert-Env $content "NUXT_PUBLIC_SG_APIURL" "http://$($script:DeployIP):3003" [System.IO.File]::WriteAllText((Join-Path $script:Root ".env"), $content, [System.Text.UTF8Encoding]::new($false)) Ok ".env configured for IP=$($script:DeployIP)" } function Test-DockerInstalled { $docker = Get-Command docker -ErrorAction SilentlyContinue if (-not $docker) { return $false } try { & docker --version *> $null; return $true } catch { return $false } } function Test-DockerRunning { param([int]$TimeoutSeconds = 10) try { $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.FileName = "docker" $psi.Arguments = "info" $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true $psi.UseShellExecute = $false $psi.CreateNoWindow = $true $p = [System.Diagnostics.Process]::Start($psi) $ok = $p.WaitForExit($TimeoutSeconds * 1000) if (-not $ok) { try { $p.Kill() } catch { }; return $false } return ($p.ExitCode -eq 0) } catch { return $false } } function Start-DockerDesktopAndWait { param([int]$MaxWaitSeconds = 120, [int]$CheckIntervalSeconds = 5) Step "Docker not running, trying to start Docker Desktop..." $path = Join-Path $env:ProgramFiles "Docker\Docker\Docker Desktop.exe" if (-not (Test-Path $path)) { $path = Join-Path ${env:ProgramFiles(x86)} "Docker\Docker\Docker Desktop.exe" } if (-not (Test-Path $path)) { Warn "Docker Desktop executable not found."; return $false } $startedByCli = $false try { & docker desktop start *> $null if ($LASTEXITCODE -eq 0) { $startedByCli = $true; Step "Sent docker desktop start command" } } catch { } if (-not $startedByCli) { Start-Process -FilePath $path -WindowStyle Hidden } $elapsed = 0 while ($elapsed -lt $MaxWaitSeconds) { Start-Sleep -Seconds $CheckIntervalSeconds $elapsed += $CheckIntervalSeconds if (Test-DockerRunning) { Ok "Docker engine ready"; return $true } } Warn "Timeout waiting for Docker." return $false } function Ensure-DockerRunning { if (-not (Test-DockerInstalled)) { return $false } if (Test-DockerRunning) { Ok "Docker engine running"; return $true } return (Start-DockerDesktopAndWait) } function Install-DockerDesktop { function Test-Winget { return [bool](Get-Command winget -ErrorAction SilentlyContinue) } function Test-Choco { return [bool](Get-Command choco -ErrorAction SilentlyContinue) } if (Test-Winget) { Step "Installing Docker Desktop via winget..." Write-Log "winget install Docker.DockerDesktop" $wingetOut = & winget install --id Docker.DockerDesktop -e --accept-source-agreements --accept-package-agreements 2>&1 $wingetOut | ForEach-Object { Write-Host $_ } $wingetCode = $LASTEXITCODE Write-Log "winget exit code: $wingetCode" if ($wingetCode -eq 0) { Ok "Install started. Reopen terminal and rerun script." Write-Log "winget reported success" exit 0 } if (Test-DockerInstalled) { Ok "Docker CLI detected after winget. Reopen terminal if docker commands fail, then rerun script." Write-Log "winget exit $wingetCode but docker is available" exit 0 } Warn "winget did not report success (exit code: $wingetCode). Trying Chocolatey if available..." } else { Warn "winget not found (install 'App Installer' from Microsoft Store or use Windows 11 / recent Windows 10). Trying Chocolatey if available..." } if (Test-Choco) { Step "Installing Docker Desktop via choco..." Write-Log "choco install docker-desktop" $chocoOut = & choco install docker-desktop -y 2>&1 $chocoOut | ForEach-Object { Write-Host $_ } $chocoCode = $LASTEXITCODE Write-Log "choco exit code: $chocoCode" if ($chocoCode -eq 0) { Ok "Install started. Reopen terminal and rerun script." exit 0 } if (Test-DockerInstalled) { Ok "Docker CLI detected after choco. Reopen terminal if docker commands fail, then rerun script." exit 0 } Warn "choco did not report success (exit code: $chocoCode)." } else { Warn "Chocolatey (choco) not found." } Warn "Automatic install was not completed. Please install Docker Desktop manually:" Write-Host " $script:DockerDesktopUrl" Write-Log "Docker auto-install failed (winget/choco unavailable or non-zero exit)" Wait-ForExit exit 1 } function Test-Docker { Write-Host "" Write-Host "================================" Write-Host " Docker Check" Write-Host "================================" Write-Host "" if (Test-DockerInstalled) { Ok "Docker installed" if (-not (Ensure-DockerRunning)) { Warn "Docker failed to start automatically."; Wait-ForExit; exit 1 } return } Warn "Docker Desktop not detected." Write-Host "Choose:" Write-Host " [1] Manual install (open URL and exit)" Write-Host " [2] Auto install (winget/choco)" $choice = (Read-Host "Select [1/2]").Trim() switch ($choice) { "1" { Start-Process $script:DockerDesktopUrl; Wait-ForExit; exit 1 } "2" { Install-DockerDesktop } default { Fail "Invalid selection." } } } function Start-Services { Write-Host "" Step "Starting EasyAI services..." $useComposeV2 = $false try { & docker compose version *> $null; if ($LASTEXITCODE -eq 0) { $useComposeV2 = $true } } catch { } if ($useComposeV2) { docker compose pull if ($LASTEXITCODE -ne 0) { Fail "docker compose pull failed." } docker compose up -d if ($LASTEXITCODE -ne 0) { Fail "docker compose up failed." } } else { docker-compose pull if ($LASTEXITCODE -ne 0) { Fail "docker-compose pull failed." } docker-compose up -d if ($LASTEXITCODE -ne 0) { Fail "docker-compose up failed." } } Ok "EasyAI started" } function Main { Init-ProjectDir if ((Test-Path ".env") -and -not $env:DEPLOY_FORCE_RECONFIG -and -not $env:DEPLOY_IP) { $answer = (Read-Host "Existing .env found. Reconfigure? [y/N]").Trim().ToLower() $yes = ($answer.Length -gt 0) -and ($answer[0] -eq [char]121) if (-not $yes) { $line = Get-Content ".env" -Encoding UTF8 | Where-Object { $_ -like "NUXT_PUBLIC_BASE_APIURL=*" } | Select-Object -First 1 if ($line -and $line -like "*:3001*") { $script:DeployIP = ($line -replace ".*http://", "" -replace ":3001.*", "").Trim() } $script:SkipDeployQuestions = $true Step "Using existing env config" } } if (-not $script:SkipDeployQuestions) { Run-DeployQuestions Setup-EnvFiles } else { Ensure-FileFromSample ".env.tools" ".env.tools.sample" Ensure-FileFromSample ".env.ASG" ".env.ASG.sample" Ensure-FileFromSample ".env.AMS" ".env.AMS.sample" } if ($script:DeployDryRun) { Warn "dry-run mode: skip docker and services" } else { Test-Docker Start-Services } $ip = if ($script:DeployIP) { $script:DeployIP } else { "127.0.0.1" } Write-Host "" if ($script:DeployDryRun) { Write-Host "================================" -ForegroundColor Yellow Write-Host " 配置已生成(未启动 Docker 服务)" -ForegroundColor Yellow Write-Host "================================" -ForegroundColor Yellow Write-Host "" Write-Host " 预期访问: " -NoNewline; Write-Host "http://${ip}:3010" -ForegroundColor Cyan Write-Host "" Write-Host " 默认管理员: admin / 123456(启动服务后用于登录)" -ForegroundColor DarkGray } else { Write-Host "================================" -ForegroundColor Green Write-Host " 部署成功" -ForegroundColor Green Write-Host "================================" -ForegroundColor Green Write-Host "" Write-Host " 访问地址: " -NoNewline; Write-Host "http://${ip}:3010" -ForegroundColor Cyan Write-Host "" Write-Host " -------- 默认管理员(首次登录后请修改密码)--------" -ForegroundColor Yellow Write-Host " 账号: " -NoNewline; Write-Host "admin" -ForegroundColor White Write-Host " 密码: " -NoNewline; Write-Host "123456" -ForegroundColor White } Write-Host "" Wait-ForExit } Write-Log "=== script start ===" Write-Host "EasyAI deploy script starting..." -ForegroundColor Cyan Write-Host "Log file: $script:LogFile" Write-Host "" Main Write-Log "=== script end ==="