提交 8741f56e authored 作者: 陈泽健's avatar 陈泽健

feat(script): 添加远程更新脚本功能

- 新增远程容器更新脚本(remote_container_update_win.ps1),支持Windows环境执行
- 实现自动连接指定服务器功能,支持预设服务器列表和手动输入模式
- 添加容器类型选择功能,支持ujava、uemqx、uredis、upython、unacos、unginx六种容器
- 集成文件传输功能,自动传输镜像文件和部署脚本到目标服务器
- 实现平台类型自动检测,区分新统一平台和传统平台
- 添加架构校验功能,确保目标服务器为x86架构
- 集成版本校验机制,避免重复更新相同版本的容器
- 支持EMQX和Nginx配置文件同步功能
- 实现部署完成后的自动清理机制
- 添加详细的日志记录和审计功能,便于问题排查
- 更新需求文档,详细描述远程程序更新脚本的功能需求
- 添加备份机制,在更新前自动备份原有数据和文件
- 实现根据不同系统类型和更新类型的差异化更新策略
上级 019b8201
<#
功能:Windows 端远程更新入口脚本(实现 PRD 3.1、3.2)
依赖:PuTTY 的 plink.exe / pscp.exe
#>
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function Write-Info($msg) { Write-Host "[INFO] $msg" -ForegroundColor Cyan }
function Write-Warn($msg) { Write-Host "[WARN] $msg" -ForegroundColor Yellow }
function Write-Err ($msg) { Write-Host "[ERROR] $msg" -ForegroundColor Red }
function Read-NonEmpty([string]$prompt, [string]$default = "") {
while ($true) {
if ($default) {
$v = Read-Host "$prompt (默认: $default)"
if ([string]::IsNullOrWhiteSpace($v)) { return $default }
return $v
} else {
$v = Read-Host $prompt
if (-not [string]::IsNullOrWhiteSpace($v)) { return $v }
}
}
}
function Read-Choice([string]$prompt, [string[]]$choices) {
Write-Host $prompt
for ($i=0; $i -lt $choices.Count; $i++) {
Write-Host " [$($i+1)] $($choices[$i])"
}
while ($true) {
$sel = Read-Host "请输入序号(1-$($choices.Count))"
if ($sel -match '^\d+$') {
$idx = [int]$sel - 1
if ($idx -ge 0 -and $idx -lt $choices.Count) { return $choices[$idx] }
}
Write-Warn "输入无效,请重新输入。"
}
}
function Find-Tool([string]$name) {
$cmd = Get-Command $name -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
$local = Join-Path $PSScriptRoot $name
if (Test-Path $local) { return $local }
return $null
}
function Require-PuttyTools {
$plink = Find-Tool "plink.exe"
$pscp = Find-Tool "pscp.exe"
if (-not $plink -or -not $pscp) {
Write-Err "未找到 plink.exe/pscp.exe。请安装 PuTTY 并加入 PATH,或将 plink.exe/pscp.exe 放在脚本同目录。"
throw "Missing PuTTY tools"
}
return @{ plink=$plink; pscp=$pscp }
}
function Invoke-PlinkCommand {
param(
[Parameter(Mandatory=$true)][string]$PlinkPath,
[Parameter(Mandatory=$true)][string]$HostName,
[Parameter(Mandatory=$true)][int]$Port,
[Parameter(Mandatory=$true)][string]$User,
[Parameter(Mandatory=$true)][string]$Password,
[Parameter(Mandatory=$true)][string]$Command
)
$args = @(
"-batch",
"-no-antispoof",
"-ssh",
"-P", "$Port",
"-l", $User,
"-pw", $Password,
$HostName,
$Command
)
Write-Info "远端执行: $Command"
$p = Start-Process -FilePath $PlinkPath -ArgumentList $args -NoNewWindow -Wait -PassThru
if ($p.ExitCode -ne 0) {
throw "plink 执行失败,ExitCode=$($p.ExitCode)。命令:$Command"
}
}
function Invoke-PscpUpload {
param(
[Parameter(Mandatory=$true)][string]$PscpPath,
[Parameter(Mandatory=$true)][string]$HostName,
[Parameter(Mandatory=$true)][int]$Port,
[Parameter(Mandatory=$true)][string]$User,
[Parameter(Mandatory=$true)][string]$Password,
[Parameter(Mandatory=$true)][string[]]$LocalFiles,
[Parameter(Mandatory=$true)][string]$RemoteDir
)
foreach ($lf in $LocalFiles) {
if (-not (Test-Path $lf)) {
throw "本地文件不存在:$lf"
}
}
if ($RemoteDir.EndsWith("/") -eq $false) { $RemoteDir = "$RemoteDir/" }
$remoteTarget = ("{0}@{1}:{2}" -f $User, $HostName, $RemoteDir)
foreach ($lf in $LocalFiles) {
$args = @(
"-batch",
"-P", "$Port",
"-pw", $Password,
$lf,
$remoteTarget
)
Write-Info "上传: $(Split-Path $lf -Leaf) -> $RemoteDir"
$p = Start-Process -FilePath $PscpPath -ArgumentList $args -NoNewWindow -Wait -PassThru
if ($p.ExitCode -ne 0) {
throw "pscp 上传失败,ExitCode=$($p.ExitCode)。文件:$lf"
}
}
}
# -------------------- 3.1 采集参数 --------------------
Write-Host "=== 远程程序更新(Windows 端入口)===" -ForegroundColor Green
$serverIp = Read-NonEmpty "请输入服务器IP"
$sshPortStr = Read-NonEmpty "请输入SSH端口" "22"
$sshPort = [int]$sshPortStr
$username = Read-NonEmpty "请输入用户名" "root"
$securePwd = Read-Host "请输入密码" -AsSecureString
$plainPwd = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePwd))
$remoteDir = Read-NonEmpty "请输入远端文件存放路径" "/home/Update/"
$platformType = Read-Choice "请选择平台类型:" @("新统一平台", "传统平台")
$systemType = Read-Choice "请选择更新系统类型:" @("会议预定系统", "运维集控系统", "讯飞转录系统")
$updateType = Read-Choice "请选择更新类型:" @("前端更新", "后端更新", "全量更新")
Write-Info "参数确认:IP=$serverIp Port=$sshPort User=$username RemoteDir=$remoteDir 平台=$platformType 系统=$systemType 更新=$updateType"
# -------------------- 工具检测 --------------------
$tools = Require-PuttyTools
$plink = $tools.plink
$pscp = $tools.pscp
# -------------------- 连接测试(3.1) --------------------
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command "echo Connected; uname -a"
Write-Info "SSH 连接测试成功。"
# 1) 远端创建目录 + 检查 unzip
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command "mkdir -p '$remoteDir'; command -v unzip >/dev/null 2>&1 || (echo '缺少 unzip,请安装:yum install -y unzip 或 apt-get install -y unzip' && exit 2)"
# ====== 3.2 本地准备:找到 zip 包 + program_update.sh ======
# 约定:zip 压缩包与本 remote_program_update.ps1 同级目录
$zipFiles = @(
Get-ChildItem -Path $PSScriptRoot -Filter *.zip -File |
Select-Object -ExpandProperty FullName
)
if ($zipFiles.Length -eq 0) {
throw "未找到 zip 压缩包:请将更新包(*.zip)放到脚本同级目录:$PSScriptRoot"
}
if ($zipFiles.Length -gt 1) {
Write-Warn "检测到多个 zip,默认使用第一个:$($zipFiles[0])"
}
$zipPath = $zipFiles[0]
$programUpdateSh = Join-Path $PSScriptRoot "program_update.sh"
if (-not (Test-Path $programUpdateSh)) {
throw "未找到 program_update.sh:$programUpdateSh(请放在脚本同级目录)"
}
Write-Info "将上传更新包:$zipPath"
Write-Info "将上传更新脚本:$programUpdateSh"
# ====== 3.2 本地准备结束 ======
# 2) 上传 zip + program_update.sh
Invoke-PscpUpload -PscpPath $pscp -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -LocalFiles @($zipPath, $programUpdateSh) -RemoteDir $remoteDir
# 3) 远端解压(解压到 remoteDir)
$zipName = Split-Path $zipPath -Leaf
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command "cd '$remoteDir' && unzip -o '$zipName' -d '$remoteDir'"
# 可选执行入口
if ($runNow -eq "是") {
$cmd = "cd '$remoteDir' && chmod +x ./program_update.sh && ./program_update.sh --platform '$platformType' --system '$systemType' --update '$updateType' --workdir '$remoteDir'"
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command $cmd
}
\ No newline at end of file
#Requires -Version 5.1
# ================================
# remote_update.ps1 - Remote Container Update Script (PowerShell Version)
# ================================
# Features:
# 1. Verify target server architecture is x86
# 2. Transfer image files and deployment scripts to remote directory
# 3. Stop old containers on remote server
# 4. Auto-detect target server platform type (new/traditional)
# 5. Auto-increment container numbers and execute remote deployment script
# 6. Clean up remote image files after deployment
#
# Dependencies:
# - plink.exe (PuTTY Link) - SSH command execution
# - pscp.exe (PuTTY SCP) - File transfer
# Please ensure these tools are installed and added to system PATH
# ================================
param(
[string]$RemoteDir = "/home/containerUpdate"
)
# Strict mode
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# ================================
# Global Configuration
# ================================
$SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path
$REMOTE_ARCH_ALLOW_REGEX = '^(x86_64|amd64|i386|i686)$'
$SSH_TIMEOUT = 30
# Preset server list
$ServerList = @{
"1" = @{
IP = "192.168.5.48"
User = "root"
Pass = "Ubains@123"
Desc = "Standard Reservation Server"
}
"2" = @{
IP = "192.168.5.67"
User = "root"
Pass = "Ubains@123"
Desc = "Oman Project Server"
}
"3" = @{
IP = "192.168.5.47"
User = "root"
Pass = "Ubains@1234"
Desc = "Test Release Server"
}
}
# Container and image mapping configuration
$ContainerOptions = @("ujava", "uemqx", "uredis", "upython", "unacos", "unginx")
$ContainerImage = @{
"ujava" = "java1.8.0_472.tar.gz"
"uemqx" = "uemqx5.8.4.tar.gz"
"uredis" = "redis8.2.2.tar.gz"
"upython" = "python_v15.tar.gz"
"unacos" = "nacos-server-v2.5.2.tar.gz"
"unginx" = "nginx-1.29.3.tar.gz"
}
# Container Docker image names (for version verification)
$ContainerDockerImage = @{
"ujava" = "139.9.60.86:5000/ujava:v6"
"uemqx" = "139.9.60.86:5000/uemqx:v2"
"uredis" = "139.9.60.86:5000/redis:v3"
"upython_new" = "139.9.60.86:5000/upython:v15"
"upython_old" = "139.9.60.86:5000/upython:v14"
"unacos" = "nacos-server:v2.5.2"
"unginx" = "nginx:1.29.3"
}
# Local directory configuration
$LOCAL_EMQX_DIR = "/data/middleware/emqx"
$LOCAL_PYTHON_DIR = "/data/services/api/python-cmdb"
$LOCAL_NGINX_DIR = "/data/middleware/nginx"
# Deployment script name
$DEPLOY_SCRIPT = "container_update.sh"
# ================================
# Log Function
# ================================
function Write-Log {
param(
[Parameter(Mandatory=$true)]
[ValidateSet("INFO", "WARN", "ERROR")]
[string]$Level,
[Parameter(Mandatory=$true)]
[string]$Message
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$colorMap = @{
"INFO" = "Green"
"WARN" = "Yellow"
"ERROR" = "Red"
}
$color = $colorMap[$Level]
Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color
}
# ================================
# Check Dependencies
# ================================
function Test-Dependencies {
Write-Log -Level "INFO" -Message "Checking dependencies..."
$tools = @("plink", "pscp")
$missingTools = @()
foreach ($tool in $tools) {
try {
$null = Get-Command $tool -ErrorAction Stop
Write-Log -Level "INFO" -Message " [OK] $tool installed"
}
catch {
$missingTools += $tool
Write-Log -Level "ERROR" -Message " [FAIL] $tool not installed"
}
}
if ($missingTools.Count -gt 0) {
Write-Log -Level "ERROR" -Message "Missing tools: $($missingTools -join ', ')"
Write-Log -Level "ERROR" -Message "Please install PuTTY toolkit and ensure plink.exe and pscp.exe are in system PATH"
Write-Log -Level "ERROR" -Message "Download: https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html"
return $false
}
Write-Log -Level "INFO" -Message "[OK] All dependencies ready"
return $true
}
# ================================
# SSH Execute Remote Command
# ================================
function Invoke-SSHCommand {
param(
[string]$HostName,
[string]$User,
[string]$Pass,
[int]$Port = 22,
[string]$Command
)
$plinkArgs = @(
"-ssh",
"-P", $Port,
"-l", $User,
"-pw", $Pass,
"-batch",
$HostName,
$Command
)
$result = & plink @plinkArgs 2>&1
$exitCode = $LASTEXITCODE
return @{
Output = ($result -join "`n")
ExitCode = $exitCode
}
}
# ================================
# SCP File Transfer
# ================================
function Send-SCPFile {
param(
[string]$LocalPath,
[string]$RemoteHost,
[string]$RemoteUser,
[string]$RemotePass,
[int]$RemotePort = 22,
[string]$RemotePath
)
$pscpArgs = @(
"-P", $RemotePort,
"-l", $RemoteUser,
"-pw", $RemotePass,
"-batch",
$LocalPath,
"${RemoteUser}@${RemoteHost}:${RemotePath}"
)
$result = & pscp @pscpArgs 2>&1
$exitCode = $LASTEXITCODE
return @{
Output = ($result -join "`n")
ExitCode = $exitCode
}
}
# ================================
# SCP Directory Transfer (Recursive)
# ================================
function Send-SCPDirectory {
param(
[string]$LocalPath,
[string]$RemoteHost,
[string]$RemoteUser,
[string]$RemotePass,
[int]$RemotePort = 22,
[string]$RemotePath
)
$pscpArgs = @(
"-P", $RemotePort,
"-l", $RemoteUser,
"-pw", $RemotePass,
"-batch",
"-r",
$LocalPath,
"${RemoteUser}@${RemoteHost}:${RemotePath}"
)
$result = & pscp @pscpArgs 2>&1
$exitCode = $LASTEXITCODE
return @{
Output = ($result -join "`n")
ExitCode = $exitCode
}
}
# ================================
# Select Container
# ================================
function Select-Container {
Write-Log -Level "INFO" -Message "Available containers:"
for ($i = 0; $i -lt $ContainerOptions.Count; $i++) {
$idx = $i + 1
Write-Host " [$idx] $($ContainerOptions[$i])"
}
$containerKey = Read-Host "Enter container number"
if ($containerKey -notmatch '^[1-6]$') {
Write-Log -Level "ERROR" -Message "Invalid container number, please enter 1-6"
return $null
}
$selectedIndex = [int]$containerKey - 1
$selectedContainer = $ContainerOptions[$selectedIndex]
Write-Log -Level "INFO" -Message "Selected container: $selectedContainer"
return $selectedContainer
}
# ================================
# Select Server
# ================================
function Select-Server {
Write-Log -Level "INFO" -Message "Available target servers:"
foreach ($key in ($ServerList.Keys | Sort-Object)) {
$server = $ServerList[$key]
Write-Host " [$key] $($server.Desc) ($($server.IP) $($server.User))"
}
Write-Host " [0] Manual input server info"
$serverKey = Read-Host "Enter server number"
if ($serverKey -eq "0") {
Write-Log -Level "INFO" -Message "Entering manual input mode"
$remoteHost = Read-Host "Enter target server IP address"
if ([string]::IsNullOrEmpty($remoteHost)) {
Write-Log -Level "ERROR" -Message "Server IP address cannot be empty"
return $null
}
$sshPortInput = Read-Host "Enter SSH port [default 22]"
if ([string]::IsNullOrEmpty($sshPortInput)) {
$sshPort = 22
} else {
$sshPort = [int]$sshPortInput
}
$remoteUserInput = Read-Host "Enter username [default root]"
if ([string]::IsNullOrEmpty($remoteUserInput)) {
$remoteUser = "root"
} else {
$remoteUser = $remoteUserInput
}
$remotePassSecure = Read-Host "Enter password" -AsSecureString
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($remotePassSecure)
$remotePass = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
if ([string]::IsNullOrEmpty($remotePass)) {
Write-Log -Level "ERROR" -Message "Password cannot be empty"
return $null
}
Write-Log -Level "INFO" -Message "Configured target server: ${remoteUser}@${remoteHost}:${sshPort}"
return @{
IP = $remoteHost
User = $remoteUser
Pass = $remotePass
Port = $sshPort
Desc = "Manual input server"
}
}
elseif ($ServerList.ContainsKey($serverKey)) {
$server = $ServerList[$serverKey].Clone()
$server.Port = 22
Write-Log -Level "INFO" -Message "Selected $($server.Desc) ($($server.IP))"
return $server
}
else {
Write-Log -Level "ERROR" -Message "Number $serverKey does not exist, please re-run script"
return $null
}
}
# ================================
# Test SSH Connection
# ================================
function Test-SSHConnection {
param(
[hashtable]$Server
)
Write-Log -Level "INFO" -Message "Testing SSH connection..."
$result = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "echo CONNECTION_OK"
if (($result.ExitCode -ne 0) -or ($result.Output -notmatch "CONNECTION_OK")) {
Write-Log -Level "ERROR" -Message "SSH connection failed!"
Write-Log -Level "ERROR" -Message "Output: $($result.Output)"
Write-Log -Level "ERROR" -Message "Please check: 1) IP address 2) Port 3) Password 4) Network connectivity"
return $false
}
Write-Log -Level "INFO" -Message "SSH connection test passed"
return $true
}
# ================================
# Verify Remote Architecture
# ================================
function Test-RemoteArchitecture {
param(
[hashtable]$Server
)
Write-Log -Level "INFO" -Message "Verifying remote architecture ($($Server.IP))"
$result = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "uname -m"
if ($result.ExitCode -ne 0) {
Write-Log -Level "ERROR" -Message "Cannot get remote architecture, please check network or permissions"
Write-Log -Level "ERROR" -Message "SSH output: $($result.Output)"
return $null
}
$outputLines = $result.Output -split "`n" | Where-Object { $_ -match '\S' }
$arch = ($outputLines | Select-Object -Last 1).Trim()
if ($arch -notmatch $REMOTE_ARCH_ALLOW_REGEX) {
Write-Log -Level "ERROR" -Message "Remote architecture is $arch, not x86, operation terminated"
return $null
}
Write-Log -Level "INFO" -Message "Remote architecture $arch verified"
return $arch
}
# ================================
# Auto-detect Platform Type
# ================================
function Get-PlatformType {
param(
[hashtable]$Server
)
Write-Log -Level "INFO" -Message "Auto-detecting target server platform type..."
$result = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "[ -d /data/services ] && echo 'NEW_PLATFORM' || echo 'OLD_PLATFORM'"
$outputLines = $result.Output -split "`n" | Where-Object { $_ -match '\S' }
$platformCheck = ($outputLines | Select-Object -Last 1).Trim()
if ($platformCheck -eq "NEW_PLATFORM") {
Write-Log -Level "INFO" -Message "[OK] Detected /data/services directory, identified as new unified platform"
return @{
Type = "new"
Flag = "--new-platform"
}
}
elseif ($platformCheck -eq "OLD_PLATFORM") {
Write-Log -Level "INFO" -Message "[OK] No /data/services directory detected, identified as traditional platform"
return @{
Type = "old"
Flag = ""
}
}
else {
Write-Log -Level "WARN" -Message "[WARN] Auto-detection failed, switching to manual confirmation mode"
$platformInput = Read-Host "Is target server new unified platform? (y/n)"
switch ($platformInput.ToLower()) {
"y" {
Write-Log -Level "INFO" -Message "Marked as new unified platform"
return @{
Type = "new"
Flag = "--new-platform"
}
}
"n" {
Write-Log -Level "INFO" -Message "Marked as traditional platform"
return @{
Type = "old"
Flag = ""
}
}
default {
Write-Log -Level "ERROR" -Message "Invalid input, please enter y or n"
return $null
}
}
}
}
# ================================
# Verify Remote Image Version
# ================================
function Test-RemoteImageVersion {
param(
[hashtable]$Server,
[string]$ContainerPrefix,
[string]$PlatformType
)
# Get expected image name based on container type and platform type
if ($ContainerPrefix -eq "upython") {
if ($PlatformType -eq "new") {
$expectedImage = $ContainerDockerImage["upython_new"]
} else {
$expectedImage = $ContainerDockerImage["upython_old"]
}
} else {
$expectedImage = $ContainerDockerImage[$ContainerPrefix]
}
if ([string]::IsNullOrEmpty($expectedImage)) {
Write-Log -Level "WARN" -Message "No image version configured for container $ContainerPrefix, skipping version check"
return $true
}
Write-Log -Level "INFO" -Message "Checking remote server image version..."
$result = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "docker images --format '{{.Repository}}:{{.Tag}}'"
$remoteImages = $result.Output
$escapedImage = [regex]::Escape($expectedImage)
if ($remoteImages -match $escapedImage) {
Write-Log -Level "WARN" -Message "[WARN] Target image already exists on remote server: $expectedImage"
Write-Log -Level "WARN" -Message "[WARN] Target server may have already been updated"
# Check if related container is running
$containerCheckResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "docker ps --format '{{.Names}}' | grep -E '^${ContainerPrefix}([0-9]+)?$' | head -n1"
$runningContainer = $containerCheckResult.Output.Trim()
if (-not [string]::IsNullOrEmpty($runningContainer)) {
# Get image used by running container
$imageCheckResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "docker inspect --format '{{.Config.Image}}' '$runningContainer'"
$containerImage = $imageCheckResult.Output.Trim()
if ($containerImage -eq $expectedImage) {
Write-Log -Level "WARN" -Message "[WARN] Container $runningContainer is using target image $expectedImage"
Write-Log -Level "WARN" -Message "[WARN] Target server container is already latest version, no update needed"
$continueInput = Read-Host "Continue update anyway? (y/n)"
switch ($continueInput.ToLower()) {
"y" {
Write-Log -Level "INFO" -Message "User chose to continue update"
return $true
}
"n" {
Write-Log -Level "INFO" -Message "User chose to skip update"
return $false
}
default {
Write-Log -Level "ERROR" -Message "Invalid input, operation terminated"
return $false
}
}
}
}
$continueInput = Read-Host "Remote already has target image, continue update anyway? (y/n)"
switch ($continueInput.ToLower()) {
"y" {
Write-Log -Level "INFO" -Message "User chose to continue update"
return $true
}
"n" {
Write-Log -Level "INFO" -Message "User chose to skip update"
return $false
}
default {
Write-Log -Level "ERROR" -Message "Invalid input, operation terminated"
return $false
}
}
}
else {
Write-Log -Level "INFO" -Message "[OK] Target image $expectedImage not installed on remote server, continuing update"
return $true
}
}
# ================================
# Sync EMQX Directory
# ================================
function Sync-EmqxAssets {
param(
[hashtable]$Server,
[string]$RemoteTargetDir,
[string]$PlatformLabel
)
Write-Log -Level "INFO" -Message "Preparing to sync EMQX directory ($LOCAL_EMQX_DIR -> $($Server.IP):$RemoteTargetDir)"
# Create remote directory
$null = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "mkdir -p '$RemoteTargetDir'"
# Backup remote directory
$backupCmd = "set -e; TARGET_DIR='$RemoteTargetDir'; if [ -d `"`$TARGET_DIR`" ] && [ -n `"`$(ls -A `$TARGET_DIR 2>/dev/null)`" ]; then ts=`$(date +%Y%m%d_%H%M%S); backup=`"`${TARGET_DIR}_backup_`${ts}`"; cp -r `"`$TARGET_DIR`" `"`$backup`"; echo `$backup; fi"
$backupResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $backupCmd
$backupPath = $backupResult.Output.Trim()
if (-not [string]::IsNullOrEmpty($backupPath)) {
Write-Log -Level "INFO" -Message "Remote directory backed up to $backupPath"
}
else {
Write-Log -Level "INFO" -Message "Remote directory empty or does not exist, skipping backup"
}
# Use pscp to sync directory
Write-Log -Level "INFO" -Message "Using pscp to sync EMQX directory"
$localEmqxPath = Join-Path $SCRIPT_DIR "emqx"
if (Test-Path $localEmqxPath) {
$scpResult = Send-SCPDirectory -LocalPath $localEmqxPath -RemoteHost $Server.IP -RemoteUser $Server.User -RemotePass $Server.Pass -RemotePort $Server.Port -RemotePath $RemoteTargetDir
if ($scpResult.ExitCode -ne 0) {
Write-Log -Level "ERROR" -Message "EMQX directory sync failed: $($scpResult.Output)"
return $false
}
}
else {
Write-Log -Level "WARN" -Message "Local EMQX directory does not exist: $localEmqxPath, skipping sync"
}
Write-Log -Level "INFO" -Message "EMQX directory sync completed ($PlatformLabel platform)"
return $true
}
# ================================
# Sync Nginx Directory
# ================================
function Sync-NginxAssets {
param(
[hashtable]$Server,
[string]$RemoteTargetDir,
[string]$PlatformLabel
)
Write-Log -Level "INFO" -Message "Preparing to sync Nginx directory ($LOCAL_NGINX_DIR -> $($Server.IP):$RemoteTargetDir)"
# Create remote directory
$null = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "mkdir -p '$RemoteTargetDir'"
# Backup remote directory
$backupCmd = "set -e; TARGET_DIR='$RemoteTargetDir'; if [ -d `"`$TARGET_DIR`" ] && [ -n `"`$(ls -A `$TARGET_DIR 2>/dev/null)`" ]; then ts=`$(date +%Y%m%d_%H%M%S); backup=`"`${TARGET_DIR}_backup_`${ts}`"; cp -r `"`$TARGET_DIR`" `"`$backup`"; echo `$backup; fi"
$backupResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $backupCmd
$backupPath = $backupResult.Output.Trim()
if (-not [string]::IsNullOrEmpty($backupPath)) {
Write-Log -Level "INFO" -Message "Remote Nginx directory backed up to $backupPath"
}
else {
Write-Log -Level "INFO" -Message "Remote Nginx directory empty or does not exist, skipping backup"
}
# Use pscp to sync directory
Write-Log -Level "INFO" -Message "Using pscp to sync Nginx directory"
$localNginxPath = Join-Path $SCRIPT_DIR "nginx"
if (Test-Path $localNginxPath) {
$scpResult = Send-SCPDirectory -LocalPath $localNginxPath -RemoteHost $Server.IP -RemoteUser $Server.User -RemotePass $Server.Pass -RemotePort $Server.Port -RemotePath $RemoteTargetDir
if ($scpResult.ExitCode -ne 0) {
Write-Log -Level "ERROR" -Message "Nginx directory sync failed: $($scpResult.Output)"
return $false
}
}
else {
Write-Log -Level "WARN" -Message "Local Nginx directory does not exist: $localNginxPath, skipping sync"
}
Write-Log -Level "INFO" -Message "Nginx directory sync completed ($PlatformLabel platform)"
return $true
}
# ================================
# Find and Stop Remote Old Container
# ================================
function Stop-RemoteContainer {
param(
[hashtable]$Server,
[string]$ContainerPrefix
)
Write-Log -Level "INFO" -Message "Finding and stopping remote old container"
$result = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "docker ps --format '{{.Names}}' | grep -E '^${ContainerPrefix}([0-9]+)?$' | sort -V | tail -n1"
$currentContainer = $result.Output.Trim()
if (-not [string]::IsNullOrEmpty($currentContainer)) {
Write-Log -Level "INFO" -Message "Detected old container $currentContainer, stopping it"
$null = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "docker stop '$currentContainer'"
return $currentContainer
}
else {
Write-Log -Level "INFO" -Message "No running container matching $ContainerPrefix or ${ContainerPrefix}[number] found"
return $null
}
}
# ================================
# Determine New Container Name (Auto-increment)
# ================================
function Get-NewContainerName {
param(
[string]$CurrentContainer,
[string]$ContainerPrefix
)
if (-not [string]::IsNullOrEmpty($CurrentContainer)) {
# Extract container number
if ($CurrentContainer -match "${ContainerPrefix}(\d+)$") {
$lastNum = [int]$Matches[1]
}
else {
$lastNum = 0
}
$nextNum = $lastNum + 1
}
else {
$nextNum = 1
}
$newContainer = "${ContainerPrefix}${nextNum}"
Write-Log -Level "INFO" -Message "New container name determined: $newContainer"
return $newContainer
}
# ================================
# Clean Remote Image Files and Deployment Script
# ================================
function Clear-RemoteFiles {
param(
[hashtable]$Server,
[string]$RemoteImagePath,
[string]$RemoteTargetDir,
[string]$DeployName
)
Write-Log -Level "INFO" -Message "Starting cleanup of remote image files..."
$cleanupCmd = "set -e; if [ -f '$RemoteImagePath' ]; then rm -f '$RemoteImagePath'; echo 'Deleted image: $RemoteImagePath'; fi; if [ -f '$RemoteTargetDir/$DeployName' ]; then rm -f '$RemoteTargetDir/$DeployName'; echo 'Deleted script: $RemoteTargetDir/$DeployName'; fi; if [ -d '$RemoteTargetDir' ] && [ -z `"`$(ls -A '$RemoteTargetDir')`" ]; then rmdir '$RemoteTargetDir'; echo 'Deleted empty dir: $RemoteTargetDir'; fi"
$cleanupResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cleanupCmd
if ($cleanupResult.ExitCode -eq 0) {
Write-Log -Level "INFO" -Message "[OK] Remote image cleanup completed"
$cleanupResult.Output -split "`n" | Where-Object { $_ -match '\S' } | ForEach-Object {
Write-Log -Level "INFO" -Message " $_"
}
}
else {
Write-Log -Level "WARN" -Message "[WARN] Warning during remote cleanup, but does not affect deployment result"
}
}
# ================================
# Main Function
# ================================
function Main {
Write-Host ""
Write-Host "=================================================================="
Write-Host " Remote Container Update Tool (PowerShell Version)"
Write-Host "=================================================================="
Write-Host ""
# Check dependencies
if (-not (Test-Dependencies)) {
exit 1
}
# Check if deployment script exists
$deployScriptPath = Join-Path $SCRIPT_DIR $DEPLOY_SCRIPT
if (-not (Test-Path $deployScriptPath)) {
Write-Log -Level "ERROR" -Message "Deployment script $DEPLOY_SCRIPT does not exist"
exit 3
}
# Select container
$containerPrefix = Select-Container
if ($null -eq $containerPrefix) {
exit 9
}
# Get image file
$imageFile = $ContainerImage[$containerPrefix]
if ([string]::IsNullOrEmpty($imageFile)) {
Write-Log -Level "ERROR" -Message "Container $containerPrefix has no image mapping configured"
exit 10
}
# Check if image file exists
$localImagePath = Join-Path $SCRIPT_DIR $imageFile
$imageName = $imageFile
if (-not (Test-Path $localImagePath)) {
Write-Log -Level "ERROR" -Message "Image file $localImagePath does not exist, please place it in script directory and retry"
exit 2
}
Write-Log -Level "INFO" -Message "Image file: $localImagePath"
# Select server
$server = Select-Server
if ($null -eq $server) {
exit 7
}
# Test SSH connection
if (-not (Test-SSHConnection -Server $server)) {
exit 4
}
# Verify remote architecture
$arch = Test-RemoteArchitecture -Server $server
if ($null -eq $arch) {
exit 5
}
# Auto-detect platform type
$platform = Get-PlatformType -Server $server
if ($null -eq $platform) {
exit 6
}
# Verify remote image version
if (-not (Test-RemoteImageVersion -Server $server -ContainerPrefix $containerPrefix -PlatformType $platform.Type)) {
Write-Log -Level "INFO" -Message "User cancelled update, process ended"
exit 0
}
# Create remote directory
Write-Log -Level "INFO" -Message "Creating remote directory $RemoteDir"
$null = Invoke-SSHCommand -HostName $server.IP -User $server.User -Pass $server.Pass -Port $server.Port -Command "mkdir -p '$RemoteDir'"
# Transfer image and deployment script to remote directory
Write-Log -Level "INFO" -Message "Transferring image and deployment script to remote directory"
# Transfer image file
$scpImageResult = Send-SCPFile -LocalPath $localImagePath -RemoteHost $server.IP -RemoteUser $server.User -RemotePass $server.Pass -RemotePort $server.Port -RemotePath "$RemoteDir/"
if ($scpImageResult.ExitCode -ne 0) {
Write-Log -Level "ERROR" -Message "Image file transfer failed: $($scpImageResult.Output)"
exit 11
}
Write-Log -Level "INFO" -Message "[OK] Image file transfer completed"
# Transfer deployment script
$scpScriptResult = Send-SCPFile -LocalPath $deployScriptPath -RemoteHost $server.IP -RemoteUser $server.User -RemotePass $server.Pass -RemotePort $server.Port -RemotePath "$RemoteDir/"
if ($scpScriptResult.ExitCode -ne 0) {
Write-Log -Level "ERROR" -Message "Deployment script transfer failed: $($scpScriptResult.Output)"
exit 12
}
Write-Log -Level "INFO" -Message "[OK] Deployment script transfer completed"
# Find and stop remote old container
$currentContainer = Stop-RemoteContainer -Server $server -ContainerPrefix $containerPrefix
# Sync container-specific files
switch ($containerPrefix) {
"uemqx" {
if ($platform.Type -eq "new") {
$remoteEmqxDir = "/data/middleware/emqx"
} else {
$remoteEmqxDir = "/var/www/emqx"
}
$null = Sync-EmqxAssets -Server $server -RemoteTargetDir $remoteEmqxDir -PlatformLabel $platform.Type
}
"upython" {
Write-Log -Level "INFO" -Message "Python container does not sync files, only deploying container"
}
"unginx" {
if ($platform.Type -eq "new") {
$remoteNginxDir = "/data/middleware/nginx"
$null = Sync-NginxAssets -Server $server -RemoteTargetDir $remoteNginxDir -PlatformLabel $platform.Type
}
else {
Write-Log -Level "ERROR" -Message "Nginx container only supports new unified platform, please re-select"
exit 20
}
}
}
# Determine new container name
$newContainer = Get-NewContainerName -CurrentContainer $currentContainer -ContainerPrefix $containerPrefix
# Determine remote image path
$remoteImagePath = "$RemoteDir/$imageName"
# Build remote execution command
if (-not [string]::IsNullOrEmpty($platform.Flag)) {
$platformTail = " '$($platform.Flag)'"
} else {
$platformTail = ""
}
$remoteCmd = "set -euo pipefail; cd '$RemoteDir'; sed -i 's/\r$//' '$DEPLOY_SCRIPT' 2>/dev/null || true; chmod +x '$DEPLOY_SCRIPT'; ./'$DEPLOY_SCRIPT' '$newContainer' '$remoteImagePath'$platformTail"
# Execute remote deployment script
Write-Log -Level "INFO" -Message "Starting remote deployment script execution"
# Use plink to execute remote command (with TTY)
$plinkArgs = @(
"-ssh",
"-P", $server.Port,
"-l", $server.User,
"-pw", $server.Pass,
"-t",
$server.IP,
$remoteCmd
)
& plink @plinkArgs
if ($LASTEXITCODE -ne 0) {
Write-Log -Level "ERROR" -Message "Remote deployment execution failed"
exit 13
}
Write-Log -Level "INFO" -Message "Remote deployment execution completed"
# Clean remote image files and deployment script
Clear-RemoteFiles -Server $server -RemoteImagePath $remoteImagePath -RemoteTargetDir $RemoteDir -DeployName $DEPLOY_SCRIPT
Write-Log -Level "INFO" -Message "Remote update process ended"
Write-Host ""
Write-Host "=================================================================="
Write-Host " [OK] Remote container update completed!"
Write-Host "=================================================================="
Write-Host ""
}
# Execute main function
Main
...@@ -10,12 +10,89 @@ ...@@ -10,12 +10,89 @@
remote_update.ps1为Windows执行脚本,用以远程更新程序 remote_update.ps1为Windows执行脚本,用以远程更新程序
program_update.sh为Linux执行脚本,用于在远程服务器上执行更新操作 program_update.sh为Linux执行脚本,用于在远程服务器上执行更新操作
### 三、功能需求: ### 三、功能需求:
#### 3.1.支持远程连接到指定服务器。 #### 3.1.支持远程连接到指定服务器。(remote_program_update.ps1)
- 输入服务器IP - 输入服务器IP
- ssh端口号 - ssh端口号
- 输入用户名密码 - 输入用户名密码
- 输入文件存放路径(默认为/home/Update/) - 输入文件存放路径(默认为/home/Update/)
#### 3.2.能够上传更新压缩文件以及更新脚本。 - 判断平台类型(新统一平台/传统平台)
- 将当前脚本同级目录下的压缩包和program_update.sh脚本上传到指定服务器的指定路径下。 - 输入更新系统类型(会议预定系统、运维集控系统、讯飞转录系统)
#### 3.3.自动停止相关服务,进行包的替换和更新。 - 输入更新类型(前端更新、后端更新、全量更新)
#### 3.4.重启服务并验证更新是否成功。
\ No newline at end of file #### 3.2.能够上传更新压缩文件以及更新脚本。(remote_program_update.ps1)
- 将当前脚本同级目录下的zip压缩包和program_update.sh脚本上传到指定服务器的指定路径下。
- 解压缩压缩包,通过unzip命令解压到指定目录(/home/Update/)。
#### 3.3.备份原有数据. (program_update.sh)
- 在执行更新操作前,先备份原有的前端或后端文件夹到备份目录(/home/Backup/)。
-- 创建备份目录:
执行mkdir -p /home/Backup/Bak时间戳
-- 备份前端文件夹:
cp -r 根据系统类型和更新类型选择对应的前端路径及文件 备份目录路径
-- 备份后端文件夹:
cp -r 根据系统类型和更新类型选择对应的后端路径及文件 备份目录路径
-- 备份数据库操作如下:
1.查询MySQL容器名称
2.进入MySQL容器
3.执行mysqldump -uroot -p 根据系统类型选择对应的数据库名称 > /备份目录路径/数据库名称_时间戳.sql
- 打压缩当前备份包,并导出至桌面
-- 执行tar -czvf /home/Backup/Bak时间戳.tar.gz /home/Backup/Bak时间戳
-- 将压缩包移动到桌面:mv /home/Backup/Bak时间戳.tar.gz remote_update.ps1所在路径
#### 3.4.更新服务(program_update.sh)
- 根据系统类型和更新类型选择对应的更新脚本执行更新操作
- 前端更新操作:
-- 传统平台
1.会议预定系统:
- 前台前端:
cd /home/Update/ubains-web-2.0
mv * /var/www/java/ubains-web-2.0/(覆盖操作)
- 后台前端:
cd /home/Update/ubains-web-admin
mv * /var/www/java/ubains-web-admin/(覆盖操作)
- 对内后端:
cd /home/Update/api-java-meeting2.0
mv * /var/www/java/api-java-meeting2.0/(覆盖操作)
- 对外后端:
cd /home/Update/external-meeting-api
mv * /var/www/java/external-meeting-api/(覆盖操作)
2.运维集控系统:
- 前端:
cd /home/Update/web-vue-rms
mv * /var/www/html/web-vue-rms/(覆盖操作)
- 后端:
cd /home/Update/cmdb
mv * /var/www/html/(覆盖操作)
3.讯飞转录系统:
- 前端:
cd /home/Update/web-vue-rms
mv * /var/www/html/web-vue-rms/(覆盖操作)
- 后端:
cd /home/Update/cmdb
mv * /var/www/html/(覆盖操作)
#### 3.5.重启服务并验证更新是否成功。
- 如果更新类型为前端更新,则无需重启服务
- 如果更新类型为后端更新,则需重启服务
-- 通过系统名称类型重启对应服务
1.会议预定系统:
- cd /var/www/java/external-meeting-api
- ./run.sh;tail -f logs/ubains-INFO-AND-ERROR.log
- docker exec -it ujava2 bash
- cd /var/www/java/api-java-meeting2.0
- ./run.sh;tail -f logs/ubains-INFO-AND-ERROR.log
2.运维集控系统:
- cd /var/www/html
- docker restart upython
- tail -f log/uinfo.log
3.讯飞转录系统:
- cd /var/www/html
- docker restart upython
- tail -f log/uinfo.log
### 四、不同平台的路径映射:
#### 4.1.传统平台路径映射:
| 系统名称 | 前端路径及文件 | 后端路径及文件 | 数据库名称 |
|:会议预定系统: | :前台前端:/var/www/java/ubains-web-2.0 *.js static/ index.html 后台前端:/var/www/java/ubains-web-admin index.html static/: | :对内后端:/var/www/java/api-java-meeting2.0 *.jar 对外后端:/var/www/java/external-meeting-api *.jar: | :数据库:ubains: |
|:运维集控系统: | :前端:/var/www/html/web-vue-rms static/ index.html: | :后端:/var/www/html cmdb/ UbainsDevOps/: | :数据库:devops: |
|:讯飞转录系统: | :前端:/var/www/html/web-vue-voice static/ index.html: | :后端:/var/www/html UbainsDevOps/: | :数据库:devops: |
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论