fix(deploy): 改进备份命名和清理逻辑,防止版本重复
问题:同一版本多次备份时,备份文件仅时间戳不同,内容完全相同,
导致清理后仍保留多个相同版本的备份,失去回滚多样性。
解决方案:
1. 备份文件名加入 Git hash + MD5:{timestamp}.{gitHash}.{md5}
2. 清理逻辑按 MD5 去重:相同版本的多个备份只保留最早一个
3. 手动回滚支持选择特定版本
影响:
- 回滚时可明确知道恢复到哪个版本(Git hash + MD5 可验证)
- 清理不再基于简单时间戳,而是基于版本内容
- 保留了真正的版本多样性
This commit is contained in:
123
deploy.sh
123
deploy.sh
@@ -295,20 +295,44 @@ status() {
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 回滚(手动触发)
|
||||
# 回滚(手动触发)- 支持选择特定版本
|
||||
rollback() {
|
||||
log_step "查找最新备份..."
|
||||
log_step "查找可用备份..."
|
||||
local latest_backup
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
latest_backup="${REMOTE_DIR}/${PROJECT_NAME}.backup.20240101_120000"
|
||||
latest_backup="${REMOTE_DIR}/${PROJECT_NAME}.backup.20240101_120000.unknown.12345678"
|
||||
log_dry "latest_backup=${latest_backup}"
|
||||
else
|
||||
latest_backup=$(ssh "${REMOTE_USER}@${REMOTE_HOST}" \
|
||||
"ls -t ${REMOTE_DIR}/${PROJECT_NAME}.backup.* 2>/dev/null | head -1" || echo "")
|
||||
# 列出所有备份,让用户选择
|
||||
local backups
|
||||
backups=$(ssh "${REMOTE_USER}@${REMOTE_HOST}" \
|
||||
"ls -t ${REMOTE_DIR}/${PROJECT_NAME}.backup.* 2>/dev/null" || echo "")
|
||||
if [ -z "$backups" ]; then
|
||||
log_error "没有找到可用的备份"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}可用备份列表:${NC}"
|
||||
echo "$backups" | head -10 | nl -v 0
|
||||
echo ""
|
||||
|
||||
if [ "$SKIP_CONFIRM" = false ]; then
|
||||
local count
|
||||
count=$(echo "$backups" | wc -l)
|
||||
read -p "选择要回滚的备份 [0-$(($count > 10 ? 9 : $count - 1)),默认 0 最新]: " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[0-9]$ ]]; then
|
||||
REPLY=0
|
||||
fi
|
||||
latest_backup=$(echo "$backups" | sed -n "$((REPLY + 1))p" || echo "")
|
||||
else
|
||||
latest_backup=$(echo "$backups" | head -1)
|
||||
fi
|
||||
|
||||
if [ -z "$latest_backup" ]; then
|
||||
log_error "没有找到可用的备份"
|
||||
log_error "无效的备份选择"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
@@ -336,7 +360,7 @@ rollback() {
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log_dry "ssh ${REMOTE_USER}@${REMOTE_HOST} 'cp ${latest_backup} ${REMOTE_DIR}/${PROJECT_NAME}'"
|
||||
else
|
||||
ssh "${REMOTE_USER}@${REMOTE_HOST}" "cp ${latest_backup} ${REMOTE_DIR}/${PROJECT_NAME}"
|
||||
ssh "${REMOTE_USER}@${REMOTE_HOST}" "cp '${latest_backup}' '${REMOTE_DIR}/${PROJECT_NAME}'"
|
||||
fi
|
||||
|
||||
log_step "启动服务..."
|
||||
@@ -395,35 +419,80 @@ rollback_on_failure() {
|
||||
send_webhook "failure" "部署测试失败,已自动回滚到: $(basename $backup_path)"
|
||||
}
|
||||
|
||||
# 清理旧备份,只保留最近 N 个
|
||||
# 清理旧备份,只保留最近 N 个(按内容去重,相同版本只保留最新的)
|
||||
cleanup_old_backups() {
|
||||
local keep_count="${1:-5}"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log_dry "保留最近 ${keep_count} 个备份,删除其余"
|
||||
log_dry "保留最近 ${keep_count} 个备份(相同版本只保留最新)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local backup_count
|
||||
backup_count=$(ssh "${REMOTE_USER}@${REMOTE_HOST}" \
|
||||
"ls -t ${REMOTE_DIR}/${PROJECT_NAME}.backup.* 2>/dev/null | wc -l" || echo "0")
|
||||
# 获取所有备份文件(按时间倒序)
|
||||
local backups
|
||||
backups=$(ssh "${REMOTE_USER}@${REMOTE_HOST}" \
|
||||
"ls -t ${REMOTE_DIR}/${PROJECT_NAME}.backup.* 2>/dev/null" || echo "")
|
||||
|
||||
if [ "$backup_count" -le "$keep_count" ]; then
|
||||
if [ -z "$backups" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local to_delete
|
||||
to_delete=$(ssh "${REMOTE_USER}@${REMOTE_HOST}" \
|
||||
"ls -t ${REMOTE_DIR}/${PROJECT_NAME}.backup.* 2>/dev/null | tail -n +$((keep_count + 1))" || echo "")
|
||||
# 提取所有唯一版本的 MD5(从备份文件名中提取,格式:.backup.timestamp.gitHash.md5)
|
||||
# 只保留每个 MD5 最早的备份(即该版本的第一次备份)
|
||||
local seen_md5s=""
|
||||
local to_delete=""
|
||||
local kept_count=0
|
||||
|
||||
while IFS= read -r backup; do
|
||||
if [ -z "$backup" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# 从文件名提取 MD5(最后一段)
|
||||
local md5
|
||||
md5=$(basename "$backup" | rev | cut -d'.' -f2 | rev || echo "")
|
||||
|
||||
if [ -z "$md5" ]; then
|
||||
# 旧格式备份,直接删除
|
||||
to_delete="${to_delete}${backup}"$'\n'
|
||||
continue
|
||||
fi
|
||||
|
||||
if echo "$seen_md5s" | grep -q "^${md5}$"; then
|
||||
# 已见过该版本,标记删除
|
||||
to_delete="${to_delete}${backup}"$'\n'
|
||||
else
|
||||
# 新版本,保留
|
||||
seen_md5s="${seen_md5s}${md5}"$'\n'
|
||||
kept_count=$((kept_count + 1))
|
||||
fi
|
||||
done <<< "$backups"
|
||||
|
||||
# 如果保留的数量超过 keep_count,则从最早的开始删除
|
||||
if [ "$kept_count" -gt "$keep_count" ]; then
|
||||
log_step "保留版本数超过 ${keep_count},按时间清理多余版本..."
|
||||
|
||||
# 重新处理,删除超出数量的旧备份(基于时间顺序)
|
||||
local count=0
|
||||
while IFS= read -r backup; do
|
||||
if [ -z "$backup" ]; then
|
||||
continue
|
||||
fi
|
||||
count=$((count + 1))
|
||||
if [ "$count" -gt "$keep_count" ]; then
|
||||
to_delete="${to_delete}${backup}"$'\n'
|
||||
fi
|
||||
done <<< "$backups"
|
||||
fi
|
||||
|
||||
if [ -z "$to_delete" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_step "清理旧备份(保留 ${keep_count} 个)..."
|
||||
echo "$to_delete" | while read -r file; do
|
||||
log_step "清理旧备份(保留 ${keep_count} 个不同版本)..."
|
||||
echo "$to_delete" | while IFS= read -r file; do
|
||||
if [ -n "$file" ]; then
|
||||
ssh "${REMOTE_USER}@${REMOTE_HOST}" "rm -f '${file}'" 2>/dev/null && echo " 已删除: ${file}" || true
|
||||
ssh "${REMOTE_USER}@${REMOTE_HOST}" "rm -f '${file}'" 2>/dev/null && echo " 已删除: $(basename "$file")" || true
|
||||
fi
|
||||
done
|
||||
}
|
||||
@@ -500,10 +569,22 @@ backup_old_version() {
|
||||
local binary_path="./target/release/${PROJECT_NAME}"
|
||||
local timestamp
|
||||
timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_PATH="${REMOTE_DIR}/${PROJECT_NAME}.backup.${timestamp}"
|
||||
|
||||
# 获取当前运行的二进制文件的 MD5,用于区分不同版本
|
||||
local current_md5=""
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
current_md5=$(ssh "${REMOTE_USER}@${REMOTE_HOST}" "md5sum '${REMOTE_DIR}/${PROJECT_NAME}' 2>/dev/null | cut -d' ' -f1" || echo "")
|
||||
fi
|
||||
|
||||
# 获取 Git commit hash 用于版本标识
|
||||
local git_hash
|
||||
git_hash=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
|
||||
# 组合备份文件名:时间戳 + Git hash + MD5 前8位
|
||||
BACKUP_PATH="${REMOTE_DIR}/${PROJECT_NAME}.backup.${timestamp}.${git_hash}.${current_md5:0:8}"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log_dry "ssh ${REMOTE_USER}@${REMOTE_HOST} 'if [ -f ${REMOTE_DIR}/${PROJECT_NAME} ]; then cp ${REMOTE_DIR}/${PROJECT_NAME} ${BACKUP_PATH}; echo \"\${BACKUP_PATH}\" > ${REMOTE_DIR}/.last_deployed; fi'"
|
||||
log_dry "ssh ${REMOTE_USER}@${REMOTE_HOST} 'if [ -f ${REMOTE_DIR}/${PROJECT_NAME} ]; then cp ${REMOTE_DIR}/${PROJECT_NAME} '${BACKUP_PATH}'; echo \"\${BACKUP_PATH}\" > ${REMOTE_DIR}/.last_deployed; fi'"
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user