Skip to content

第 1 步:准备工作 - 设置 SSH 免密登录

为了让脚本能自动运行而不需要手动输入密码,你需要设置从源服务器(运行 Docker 的服务器)目标备份服务器的 SSH 免密登录。

假设:

  • 源服务器:你当前操作的服务器,IP 为 SOURCE_IP
  • 目标服务器:用于存放备份的服务器,IP 为 TARGET_IP,你将使用 backup_user 用户登录。

在源服务器上执行以下步骤:

  1. 生成 SSH 密钥对(如果还没有的话):

    bash
    ssh-keygen -t rsa -b 4096

    一路按回车,不要设置密码。这会在 ~/.ssh/ 目录下生成 id_rsa (私钥) 和 id_rsa.pub (公钥)。

  2. 将公钥复制到目标服务器: 这是最关键的一步。使用 ssh-copy-id 命令可以轻松完成。

    bash
    ssh-copy-id backup_user@TARGET_IP

    系统会提示你输入一次 backup_user 在目标服务器上的密码。成功后,ssh-copy-id 会自动将你的公钥追加到目标服务器上 ~/.ssh/authorized_keys 文件中。

  3. 测试免密登录: 尝试从源服务器 SSH 到目标服务器,看是否还需要输入密码。

    bash
    ssh backup_user@TARGET_IP

    如果直接登录成功,没有提示输入密码,那么准备工作就完成了!

目标文件结构

/opt/backup/
├── backup.conf   # <-- 新的配置文件,你只需要修改这个文件
└── backup.sh     # <-- 主执行脚本,通常无需修改

第 2 步:创建配置文件 backup.conf

/opt/backup/ 目录下创建一个名为 backup.conf 的文件。将以下内容复制进去,并根据你的实际情况修改这些值

文件: /opt/backup/backup.conf

ini
# =======================================================
# 数据库备份脚本的配置文件 (backup.conf)
# =======================================================

# --- Docker & 数据库配置 ---
CONTAINER_NAME=""
DB_USER="root"
DB_PASS=""
DB_NAME=""

# --- 本地备份配置 ---
# 本地备份文件的临时存储目录 (请使用绝对路径)
# 脚本会自动将备份文件保存在这个目录的 'temp' 子目录中
# 例如,如果这里设置为 /opt/backup,实际文件会存在 /opt/backup/temp
BACKUP_BASE_DIR="/opt/backup"
# 本地备份的保留天数
LOCAL_RETENTION_DAYS=2

# --- rsync 远程同步与清理配置 ---
# 远程服务器上用于登录的用户名
REMOTE_USER="backup_user"
# 远程服务器的 IP 地址或域名
REMOTE_HOST="target_server_ip_or_domain"
# 远程服务器上用于存放备份文件的目录 (请使用绝对路径)
REMOTE_BACKUP_DIR="/path/on/remote/server/for/backups"
# 远程服务器上备份的保留天数 (例如 30 天)
REMOTE_RETENTION_DAYS=30

# --- 通知配置 ---
# 企业微信 Webhook URL (留空则不发送通知)
WEBHOOK_URL="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=  "

第 3 步:创建主脚本 backup.sh

现在,在同一目录 /opt/backup/ 下创建主脚本 backup.sh。这个脚本被设计为可移植的,它会自动查找并加载与它在同一目录下的 backup.conf 文件。

文件: /opt/backup/backup.sh

bash
#!/bin/bash

# ==============================================================================
# Docker 数据库备份、rsync 同步及远程清理的脚本
#
# 此脚本会自动加载同目录下的 backup.conf 配置文件。
# ==============================================================================

# --- 核心逻辑开始,通常无需修改 ---

# 获取脚本所在的绝对路径
# 这使得脚本可以从任何地方被调用(例如在 cron 中)
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
CONFIG_FILE="${SCRIPT_DIR}/backup.conf"

# 检查并加载配置文件
if [ -f "$CONFIG_FILE" ]; then
  # shellcheck source=/dev/null
  source "$CONFIG_FILE"
else
  echo "!!! 致命错误: 配置文件未找到于 ${CONFIG_FILE}"
  exit 1
fi

# 检查关键配置是否已设置
if [ -z "$DB_USER" ] || [ -z "$REMOTE_HOST" ] || [ -z "$BACKUP_BASE_DIR" ]; then
    echo "!!! 致命错误: 配置文件中的关键变量(如 DB_USER, REMOTE_HOST, BACKUP_BASE_DIR)未设置。"
    exit 1
fi

# 脚本健壮性设置:任何命令失败则立即退出
set -e

# --- 动态定义变量 ---
# 定义本地临时备份目录
BACKUP_DIR="${BACKUP_BASE_DIR}/temp"

# --- 函数定义 ---
# 函数:发送通知
send_notification() {
  if [ -z "$WEBHOOK_URL" ]; then return; fi
  local status="$1" message="$2" color="info" title="✅ 数据库备份及远程同步成功"
  [ "$status" = "failure" ] && { color="warning"; title="🔥 数据库备份/同步失败"; }
  local hostname; hostname=$(hostname)
  local json_payload; json_payload=$(printf '{"msgtype": "markdown", "markdown": {"content": "**%s**\n>源主机: `%s`\n>数据库: `%s`\n>%s"}}' "$title" "$hostname" "$DB_NAME" "$message")
  curl -s -X POST "$WEBHOOK_URL" -H 'Content-Type: application/json' -d "$json_payload" > /dev/null
}

# 错误处理
handle_error() {
  local error_message="错误发生在脚本的第 $1 行。操作已中断。"
  echo "!!! $error_message"
  send_notification "failure" "$error_message"
  exit 1
}
trap 'handle_error $LINENO' ERR

# --- 脚本主流程 ---
echo "==> [$(date +'%Y-%m-%d %H:%M:%S')] 开始执行备份、同步和清理任务..."

# 1. 依赖检查
if ! command -v rsync &> /dev/null; then echo "!!! 错误: 未找到 'rsync' 命令。"; exit 1; fi
if ! command -v ssh &> /dev/null; then echo "!!! 错误: 未找到 'ssh' 命令。"; exit 1; fi

# 2. 创建本地和远程备份目录
echo "--> 确保本地备份目录存在: ${BACKUP_DIR}"
mkdir -p "$BACKUP_DIR"
echo "--> 确保远程备份目录存在: ${REMOTE_BACKUP_DIR}"
ssh "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p ${REMOTE_BACKUP_DIR}"

# 3. 执行本地数据库备份
DATE_FORMAT=$(date +'%Y%m%d_%H%M%S')
BACKUP_FILENAME="${DB_NAME}_${DATE_FORMAT}.sql.gz"
LOCAL_BACKUP_FILE="${BACKUP_DIR}/${BACKUP_FILENAME}"

echo "--> 正在创建本地备份: ${LOCAL_BACKUP_FILE}"
docker exec -e MYSQL_PWD="$DB_PASS" "$CONTAINER_NAME" mysqldump -u "$DB_USER" --databases "$DB_NAME" | gzip > "$LOCAL_BACKUP_FILE"
LOCAL_BACKUP_SIZE=$(du -sh "$LOCAL_BACKUP_FILE" | awk '{print $1}')
echo "--> 本地备份创建成功,大小: ${LOCAL_BACKUP_SIZE}"

# 4. 使用 rsync 同步到远程服务器
REMOTE_PATH="${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_BACKUP_DIR}"
echo "--> 正在同步到远程服务器: ${REMOTE_PATH}"
rsync -avz --progress "$LOCAL_BACKUP_FILE" "$REMOTE_PATH"
echo "--> 同步成功。"

# 5. 清理远程服务器上的旧备份
echo "--> 正在清理远程服务器上 ${REMOTE_RETENTION_DAYS} 天前的旧备份..."
ssh "${REMOTE_USER}@${REMOTE_HOST}" "find ${REMOTE_BACKUP_DIR} -name '*.sql.gz' -mtime +${REMOTE_RETENTION_DAYS} -exec rm -f {} \;"
echo "--> 远程清理指令已发送。"

# 6. 清理本地旧备份
echo "--> 正在清理本地 ${LOCAL_RETENTION_DAYS} 天前的旧备份..."
LOCAL_DELETED_COUNT=$(find "$BACKUP_DIR" -name "*.sql.gz" -mtime +"$LOCAL_RETENTION_DAYS" -print | wc -l)
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +"$LOCAL_RETENTION_DAYS" -exec rm -f {} \;
echo "--> 本地清理完成,共删除 ${LOCAL_DELETED_COUNT} 个文件。"

# 7. 发送成功通知
SUCCESS_MSG=$(printf "本地备份: `%s (%s)`\n>同步至: `%s`\n>本地清理: `%d` 个\n>远程保留: 最近 `%d` 天" \
  "$LOCAL_BACKUP_FILE" "$LOCAL_BACKUP_SIZE" "${REMOTE_HOST}:${REMOTE_BACKUP_DIR}" "$LOCAL_DELETED_COUNT" "$REMOTE_RETENTION_DAYS")
send_notification "success" "$SUCCESS_MSG"

echo "--> 成功通知已发送。"
echo "==> [$(date +'%Y-%m-%d %H:%M:%S')] 所有操作完成!"

exit 0

第 4 步:如何使用

  1. 创建目录和文件

    bash
    # 创建主目录
    mkdir -p /opt/backup
    
    # 进入目录
    cd /opt/backup
    
    # 创建并编辑配置文件
    nano backup.conf
    # (将上面的配置内容粘贴进去并修改)
    
    # 创建并编辑主脚本
    nano backup.sh
    # (将上面的脚本内容粘贴进去)
  2. 授予执行权限: 你只需要给主脚本 backup.sh 添加执行权限。

    bash
    chmod +x /opt/backup/backup.sh
  3. 测试运行: 直接运行主脚本。它会自动加载旁边的 backup.conf 文件。

    bash
    /opt/backup/backup.sh
  4. 更新定时任务 (Cron Job): 最后,确保你的 cron 任务指向的是这个主脚本的绝对路径

    bash
    crontab -e

    在编辑器中,你的定时任务应该看起来像这样:

    crontab
    30 3 * * * /opt/backup/backup.sh >> /opt/backup/backup.log 2>&1

    这里我将日志文件也放在了 /opt/backup/ 目录下,方便集中管理。

现在你的设置就非常清晰和专业了:配置和代码完全分离,日常维护只需要修改 backup.conf 文件,而无需触碰核心的 backup.sh 脚本。