Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
c60bc54fdc
445
start.ps1
445
start.ps1
@ -1,189 +1,149 @@
|
||||
#Requires -Version 5.1
|
||||
# EasyAI Windows 一键部署脚本
|
||||
# 仅支持 IP 访问(本地/局域网),不含域名与 HTTPS
|
||||
# 一行命令: git clone https://git.51easyai.com/wangbo/easyai; cd easyai; .\start.ps1
|
||||
#Requires -Version 5.1
|
||||
|
||||
# 设置控制台编码为 UTF-8,确保中文正确显示
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$script:LogDir = $PSScriptRoot
|
||||
if (-not $script:LogDir) { $script:LogDir = $env:TEMP }
|
||||
$script:LogFile = Join-Path $script:LogDir "start.ps1.log"
|
||||
# 尽早写入启动标记,便于闪退时排查
|
||||
try { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] === 脚本加载 ===" | Out-File -FilePath $script:LogFile -Append -Encoding utf8 } catch { }
|
||||
|
||||
$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]$Msg)
|
||||
param([string]$Message)
|
||||
try {
|
||||
$line = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Msg"
|
||||
Add-Content -Path $script:LogFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||
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 {
|
||||
$errMsg = $_.Exception.Message
|
||||
$errStack = if ($_.ScriptStackTrace) { " | 堆栈: $($_.ScriptStackTrace)" } else { "" }
|
||||
try {
|
||||
$logPath = $script:LogFile
|
||||
if (-not $logPath) { $logPath = Join-Path $env:TEMP "start.ps1.log" }
|
||||
$logLine = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] 错误退出: $errMsg$errStack"
|
||||
Add-Content -Path $logPath -Value $logLine -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||
} catch { }
|
||||
try {
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Red
|
||||
Write-Host " 发生错误,详见日志: $script:LogFile" -ForegroundColor Red
|
||||
Write-Host "========================================" -ForegroundColor Red
|
||||
Write-Host $errMsg -ForegroundColor Red
|
||||
if ($errStack) { Write-Host $errStack }
|
||||
} catch { }
|
||||
$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
|
||||
}
|
||||
|
||||
# 结束时保持窗口不关闭,便于查看输出和 Debug(CI 环境自动跳过)
|
||||
function Wait-ForExit {
|
||||
if ($env:CI -eq "true") { return }
|
||||
Write-Host ""
|
||||
Read-Host "按 Enter 键退出"
|
||||
}
|
||||
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 }
|
||||
|
||||
# 仅配置模式:DEPLOY_DRY_RUN=1 只生成配置文件,不执行 Docker 安装和启动
|
||||
$script:DeployDryRun = if ($env:DEPLOY_DRY_RUN -eq "1") { $true } else { $false }
|
||||
$script:DeployIP = ""
|
||||
$script:DeployModeSkip = $false
|
||||
|
||||
# Docker Desktop for Windows 下载链接
|
||||
$DockerDesktopUrl = "https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe"
|
||||
|
||||
function Write-Step { param($Msg) Write-Host $Msg }
|
||||
function Write-Ok { param($Msg) Write-Host " ✓ $Msg" -ForegroundColor Green }
|
||||
function Write-Err { param($Msg) Write-Host "❌ $Msg" -ForegroundColor Red; throw $Msg }
|
||||
function Write-Warn { param($Msg) Write-Host "⚠️ $Msg" -ForegroundColor Yellow }
|
||||
|
||||
# ==================== 项目初始化 ====================
|
||||
function Init-ProjectDir {
|
||||
$scriptDir = $PSScriptRoot
|
||||
if (-not $scriptDir) { throw "无法获取脚本所在目录。请在 easyai 目录下打开 PowerShell 执行: .\start.ps1" }
|
||||
$composePath = Join-Path $scriptDir "docker-compose.yml"
|
||||
if (Test-Path $composePath) {
|
||||
Set-Location $scriptDir
|
||||
Write-Step "📁 项目目录: $scriptDir"
|
||||
return
|
||||
$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
|
||||
}
|
||||
Write-Err "未找到 docker-compose.yml,请在 easyai 项目目录下运行 start.ps1"
|
||||
}
|
||||
|
||||
# ==================== 配置问答 ====================
|
||||
function Run-DeployQuestions {
|
||||
Write-Host ""
|
||||
Write-Host "================================"
|
||||
Write-Host " EasyAI 部署配置(Windows)"
|
||||
Write-Host " EasyAI Deployment Config"
|
||||
Write-Host "================================"
|
||||
Write-Host ""
|
||||
|
||||
# 非交互模式:环境变量 DEPLOY_IP 已设置
|
||||
if ($env:DEPLOY_IP) {
|
||||
$script:DeployIP = $env:DEPLOY_IP.Trim()
|
||||
Write-Step "使用环境变量: DEPLOY_IP=$($script:DeployIP)"
|
||||
Step "Using DEPLOY_IP=$($script:DeployIP)"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "请选择访问方式:"
|
||||
Write-Host " [1] 本地访问 (127.0.0.1) - 仅本机访问,无需放行端口"
|
||||
Write-Host " [2] 局域网访问 - 同网段设备访问,需放行 3001、3002、3003 端口"
|
||||
$choice = Read-Host "请选择 [1/2]"
|
||||
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"
|
||||
Write-Step "已选择本地访问"
|
||||
}
|
||||
"1" { $script:DeployIP = "127.0.0.1"; Step "Selected local mode" }
|
||||
"2" {
|
||||
# 自动获取局域网 IP(排除回环和 APIPA 地址)
|
||||
$detectedIp = $null
|
||||
try {
|
||||
$addrs = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.InterfaceAlias -notlike "*Loopback*" -and
|
||||
$_.IPAddress -notmatch "^127\." -and
|
||||
$_.IPAddress -notmatch "^169\.254\."
|
||||
}
|
||||
$detectedIp = ($addrs | Sort-Object InterfaceIndex | Select-Object -First 1).IPAddress
|
||||
} catch { }
|
||||
if ($detectedIp) {
|
||||
$defaultHint = "回车使用 [$detectedIp]"
|
||||
Write-Host " 检测到局域网 IP: $detectedIp"
|
||||
$inputIp = Read-Host "请输入本机局域网 IP($defaultHint 或手动输入)"
|
||||
$inputIp = $inputIp.Trim()
|
||||
$script:DeployIP = if ([string]::IsNullOrWhiteSpace($inputIp)) { $detectedIp } else { $inputIp }
|
||||
$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 {
|
||||
Write-Host " 提示: 未自动检测到局域网 IP,可在本机运行 ipconfig 查看"
|
||||
$lanIp = Read-Host "请输入本机局域网 IP 地址"
|
||||
$lanIp = $lanIp.Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($lanIp)) { Write-Err "IP 不能为空" }
|
||||
$script:DeployIP = $lanIp
|
||||
$inputIp = (Read-Host "Input LAN IP").Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($inputIp)) { Fail "IP cannot be empty." }
|
||||
$script:DeployIP = $inputIp
|
||||
}
|
||||
Write-Warn "请确保防火墙已放行 3001、3002、3003 端口"
|
||||
}
|
||||
default {
|
||||
Write-Err "无效选择"
|
||||
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 ""
|
||||
Write-Step "📝 配置环境文件..."
|
||||
Step "Configuring env files..."
|
||||
|
||||
# 复制 .env.tools、.env.ASG、.env.AMS(无 example 后缀的从 .sample 生成)
|
||||
if (-not (Test-Path ".env.tools")) {
|
||||
Copy-Item ".env.tools.sample" ".env.tools"
|
||||
Write-Ok ".env.tools"
|
||||
}
|
||||
if (-not (Test-Path ".env.ASG")) {
|
||||
Copy-Item ".env.ASG.sample" ".env.ASG"
|
||||
Write-Ok ".env.ASG"
|
||||
}
|
||||
if (-not (Test-Path ".env.AMS")) {
|
||||
Copy-Item ".env.AMS.sample" ".env.AMS"
|
||||
Write-Ok ".env.AMS"
|
||||
}
|
||||
|
||||
# 配置 .env
|
||||
if (-not (Test-Path ".env")) {
|
||||
Copy-Item ".env.sample" ".env"
|
||||
}
|
||||
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 = $content -replace '(?m)^NUXT_PUBLIC_BASE_APIURL=.*', "NUXT_PUBLIC_BASE_APIURL=http://$($script:DeployIP):3001"
|
||||
$content = $content -replace '(?m)^NUXT_PUBLIC_BASE_SOCKETURL=.*', "NUXT_PUBLIC_BASE_SOCKETURL=ws://$($script:DeployIP):3002"
|
||||
$content = $content -replace '(?m)^NUXT_PUBLIC_SG_APIURL=.*', "NUXT_PUBLIC_SG_APIURL=http://$($script:DeployIP):3003"
|
||||
$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"
|
||||
|
||||
# 保持文件末尾换行
|
||||
if ($content -and -not $content.EndsWith("`n")) { $content += "`n" }
|
||||
$envPath = Join-Path (Get-Location) ".env"
|
||||
[System.IO.File]::WriteAllText($envPath, $content, [System.Text.UTF8Encoding]::new($false))
|
||||
Write-Ok ".env 已配置为 IP 模式 ($($script:DeployIP))"
|
||||
[System.IO.File]::WriteAllText((Join-Path $script:Root ".env"), $content, [System.Text.UTF8Encoding]::new($false))
|
||||
Ok ".env configured for IP=$($script:DeployIP)"
|
||||
}
|
||||
|
||||
# ==================== Docker 检测 ====================
|
||||
function Test-DockerInstalled {
|
||||
$docker = Get-Command docker -ErrorAction SilentlyContinue
|
||||
if (-not $docker) { return $false }
|
||||
try {
|
||||
$null = & docker --version 2>&1
|
||||
return $true
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
try { & docker --version *> $null; return $true } catch { return $false }
|
||||
}
|
||||
|
||||
# 检查 Docker 引擎是否已启动并可响应(带超时,避免 docker info 卡住)
|
||||
function Test-DockerRunning {
|
||||
param([int]$TimeoutSeconds = 10)
|
||||
try {
|
||||
@ -195,246 +155,151 @@ function Test-DockerRunning {
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.CreateNoWindow = $true
|
||||
$p = [System.Diagnostics.Process]::Start($psi)
|
||||
$exited = $p.WaitForExit($TimeoutSeconds * 1000)
|
||||
if (-not $exited) {
|
||||
try { $p.Kill() } catch { }
|
||||
return $false
|
||||
}
|
||||
$ok = $p.WaitForExit($TimeoutSeconds * 1000)
|
||||
if (-not $ok) { try { $p.Kill() } catch { }; return $false }
|
||||
return ($p.ExitCode -eq 0)
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# 启动 Docker Desktop 并等待就绪
|
||||
function Start-DockerDesktopAndWait {
|
||||
param(
|
||||
[int]$MaxWaitSeconds = 120,
|
||||
[int]$CheckIntervalSeconds = 5
|
||||
)
|
||||
param([int]$MaxWaitSeconds = 120, [int]$CheckIntervalSeconds = 5)
|
||||
Step "Docker not running, trying to start Docker Desktop..."
|
||||
|
||||
Write-Step "Docker 未运行,正在启动 Docker Desktop..."
|
||||
$dockerDesktopPath = "${env:ProgramFiles}\Docker\Docker\Docker Desktop.exe"
|
||||
if (-not (Test-Path $dockerDesktopPath)) {
|
||||
$dockerDesktopPath = "${env:ProgramFiles(x86)}\Docker\Docker\Docker Desktop.exe"
|
||||
}
|
||||
if (-not (Test-Path $dockerDesktopPath)) {
|
||||
Write-Warn "未找到 Docker Desktop 可执行文件"
|
||||
Write-Host "请手动启动 Docker Desktop 后重新运行本脚本。"
|
||||
return $false
|
||||
}
|
||||
$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 }
|
||||
|
||||
# 尝试使用 docker desktop start(Docker Desktop 4.38+ 支持)
|
||||
$useCli = $false
|
||||
$startedByCli = $false
|
||||
try {
|
||||
$null = & docker desktop start 2>&1
|
||||
$useCli = $true
|
||||
Write-Step "已通过 docker desktop start 发送启动命令"
|
||||
} catch {
|
||||
# 回退到直接启动进程
|
||||
}
|
||||
& 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 }
|
||||
|
||||
if (-not $useCli) {
|
||||
Start-Process -FilePath $dockerDesktopPath -WindowStyle Hidden
|
||||
Write-Step "已启动 Docker Desktop 进程"
|
||||
}
|
||||
|
||||
Write-Host "等待 Docker 引擎就绪(最长 $MaxWaitSeconds 秒)..." -ForegroundColor Cyan
|
||||
$elapsed = 0
|
||||
while ($elapsed -lt $MaxWaitSeconds) {
|
||||
Start-Sleep -Seconds $CheckIntervalSeconds
|
||||
$elapsed += $CheckIntervalSeconds
|
||||
Write-Host " 已等待 $elapsed 秒..." -NoNewline
|
||||
if (Test-DockerRunning) {
|
||||
Write-Host " 完成" -ForegroundColor Green
|
||||
Write-Ok "Docker 已就绪"
|
||||
return $true
|
||||
}
|
||||
Write-Host ""
|
||||
if (Test-DockerRunning) { Ok "Docker engine ready"; return $true }
|
||||
}
|
||||
|
||||
Write-Warn "等待超时,Docker 可能仍在启动中"
|
||||
Write-Host "请稍后手动执行 docker info 确认,或重新运行本脚本。"
|
||||
Warn "Timeout waiting for Docker."
|
||||
return $false
|
||||
}
|
||||
|
||||
# 确保 Docker 已安装且运行,若未运行则尝试启动
|
||||
function Ensure-DockerRunning {
|
||||
if (-not (Test-DockerInstalled)) {
|
||||
return $false
|
||||
}
|
||||
if (Test-DockerRunning) {
|
||||
Write-Ok "Docker 引擎已运行"
|
||||
$v = docker --version 2>$null
|
||||
if ($v) { Write-Host " $v" }
|
||||
return $true
|
||||
}
|
||||
if (-not (Test-DockerInstalled)) { return $false }
|
||||
if (Test-DockerRunning) { Ok "Docker engine running"; return $true }
|
||||
return (Start-DockerDesktopAndWait)
|
||||
}
|
||||
|
||||
function Install-DockerDesktop {
|
||||
$isWin = ($env:OS -eq "Windows_NT") -or ($PSVersionTable.PSVersion.Major -ge 6 -and $IsWindows)
|
||||
if (-not $isWin) {
|
||||
Write-Warn "自动安装仅支持 Windows。请手动安装 Docker Desktop。"
|
||||
Write-Host "下载地址: $DockerDesktopUrl"
|
||||
Wait-ForExit; exit 1
|
||||
}
|
||||
|
||||
# 优先 winget,其次 Chocolatey
|
||||
if (Get-Command winget -ErrorAction SilentlyContinue) {
|
||||
Write-Step "正在通过 winget 安装 Docker Desktop for Windows..."
|
||||
Step "Installing Docker Desktop via winget..."
|
||||
winget install --id Docker.DockerDesktop -e --accept-source-agreements --accept-package-agreements
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Ok "安装已启动。安装完成后请重启终端,然后重新运行本脚本。"
|
||||
exit 0
|
||||
}
|
||||
if ($LASTEXITCODE -eq 0) { Ok "Install started. Reopen terminal and rerun script."; exit 0 }
|
||||
}
|
||||
if (Get-Command choco -ErrorAction SilentlyContinue) {
|
||||
Write-Step "正在通过 Chocolatey 安装 Docker Desktop for Windows..."
|
||||
Step "Installing Docker Desktop via choco..."
|
||||
choco install docker-desktop -y
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Ok "安装已启动。安装完成后请重启终端,然后重新运行本脚本。"
|
||||
exit 0
|
||||
}
|
||||
if ($LASTEXITCODE -eq 0) { Ok "Install started. Reopen terminal and rerun script."; exit 0 }
|
||||
}
|
||||
|
||||
Write-Warn "未找到 winget 或 Chocolatey,请手动安装 Docker Desktop。"
|
||||
Write-Host "下载地址: $DockerDesktopUrl"
|
||||
Wait-ForExit; exit 1
|
||||
Warn "Please install Docker Desktop manually:"
|
||||
Write-Host " $script:DockerDesktopUrl"
|
||||
Wait-ForExit
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Test-Docker {
|
||||
Write-Host ""
|
||||
Write-Host "================================"
|
||||
Write-Host " Docker 检查"
|
||||
Write-Host " Docker Check"
|
||||
Write-Host "================================"
|
||||
Write-Host ""
|
||||
|
||||
if (Test-DockerInstalled) {
|
||||
Write-Ok "Docker 已安装"
|
||||
# 检查并确保 Docker 引擎运行
|
||||
if (-not (Ensure-DockerRunning)) {
|
||||
Write-Warn "Docker 未能自动启动"
|
||||
Write-Host ""
|
||||
Write-Host "请手动启动 Docker Desktop,确认其完全启动后再重新运行本脚本。"
|
||||
Wait-ForExit
|
||||
exit 1
|
||||
}
|
||||
Ok "Docker installed"
|
||||
if (-not (Ensure-DockerRunning)) { Warn "Docker failed to start automatically."; Wait-ForExit; exit 1 }
|
||||
return
|
||||
}
|
||||
|
||||
Write-Warn "未检测到 Docker Desktop for Windows"
|
||||
Write-Host ""
|
||||
Write-Host "请选择:"
|
||||
Write-Host " [1] 手动安装 - 打开下载页面,退出脚本"
|
||||
Write-Host " [2] 自动安装 - 通过 winget 或 Chocolatey 安装(需管理员权限)"
|
||||
$choice = Read-Host "请选择 [1/2]"
|
||||
|
||||
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" {
|
||||
Write-Host ""
|
||||
Write-Host "Docker Desktop for Windows 下载地址:"
|
||||
Write-Host " $DockerDesktopUrl"
|
||||
Write-Host ""
|
||||
Write-Host "安装完成后请重新运行本脚本。"
|
||||
if ($env:OS -eq "Windows_NT") {
|
||||
Start-Process $DockerDesktopUrl
|
||||
}
|
||||
Wait-ForExit; exit 1
|
||||
}
|
||||
"2" {
|
||||
Install-DockerDesktop
|
||||
}
|
||||
default {
|
||||
Write-Err "无效选择"
|
||||
}
|
||||
"1" { Start-Process $script:DockerDesktopUrl; Wait-ForExit; exit 1 }
|
||||
"2" { Install-DockerDesktop }
|
||||
default { Fail "Invalid selection." }
|
||||
}
|
||||
}
|
||||
|
||||
# ==================== 启动服务 ====================
|
||||
function Start-Services {
|
||||
Write-Host ""
|
||||
Write-Step "🚀 启动 EasyAI 服务..."
|
||||
Step "Starting EasyAI services..."
|
||||
$useComposeV2 = $false
|
||||
try { & docker compose version *> $null; if ($LASTEXITCODE -eq 0) { $useComposeV2 = $true } } catch { }
|
||||
|
||||
$hasComposeV2 = $false
|
||||
try {
|
||||
$null = docker compose version 2>&1
|
||||
$hasComposeV2 = $true
|
||||
} catch { }
|
||||
|
||||
if ($hasComposeV2) {
|
||||
if ($useComposeV2) {
|
||||
docker compose pull
|
||||
if ($LASTEXITCODE -ne 0) { Write-Err "docker compose pull 失败" }
|
||||
if ($LASTEXITCODE -ne 0) { Fail "docker compose pull failed." }
|
||||
docker compose up -d
|
||||
if ($LASTEXITCODE -ne 0) { Write-Err "docker compose up 失败" }
|
||||
if ($LASTEXITCODE -ne 0) { Fail "docker compose up failed." }
|
||||
} else {
|
||||
docker-compose pull
|
||||
if ($LASTEXITCODE -ne 0) { Write-Err "docker-compose pull 失败" }
|
||||
if ($LASTEXITCODE -ne 0) { Fail "docker-compose pull failed." }
|
||||
docker-compose up -d
|
||||
if ($LASTEXITCODE -ne 0) { Write-Err "docker-compose up 失败" }
|
||||
if ($LASTEXITCODE -ne 0) { Fail "docker-compose up failed." }
|
||||
}
|
||||
Write-Host "🎉 EasyAI 应用启动成功"
|
||||
Ok "EasyAI started"
|
||||
}
|
||||
|
||||
# ==================== 主流程 ====================
|
||||
function Main {
|
||||
Init-ProjectDir
|
||||
|
||||
# 检查是否已有 .env 且非强制重新配置
|
||||
if ((Test-Path ".env") -and -not $env:DEPLOY_FORCE_RECONFIG -and -not $env:DEPLOY_IP) {
|
||||
Write-Step "📁 检测到已有 .env 配置"
|
||||
$reconfigure = Read-Host "是否重新配置访问方式?[y/N]"
|
||||
$trimmed = $reconfigure.Trim().ToLower()
|
||||
$startsWithY = ($trimmed.Length -gt 0) -and ($trimmed[0] -eq [char]121)
|
||||
if (-not $startsWithY) {
|
||||
Write-Step "使用现有配置继续..."
|
||||
# 从现有 .env 读取 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*") {
|
||||
$prefix = ".*http://"
|
||||
$suffix = ":3001.*"
|
||||
$script:DeployIP = ($line -replace $prefix, "" -replace $suffix, "").Trim()
|
||||
}
|
||||
$script:DeployModeSkip = $true
|
||||
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:DeployModeSkip) {
|
||||
if (-not $script:SkipDeployQuestions) {
|
||||
Run-DeployQuestions
|
||||
}
|
||||
|
||||
if (-not $script:DeployModeSkip) {
|
||||
Setup-EnvFiles
|
||||
} else {
|
||||
if (-not (Test-Path ".env.tools")) { Copy-Item ".env.tools.sample" ".env.tools" }
|
||||
if (-not (Test-Path ".env.ASG")) { Copy-Item ".env.ASG.sample" ".env.ASG" }
|
||||
if (-not (Test-Path ".env.AMS")) { Copy-Item ".env.AMS.sample" ".env.AMS" }
|
||||
Ensure-FileFromSample ".env.tools" ".env.tools.sample"
|
||||
Ensure-FileFromSample ".env.ASG" ".env.ASG.sample"
|
||||
Ensure-FileFromSample ".env.AMS" ".env.AMS.sample"
|
||||
}
|
||||
|
||||
if ($script:DeployDryRun) {
|
||||
Write-Host ""
|
||||
Write-Warn "dry-run 模式:跳过 Docker 安装和服务启动"
|
||||
Write-Host " 配置文件已生成,可直接运行 .\start.ps1 完成部署"
|
||||
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 " 部署完成"
|
||||
Write-Host " Deployment Done"
|
||||
Write-Host "================================"
|
||||
$accessIp = if ($script:DeployIP) { $script:DeployIP } else { "127.0.0.1" }
|
||||
Write-Host "访问地址: http://${accessIp}:3010"
|
||||
Write-Host "默认登录账户: admin"
|
||||
Write-Host "默认登录密码: 123456"
|
||||
Write-Host ""
|
||||
Write-Host "URL: http://${ip}:3010"
|
||||
Write-Host "User: admin"
|
||||
Write-Host "Password: 123456"
|
||||
Wait-ForExit
|
||||
}
|
||||
|
||||
Write-Log "=== 脚本启动 ==="
|
||||
Write-Host "EasyAI Windows 部署脚本启动中..." -ForegroundColor Cyan
|
||||
Write-Host "日志文件: $script:LogFile"
|
||||
Write-Log "=== script start ==="
|
||||
Write-Host "EasyAI deploy script starting..." -ForegroundColor Cyan
|
||||
Write-Host "Log file: $script:LogFile"
|
||||
Write-Host ""
|
||||
Main
|
||||
Write-Log "=== 脚本正常结束 ==="
|
||||
Write-Log "=== script end ==="
|
||||
|
||||
Loading…
Reference in New Issue
Block a user