#!/bin/bash # # Name : backup_all.sh # Description : 备份源服务器数据并传输到目标机器执行数据恢复 # Version : 2.5 # Author :James # Date : 2025年12月18日 17:30 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Update # 重构了大部分逻辑,更新细节如下 # ● 使用rsync进行数据传输 # ● 使用pgiz进行全核压缩 # ● 增加对于密钥连接的使用 # ● 添加迁移时间及时长统计 # ● 添加双方系统的版本检查 # ● 使用ssh保活参数防止连接中断 # ● 迁移失败时重启系统程序 # ● 增强脚本对错误处理的健壮性 # ● 优化tar压缩data子目录逻辑 # ● 添加crm-import目录 # ● 终端迁移日志保存至文件(/home/migration.log) # ● 数据库备份添加删库参数 # ● 只对业务库进行导出 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # store_file="/root/restore_data.sh" prikey="/root/.ssh/migration" pubkey="/root/.ssh/migration.pub" ver_file="/ipcc/etc/pub/version" dbpw="sg6d9ybbnMv41SKT" db_list="ccdata ccsys syslog userdata" ssh_opts='-o ServerAliveInterval=60 -o ServerAliveCountMax=3' copy_id_opts='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 -o ConnectionAttempts=1' running_flag=1 all_tmp="" sql_tmp="" function confirm() { if [ ! -t 0 ]; then echo "非交互环境,脚本退出!" exit 1 fi while true; do read -p "确认退出?[y/n] " -n 1 choice echo case $choice in [yY]) echo "脚本已退出!" exit 1 ;; [nN]) echo "脚本继续运行..." break ;; *) echo "无效值,请重新输入!" ;; esac done } function cleanup() { local exit_code=$? local delete_flag=0 if [ $exit_code -ne 0 ] && [ $running_flag -eq 0 ]; then echo "脚本非正常退出,重启程序..." start_service echo "删除tmp文件..." rm -rf $all_tmp rm -rf $sql_tmp fi [ -f $prikey ] && rm -rf $prikey && delete_flag=1 [ -f $pubkey ] && rm -rf $pubkey && delete_flag=1 [ "$delete_flag" -eq 1 ] && echo "删除密钥对..." } function timelen() { local start_time=$(date +"%m-%d %H:%M") local start_second=$SECONDS "$@" local stop_second=$SECONDS local stop_time=$(date +"%m-%d %H:%M") local duration=$(($stop_second - $start_second)) # 特殊处理总耗时为0秒的情况 if [ $duration -eq 0 ]; then echo "$* 迁移耗时 0 秒" return fi # 计算小时、分钟和秒 local hours=$(($duration / 3600)) local minutes=$((($duration / 60) % 60)) local seconds=$(($duration % 60)) # 根据时长拼接字符 local time_string="" if [ $hours -gt 0 ]; then time_string+=" ${hours} 小时" fi if [ $minutes -gt 0 ]; then time_string+=" ${minutes} 分" fi if [ $seconds -gt 0 ]; then time_string+=" ${seconds} 秒" fi # 输出拼接的时间 echo "$* 开始执行时间:$start_time,结束执行时间:$stop_time,耗时$time_string" } #停止服务 function stop_service() { /etc/init.d/ipccd stop /etc/init.d/freeswitch stop /etc/init.d/nginx stop /etc/init.d/php-fpm stop /etc/init.d/redis stop running_flag=0 } #启用服务 function start_service() { /etc/init.d/freeswitch start /etc/init.d/nginx start /etc/init.d/php-fpm start /etc/init.d/redis start /etc/init.d/ipccd start running_flag=1 } #安装必要软件 function install_pkg() { local pkgs=("pigz" "rsync") local pkg local role [ "$1" == 0 ] && role="本机" # shellcheck disable=SC2034 [ "$1" == 1 ] && role="目标机器" for pkg in "${pkgs[@]}"; do command -v $pkg &>/dev/null && echo "$role$pkg已安装" || yum install "$pkg" -y if [ $? -ne 0 ]; then echo "${pkg}未成功安装,脚本退出!" exit 1 fi done } function check_version() { [ -f $ver_file ] && master_ver=$(cat $ver_file) slave_ver=$(ssh $ssh_opts -p $port root@$new_host -i $prikey -- "[ -f $ver_file ]&&cat $ver_file") if [ "$master_ver" == "$slave_ver" ]; then echo "版本检查通过,开始执行迁移。" else echo -e "本机版本:$master_ver\n待迁移机器版本:$slave_ver\n系统版本不一致,迁移退出!" exit 1 fi } function check_dirs() { local base_path="/home/ipcc/data" local sub_dirs=("audio" "blacklist" "callee" "call-script" "certification" "electronic" "hide-number-tmp-dir" "inbound-plot" "ivraudio" "ivraudio1" "knowledge-file" "satisfaction" "theTxtModel" "upload-file" "voice-notify-audio" "crm-import") local existing=() for dir in "${sub_dirs[@]}"; do local full_path="$base_path/$dir" if [ ! -d "$full_path" ]; then continue fi if [ -z "$(ls -A "$full_path")" ]; then continue fi existing+=("$dir") done printf '%s\n' "${existing[@]}" } function migrate_data() { local sql_file="/home/alldatabase.sql" local all_file="/home/backup_data.tar.gz" all_tmp="$all_file.tmp" sql_tmp="$sql_file.tmp" local backup_dirs=() mapfile -t backup_dirs < <(check_dirs) check_version #检测是否存在旧的备份文件 if [ -f $sql_file ] || [ -f $all_file ]; then echo "检测到存在之前的备份文件:" [ -f $sql_file ] && echo " - $sql_file" [ -f $all_file ] && echo " - $all_file" while true; do read -p "是否使用该文件继续迁移?[y/n] " -n 1 use_old echo case $use_old in [yY]) echo "使用旧备份文件继续..." break ;; [nN]) echo "删除旧备份文件,重新备份..." rm -f $sql_file $all_file break ;; *) echo "无效值,请重新输入!" ;; esac done fi stop_service #数据库备份 [ -f $sql_file ] || { # /usr/local/mysql/bin/mysqldump -uroot -p$dbpw --add-drop-database --databases $db_list >$sql_tmp /usr/local/mysql/bin/mysqldump -uroot -p$dbpw --databases $db_list >$sql_tmp if [ $? -ne 0 ]; then echo "数据库备份失败!" exit 1 else mv $sql_tmp $sql_file fi } #将所有备份数据进行打包压缩 [ -f $all_file ] || { tar -cf - \ -C /home/ipcc/data "${backup_dirs[@]}" \ -C /usr/local/freeswitch {conf,db,sounds} \ -C /usr/local/redis-5.0.4/run dump.rdb \ -C /var/www/web-v2 oem \ -C /home alldatabase.sql | pigz >$all_tmp # if [ "${PIPESTATUS[0]}" -ne 0 ]||[ "${PIPESTATUS[1]}" -ne 0 ]; then if [ $? -ne 0 ]; then echo "备份文件打包失败" exit 1 else mv $all_tmp $all_file fi } #将备份数据拷贝至新服务器上,并自动执行脚本恢复 rsync -avPh -e "ssh $ssh_opts -p $port -i $prikey" $all_file $store_file root@$new_host:/home && ssh $ssh_opts -p $port root@$new_host -i $prikey -- "bash /home/restore_data.sh" if [ $? -ne 0 ]; then echo "数据恢复失败,脚本退出!" exit 1 fi #删除备份压缩包 rm -rf $sql_file $all_file start_service echo "***** 数据迁移完成,下一步请对${new_host}进行授权!*****" } function migrate_voicerecord() { local voicerecord="/home/ipcc/data/voicerecord" local zip_record="/home/voicerecord.tar.gz" #将录音目录打包 tar -cf - -C $voicerecord . | pigz >$zip_record #将录音包传至新服务器并执行录音恢复操作 rsync -avPh -e "ssh $ssh_opts -p $port -i $prikey" $zip_record root@$new_host:$voicerecord && ssh $ssh_opts -p $port root@$new_host -i $prikey -- " \ pigz -dc $voicerecord/voicerecord.tar.gz|tar -xf - -C $voicerecord && \ chown -R nobody:nobody /home/ipcc/data/voicerecord && \ chown -R nobody:nobody /home/ipcc/data/record-tmp" if [ $? -ne 0 ]; then echo "录音恢复执行失败,脚本退出!" exit 1 fi #删除录音压缩文件 rm -rf $zip_record echo "***** 录音迁移完成,请检查录音文件夹是否创建! *****" } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 保存迁移日志到文件 exec > >(tee -a /home/migration.log) 2>&1 # 捕获信号执行对应函数 trap confirm INT trap cleanup EXIT # restore脚本限定在/root目录下 if [ ! -f "$store_file" ]; then echo "必须存放在/root目录下!" exit 1 fi # 本机安装软件包 install_pkg 0 # 输入服务器地址和端口 read -p "请输入新服务器的IP地址:" new_host read -p "请输入新服务器的 SSH 端口(默认22):" port port=${port:-22} # 使用公私钥实现ssh免密登录 yes | ssh-keygen -t rsa -P "" -f $prikey ssh -p $port $copy_id_opts root@$new_host "mkdir -p ~/.ssh && cat > ~/.ssh/authorized_keys" < $pubkey || { echo "连接失败,脚本退出!" exit 1 } # ssh-copy-id -f -i $pubkey -p $port $copy_id_opts root@$new_host || { # echo "连接失败,脚本退出!" # exit 1 # } # 对端安装软件包 max_retries=3 for ((i = 0; i <= $max_retries; i++)); do ssh $ssh_opts -p $port root@$new_host -i $prikey -- "$(declare -f install_pkg); install_pkg 1" if [ $? -eq 0 ]; then break else if [ $i -eq $max_retries ]; then echo "安装软件包失败,请检查后手动安装!" exit 1 fi echo "安装失败,重试第[$(($i + 1))/$max_retries]次..." fi done # 迁移类型选择 echo "< 1.将除语音文件外的所有数据迁移到新服务器 >" echo "< 2.仅将语音文件迁移到新服务器 >" while true; do read -p "请输入您的选择[1/2]:" choice case $choice in 1) timelen migrate_data break ;; 2) timelen migrate_voicerecord break ;; *) echo "请输入选项1或2!" ;; esac done