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

feat(update-script): 实现远程程序更新脚本的备份功能

- 添加program_update.sh脚本实现3.3备份功能
- 更新PRD文档标记已完成的功能点
- 在数据库备份命令中添加固定密码配置
- 优化Plink命令执行的日志输出和错误处理
- 实现备份文件从远程服务器下载到本地功能
- 集成备份流程到远程更新主流程中自动执行
上级 e446b17b
#!/usr/bin/env bash
set -euo pipefail
log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*" >&2; }
usage() {
cat <<'EOF'
program_update.sh (PRD 3.3 Backup)
Usage:
./program_update.sh --platform "新统一平台|传统平台" \
--system "会议预定系统|运维集控系统|讯飞转录系统" \
--update "前端更新|后端更新|全量更新" \
[--workdir /home/Update/]
Behavior (3.3):
- mkdir -p /home/Backup/Bak<timestamp>
- backup frontend/backend main files based on PRD mapping
- backup database by finding mysql container and running mysqldump
- tar backup folder to /home/Backup/Bak<timestamp>.tar.gz
- print BACKUP_TAR path for caller to download
EOF
}
PLATFORM=""
SYSTEM=""
UPDATE_TYPE=""
WORKDIR="/home/Update"
while [[ $# -gt 0 ]]; do
case "$1" in
--platform) PLATFORM="${2:-}"; shift 2;;
--system) SYSTEM="${2:-}"; shift 2;;
--update) UPDATE_TYPE="${2:-}"; shift 2;;
--workdir) WORKDIR="${2:-}"; shift 2;;
-h|--help) usage; exit 0;;
*) log "Unknown arg: $1"; usage; exit 2;;
esac
done
if [[ -z "$PLATFORM" || -z "$SYSTEM" || -z "$UPDATE_TYPE" ]]; then
usage
exit 2
fi
TS="$(date '+%Y%m%d_%H%M%S')"
BACKUP_ROOT="/home/Backup"
BACKUP_DIR="${BACKUP_ROOT}/Bak${TS}"
BACKUP_TAR="${BACKUP_ROOT}/Bak${TS}.tar.gz"
mkdir -p "$BACKUP_DIR"
log "Backup dir: $BACKUP_DIR"
# PRD 指定数据库密码(固定值)
MYSQL_ROOT_PASSWORD='dNrprU&2S'
# glob 没匹配到时展开为空,避免把 "*.jar" 当成字面量
shopt -s nullglob
# 只备份关键文件/目录(支持 glob)
# 用法:backup_globs "label" "/source/base" "/backup/dest" "glob1" "glob2" ...
backup_globs() {
local label="$1"; shift
local base="$1"; shift
local dest="$1"; shift
mkdir -p "$dest"
if [[ ! -d "$base" ]]; then
log "WARN: base not found, skip: $label ($base)"
return 0
fi
log "Backup [$label]: base=$base -> $dest"
local matched=0
local g p
for g in "$@"; do
for p in "$base"/$g; do
matched=1
cp -a "$p" "$dest/"
done
done
if [[ "$matched" -eq 0 ]]; then
log "WARN: no files matched for [$label] under $base"
fi
}
# 传统平台映射(按 PRD 4.1:仅备份关键文件)
DB_NAME=""
# 为了适配“会议预定系统前端两套、后端两套”,这里最多给两组规则
FRONT_1_BASE=""; FRONT_1_GLOBS=()
FRONT_2_BASE=""; FRONT_2_GLOBS=()
BACK_1_BASE=""; BACK_1_GLOBS=()
BACK_2_BASE=""; BACK_2_GLOBS=()
case "$PLATFORM" in
"传统平台")
case "$SYSTEM" in
"会议预定系统")
DB_NAME="ubains"
# 前台前端:/var/www/java/ubains-web-2.0 *.js static/ index.html
FRONT_1_BASE="/var/www/java/ubains-web-2.0"
FRONT_1_GLOBS=("index.html" "static" "*.js")
# 后台前端:/var/www/java/ubains-web-admin index.html static/
FRONT_2_BASE="/var/www/java/ubains-web-admin"
FRONT_2_GLOBS=("index.html" "static")
# 对内后端:/var/www/java/api-java-meeting2.0 *.jar(只匹配以 .jar 结尾,避免 jar2/jarbak/jarBak)
BACK_1_BASE="/var/www/java/api-java-meeting2.0"
BACK_1_GLOBS=("*.jar")
# 对外后端:/var/www/java/external-meeting-api *.jar
BACK_2_BASE="/var/www/java/external-meeting-api"
BACK_2_GLOBS=("*.jar")
;;
"运维集控系统")
DB_NAME="devops"
# 前端:/var/www/html/web-vue-rms static/ index.html
FRONT_1_BASE="/var/www/html/web-vue-rms"
FRONT_1_GLOBS=("index.html" "static")
# 后端:/var/www/html cmdb/ UbainsDevOps/
BACK_1_BASE="/var/www/html"
BACK_1_GLOBS=("cmdb" "UbainsDevOps")
;;
"讯飞转录系统")
DB_NAME="devops"
# 前端:/var/www/html/web-vue-voice static/ index.html
FRONT_1_BASE="/var/www/html/web-vue-voice"
FRONT_1_GLOBS=("index.html" "static")
# 后端:/var/www/html UbainsDevOps/
BACK_1_BASE="/var/www/html"
BACK_1_GLOBS=("UbainsDevOps")
;;
*)
log "Unsupported system: $SYSTEM"
exit 3
;;
esac
;;
"新统一平台")
log "ERROR: 新统一平台的路径映射未在 PRD 中提供,暂无法执行备份。请补充 4.2 映射后再启用。"
exit 3
;;
*)
log "Unsupported platform: $PLATFORM"
exit 3
;;
esac
# 3.3 备份前端/后端(按更新类型)
if [[ "$UPDATE_TYPE" == "前端更新" || "$UPDATE_TYPE" == "全量更新" ]]; then
mkdir -p "$BACKUP_DIR/frontend"
if [[ -n "$FRONT_1_BASE" ]]; then
backup_globs "frontend-1" "$FRONT_1_BASE" "$BACKUP_DIR/frontend/$(basename "$FRONT_1_BASE")" "${FRONT_1_GLOBS[@]}"
fi
if [[ -n "$FRONT_2_BASE" ]]; then
backup_globs "frontend-2" "$FRONT_2_BASE" "$BACKUP_DIR/frontend/$(basename "$FRONT_2_BASE")" "${FRONT_2_GLOBS[@]}"
fi
fi
if [[ "$UPDATE_TYPE" == "后端更新" || "$UPDATE_TYPE" == "全量更新" ]]; then
mkdir -p "$BACKUP_DIR/backend"
if [[ -n "$BACK_1_BASE" ]]; then
backup_globs "backend-1" "$BACK_1_BASE" "$BACKUP_DIR/backend/$(basename "$BACK_1_BASE")" "${BACK_1_GLOBS[@]}"
fi
if [[ -n "$BACK_2_BASE" ]]; then
backup_globs "backend-2" "$BACK_2_BASE" "$BACKUP_DIR/backend/$(basename "$BACK_2_BASE")" "${BACK_2_GLOBS[@]}"
fi
fi
# 备份数据库(按 PRD:查 MySQL 容器名 -> 进入容器 -> mysqldump)
backup_db() {
local db="$1"
[[ -z "$db" ]] && return 0
if ! command -v docker >/dev/null 2>&1; then
log "WARN: docker not found, skip DB backup."
return 0
fi
local mysql_container=""
mysql_container="$(docker ps --format '{{.Names}}' | grep -Eiw 'mysql|mariadb' | head -n 1 || true)"
if [[ -z "$mysql_container" ]]; then
mysql_container="$(docker ps --format '{{.Names}} {{.Image}}' | grep -Ei 'mysql|mariadb' | awk '{print $1}' | head -n 1 || true)"
fi
if [[ -z "$mysql_container" ]]; then
log "WARN: MySQL container not found, skip DB backup."
return 0
fi
mkdir -p "$BACKUP_DIR/db"
local out="${BACKUP_DIR}/db/${db}_${TS}.sql"
log "DB backup: container=$mysql_container db=$db -> $out"
docker exec "$mysql_container" sh -lc "mysqldump -uroot -p'${MYSQL_ROOT_PASSWORD}' '${db}'" > "$out"
}
backup_db "$DB_NAME"
# 打包备份目录
log "Packing backup to: $BACKUP_TAR"
tar -czvf "$BACKUP_TAR" "$BACKUP_DIR" >/dev/null
log "Backup done."
echo "BACKUP_DIR=$BACKUP_DIR"
echo "BACKUP_TAR=$BACKUP_TAR"
\ No newline at end of file
...@@ -70,7 +70,6 @@ function Invoke-PlinkCommand { ...@@ -70,7 +70,6 @@ function Invoke-PlinkCommand {
$args = @( $args = @(
"-batch", "-batch",
"-no-antispoof",
"-ssh", "-ssh",
"-P", "$Port", "-P", "$Port",
"-l", $User, "-l", $User,
...@@ -80,10 +79,28 @@ function Invoke-PlinkCommand { ...@@ -80,10 +79,28 @@ function Invoke-PlinkCommand {
) )
Write-Info "远端执行: $Command" Write-Info "远端执行: $Command"
$p = Start-Process -FilePath $PlinkPath -ArgumentList $args -NoNewWindow -Wait -PassThru
$tmpOut = Join-Path $env:TEMP ("plink_{0}.out.log" -f ([guid]::NewGuid().ToString("N")))
$tmpErr = Join-Path $env:TEMP ("plink_{0}.err.log" -f ([guid]::NewGuid().ToString("N")))
$p = Start-Process -FilePath $PlinkPath -ArgumentList $args -NoNewWindow -Wait -PassThru `
-RedirectStandardOutput $tmpOut -RedirectStandardError $tmpErr
$outText = ""
$errText = ""
if (Test-Path $tmpOut) { $outText = (Get-Content $tmpOut -Raw) }
if (Test-Path $tmpErr) { $errText = (Get-Content $tmpErr -Raw) }
if (-not [string]::IsNullOrWhiteSpace($outText)) { Write-Host $outText }
if (-not [string]::IsNullOrWhiteSpace($errText)) { Write-Warn $errText }
Remove-Item -ErrorAction SilentlyContinue $tmpOut, $tmpErr
if ($p.ExitCode -ne 0) { if ($p.ExitCode -ne 0) {
throw "plink 执行失败,ExitCode=$($p.ExitCode)。命令:$Command" throw "plink 执行失败,ExitCode=$($p.ExitCode)。命令:$Command"
} }
return $outText
} }
function Invoke-PscpUpload { function Invoke-PscpUpload {
...@@ -124,6 +141,38 @@ function Invoke-PscpUpload { ...@@ -124,6 +141,38 @@ function Invoke-PscpUpload {
} }
} }
function Invoke-PscpDownload {
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]$RemoteFile,
[Parameter(Mandatory=$true)][string]$LocalDir
)
if (-not (Test-Path $LocalDir)) {
New-Item -ItemType Directory -Path $LocalDir | Out-Null
}
$remoteSpec = ("{0}@{1}:{2}" -f $User, $HostName, $RemoteFile)
$args = @(
"-batch",
"-P", "$Port",
"-pw", $Password,
$remoteSpec,
$LocalDir
)
Write-Info "下载: $RemoteFile -> $LocalDir"
$p = Start-Process -FilePath $PscpPath -ArgumentList $args -NoNewWindow -Wait -PassThru
if ($p.ExitCode -ne 0) {
throw "pscp 下载失败,ExitCode=$($p.ExitCode)。远端文件:$RemoteFile"
}
}
# -------------------- 3.1 采集参数 -------------------- # -------------------- 3.1 采集参数 --------------------
Write-Host "=== 远程程序更新(Windows 端入口)===" -ForegroundColor Green Write-Host "=== 远程程序更新(Windows 端入口)===" -ForegroundColor Green
...@@ -185,8 +234,21 @@ Invoke-PscpUpload -PscpPath $pscp -HostName $serverIp -Port $sshPort -User $user ...@@ -185,8 +234,21 @@ Invoke-PscpUpload -PscpPath $pscp -HostName $serverIp -Port $sshPort -User $user
$zipName = Split-Path $zipPath -Leaf $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'" Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command "cd '$remoteDir' && unzip -o '$zipName' -d '$remoteDir'"
# 可选执行入口 # ====== 默认执行 program_update.sh(实现 3.3 备份)并下载备份包到本机脚本目录 ======
if ($runNow -eq "是") { $execCmd = "cd '$remoteDir' && chmod +x ./program_update.sh && ./program_update.sh --platform '$platformType' --system '$systemType' --update '$updateType' --workdir '$remoteDir'"
$cmd = "cd '$remoteDir' && chmod +x ./program_update.sh && ./program_update.sh --platform '$platformType' --system '$systemType' --update '$updateType' --workdir '$remoteDir'" $out = Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command $execCmd
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command $cmd
} # 从输出解析 BACKUP_TAR
\ No newline at end of file # program_update.sh 输出格式:BACKUP_TAR=/home/Backup/BakYYYYmmdd_HHMMSS.tar.gz
$backupTar = $null
if ($out -match '(?m)^BACKUP_TAR=(.+)\s*$') {
$backupTar = $Matches[1].Trim()
}
if (-not $backupTar) {
throw "未从远端输出中解析到 BACKUP_TAR。请确认 program_update.sh 已实现 3.3 且会 echo BACKUP_TAR=..."
}
Write-Info "远端备份包:$backupTar"
Invoke-PscpDownload -PscpPath $pscp -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -RemoteFile $backupTar -LocalDir $PSScriptRoot
Write-Info "备份包已下载到:$PSScriptRoot"
\ No newline at end of file
...@@ -10,18 +10,18 @@ ...@@ -10,18 +10,18 @@
remote_update.ps1为Windows执行脚本,用以远程更新程序 remote_update.ps1为Windows执行脚本,用以远程更新程序
program_update.sh为Linux执行脚本,用于在远程服务器上执行更新操作 program_update.sh为Linux执行脚本,用于在远程服务器上执行更新操作
### 三、功能需求: ### 三、功能需求:
#### 3.1.支持远程连接到指定服务器。(remote_program_update.ps1) #### 3.1.支持远程连接到指定服务器。(remote_program_update.ps1)✅ 已完成
- 输入服务器IP - 输入服务器IP
- ssh端口号 - ssh端口号
- 输入用户名密码 - 输入用户名密码
- 输入文件存放路径(默认为/home/Update/) - 输入文件存放路径(默认为/home/Update/)
- 判断平台类型(新统一平台/传统平台) - 判断平台类型(新统一平台/传统平台)
- 输入更新系统类型(会议预定系统、运维集控系统、讯飞转录系统) - 输入更新系统类型(会议预定系统、运维集控系统、讯飞转录系统)
- 输入更新类型(前端更新、后端更新、全量更新) - 输入更新类型(前端更新、后端更新、全量更新)
#### 3.2.能够上传更新压缩文件以及更新脚本。(remote_program_update.ps1) #### 3.2.能够上传更新压缩文件以及更新脚本。(remote_program_update.ps1)✅ 已完成
- 将当前脚本同级目录下的zip压缩包和program_update.sh脚本上传到指定服务器的指定路径下。 - 将当前脚本同级目录下的zip压缩包和program_update.sh脚本上传到指定服务器的指定路径下。
- 解压缩压缩包,通过unzip命令解压到指定目录(/home/Update/)。 - 解压缩压缩包,通过unzip命令解压到指定目录(/home/Update/)。
#### 3.3.备份原有数据. (program_update.sh) #### 3.3.备份原有数据. (program_update.sh)
- 在执行更新操作前,先备份原有的前端或后端文件夹到备份目录(/home/Backup/)。 - 在执行更新操作前,先备份原有的前端或后端文件夹到备份目录(/home/Backup/)。
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
-- 备份数据库操作如下: -- 备份数据库操作如下:
1.查询MySQL容器名称 1.查询MySQL容器名称
2.进入MySQL容器 2.进入MySQL容器
3.执行mysqldump -uroot -p 根据系统类型选择对应的数据库名称 > /备份目录路径/数据库名称_时间戳.sql 3.执行mysqldump -uroot -p 根据系统类型选择对应的数据库名称 > /备份目录路径/数据库名称_时间戳.sql(数据库密码为:dNrprU&2S)
- 打压缩当前备份包,并导出至桌面 - 打压缩当前备份包,并导出至桌面
-- 执行tar -czvf /home/Backup/Bak时间戳.tar.gz /home/Backup/Bak时间戳 -- 执行tar -czvf /home/Backup/Bak时间戳.tar.gz /home/Backup/Bak时间戳
-- 将压缩包移动到桌面:mv /home/Backup/Bak时间戳.tar.gz remote_update.ps1所在路径 -- 将压缩包移动到桌面:mv /home/Backup/Bak时间戳.tar.gz remote_update.ps1所在路径
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论