贴一个自用的备份数据库并发送到QQ邮箱的脚本

2026年06月13日 8点热度 0人点赞 0条评论

限制:

QQ邮箱最大附件为50M,这里附件分卷大小设置为36M,编码后会增加30%,接近50M上限,并分为多个邮件发送。

数据库账号为:backup,密码保存在:/etc/my.cnf.d/backup.conf ,文件内容为:

[client]
password="123456"

邮件mail配置为 /etc/mail.rc,在最后增加内容,请勿直接复制,替换成自己的内容:

#QQ邮箱
set smtp=smtps://smtp.qq.com
set from="xxxxxx@qq.com"
set smtp-auth-user="xxxxxx@qq.com"
set smtp-auth-password="QQ邮箱TOKEN"
set smtp-auth=login
set smtp-use=starttls

 

备份脚本:backup_database.h

#!/bin/bash
set -euo pipefail

# ========== 配置区 ==========
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)
WORK_DIR="/tmp/db_backup_$$"
FINAL_ARCHIVE="${WORK_DIR}/${BACKUP_DATE}.database.tar.gz"
SPLIT_SIZE="36m"
MAIL_TO="support@mdeve.com"
MAIL_SUBJECT_PREFIX="网站数据备份-${BACKUP_DATE}"
MYSQL_HOST="127.0.0.1"
MYSQL_USER="backup"
MYSQL_CNF="/etc/my.cnf.d/backup.conf"
DATABASES=(wordpress weather_ical proftpd phpmyadmin platform_public platform_logs smsfilter)

# 日志配置
LOG_DIR="/var/log/db_backup"
LOG_FILE="${LOG_DIR}/$(date +%Y%m%d).log"
# ===========================

# 初始化日志目录
if [ ! -d "$LOG_DIR" ]; then
    mkdir -p "$LOG_DIR"
fi

# 日志函数:同时输出到终端和日志文件
log() {
    local level="$1"
    shift
    local message="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}

# 错误处理函数
error_exit() {
    log "ERROR" "$1"
    exit "${2:-1}"
}

# 开始执行
log "INFO" "========== 备份任务开始 =========="
log "INFO" "备份标识: $BACKUP_DATE"
log "INFO" "日志文件: $LOG_FILE"

# 检查依赖命令
for cmd in mysqldump tar split mail; do
    if ! command -v "$cmd" &>/dev/null; then
        error_exit "未找到命令 $cmd,请先安装。"
    fi
done
log "INFO" "依赖命令检查通过"

# 检查 MySQL 配置文件
if [ ! -f "$MYSQL_CNF" ]; then
    error_exit "MySQL配置文件 $MYSQL_CNF 不存在,请创建。"
fi
PERM=$(stat -c "%a" "$MYSQL_CNF" 2>/dev/null || stat -f "%Lp" "$MYSQL_CNF" 2>/dev/null)
if [ "$PERM" != "600" ]; then
    log "WARN" "配置文件 $MYSQL_CNF 权限为 $PERM,建议设为600"
fi

# 创建临时工作目录
cleanup() {
    log "INFO" "清理临时目录: $WORK_DIR"
    rm -rf "$WORK_DIR"
}
trap cleanup EXIT
mkdir -p "$WORK_DIR/sql_dumps"
log "INFO" "临时工作目录: $WORK_DIR"

# 复制脚本自身到工作目录作为附件
SCRIPT_ATTACH="${WORK_DIR}/backup_script.sh"
if [ -f "$0" ]; then
    cp "$0" "$SCRIPT_ATTACH"
    chmod 644 "$SCRIPT_ATTACH"
    log "INFO" "已准备脚本附件: $SCRIPT_ATTACH"
else
    log "WARN" "无法获取脚本自身路径,跳过脚本附件"
    SCRIPT_ATTACH=""
fi

# 导出所有数据库
log "INFO" "开始导出数据库..."
for db in "${DATABASES[@]}"; do
    log "INFO" "  正在导出: $db"
    if mysqldump --defaults-extra-file="$MYSQL_CNF" \
                 -h "$MYSQL_HOST" -u "$MYSQL_USER" \
                 "$db" > "$WORK_DIR/sql_dumps/${db}.sql" 2>>"$LOG_FILE"; then
        log "INFO" "    ✓ $db 导出成功"
    else
        error_exit "    ✗ $db 导出失败"
    fi
done

# 打包所有 SQL 文件
log "INFO" "打包所有SQL文件..."
if tar czf "$FINAL_ARCHIVE" -C "$WORK_DIR/sql_dumps" . 2>>"$LOG_FILE"; then
    log "INFO" "✓ 打包成功: $FINAL_ARCHIVE"
else
    error_exit "✗ tar打包失败"
fi

# 分卷压缩
log "INFO" "分卷压缩(每卷 $SPLIT_SIZE)..."
cd "$WORK_DIR"
if split -b "$SPLIT_SIZE" "$FINAL_ARCHIVE" "${BACKUP_DATE}.database.tar.gz.part-" 2>>"$LOG_FILE"; then
    log "INFO" "✓ 分卷压缩完成"
else
    error_exit "✗ 分卷压缩失败"
fi

# 获取分卷文件列表
mapfile -t PARTS < <(ls "${BACKUP_DATE}.database.tar.gz.part-"* 2>/dev/null)
if [ ${#PARTS[@]} -eq 0 ]; then
    error_exit "没有生成任何分卷文件"
fi
TOTAL_PARTS=${#PARTS[@]}
log "INFO" "共生成 $TOTAL_PARTS 个分卷"

# 准备日志附件文件(将完整日志复制到工作目录作为附件)
LOG_ATTACH="${WORK_DIR}/execution_log.txt"
if [ -f "$LOG_FILE" ]; then
    cp "$LOG_FILE" "$LOG_ATTACH"
    log "INFO" "已准备日志附件: $LOG_ATTACH"
else
    echo "日志文件不存在" > "$LOG_ATTACH"
    log "WARN" "日志文件不存在,已创建空附件"
fi

# 逐个分卷发送邮件(每封邮件附带相同的日志附件和脚本附件)
log "INFO" "开始发送邮件(共 $TOTAL_PARTS 封)..."
for i in "${!PARTS[@]}"; do
    PART_FILE="${PARTS[$i]}"
    PART_NUM=$((i+1))
    MAIL_SUBJECT="${MAIL_SUBJECT_PREFIX} - 分卷 ${PART_NUM}/${TOTAL_PARTS}"
    
    log "INFO" "发送分卷 ${PART_NUM}/${TOTAL_PARTS}: $(basename "$PART_FILE")"
    
    # 构建附件参数(分卷文件 + 日志附件 + 脚本附件)
    ATTACH_ARGS=(-a "$PART_FILE" -a "$LOG_ATTACH")
    if [ -n "$SCRIPT_ATTACH" ] && [ -f "$SCRIPT_ATTACH" ]; then
        ATTACH_ARGS+=(-a "$SCRIPT_ATTACH")
    fi
    
    # 简化的邮件正文
    MAIL_BODY=$(cat <<EOF
网站数据库备份分卷 ${PART_NUM}/${TOTAL_PARTS}。

备份标识:${BACKUP_DATE}
分卷大小:每卷 ${SPLIT_SIZE}
当前附件:$(basename "$PART_FILE")
总卷数:${TOTAL_PARTS}

合并方法(将所有分卷下载到同一目录后执行):
  cat ${BACKUP_DATE}.database.tar.gz.part-* > ${BACKUP_DATE}.database.tar.gz
  tar xzvf ${BACKUP_DATE}.database.tar.gz

本邮件仅包含第 ${PART_NUM} 个分卷,请确保收到所有 ${TOTAL_PARTS} 个分卷后再进行合并。

附带附件:
- execution_log.txt:完整的执行日志
- backup_script.sh:本次使用的备份脚本

详细的执行日志请查看附件:execution_log.txt
EOF
)
    
    # 发送邮件:分卷文件 + 日志附件 + 脚本附件(增加 -v 参数)
    if echo "$MAIL_BODY" | mail -v -s "$MAIL_SUBJECT" "${ATTACH_ARGS[@]}" -- "$MAIL_TO" >>"$LOG_FILE" 2>&1; then
        log "INFO" "  ✓ 分卷 ${PART_NUM}/${TOTAL_PARTS} 发送成功(已附带日志和脚本附件)"
    else
        error_exit "  ✗ 分卷 ${PART_NUM}/${TOTAL_PARTS} 发送失败"
    fi
    
    # 避免发送过快被限流
    sleep 2
done

log "INFO" "所有 ${TOTAL_PARTS} 个分卷已通过 ${TOTAL_PARTS} 封邮件发送完成"
log "INFO" "========== 备份任务结束 =========="

# 删除本次执行生成的临时日志文件(正常结束时)
rm -f "$LOG_FILE"

exit 0

路灯

这个人很懒,什么都没留下

文章评论