服务器自动备份与日志清理Shell脚本完全指南
嘿,朋友!今天咱们来聊聊服务器运维中最实用的两件事——自动备份和日志清理。这两招学会了,你的服务器日常管理就能轻松一大半。我会给你可以直接复制使用的脚本,保证你看完就能上手。
一、先搞懂为什么需要这些脚本
想象一下,你的服务器就像你家的小仓库。里面存着重要的文件(数据库、网站代码等),每天产生的日志文件就像仓库里的快递单——时间一长,堆得乱七八糟,还占地方。
自动备份就是定时把你仓库里最值钱的东西拷贝一份存起来,万一出问题了能恢复。
日志清理就是定期把那些过期的快递单扔掉,给仓库腾地方。
这两个事儿,靠手工做太麻烦了,所以我们要写脚本让电脑帮我们干。
二、数据库自动备份脚本
下面这个脚本可以给MySQL数据库做自动备份,支持多台数据库实例,备份后会压缩并保留最近N天的数据。
#!/bin/bash
# ============================================
# MySQL数据库自动备份脚本
# 作者: Agnes
# 功能: 定时备份MySQL数据库,压缩存储,自动清理旧备份
# ============================================
# ========== 配置区(按需修改)==========
BACKUP_DIR="/data/backup/mysql" # 备份文件存放目录
DB_USER="root" # 数据库用户名
DB_PASS="your_password" # 数据库密码
DB_HOST="localhost" # 数据库地址
LOG_FILE="/data/backup/mysql/backup.log" # 操作日志文件
RETENTION_DAYS=7 # 保留多少天的备份(超过的自动删除)
# 要备份的数据库列表,空则备份全部
DATABASES=("db_article" "db_user" "db_order")
# ============================================
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 初始化日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "========== 开始数据库备份 =========="
# 获取当前日期,用于文件名
DATE=$(date '+%Y%m%d_%H%M%S')
SUCCESS_COUNT=0
FAIL_COUNT=0
# 如果DATABASES为空,则备份所有数据库
if [ ${#DATABASES[@]} -eq 0 ]; then
# 获取所有数据库名称
DATABASES=($(mysql -u"$DB_USER" -p"$DB_PASS" -h"$DB_HOST" -e "SHOW DATABASES;" \
2>/dev/null | grep -Ev "^(Database|information_schema|performance_schema|mysql|sys)$"))
log "未指定数据库,将备份全部数据库: ${DATABASES[*]}"
fi
# 遍历每个数据库进行备份
for DB in "${DATABASES[@]}"; do
BACKUP_FILE="$BACKUP_DIR/${DB}_${DATE}.sql"
GZ_FILE="$BACKUP_DIR/${DB}_${DATE}.sql.gz"
log "正在备份数据库: $DB"
# 使用mysqldump备份数据库
if mysqldump -u"$DB_USER" -p"$DB_PASS" -h"$DB_HOST" \
--single-transaction --routines --triggers --events \
--quick --lock-tables=false "$DB" > "$BACKUP_FILE" 2>>"$LOG_FILE"; then
# 压缩备份文件
gzip "$BACKUP_FILE"
if [ -f "$GZ_FILE" ]; then
BACKUP_SIZE=$(du -sh "$GZ_FILE" | cut -f1)
log "✓ 数据库 $DB 备份成功,压缩后大小: $BACKUP_SIZE"
((SUCCESS_COUNT++))
else
log "✗ 数据库 $DB 压缩失败"
((FAIL_COUNT++))
fi
else
log "✗ 数据库 $DB 备份失败,请检查密码或数据库连接"
# 清理可能产生的空文件
rm -f "$BACKUP_FILE" "$GZ_FILE"
((FAIL_COUNT++))
fi
done
# 清理超过保留天数的旧备份
log "正在清理 ${RETENTION_DAYS} 天前的旧备份..."
DELETED_COUNT=0
while IFS= read -r -d '' OLD_FILE; do
rm -f "$OLD_FILE"
((DELETED_COUNT++))
done < <(find "$BACKUP_DIR" -name "*.sql.gz" -type f -mtime +${RETENTION_DAYS} -print0)
if [ $DELETED_COUNT -gt 0 ]; then
log "已清理 $DELETED_COUNT 个旧备份文件"
fi
# 显示备份目录使用情况
BACKUP_TOTAL_SIZE=$(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1)
log "备份目录总大小: $BACKUP_TOTAL_SIZE"
log "备份结果: 成功 $SUCCESS_COUNT 个,失败 $FAIL_COUNT 个"
log "========== 数据库备份结束 =========="
三、服务器系统备份脚本
这个脚本可以备份整个网站目录、配置文件和数据库,一键搞定服务器整体备份。
#!/bin/bash
# ============================================
# 服务器整体自动备份脚本
# 功能: 备份网站文件、配置、数据库,支持远程传输
# ============================================
# ========== 配置区 ==========
# 备份根目录
BACKUP_ROOT="/data/backup"
BACKUP_DATE=$(date '+%Y%m%d_%H%M%S')
BACKUP_DIR="$BACKUP_ROOT/$BACKUP_DATE"
LOG_FILE="$BACKUP_ROOT/backup.log"
RETENTION_DAYS=15
# 要备份的目录(可添加多个)
SOURCE_DIRS=(
"/var/www/html" # 网站代码
"/etc/nginx" # Nginx配置
"/etc/apache2" # Apache配置(如果有)
"/home" # 用户数据
)
# MySQL配置
MYSQL_USER="root"
MYSQL_PASS="your_password"
MYSQL_BACKUP_FILE="$BACKUP_DIR/mysql_all.sql.gz"
# SFTP远程传输配置(可选,留空则不传输)
SFTP_HOST=""
SFTP_USER=""
SFTP_PASS=""
SFTP_REMOTE_DIR="/remote_backup"
# ========== 函数定义 ==========
log() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
echo "$msg" | tee -a "$LOG_FILE"
}
check_command() {
command -v "$1" >/dev/null 2>&1 || { log "错误: 缺少命令 $1,请先安装"; exit 1; }
}
# ========== 开始执行 ==========
log "========== 服务器整体备份开始 =========="
# 检查必要命令
check_command "mysqldump"
check_command "tar"
check_command "gzip"
# 创建备份目录结构
mkdir -p "$BACKUP_DIR"/{files,mysql}
log "备份目录已创建: $BACKUP_DIR"
# ---- 第一部分:备份数据库 ----
log "--- 开始备份MySQL数据库 ---"
if mysqldump -u"$MYSQL_USER" -p"$MYSQL_PASS" \
--all-databases --single-transaction --routines --triggers \
--quick --lock-tables=false 2>/dev/null | gzip > "$MYSQL_BACKUP_FILE"; then
DB_SIZE=$(du -sh "$MYSQL_BACKUP_FILE" | cut -f1)
log "✓ MySQL数据库备份成功,大小: $DB_SIZE"
else
log "✗ MySQL数据库备份失败,跳过"
fi
# ---- 第二部分:备份网站文件和配置 ----
log "--- 开始备份文件和配置 ---"
for DIR in "${SOURCE_DIRS[@]}"; do
if [ -d "$DIR" ]; then
DIR_NAME=$(echo "$DIR" | sed 's|/||g')
ARCHIVE_FILE="$BACKUP_DIR/files/${DIR_NAME}.tar.gz"
log "正在打包: $DIR"
# 排除一些不需要备份的内容(临时文件、缓存等)
tar -czf "$ARCHIVE_FILE" \
--exclude="*/node_modules/*" \
--exclude="*/.git/*" \
--exclude="*/vendor/*" \
--exclude="*/__pycache__/*" \
--exclude="*/cache/*" \
--exclude="*/tmp/*" \
-C "$(dirname "$DIR")" "$(basename "$DIR")" 2>/dev/null
if [ $? -eq 0 ]; then
FILE_SIZE=$(du -sh "$ARCHIVE_FILE" | cut -f1)
log "✓ $DIR 备份成功,大小: $FILE_SIZE"
else
log "✗ $DIR 备份失败"
rm -f "$ARCHIVE_FILE"
fi
else
log "! 跳过不存在的目录: $DIR"
fi
done
# ---- 第三部分:生成备份清单 ----
MANIFEST_FILE="$BACKUP_DIR/manifest.txt"
log "正在生成备份清单..."
echo "备份时间: $(date '+%Y-%m-%d %H:%M:%S')" > "$MANIFEST_FILE"
echo "备份服务器: $(hostname)" >> "$MANIFEST_FILE"
echo "--- 备份文件列表 ---" >> "$MANIFEST_FILE"
find "$BACKUP_DIR" -type f -exec ls -lh {} \; >> "$MANIFEST_FILE"
echo "--- 总大小 ---" >> "$MANIFEST_FILE"
du -sh "$BACKUP_DIR" >> "$MANIFEST_FILE"
log "✓ 备份清单已生成: $MANIFEST_FILE"
# ---- 第四部分:上传到远程服务器(可选)----
if [ -n "$SFTP_HOST" ] && [ -n "$SFTP_USER" ]; then
log "--- 开始上传到远程服务器 ---"
REMOTE_PATH="${SFTP_USER}@${SFTP_HOST}:${SFTP_REMOTE_DIR}/${BACKUP_DATE}"
# 检查是否安装了lftp
if command -v lftp >/dev/null 2>&1; then
lftp -u "$SFTP_USER","$SFTP_PASS" sftp://$SFTP_HOST <<EOF
set ssl:verify-certificate no
mirror -R --ignore-time --parallel=4 "$BACKUP_DIR" "$SFTP_REMOTE_DIR"
bye
EOF
if [ $? -eq 0 ]; then
log "✓ 备份已上传到远程服务器"
else
log "✗ 远程上传失败,请检查网络连接"
fi
else
log "! 未安装lftp,跳过远程传输,可用 'apt install lftp' 安装"
fi
fi
# ---- 第五部分:清理旧备份 ----
log "--- 清理 ${RETENTION_DAYS} 天前的旧备份 ---"
DELETED_DIRS=0
while IFS= read -r -d '' OLD_DIR; do
rm -rf "$OLD_DIR"
((DELETED_DIRS++))
log "已删除旧备份: $OLD_DIR"
done < <(find "$BACKUP_ROOT" -maxdepth 1 -type d -mtime +${RETENTION_DAYS} -print0)
log "共清理 $DELETED_DIRS 个旧备份目录"
# ---- 第六部分:统计总结 ----
TOTAL_SIZE=$(du -sh "$BACKUP_ROOT" 2>/dev/null | cut -f1)
BACKUP_COUNT=$(ls -1 "$BACKUP_ROOT" | grep -E '^[0-9]{8}' | wc -l)
log "========== 备份完成 =========="
log "当前备份目录总数: $BACKUP_COUNT 个"
log "备份目录总大小: $TOTAL_SIZE"
log "日志文件: $LOG_FILE"
四、日志自动清理脚本
服务器运行久了,日志文件会堆积如山。这个脚本帮你智能清理各类日志,既保留最近必要的日志,又不会误删重要数据。
#!/bin/bash
# ============================================
# 服务器日志智能清理脚本
# 功能: 清理系统日志、应用日志、压缩旧日志
# ============================================
# ========== 配置区 ==========
LOG_FILE="/var/log/log_cleaner.log"
DRY_RUN=true # true=只查看不删除(测试用),false=真正执行清理
# 日志清理规则
# 格式: 目录:保留天数:操作(compress/delete/purge)
declare -A CLEAN_RULES=(
["/var/log/nginx"]=30:delete
["/var/log/apache2"]=30:delete
["/var/log/mysql"]=7:delete
["/var/log/messages"]=30:compress
["/var/log/secure"]=30:compress
["/home/*/.local/share/Trash"]=30:delete
["/tmp"]=7:delete
["/var/tmp"]=7:delete
)
# 应用日志目录(根据实际需求修改)
APP_LOG_DIRS=(
"/var/www/*/logs"
"/data/logs"
"/home/*/logs"
)
# ========== 函数定义 ==========
log() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
echo "$msg" >> "$LOG_FILE"
echo "$msg"
}
# 计算目录大小
get_dir_size() {
du -sh "$1" 2>/dev/null | cut -f1
}
# 统计文件数量
count_files() {
find "$1" -type f 2>/dev/null | wc -l
}
# ========== 开始执行 ==========
log "========== 日志清理任务开始 =========="
if [ "$DRY_RUN" = true ]; then
log "[测试模式] 当前不会删除任何文件,仅显示将要清理的内容"
fi
TOTAL_FREED=0
TOTAL_FILES=0
# ---- 第一部分:清理系统日志 ----
log "--- 清理系统日志 ---"
for DIR in "${!CLEAN_RULES[@]}"; do
RULE="${CLEAN_RULES[$DIR]}"
DAYS="${RULE%:*}"
ACTION="${RULE#*:}"
# 扩展通配符目录
for REAL_DIR in $DIR; do
if [ ! -d "$REAL_DIR" ]; then
continue
fi
DIR_SIZE=$(get_dir_size "$REAL_DIR")
FILE_COUNT=$(count_files "$REAL_DIR")
log "扫描目录: $REAL_DIR (大小: $DIR_SIZE, 文件数: $FILE_COUNT)"
if [ "$ACTION" = "delete" ]; then
# 查找并删除超过保留天数的文件
while IFS= read -r -d '' FILE; do
FILE_SIZE=$(du -sh "$FILE" 2>/dev/null | cut -f1)
if [ "$DRY_RUN" != true ]; then
rm -f "$FILE"
log " 已删除: $FILE ($FILE_SIZE)"
else
log " [将删除]: $FILE ($FILE_SIZE)"
fi
((TOTAL_FREED+=$(stat -c%s "$FILE" 2>/dev/null || echo 0)))
((TOTAL_FILES++))
done < <(find "$REAL_DIR" -type f -mtime +${DAYS} ! -name "*.gz" -print0)
elif [ "$ACTION" = "compress" ]; then
# 查找超过保留天数且未压缩的文件进行压缩
while IFS= read -r -d '' FILE; do
FILE_SIZE=$(du -sh "$FILE" 2>/dev/null | cut -f1)
if [ "$DRY_RUN" != true ]; then
gzip "$FILE"
log " 已压缩: $FILE -> ${FILE}.gz"
else
log " [将压缩]: $FILE ($FILE_SIZE)"
fi
((TOTAL_FILES++))
done < <(find "$REAL_DIR" -type f -mtime +${DAYS} ! -name "*.gz" -print0)
fi
done
done
# ---- 第二部分:清理应用日志 ----
log "--- 清理应用日志 ---"
for PATTERN in "${APP_LOG_DIRS[@]}"; do
for APP_DIR in $PATTERN; do
if [ ! -d "$APP_DIR" ]; then
continue
fi
# 清理超过30天的日志
while IFS= read -r -d '' FILE; do
if [ "$DRY_RUN" != true ]; then
rm -f "$FILE"
log " 已删除应用日志: $FILE"
else
log " [将删除]: $FILE"
fi
((TOTAL_FILES++))
done < <(find "$APP_DIR" -type f \( -name "*.log" -o -name "*.log.*" \) -mtime +30 -print0)
# 压缩超过7天的未压缩日志
while IFS= read -r -d '' FILE; do
if [ "$DRY_RUN" != true ]; then
gzip "$FILE"
log " 已压缩应用日志: $FILE"
else
log " [将压缩]: $FILE"
fi
((TOTAL_FILES++))
done < <(find "$APP_DIR" -type f -name "*.log" -mtime +7 ! -name "*.gz" -print0)
done
done
# ---- 第三部分:清理Docker日志(如果有)----
if command -v docker >/dev/null 2>&1; then
log "--- 清理Docker容器日志 ---"
DOCKER_LOG_COUNT=0
while IFS= read -r -d '' CONTAINER; do
LOG_PATH=$(docker inspect "$CONTAINER" --format='{{.LogPath}}' 2>/dev/null)
if [ -n "$LOG_PATH" ] && [ -f "$LOG_PATH" ]; then
LOG_SIZE=$(du -sh "$LOG_PATH" 2>/dev/null | cut -f1)
CONTAINER_NAME=$(docker inspect "$CONTAINER" --format='{{.Name}}' 2>/dev/null)
if [ "$DRY_RUN" != true ]; then
# 清空日志文件内容(不删除文件本身,避免容器报错)
truncate -s 0 "$LOG_PATH"
log " 已清空容器日志: $CONTAINER_NAME ($LOG_SIZE)"
else
log " [将清空] 容器 $CONTAINER_NAME 日志: $LOG_PATH ($LOG_SIZE)"
fi
((DOCKER_LOG_COUNT++))
fi
done < <(docker ps -q 2>/dev/null)
log "共处理 $DOCKER_LOG_COUNT 个Docker容器日志"
fi
# ---- 第四部分:清理系统临时文件 ----
log "--- 清理临时文件 ---"
# 清理apt缓存
if command -v apt-get >/dev/null 2>&1; then
if [ "$DRY_RUN" != true ]; then
apt-get autoremove -y >/dev/null 2>&1
apt-get clean >/dev/null 2>&1
log " 已清理apt缓存"
else
log " [将清理] apt缓存"
fi
fi
# 清理pip缓存
if command -v pip3 >/dev/null 2>&1; then
if [ "$DRY_RUN" != true ]; then
pip3 cache purge >/dev/null 2>&1
log " 已清理pip缓存"
else
log " [将清理] pip缓存"
fi
fi
# 清理用户回收站
for TRASH_DIR in /home/*/Trash; do
if [ -d "$TRASH_DIR" ]; then
while IFS= read -r -d '' FILE; do
if [ "$DRY_RUN" != true ]; then
rm -rf "$FILE"
((TOTAL_FILES++))
fi
done < <(find "$TRASH_DIR" -type f -mtime +7 -print0)
fi
done
# ========== 统计总结 ==========
log "========== 日志清理任务结束 =========="
log "共处理 $TOTAL_FILES 个文件"
log "清理日志文件: $LOG_FILE"
log "=============================="
五、配置定时任务让脚本自动运行
脚本写好了,但要让它每天自动执行,还得配置crontab。下面教你如何设置。
5.1 编辑crontab
在终端执行:
crontab -e
5.2 添加定时任务
根据你的需求,在文件末尾添加如下内容:
# 每天凌晨2点执行数据库备份
0 2 * * * /bin/bash /data/backup/mysql_backup.sh >> /data/backup/mysql/backup.log 2>&1
# 每天凌晨3点执行服务器整体备份
0 3 * * * /bin/bash /data/backup/server_backup.sh >> /data/backup/backup.log 2>&1
# 每周日凌晨4点执行日志清理
0 4 * * 0 /bin/bash /data/backup/log_cleaner.sh >> /data/backup/log_cleaner.log 2>&1
# 每月1号凌晨5点执行深度清理(清理更旧的备份)
0 5 1 * * /bin/bash /data/backup/log_cleaner.sh --deep-clean >> /data/backup/log_cleaner.log 2>&1
5.3 设置脚本执行权限
chmod +x /data/backup/mysql_backup.sh
chmod +x /data/backup/server_backup.sh
chmod +x /data/backup/log_cleaner.sh
六、一些实用小技巧
6.1 备份前的健康检查
在备份脚本最前面加上这个检查,可以让脚本更智能:
# 检查磁盘空间是否充足(少于1GB时报警)
AVAILABLE_SPACE=$(df -BG / | tail -1 | awk '{print $4}' | sed 's/G//')
if [ "$AVAILABLE_SPACE" -lt 1 ]; then
echo "磁盘空间不足,当前可用: ${AVAILABLE_SPACE}GB" | tee -a "$LOG_FILE"
exit 1
fi
# 检查MySQL是否在运行
if ! pgrep -x "mysqld" > /dev/null; then
echo "MySQL未运行,尝试启动..." | tee -a "$LOG_FILE"
systemctl start mysqld
sleep 3
if ! pgrep -x "mysqld" > /dev/null; then
echo "MySQL启动失败,备份终止" | tee -a "$LOG_FILE"
exit 1
fi
fi
6.2 备份成功后的通知
你可以让脚本在备份完成后发送通知:
# 方式1: 发送邮件通知
echo "数据库备份完成,成功: ${SUCCESS_COUNT}个,失败: ${FAIL_COUNT}个" | \
mail -s "服务器备份通知 - $(hostname)" your_email@example.com
# 方式2: 发送微信/钉钉通知(需要webhook)
curl -s -X POST "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"msgtype\": \"text\", \"text\": {\"content\": \"服务器备份完成,成功: ${SUCCESS_COUNT}个,失败: ${FAIL_COUNT}个\"}}"
6.3 验证备份文件是否完整
在备份脚本中加入验证步骤,确保备份可用:
# 验证MySQL备份文件是否完整
if file "$GZ_FILE" | grep -q "gzip compressed"; then
# 尝试解压并检查内容
if zcat "$GZ_FILE" | head -5 | grep -q "MySQL dump"; then
log "✓ 备份文件验证通过"
else
log "✗ 备份文件可能损坏,请检查"
fi
fi
七、完整使用流程总结
创建备份目录
mkdir -p /data/backup/mysql mkdir -p /data/backup/server将脚本保存到服务器
- 把上面的脚本分别保存为:
/data/backup/mysql_backup.sh/data/backup/server_backup.sh/data/backup/log_cleaner.sh
- 把上面的脚本分别保存为:
修改配置区参数
- 根据你自己的服务器情况,修改脚本顶部的配置参数
- 数据库密码、备份路径、保留天数等
设置执行权限
chmod +x /data/backup/*.sh先用测试模式跑一次
DRY_RUN=true /data/backup/log_cleaner.sh这样可以看看脚本会清理什么,确认没问题后再关掉测试模式。
配置crontab定时执行
crontab -e # 添加上面的定时任务定期检查备份和日志 “`bash
查看最近的备份
ls -lh /data/backup/mysql/
# 查看备份日志 tail -50 /data/backup/mysql/backup.log
# 查看清理日志 tail -50 /data/backup/log_cleaner.log “`
好了,以上就是你直接能用的服务器备份和日志清理全套方案。记住,先测试再上线,尤其是清理脚本,建议先用DRY_RUN=true跑一遍看看效果,确认不会误删重要文件后再正式运行。
有问题随时问我,祝你服务器运维一路顺风!
