本文目录导读:

下面给出一个通用的一键回滚 Shell 脚本模板,以及两种常见场景:文件/目录回滚和应用版本回滚(基于备份/软链接),可以根据实际部署方式选择或组合使用。
通用思路
- 备份阶段:在更新前,将当前版本或配置文件备份到指定目录(带时间戳)。
- 回滚阶段:检测备份目录,提供列表供选择,恢复对应备份。
- 一键触发:通过
./rollback.sh执行,自动完成。
场景一:文件/目录级回滚(基于备份)
适用于更新了配置、静态文件等场景。
脚本示例:rollback_files.sh
#!/bin/bash
# 配置
TARGET_DIR="/var/www/html" # 待回滚的目标目录
BACKUP_ROOT="/backups/html" # 备份存放根目录
ROLLBACK_LOG="/var/log/rollback.log"
# 颜色输出
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
log() {
echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$ROLLBACK_LOG"
}
error_exit() {
log "${RED}错误:$1${NC}"
exit 1
}
# 检查备份目录是否存在
if [ ! -d "$BACKUP_ROOT" ]; then
error_exit "备份目录 $BACKUP_ROOT 不存在"
fi
# 列出可用备份(按最新排序)
echo "================= 可用备份列表 ================="
backups=($(ls -1t "$BACKUP_ROOT"))
if [ ${#backups[@]} -eq 0 ]; then
error_exit "无可用备份"
fi
for i in "${!backups[@]}"; do
backup_path="$BACKUP_ROOT/${backups[$i]}"
backup_size=$(du -sh "$backup_path" 2>/dev/null | awk '{print $1}')
echo " [$((i+1))] ${backups[$i]} (大小: ${backup_size:-未知})"
done
echo -e "\n请输入要回滚的编号 (1-${#backups[@]}),或输入 q 退出:"
read -r choice
if [[ "$choice" == "q" || "$choice" == "Q" ]]; then
log "用户取消回滚"
exit 0
fi
# 验证输入
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "${#backups[@]}" ]; then
error_exit "无效选择"
fi
# 计算数组下标(从0开始)
idx=$((choice-1))
selected_backup="${backups[$idx]}"
restore_source="$BACKUP_ROOT/$selected_backup"
log "开始回滚 → 目标: $TARGET_DIR, 备份源: $restore_source"
# 确认
echo -e "${RED}警告:将覆盖 $TARGET_DIR 当前内容!${NC}"
echo -n "确认执行回滚?(yes/no): "
read -r confirm
if [ "$confirm" != "yes" ]; then
log "用户取消回滚"
exit 0
fi
# 2.1 方法A:直接覆盖(适合文件较多时用 rsync)
log "正在回滚文件..."
rsync -a --delete "$restore_source/" "$TARGET_DIR/" 2>&1 | tee -a "$ROLLBACK_LOG"
if [ $? -ne 0 ]; then
error_exit "文件回滚失败"
fi
# 2.2 方法B(可选):先清空再复制
# rm -rf "$TARGET_DIR"/* && cp -ar "$restore_source"/* "$TARGET_DIR/"
log "${GREEN}回滚完成:从 $selected_backup 恢复到 $TARGET_DIR${NC}"
使用前需要:
- 提前在
${BACKUP_ROOT}下存放备份,20250307_120000_backup。 - 更新脚本前先执行一次备份,类似:
cp -a /var/www/html /backups/html/$(date +%Y%m%d_%H%M%S)_backup
场景二:应用版本回滚(基于软链接)
适用于使用 当前版本符号链接 切换的项目(如 Java jar、Node 应用)。
目录结构示例
/opt/myapp/
├── releases/
│ ├── v1.0.0/
│ ├── v1.1.0/
│ └── v1.2.0/
└── current -> releases/v1.2.0/
脚本示例:rollback_app.sh
#!/bin/bash
APP_NAME="myapp"
APP_HOME="/opt/$APP_NAME"
RELEASES_DIR="$APP_HOME/releases"
CURRENT_LINK="$APP_HOME/current"
SERVICE_NAME="myapp.service"
LOG_FILE="/var/log/rollback_app.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
error_exit() {
log "ERROR: $1"
exit 1
}
# 检查 releases 目录
if [ ! -d "$RELEASES_DIR" ]; then
error_exit "版本目录 $RELEASES_DIR 不存在"
fi
# 获取所有版本并按时间倒序
versions=($(ls -1t "$RELEASES_DIR"))
if [ ${#versions[@]} -eq 0 ]; then
error_exit "无可用版本用于回滚"
fi
echo "================= 可回滚版本列表 ================="
echo "当前版本: $(readlink "$CURRENT_LINK" 2>/dev/null || echo '无')"
echo "------------------------------------------------"
for i in "${!versions[@]}"; do
ver_dir="$RELEASES_DIR/${versions[$i]}"
# 检查版本目录是否有效
[ -d "$ver_dir" ] || continue
deploy_time=$(stat -c "%y" "$ver_dir" | cut -d'.' -f1)
# 标记当前版本
current_flag=""
if [ "$(readlink -f "$CURRENT_LINK")" = "$(readlink -f "$ver_dir")" ]; then
current_flag=" ← 当前"
fi
echo " [$((i+1))] ${versions[$i]} (部署于: $deploy_time)$current_flag"
done
echo -e "\n请输入要回滚到的版本编号 (1-${#versions[@]}):"
read -r choice
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "${#versions[@]}" ]; then
error_exit "无效选择"
fi
idx=$((choice-1))
target_version="${versions[$idx]}"
target_dir="$RELEASES_DIR/$target_version"
if [ ! -d "$target_dir" ]; then
error_exit "目标版本目录 $target_dir 不存在"
fi
# 确认
echo -e "将切换到版本:$target_version"
echo -n "确认回滚?(yes/no): "
read -r confirm
if [ "$confirm" != "yes" ]; then
log "用户取消回滚"
exit 0
fi
# 停止服务(可选)
log "停止服务 $SERVICE_NAME ..."
systemctl stop "$SERVICE_NAME" 2>/dev/null || echo "无systemctl,跳过停止"
# 切换软链接
log "切换软链接: $CURRENT_LINK -> $target_dir"
rm -f "$CURRENT_LINK"
ln -s "$target_dir" "$CURRENT_LINK"
if [ $? -ne 0 ]; then
error_exit "软链接切换失败"
fi
# 启动服务
log "启动服务 $SERVICE_NAME ..."
systemctl start "$SERVICE_NAME" 2>/dev/null || \
(echo "跳过 systemctl, 尝试手动启动..." && \
cd "$target_dir" && nohup ./start.sh > /dev/null 2>&1 &)
# 简单健康检查(等待3秒)
sleep 3
if pgrep -f "$APP_NAME" > /dev/null 2>&1; then
log "${GREEN}回滚成功:已切换到 $target_version${NC}"
else
log "${RED}警告:服务可能未正常启动,请手动检查${NC}"
fi
使用前准备:
- 每次发布新版本时,将文件放入
releases/v1.x.x/目录。 - 更新
current软链接指向新版本。 - 脚本支持 systemd 或自定义启动。
通用安全措施
# 在脚本开头建议添加: set -euo pipefail # 任何错误立即退出 # 或 set -e # 遇到错误退出
执行示例
# 赋予执行权限 chmod +x rollback_files.sh # 执行回滚 sudo ./rollback_files.sh
输出示例:
================= 可用备份列表 =================
[1] 20250307_120000_backup (大小: 45M)
[2] 20250306_080000_backup (大小: 44M)
请输入要回滚的编号 (1-2),或输入 q 退出:
1
警告:将覆盖 /var/www/html 当前内容!
确认执行回滚?(yes/no): yes
[2025-03-07 14:20:00] 正在回滚文件...
[2025-03-07 14:20:05] 回滚完成:从 20250307_120000_backup 恢复到 /var/www/html
增强建议
- 日志回滚:保留最近N次回滚日志。
- 邮件/钉钉通知:回滚完成后可以通知团队。
- 回滚前快照:回滚前先对当前错误版本做一次备份(防止丢失)。
- 并发锁:
flock防止同时执行。
如果有具体部署环境(Kubernetes、Docker、Ansible等),回滚策略会不同,可以进一步补充。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播