Some checks failed
Test start.ps1 (Windows) / test-windows (push) Has been cancelled
重建部署脚本并修复解析阶段报错,避免因脚本损坏导致运行后直接退出;同时保留 dry-run 与 Docker 检查流程,提升可诊断性与非交互执行稳定性。 Made-with: Cursor
306 lines
11 KiB
PowerShell
306 lines
11 KiB
PowerShell
#Requires -Version 5.1
|
|
|
|
[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 {
|
|
if (Get-Command winget -ErrorAction SilentlyContinue) {
|
|
Step "Installing Docker Desktop via winget..."
|
|
winget install --id Docker.DockerDesktop -e --accept-source-agreements --accept-package-agreements
|
|
if ($LASTEXITCODE -eq 0) { Ok "Install started. Reopen terminal and rerun script."; exit 0 }
|
|
}
|
|
if (Get-Command choco -ErrorAction SilentlyContinue) {
|
|
Step "Installing Docker Desktop via choco..."
|
|
choco install docker-desktop -y
|
|
if ($LASTEXITCODE -eq 0) { Ok "Install started. Reopen terminal and rerun script."; exit 0 }
|
|
}
|
|
Warn "Please install Docker Desktop manually:"
|
|
Write-Host " $script:DockerDesktopUrl"
|
|
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]"
|
|
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 ""
|
|
Write-Host "================================"
|
|
Write-Host " Deployment Done"
|
|
Write-Host "================================"
|
|
Write-Host "URL: http://${ip}:3010"
|
|
Write-Host "User: admin"
|
|
Write-Host "Password: 123456"
|
|
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 ==="
|