Some checks are pending
Test start.ps1 (Windows) / test-windows (push) Waiting to run
- start.ps1: PowerShell 部署脚本,支持本地/局域网 IP 访问 - docs/Windows一键部署方案.md: 实现方案与测试说明 - scripts/test-start-ps1-env.py: .env 替换逻辑验证 - .github/workflows/test-start-ps1.yml: Windows CI 测试 Made-with: Cursor
265 lines
9.2 KiB
PowerShell
265 lines
9.2 KiB
PowerShell
#Requires -Version 5.1
|
||
# EasyAI Windows 一键部署脚本
|
||
# 仅支持 IP 访问(本地/局域网),不含域名与 HTTPS
|
||
# 一行命令: git clone https://git.51easyai.com/wangbo/easyai; cd easyai; .\start.ps1
|
||
|
||
$ErrorActionPreference = "Stop"
|
||
|
||
# 仅配置模式: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; exit 1 }
|
||
function Write-Warn { param($Msg) Write-Host "⚠️ $Msg" -ForegroundColor Yellow }
|
||
|
||
# ==================== 项目初始化 ====================
|
||
function Init-ProjectDir {
|
||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||
$composePath = Join-Path $scriptDir "docker-compose.yml"
|
||
if (Test-Path $composePath) {
|
||
Set-Location $scriptDir
|
||
Write-Step "📁 项目目录: $scriptDir"
|
||
return
|
||
}
|
||
Write-Err "未找到 docker-compose.yml,请在 easyai 项目目录下运行 start.ps1"
|
||
}
|
||
|
||
# ==================== 配置问答 ====================
|
||
function Run-DeployQuestions {
|
||
Write-Host ""
|
||
Write-Host "================================"
|
||
Write-Host " EasyAI 部署配置(Windows)"
|
||
Write-Host "================================"
|
||
Write-Host ""
|
||
|
||
# 非交互模式:环境变量 DEPLOY_IP 已设置
|
||
if ($env:DEPLOY_IP) {
|
||
$script:DeployIP = $env:DEPLOY_IP.Trim()
|
||
Write-Step "使用环境变量: 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]"
|
||
|
||
switch ($choice) {
|
||
"1" {
|
||
$script:DeployIP = "127.0.0.1"
|
||
Write-Step "已选择本地访问"
|
||
}
|
||
"2" {
|
||
Write-Host " 提示: 可在本机运行 ipconfig 查看 IPv4 地址"
|
||
$lanIp = Read-Host "请输入本机局域网 IP 地址"
|
||
$lanIp = $lanIp.Trim()
|
||
if ([string]::IsNullOrWhiteSpace($lanIp)) {
|
||
Write-Err "IP 不能为空"
|
||
}
|
||
$script:DeployIP = $lanIp
|
||
Write-Warn "请确保防火墙已放行 3001、3002、3003 端口"
|
||
}
|
||
default {
|
||
Write-Err "无效选择"
|
||
}
|
||
}
|
||
}
|
||
|
||
# ==================== 生成配置文件 ====================
|
||
function Setup-EnvFiles {
|
||
Write-Host ""
|
||
Write-Step "📝 配置环境文件..."
|
||
|
||
# 复制 .env.tools 和 .env.ASG
|
||
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"
|
||
}
|
||
|
||
# 配置 .env
|
||
if (-not (Test-Path ".env")) {
|
||
Copy-Item ".env.sample" ".env"
|
||
}
|
||
|
||
$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"
|
||
|
||
# 保持文件末尾换行
|
||
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))"
|
||
}
|
||
|
||
# ==================== 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
|
||
}
|
||
}
|
||
|
||
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"
|
||
exit 1
|
||
}
|
||
|
||
# 优先 winget,其次 Chocolatey
|
||
if (Get-Command winget -ErrorAction SilentlyContinue) {
|
||
Write-Step "正在通过 winget 安装 Docker Desktop for Windows..."
|
||
winget install --id Docker.DockerDesktop -e --accept-source-agreements --accept-package-agreements
|
||
if ($LASTEXITCODE -eq 0) {
|
||
Write-Ok "安装已启动。安装完成后请重启终端,然后重新运行本脚本。"
|
||
exit 0
|
||
}
|
||
}
|
||
if (Get-Command choco -ErrorAction SilentlyContinue) {
|
||
Write-Step "正在通过 Chocolatey 安装 Docker Desktop for Windows..."
|
||
choco install docker-desktop -y
|
||
if ($LASTEXITCODE -eq 0) {
|
||
Write-Ok "安装已启动。安装完成后请重启终端,然后重新运行本脚本。"
|
||
exit 0
|
||
}
|
||
}
|
||
|
||
Write-Warn "未找到 winget 或 Chocolatey,请手动安装 Docker Desktop。"
|
||
Write-Host "下载地址: $DockerDesktopUrl"
|
||
exit 1
|
||
}
|
||
|
||
function Test-Docker {
|
||
Write-Host ""
|
||
Write-Host "================================"
|
||
Write-Host " Docker 检查"
|
||
Write-Host "================================"
|
||
Write-Host ""
|
||
|
||
if (Test-DockerInstalled) {
|
||
Write-Ok "Docker 已安装"
|
||
$v = docker --version 2>$null
|
||
if ($v) { Write-Host " $v" }
|
||
return
|
||
}
|
||
|
||
Write-Warn "未检测到 Docker Desktop for Windows"
|
||
Write-Host ""
|
||
Write-Host "请选择:"
|
||
Write-Host " [1] 手动安装 - 打开下载页面,退出脚本"
|
||
Write-Host " [2] 自动安装 - 通过 winget 或 Chocolatey 安装(需管理员权限)"
|
||
$choice = Read-Host "请选择 [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
|
||
}
|
||
exit 1
|
||
}
|
||
"2" {
|
||
Install-DockerDesktop
|
||
}
|
||
default {
|
||
Write-Err "无效选择"
|
||
}
|
||
}
|
||
}
|
||
|
||
# ==================== 启动服务 ====================
|
||
function Start-Services {
|
||
Write-Host ""
|
||
Write-Step "🚀 启动 EasyAI 服务..."
|
||
|
||
$hasComposeV2 = $false
|
||
try {
|
||
$null = docker compose version 2>&1
|
||
$hasComposeV2 = $true
|
||
} catch { }
|
||
|
||
if ($hasComposeV2) {
|
||
docker compose pull
|
||
if ($LASTEXITCODE -ne 0) { Write-Err "docker compose pull 失败" }
|
||
docker compose up -d
|
||
if ($LASTEXITCODE -ne 0) { Write-Err "docker compose up 失败" }
|
||
} else {
|
||
docker-compose pull
|
||
if ($LASTEXITCODE -ne 0) { Write-Err "docker-compose pull 失败" }
|
||
docker-compose up -d
|
||
if ($LASTEXITCODE -ne 0) { Write-Err "docker-compose up 失败" }
|
||
}
|
||
Write-Host "🎉 EasyAI 应用启动成功"
|
||
}
|
||
|
||
# ==================== 主流程 ====================
|
||
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]"
|
||
if ($reconfigure -notmatch '^[yY]') {
|
||
Write-Step "使用现有配置继续..."
|
||
# 从现有 .env 读取 IP(用于最后输出)
|
||
$line = Get-Content ".env" | Where-Object { $_ -match '^NUXT_PUBLIC_BASE_APIURL=http://([^:]+):3001' } | Select-Object -First 1
|
||
if ($line -match 'http://([^:]+):3001') { $script:DeployIP = $Matches[1] }
|
||
$script:DeployModeSkip = $true
|
||
}
|
||
}
|
||
|
||
if (-not $script:DeployModeSkip) {
|
||
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 ($script:DeployDryRun) {
|
||
Write-Host ""
|
||
Write-Warn "dry-run 模式:跳过 Docker 安装和服务启动"
|
||
Write-Host " 配置文件已生成,可直接运行 .\start.ps1 完成部署"
|
||
} else {
|
||
Test-Docker
|
||
Start-Services
|
||
}
|
||
|
||
Write-Host ""
|
||
Write-Host "================================"
|
||
Write-Host " 部署完成"
|
||
Write-Host "================================"
|
||
$accessIp = if ($script:DeployIP) { $script:DeployIP } else { "127.0.0.1" }
|
||
Write-Host "访问地址: http://${accessIp}:3010"
|
||
Write-Host ""
|
||
}
|
||
|
||
Main
|