254 lines
8.7 KiB
PowerShell
254 lines
8.7 KiB
PowerShell
# Docker Registry 部署脚本
|
||
# 使用容器镜像服务(如阿里云 ACR)进行部署
|
||
# 完全避免上传大文件,部署更快
|
||
|
||
[CmdletBinding()]
|
||
param(
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$Server,
|
||
|
||
[string]$Username = "root",
|
||
[string]$Password,
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$RegistryHost,
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$RegistryNamespace,
|
||
|
||
[string]$RegistryImage = "youyidian-3d-material",
|
||
|
||
[string]$RegistryUser = "",
|
||
|
||
[string]$RegistryPassword = "",
|
||
|
||
[string]$Tag = "latest",
|
||
[string]$ImageName = "youyidian-3d-material",
|
||
[string]$Dockerfile = "Dockerfile",
|
||
[string]$Context = ".",
|
||
[string]$RemotePath = "/root/deploy",
|
||
[string]$ComposeFile = "docker-compose.prod.yml",
|
||
|
||
[switch]$SkipCheck,
|
||
|
||
[string]$CacheTag = "previous",
|
||
[switch]$DisableCache,
|
||
|
||
# 可选:如果需要严格校验 SSH 主机指纹,可在此传入
|
||
[string]$SshHostKeyFingerprint = ""
|
||
)
|
||
|
||
Set-StrictMode -Version Latest
|
||
$ErrorActionPreference = "Stop"
|
||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||
|
||
function Get-PlinkPath {
|
||
$candidates = @(
|
||
"C:\Program Files\PuTTY\plink.exe",
|
||
"C:\Program Files (x86)\PuTTY\plink.exe"
|
||
)
|
||
foreach ($path in $candidates) {
|
||
if (Test-Path $path) { return $path }
|
||
}
|
||
$cmd = Get-Command plink.exe -ErrorAction SilentlyContinue
|
||
if ($cmd) { return $cmd.Source }
|
||
throw "未找到 plink.exe,请安装 PuTTY 或将其加入 PATH。"
|
||
}
|
||
|
||
function Invoke-RemoteCommand {
|
||
param(
|
||
[Parameter(Mandatory = $true)][string]$Server,
|
||
[Parameter(Mandatory = $true)][string]$Username,
|
||
[Parameter(Mandatory = $true)][string]$Command,
|
||
[string]$Password,
|
||
[string]$HostKeyFingerprint = "",
|
||
[switch]$IgnoreError
|
||
)
|
||
|
||
$plinkPath = Get-PlinkPath
|
||
$plinkArgs = @("-batch")
|
||
if ($HostKeyFingerprint) {
|
||
$plinkArgs += @("-hostkey", $HostKeyFingerprint)
|
||
}
|
||
if ($Password) {
|
||
$plinkArgs += @("-pw", $Password)
|
||
}
|
||
$plinkArgs += "$Username@$Server"
|
||
$plinkArgs += $Command
|
||
|
||
$output = & $plinkPath $plinkArgs
|
||
$exitCode = $LASTEXITCODE
|
||
if (-not $IgnoreError -and $exitCode -ne 0) {
|
||
throw "远程命令执行失败 (exit $exitCode): $Command`n$output"
|
||
}
|
||
return [pscustomobject]@{
|
||
ExitCode = $exitCode
|
||
Output = $output
|
||
}
|
||
}
|
||
|
||
# 启用 BuildKit
|
||
if (-not $env:DOCKER_BUILDKIT) {
|
||
$env:DOCKER_BUILDKIT = "1"
|
||
Write-Host "已启用 BuildKit" -ForegroundColor Green
|
||
}
|
||
|
||
$funTips = @{
|
||
Warmup = @(
|
||
"[=^_^=] 部署小助手上线,先把工具擦亮~",
|
||
"(ง •̀_•́)ง 例行热身,马上开始忙活。"
|
||
)
|
||
Build = @(
|
||
"( ̄▽ ̄)ゞ 正在拼装镜像零件,喝口水继续等。",
|
||
"♪(^∀^●) 构建火力全开,稍等就好。"
|
||
)
|
||
Push = @(
|
||
"(~ ̄▽ ̄)~ 把新鲜镜像塞进 Registry 里~",
|
||
"(=^・ω・^=) 正在排队上传,请耐心排号。"
|
||
)
|
||
Remote = @(
|
||
"( •̀ ω •́ )✧ 正在远程唤醒服务器,不要慌~",
|
||
"(ノ>ω<)ノ 让服务器也跟上节奏!"
|
||
)
|
||
Done = @(
|
||
"✧(≖ ◡ ≖✿) 大功告成,去后台转一圈吧!",
|
||
"(づ ̄ 3 ̄)づ 部署完成,撒花~"
|
||
)
|
||
}
|
||
|
||
function Show-FunTip {
|
||
param([string]$Stage)
|
||
if ($funTips.ContainsKey($Stage)) {
|
||
$msg = $funTips[$Stage] | Get-Random
|
||
Write-Host " $msg" -ForegroundColor DarkCyan
|
||
}
|
||
}
|
||
|
||
$projectRoot = Resolve-Path "$PSScriptRoot"
|
||
Push-Location $projectRoot
|
||
try {
|
||
# 构建镜像
|
||
$fullImageName = "${RegistryHost}/${RegistryNamespace}/${RegistryImage}:${Tag}"
|
||
Write-Host "===> 构建镜像: $fullImageName" -ForegroundColor Cyan
|
||
Show-FunTip -Stage "Warmup"
|
||
|
||
$buildArgs = @(
|
||
"--build-arg", "DATABASE_URL=postgresql://placeholder:placeholder@localhost:5432/placeholder",
|
||
"--build-arg", "NEXTAUTH_URL=http://45.249.208.118:4320",
|
||
"--build-arg", "APP_PUBLIC_URL=http://45.249.208.118:4320",
|
||
"--build-arg", "BUILDKIT_INLINE_CACHE=1"
|
||
)
|
||
$cacheImage = $null
|
||
if (-not $DisableCache -and $CacheTag) {
|
||
$cacheImage = "${RegistryHost}/${RegistryNamespace}/${RegistryImage}:${CacheTag}"
|
||
Write-Host "尝试拉取缓存镜像: $cacheImage" -ForegroundColor Gray
|
||
docker pull $cacheImage | Out-Null
|
||
if ($LASTEXITCODE -eq 0) {
|
||
$buildArgs += @("--cache-from", $cacheImage)
|
||
Write-Host "✓ 已启用缓存" -ForegroundColor Green
|
||
} else {
|
||
Write-Host "缓存镜像不可用,继续无缓存构建" -ForegroundColor Yellow
|
||
$cacheImage = $null
|
||
}
|
||
}
|
||
|
||
if (-not $SkipCheck) {
|
||
Write-Host "===> 运行检查" -ForegroundColor Cyan
|
||
npm run check
|
||
}
|
||
|
||
Write-Host "正在构建镜像..." -ForegroundColor Yellow
|
||
Write-Host " [友情提示] 这个阶段日志可能会暂时安静,1-2 分钟都属正常~" -ForegroundColor DarkYellow
|
||
Show-FunTip -Stage "Build"
|
||
$buildStartTime = Get-Date
|
||
docker build --progress=plain -t $fullImageName -f $Dockerfile $buildArgs $Context
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "构建失败"
|
||
}
|
||
$buildDuration = ((Get-Date) - $buildStartTime).TotalMinutes
|
||
Write-Host "构建完成,耗时: $([Math]::Round($buildDuration, 1)) 分钟" -ForegroundColor Green
|
||
|
||
# 登录 Registry(如果需要认证)
|
||
if ($RegistryUser -and $RegistryPassword) {
|
||
Write-Host "===> 登录容器镜像服务" -ForegroundColor Cyan
|
||
echo $RegistryPassword | docker login $RegistryHost -u $RegistryUser --password-stdin
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "登录 Registry 失败"
|
||
}
|
||
Write-Host "✓ 登录成功" -ForegroundColor Green
|
||
} else {
|
||
Write-Host "跳过登录(未配置认证)" -ForegroundColor Gray
|
||
}
|
||
|
||
# 推送镜像
|
||
Write-Host "===> 推送镜像到 Registry" -ForegroundColor Cyan
|
||
Show-FunTip -Stage "Push"
|
||
Write-Host "镜像: $fullImageName" -ForegroundColor Gray
|
||
docker push $fullImageName
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "推送失败"
|
||
}
|
||
Write-Host "✓ 推送成功" -ForegroundColor Green
|
||
|
||
if ($cacheImage) {
|
||
Write-Host "同步缓存标签: $CacheTag" -ForegroundColor Gray
|
||
docker tag $fullImageName $cacheImage
|
||
docker push $cacheImage | Out-Null
|
||
if ($LASTEXITCODE -eq 0) {
|
||
Write-Host "✓ 缓存标签已更新 ($cacheImage)" -ForegroundColor Green
|
||
} else {
|
||
Write-Host "缓存标签推送失败(可忽略)" -ForegroundColor Yellow
|
||
}
|
||
}
|
||
|
||
# 服务器端操作
|
||
Write-Host "===> 在服务器上拉取并部署" -ForegroundColor Cyan
|
||
Write-Host " [友情提示] 远程执行可能需要十几秒,看到日志停顿也别慌~" -ForegroundColor DarkYellow
|
||
Show-FunTip -Stage "Remote"
|
||
|
||
$remoteCommandParams = @{
|
||
Server = $Server
|
||
Username = $Username
|
||
Password = $Password
|
||
HostKeyFingerprint = $SshHostKeyFingerprint
|
||
}
|
||
|
||
# 在服务器上登录 Registry(如果需要认证)
|
||
if ($RegistryUser -and $RegistryPassword) {
|
||
Write-Host "在服务器上登录 Registry..." -ForegroundColor Gray
|
||
Invoke-RemoteCommand @remoteCommandParams -Command "echo '$RegistryPassword' | docker login $RegistryHost -u $RegistryUser --password-stdin" -IgnoreError:$false | Out-Null
|
||
} else {
|
||
Write-Host "跳过服务器登录(未配置认证)" -ForegroundColor Gray
|
||
}
|
||
|
||
# 拉取镜像
|
||
Write-Host "在服务器上拉取镜像..." -ForegroundColor Gray
|
||
Invoke-RemoteCommand @remoteCommandParams -Command "docker pull ${fullImageName}" | Out-Null
|
||
Write-Host "✓ 镜像拉取成功" -ForegroundColor Green
|
||
|
||
# 更新 docker-compose.yml
|
||
Write-Host "更新 docker-compose.yml..." -ForegroundColor Gray
|
||
$updateImageCmd = "cd ${RemotePath} && sed -i 's|image:.*youyidian-3d-material.*|image: ${fullImageName}|g' ${ComposeFile}"
|
||
Invoke-RemoteCommand @remoteCommandParams -Command $updateImageCmd | Out-Null
|
||
|
||
# 部署
|
||
Write-Host "部署应用..." -ForegroundColor Gray
|
||
Invoke-RemoteCommand @remoteCommandParams -Command "cd ${RemotePath} && docker compose -f ${ComposeFile} up -d" | Out-Null
|
||
Write-Host "✓ 部署成功" -ForegroundColor Green
|
||
|
||
Write-Host ""
|
||
Write-Host "===> 部署完成" -ForegroundColor Green
|
||
Write-Host "镜像: $fullImageName" -ForegroundColor Gray
|
||
Write-Host "服务器: $Server" -ForegroundColor Gray
|
||
Show-FunTip -Stage "Done"
|
||
}
|
||
catch {
|
||
Write-Host "✗ 部署失败: $_" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
finally {
|
||
Pop-Location
|
||
}
|
||
|