贴一个自用的自动备份服务器数据的脚本

2026年06月14日 126点热度 0人点赞 0条评论

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

[client]
user=backup
password="123456"
host=127.0.0.1

mysql备份账号权限:

REVOKE ALL PRIVILEGES ON *.* FROM 'backup'@'localhost'; REVOKE GRANT OPTION ON *.* FROM 'backup'@'localhost'; GRANT SELECT, RELOAD, PROCESS, SHOW DATABASES, LOCK TABLES, REPLICATION CLIENT, EVENT, SHOW VIEW ON *.* TO 'backup'@'localhost' REQUIRE NONE WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0;

 

每天定时备份一次 crontab 脚本:

2 3 * * * /data/backup/backup_full.sh >> /var/log/backup_full.log 1>&1

 

备份脚本:backup_full.h

#!/bin/bash
set -euo pipefail
set -o pipefail

# ==================== 配置区 ====================
BACKUP_BASE="/data/backup"
KEEP_DAYS=7
LOG_RETENTION_DAYS=30
MIN_FREE_SPACE_MB=2048          # 保底值,实际会动态计算

MYSQL_BACKUP_CONFIG="/etc/my.cnf.d/backup.conf"
MYSQL_BACKUP_OPTS="--defaults-extra-file=$MYSQL_BACKUP_CONFIG --host=127.0.0.1 --user=backup"

WEB_SITES=(
    "weather_ical:/data/www/w.mdeve.com/"
    "default:/data/www/default/"
    "platform:/data/www/platform/"
    "smsfilter:/data/www/smsfilter.mdeve.com/"
)

CONFIG_DIRS=(
    "etc:/etc/"
    "php.etc:/usr/local/php/etc/"
    "etc.nginx:/usr/local/etc/"
    "frp:/usr/local/frp/"
)

LOG_DIR="/var/log/backup"

# ==================== 初始化 ====================
BACKUP_DATETIME=$(date +%Y%m%d_%H%M%S)          # 加入时间戳,避免同天覆盖
TEMP_DIR="$BACKUP_BASE/$BACKUP_DATETIME"
FINAL_TARBALL="$BACKUP_BASE/$BACKUP_DATETIME.tar.gz"
ENCRYPTED_FILE="$BACKUP_BASE/$BACKUP_DATETIME.tar.gz.enc"
INFO_FILE="$BACKUP_BASE/${BACKUP_DATETIME}_info.txt"
LOG_FILE="$LOG_DIR/backup_${BACKUP_DATETIME}.log"

umask 077
mkdir -p "$LOG_DIR"

# ==================== 日志函数(同时输出终端和日志文件) ====================
log_info() {
    local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [信息] $*"
    echo "$msg" | tee -a "$LOG_FILE"
}
log_error() {
    local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [错误] $*"
    echo "$msg" | tee -a "$LOG_FILE" >&2
}
log_success() {
    local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [成功] $*"
    echo "$msg" | tee -a "$LOG_FILE"
}

# ==================== 清理函数 ====================
cleanup() {
    local exit_code=$?
    if [ -n "${TEMP_DIR:-}" ] && [ -d "$TEMP_DIR" ] && [ "$TEMP_DIR" != "/" ]; then
        log_info "正在清理临时目录: $TEMP_DIR"
        rm -rf "$TEMP_DIR"
    fi
    # 如果加密成功,删除未加密的中间文件
    if [ -f "$FINAL_TARBALL" ] && [ -f "$ENCRYPTED_FILE" ]; then
        rm -f "$FINAL_TARBALL"
        log_info "已删除未加密的临时压缩包"
    fi
    if [ $exit_code -eq 0 ]; then
        log_success "脚本执行成功"
    else
        log_error "脚本异常终止,退出码 $exit_code(详见日志 $LOG_FILE)"
    fi
    exit $exit_code
}
trap cleanup EXIT
trap 'log_error "命令在第 $LINENO 行失败"' ERR

# ==================== 前置检查 ====================
check_mysql_config_perms() {
    if [ ! -f "$MYSQL_BACKUP_CONFIG" ]; then
        log_error "MySQL 配置文件 $MYSQL_BACKUP_CONFIG 不存在"
        exit 1
    fi
    local perms
    perms=$(stat -c %a "$MYSQL_BACKUP_CONFIG" 2>/dev/null || stat -f %Lp "$MYSQL_BACKUP_CONFIG" 2>/dev/null)
    if [ "$perms" != "600" ]; then
        log_error "配置文件权限为 $perms(应为 600)"
        exit 1
    fi
    log_info "MySQL 配置文件权限检查通过"
}

check_mariadb_backup() {
    if ! command -v mariadb-backup &>/dev/null; then
        log_error "未找到 mariadb-backup 命令,请安装 mariadb-backup 软件包"
        exit 1
    fi
    log_info "mariadb-backup 已安装"
}

check_mysql_client() {
    if ! command -v mysql &>/dev/null; then
        log_error "未找到 mysql 客户端命令"
        exit 1
    fi
    log_info "mysql 客户端已安装"
}

check_binlog_privilege() {
    log_info "检查 backup 用户是否具备 BINLOG MONITOR 权限..."
    if mysql $MYSQL_BACKUP_OPTS -e "SHOW MASTER STATUS" >/dev/null 2>&1; then
        log_success "权限检查通过"
    else
        log_error "backup 用户缺少执行 SHOW MASTER STATUS 的权限"
        log_error "请以 root 登录 MariaDB 执行:"
        log_error "  GRANT BINLOG MONITOR ON *.* TO 'backup'@'localhost';"
        log_error "  (若版本较旧,使用 GRANT SUPER ON *.* TO 'backup'@'localhost';)"
        exit 1
    fi
}

check_disk_space() {
    # 估算所需空间:数据库数据目录大小 + 网站目录大小 + 10% 余量
    local estimated=0
    # 获取数据库数据目录大小(假设在 /var/lib/mysql)
    if [ -d /var/lib/mysql ]; then
        local db_size=$(du -sb /var/lib/mysql 2>/dev/null | cut -f1 || echo 0)
        estimated=$((estimated + db_size))
    fi
    # 网站目录大小
    for entry in "${WEB_SITES[@]}"; do
        local src_dir="${entry#*:}"
        if [ -d "$src_dir" ]; then
            local size=$(du -sb "$src_dir" 2>/dev/null | cut -f1 || echo 0)
            estimated=$((estimated + size))
        fi
    done
    # 加上配置目录等
    estimated=$((estimated + 100*1024*1024))  # 预留 100M
    estimated=$((estimated * 120 / 100))       # 加 20% 余量
    local required_mb=$((estimated / 1024 / 1024))
    [ $required_mb -lt $MIN_FREE_SPACE_MB ] && required_mb=$MIN_FREE_SPACE_MB

    local available_kb=$(df --output=avail "$BACKUP_BASE" | tail -1)
    local available_mb=$((available_kb / 1024))
    if [ $available_mb -lt $required_mb ]; then
        log_error "磁盘空间不足:可用 ${available_mb}MB,需要约 ${required_mb}MB"
        exit 1
    fi
    log_info "磁盘空间检查通过:可用 ${available_mb}MB,估算需要 ${required_mb}MB"
}

# ==================== 清理旧文件(基于日期前缀) ====================
clean_old_backups() {
    log_info "开始清理 ${KEEP_DAYS} 天前的旧备份..."
    local cutoff=$(date -d "$KEEP_DAYS days ago" +%Y%m%d)
    local count=0
    for f in "$BACKUP_BASE"/*.tar.gz.enc; do
        [ -f "$f" ] || continue
        local fname=$(basename "$f")
        # 提取日期部分(YYYYMMDD)
        local fdate=${fname%%_*}   # 取第一个下划线前的部分
        if [[ $fdate =~ ^[0-9]{8}$ ]] && [ "$fdate" -lt "$cutoff" ]; then
            log_info "删除旧备份: $f"
            rm -f "$f"
            ((count++))
        fi
    done
    for f in "$BACKUP_BASE"/*_info.txt; do
        [ -f "$f" ] || continue
        local fname=$(basename "$f")
        local fdate=${fname%%_*}   # 同样取日期
        if [[ $fdate =~ ^[0-9]{8}$ ]] && [ "$fdate" -lt "$cutoff" ]; then
            log_info "删除旧信息文件: $f"
            rm -f "$f"
        fi
    done
    log_info "共删除 $count 个旧备份文件"
}

clean_old_logs() {
    log_info "开始清理 ${LOG_RETENTION_DAYS} 天前的旧日志..."
    local cutoff=$(date -d "$LOG_RETENTION_DAYS days ago" +%Y%m%d)
    local count=0
    for f in "$LOG_DIR"/backup_*.log; do
        [ -f "$f" ] || continue
        local fname=$(basename "$f")
        if [[ $fname =~ backup_([0-9]{8})_ ]]; then
            local fdate=${BASH_REMATCH[1]}
            if [ "$fdate" -lt "$cutoff" ]; then
                log_info "删除旧日志: $f"
                rm -f "$f"
                ((count++))
            fi
        fi
    done
    log_info "共删除 $count 个旧日志文件"
}

# ==================== 生成随机密码(通过环境变量传递) ====================
generate_password() {
    local len=$((RANDOM % 13 + 21))
    export BACKUP_PASS=$(tr -dc 'A-Za-z0-9!@#$%^&*()_+-=' < /dev/urandom | head -c "$len")
}

# ==================== 生成信息文件 ====================
generate_info_file() {
    cat > "$INFO_FILE" <<EOF
============================================================
  备份信息文件
  生成时间: $(date '+%Y-%m-%d %H:%M:%S')
  备份标识: $BACKUP_DATETIME
============================================================

一、备份内容
-------------
- MariaDB 全量物理备份(目录: mysql_backup/)
- 网站目录(各 .tar 文件)
- 系统配置文件(各 .tar 文件)

打包为 ${BACKUP_DATETIME}.tar.gz,并 AES-256 加密为 ${BACKUP_DATETIME}.tar.gz.enc。

二、加密密码(请妥善保管,丢失无法恢复)
----------------------------------------
${BACKUP_PASS}

三、SHA-256 校验值(加密前压缩包内文件)
-----------------------------------------
EOF
    cd "$TEMP_DIR"
    find . -type f -exec sha256sum {} \; >> "$INFO_FILE"
    cd - >/dev/null

    cat >> "$INFO_FILE" <<EOF

四、恢复步骤
------------
1. 解密:
   export PASS="${BACKUP_PASS}"
   openssl enc -d -aes-256-cbc -salt -pbkdf2 -in ${BACKUP_DATETIME}.tar.gz.enc -out ${BACKUP_DATETIME}.tar.gz -pass env:PASS

2. 解压:
   tar -xzf ${BACKUP_DATETIME}.tar.gz

3. 恢复 MariaDB:
   systemctl stop mariadb
   rm -rf /var/lib/mysql/*
   mariadb-backup --copy-back --target-dir=./${BACKUP_DATETIME}/mysql_backup
   chown -R mysql:mysql /var/lib/mysql
   # 若启用 SELinux,执行:restorecon -R /var/lib/mysql
   systemctl start mariadb

4. 恢复网站和配置:
   根据各 .tar 文件解压至对应目录。

⚠️ 安全提示:本文件包含明文密码,请立即移至安全介质并加密存储。
============================================================
EOF
    log_info "信息文件已生成: $INFO_FILE"
}

# ==================== 主流程 ====================
log_info "==================== 备份开始 ===================="
log_info "备份标识: $BACKUP_DATETIME"
log_info "日志文件: $LOG_FILE"

check_mysql_config_perms
check_mariadb_backup
check_mysql_client
check_binlog_privilege

mkdir -p "$BACKUP_BASE"
cd "$BACKUP_BASE"
check_disk_space

[ -d "$TEMP_DIR" ] && rm -rf "$TEMP_DIR"
mkdir -p "$TEMP_DIR"

# 记录开始时间
start_time=$(date +%s)

# ==================== 1. MariaDB 全量物理备份 ====================
log_info "开始使用 mariadb-backup 进行全量备份..."
BACKUP_DATA_DIR="$TEMP_DIR/mysql_backup"
mkdir -p "$BACKUP_DATA_DIR"

if mariadb-backup $MYSQL_BACKUP_OPTS --backup --target-dir="$BACKUP_DATA_DIR" --no-version-check; then
    log_success "MariaDB 全量备份完成 -> $BACKUP_DATA_DIR"
else
    log_error "mariadb-backup 备份失败"
    exit 1
fi

# ==================== 2. 备份网站目录 ====================
log_info "开始打包网站目录..."
for entry in "${WEB_SITES[@]}"; do
    tar_name="${entry%%:*}.tar"
    src_dir="${entry#*:}"
    if [ ! -d "$src_dir" ]; then
        log_error "源目录 $src_dir 不存在"
        exit 1
    fi
    log_info "正在创建 $tar_name(来源 $src_dir)"
    if tar --exclude='*.log*' --warning=no-file-changed -cf "$TEMP_DIR/$tar_name" -C "$src_dir" . >/dev/null 2>&1; then
        log_success "打包完成: $tar_name"
    else
        log_error "打包 $src_dir 失败"
        exit 1
    fi
done

# ==================== 3. 备份配置文件 ====================
log_info "开始打包配置文件..."
for entry in "${CONFIG_DIRS[@]}"; do
    tar_name="${entry%%:*}.tar"
    src_dir="${entry#*:}"
    if [ ! -d "$src_dir" ]; then
        log_error "源目录 $src_dir 不存在"
        exit 1
    fi
    log_info "正在创建 $tar_name(来源 $src_dir)"
    if tar --warning=no-file-changed -cf "$TEMP_DIR/$tar_name" -C "$src_dir" . >/dev/null 2>&1; then
        log_success "打包完成: $tar_name"
    else
        log_error "打包 $src_dir 失败"
        exit 1
    fi
done

# ==================== 4. 打包整个临时目录 ====================
log_info "正在打包为 $FINAL_TARBALL ..."
if tar czf "$FINAL_TARBALL" -C "$BACKUP_BASE" "$BACKUP_DATETIME" >/dev/null 2>&1; then
    log_success "打包完成"
else
    log_error "创建压缩包失败"
    exit 1
fi

# ==================== 5. 生成随机密码并加密(密码通过环境变量传递) ====================
log_info "正在生成随机密码(21~33位)..."
generate_password
log_info "随机密码已生成(不显示)"

log_info "正在使用 AES-256 加密压缩包..."
if openssl enc -aes-256-cbc -salt -pbkdf2 -in "$FINAL_TARBALL" -out "$ENCRYPTED_FILE" -pass env:BACKUP_PASS; then
    log_success "加密完成: $ENCRYPTED_FILE"
else
    log_error "加密失败"
    exit 1
fi

# ==================== 6. 生成信息文件 ====================
generate_info_file

# ==================== 7. 清理中间文件 ====================
rm -rf "$TEMP_DIR"
rm -f "$FINAL_TARBALL"
log_info "已清理临时文件和未加密压缩包"

# ==================== 8. 清理旧备份和日志 ====================
clean_old_backups
clean_old_logs

# 耗时统计
end_time=$(date +%s)
duration=$((end_time - start_time))
log_info "备份总耗时: $((duration / 60)) 分 $((duration % 60)) 秒"

log_success "备份流程全部完成!"
log_info "最终加密备份: $ENCRYPTED_FILE"
log_info "信息文件: $INFO_FILE"
log_info "==================== 备份结束 ===================="

exit 0

路灯

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

文章评论