easyai/start.ps1
wangbo b7a11abe9f
Some checks are pending
Test start.ps1 (Windows) / test-windows (push) Waiting to run
feat: 新增 Windows 一键部署脚本 start.ps1
- 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
2026-03-10 21:59:12 +08:00

265 lines
9.2 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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