最近迁移服务器,优化了一直使用的 VPS 资料备份的脚本,增加结果发送到 telegram,使用配置文件等一些优化,跟大家分享一下。

1. 背景简介

在我的 1C1.3G 40G rackNerd 小鸡 上跑着 mjj 三件套(探针、图床、博客)和一些个人折腾的小脚本,所有服务已经 docker 化,外网入口到服务器后,由 docker 版 nginx 代理转发内部 dockge 管理的各个 docker 服务。

项目文件主要在:

  • 网站项目文件统一放在 /var/www/html
  • Docker 及配置文件及数据放在 /opt/docker/data

2. 备份什么?

为避免数据丢失可能导致不可逆转的损失,定期备份服务器上的文件和数据库,确保始终可以恢复到最新的状态。

每日凌晨四点备份以下数据:

  • 网站文件:包括 HTML、CSS、图片以及其它的静态文件
  • 应用程序代码:如 PHP、Python、等语言编写的源代码
  • 配置文件:有关服务器、数据库、应用程序等的配置设置
  • 数据库:MySQL 保存应用程序的动态数据
  • 镜像压缩包:自定义 Docker 镜像的压缩包

每日打包文件和数据,压缩到一个 gz 文件,并且保留备日志以备检查。

保存最近 7 天 gz 备份文件,并 rsync 同步到拷贝备份机进行异地镜像存储。

3. 如何备份?

使用 tar 命令打包压缩文件

# 备份
cd /backup/path/data && tar zcvf /backup/storage/data.tar.gz --transform='s/^\.\///' .
# 查看
tar -tvf /backup/storage/data.tar.gz
# 解压到指定路径
tar -xzf data.tar.gz -C /path/to/destination example.txt  # 或者 example_dir/

使用 mysqldump 命令备份 MySQL 数据库

# 备份
mysqldump -u $DB_USER -p$DB_PASS $DB_NAME > "/backup/storage/db_backup.sql"
# 还原
mysql -u $DB_USER -p$DB_PASS $DB_NAME < /backup/storage/db_backup.sql

脚本备份过程:

3. 主要代码

# 备份网站
backup_www() {
    cp -rf /var/www/html ${BACKUP_DAYS}/www/
    cd ${BACKUP_DAYS}/www && tar zcvf ${BACKUP_DAYS}/tar/www.tar.gz --transform='s/^\.\///' .
    if [ $? -eq 0 ]; then
        coloured_text "网站 WWW 文件打包成功" "${COLOR_GREEN}"
    else
        ERROR_MESSAGE="网站 WWW 文件打包失败"
        echo "$ERROR_MESSAGE"
        return 1
    fi
    return 0
}
# 备份数据库
backup_mysql() {
    echo "开始备份 MySQL"
    # docker 镜像名称
    local container_name="mysql"
    local backup_time=`date +%Y%m%d%H%M`    
    # database
    local backup_database=app_data
    docker exec -e MYSQL_PWD=$MySQL_PASSWORD $container_name mysqldump -h$MySQL_HOST -P$MySQL_PORT -u$MySQL_USER -B $backup_database > $BACKUP_DAYS/sql/$backup_database-$backup_time.sql
    if [ $? -eq 0 ]; then
        coloured_text "备份数据库 ${backup_database} 成功" "${COLOR_GREEN}"
    else
        ERROR_MESSAGE="备份数据库失败,请检查配置参数"
        echo "$ERROR_MESSAGE"
        return 1
    fi

    # 打包SQL文件
    echo "开始打包 MySQL"
    cd ${BACKUP_DAYS}/sql && tar zcvf ${BACKUP_DAYS}/tar/sql.tar.gz --transform='s/^\.\///' .
    if [ $? -eq 0 ]; then
        coloured_text "数据库 MySQL 文件打包成功" "${COLOR_GREEN}"
    else
        ERROR_MESSAGE="数据库 MySQL 文件打包失败"
        echo "$ERROR_MESSAGE"
        return 1
    fi
    return 0
}
# 清理旧备份文件
clean_old_files() {
    echo "开始删除过期文件"
    # data 目录    
    num=$(ls -l ${BACKUP_DATA}/backup-*.tar.gz | grep "^-" | wc -l)
    while [ ${num} -gt ${MAX_NUM} ]
    do
        filename=$(ls -rt ${BACKUP_DATA}/backup-*.tar.gz | head -n 1)
        if [ -n "${filename}" ]; then
            rm -f "${filename}"
            echo "删除旧文件 ${filename}"
        fi        
        local log=$(echo "$filename" | sed 's#/data/#/logs/#; s#\.tar\.gz$#.log#')
        if [ -n "${log}" ]; then
            rm -f "${log}"
            echo "删除日志 ${log}"
        fi
        let num--
    done
    # days 目录(保留 1 天,共2 份)
    find $BACKUP_ROOT/days -maxdepth 1 -type d -mtime +1 -exec echo "删除旧文件夹" {} \; -exec rm -r {} +
    coloured_text "删除过期文件完毕" "${COLOR_GREEN}"
    return 0
}
# 同步到远程文件机
sync_to_remote() {
    echo "开始同步文件到远程服务器"
    rsync -e "ssh -p${PUSH_TO_PORT}" -avz --delete ${BACKUP_DATA}/ root@${PUSH_TO_SERVER}:${BACKUP_DATA}/
    if [ $? -eq 0 ]; then
        coloured_text "同步文件到远程服务器成功" "${COLOR_GREEN}"
    else
        ERROR_MESSAGE="推送到备份机失败,请检查网络设置是否正确"
        echo "$ERROR_MESSAGE"
        return 1
    fi    
    return 0
}

main() {
    # 执行备份任务
    backup_www || return 1
    backup_mysql || return 1

    # 清理旧备份文件
    clean_old_files || return 1

    # 同步到远程文件机
    sync_to_remote || return 1

    # 任务结束
    return 0
}

# 捕获信号
trap cleanup INT TERM EXIT
# 执行主程序
main
status=$? 
if [ "$status" -ne 0 ]; then
    echo "script failed"
fi
# 移除捕获
trap - INT TERM EXIT
coloured_text "备份全部完毕,最终状态 ${status}" "${COLOR_GREEN}"
exit $status