宝塔面板免费版 7.2.0版实现自动备份网站到Dropbox

  • A+
所属分类:python笔记
本文信息本文由方法SEO顾问发表于2020-04-2314:38:51,共 10053 字,转载请注明:宝塔面板免费版 7.2.0版实现自动备份网站到Dropbox_【方法SEO顾问】

手贱把BT给升级到最新的了,发现它的备份模块重写了,原来捣鼓的自动备份网站到dropbox功能不能用了,花了点时间研究了一下,重新捣鼓回来了,记录一下。

工具:

1、dropbox_uploader.sh,使用过程略,不会用的自行谷歌

2、/www/server/panel/class/panelBackup.py

dropbox_uploader.sh放到/www/server/panel/class/文件夹下,然后改panelBackup.py文件,引入dropbox_uploader.sh来实现自动备份。

原理其实非常简单,在系统备份完了以后,自动调用DB的API来将备份的文件上传。

修改后的panelBackup.py源代码如下:

#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: 黄文良 <287962566@qq.com>
#-------------------------------------------------------------------

#------------------------------
# 数据备份模块
#------------------------------
import os
import sys
import json
import re
import time

os.chdir('/www/server/panel')
sys.path.insert(0,'class/')
import public
_VERSION = 1.1

class backup:
    _path = None
    _exclude = ""
    _err_log = '/tmp/backup_err.log'
    _inode_min = 10
    _db_mysql = None
    _cloud = None
    def __init__(self,cloud_object = None):
        self._cloud = cloud_object
        self._path = public.M('config').where("id=?",(1,)).getField('backup_path')

    def echo_start(self):
        print("="*90)
        print("★开始备份[{}]".format(public.format_date()))
        print("="*90)

    def echo_end(self):
        print("="*90)
        print("☆备份完成[{}]".format(public.format_date()))
        print("="*90)
        print("\n")

    def echo_info(self,msg):
        print("|-{}".format(msg))

    def echo_error(self,msg):
        print("=" * 90)
        print("|-错误:{}".format(msg))

    #构造排除
    def get_exclude(self,exclude = []):
        if not exclude:
            tmp_exclude = os.getenv('BT_EXCLUDE')
            if tmp_exclude:
                exclude = tmp_exclude.split(',')
        if not exclude: return ""
        for ex in exclude:
            self._exclude += " --exclude=\"" + ex + "\""
        self._exclude += " "
        return self._exclude

    def GetDiskInfo2(self):
        #取磁盘分区信息
        temp = public.ExecShell("df -T -P|grep '/'|grep -v tmpfs")[0]
        tempInodes = public.ExecShell("df -i -P|grep '/'|grep -v tmpfs")[0]
        temp1 = temp.split('\n')
        tempInodes1 = tempInodes.split('\n')
        diskInfo = []
        n = 0
        cuts = []
        for tmp in temp1:
            n += 1
            try:
                inodes = tempInodes1[n-1].split()
                disk = re.findall(r"^(.+)\s+([\w\.]+)\s+([\w\.]+)\s+([\w\.]+)\s+([\w\.]+)\s+([\d%]{2,4})\s+(/.{0,50})$",tmp.strip())
                if disk: disk = disk[0]
                if len(disk) < 6: continue
                if disk[2].find('M') != -1: continue
                if disk[2].find('K') != -1: continue
                if len(disk[6].split('/')) > 10: continue
                if disk[6] in cuts: continue
                if disk[6].find('docker') != -1: continue
                if disk[1].strip() in ['tmpfs']: continue
                arr = {}
                arr['filesystem'] = disk[0].strip()
                arr['type'] = disk[1].strip()
                arr['path'] = disk[6]
                tmp1 = [disk[2],disk[3],disk[4],disk[5]]
                arr['size'] = tmp1
                arr['inodes'] = [inodes[1],inodes[2],inodes[3],inodes[4]]
                diskInfo.append(arr)
            except:
                continue
        return diskInfo


    #取磁盘可用空间
    def get_disk_free(self,dfile):
        diskInfo = self.GetDiskInfo2()
        if not diskInfo: return '',0,0
        _root = None
        for d in diskInfo:
            if d['path'] == '/': 
                _root = d
                continue
            if re.match("^{}/.+".format(d['path']),dfile):
                return d['path'],float(d['size'][2]) * 1024,int(d['inodes'][2])
        if _root:
            return _root['path'],float(_root['size'][2]) * 1024,int(_root['inodes'][2])
        return '',0,0


    #备份指定目录 
    def backup_path(self,spath,dfile = None,exclude=[],save=3):
        self.echo_start()
        if not os.path.exists(spath):
            self.echo_error('指定目录{}不存在!'.format(spath))
            return False

        if spath[-1] == '/':
            spath = spath[:-1]
        
        dirname = os.path.basename(spath) 
        
        if not dfile:
            fname = 'path_{}_{}.tar.gz'.format(dirname,public.format_date("%Y%m%d_%H%M%S"))
            dfile = os.path.join(self._path,'path',fname)
        
        if not self.backup_path_to(spath,dfile,exclude):
            return False
        
        pdata = {
            'type': '2',
            'name': spath,
            'pid': 0,
            'filename': dfile,
            'addtime': public.format_date(),
            'size': os.path.getsize(dfile)
        }
        public.M('backup').insert(pdata)
        backups = public.M('backup').where('type=? and pid=? and name=?',('2',0,spath)).field('id,filename').select()
        self.delete_old(backups,save)
        self.echo_end()
        return dfile

    
    #清理过期备份文件
    def delete_old(self,backups,save):
        self.echo_info('保留最新的备份数:{} 份'.format(save))
        num = len(backups) - int(save)
        if  num > 0:
            self.echo_info('-' * 90)
            for backup in backups:
                if os.path.exists(backup['filename']):
                    os.remove(backup['filename'])
                public.M('backup').where('id=?',(backup['id'],)).delete()
                num -= 1
                self.echo_info(u"已清理过期备份文件:" + backup['filename'])
                os.system("bash ./class/dropbox_uploader.sh delete /{}".format(backup['filename'].split('/')[-1]))
                if num < 1: break

    #压缩目录
    def backup_path_to(self,spath,dfile,exclude = [],siteName = None):
        if not os.path.exists(spath):
            self.echo_error('指定目录{}不存在!'.format(spath))
            return False

        if spath[-1] == '/':
            spath = spath[:-1]

        dirname = os.path.basename(spath)
        dpath = os.path.dirname(dfile)
        if not os.path.exists(dpath):
            os.makedirs(dpath,384)
        
        p_size = public.get_path_size(spath)
        self.get_exclude(exclude)
        exclude_config = self._exclude
        if not self._exclude:
            exclude_config = "未设置"
        
        if siteName:
            self.echo_info('备份网站:{}'.format(siteName))
            self.echo_info('网站根目录:{}'.format(spath))
        else:
            self.echo_info('备份目录:{}'.format(spath))
        
        self.echo_info("目录大小:{}".format(public.to_size(p_size)))
        self.echo_info('排除设置:{}'.format(exclude_config))
        disk_path,disk_free,disk_inode = self.get_disk_free(dfile)
        self.echo_info("分区{}可用磁盘空间为:{},可用Inode为:{}".format(disk_path,public.to_size(disk_free),disk_inode))
        if disk_path:
            if disk_free < p_size:
                self.echo_error("目标分区可用的磁盘空间小于{},无法完成备份,请增加磁盘容量,或在设置页面更改默认备份目录!".format(public.to_size(p_size)))
                return False

            if disk_inode < self._inode_min:
                self.echo_error("目标分区可用的Inode小于{},无法完成备份,请增加磁盘容量,或在设置页面更改默认备份目录!".format(self._inode_min))
                return False

        stime = time.time()
        self.echo_info("开始压缩文件:{}".format(public.format_date(times=stime)))
        if os.path.exists(dfile):
            os.remove(dfile)
        public.ExecShell("cd " + os.path.dirname(spath) + " && tar zcvf '" + dfile + "' " + self._exclude + " '" + dirname + "' 2>{err_log} 1> /dev/null".format(err_log = self._err_log))
        tar_size = os.path.getsize(dfile)
        if tar_size < 1:
            self.echo_error("数据压缩失败")
            self.echo_info(public.readFile(self._err_log))
            return False
        self.echo_info("文件压缩完成,耗时{:.2f}秒,压缩包大小:{}".format(time.time() - stime,public.to_size(tar_size)))
        if siteName:
            self.echo_info("网站已备份到:{}".format(dfile))
            os.system("bash ./class/dropbox_uploader.sh upload {} /".format(dfile))
        else:
            self.echo_info("目录已备份到:{}".format(dfile))
            os.system("bash ./class/dropbox_uploader.sh upload {} /".format(dfile))
        if os.path.exists(self._err_log):
            os.remove(self._err_log)
        
        return dfile

    #备份指定站点
    def backup_site(self,siteName,save = 3 ,exclude = []):
        self.echo_start()
        find = public.M('sites').where('name=?',(siteName,)).field('id,path').find()
        spath = find['path']
        pid = find['id']
        fname = 'web_{}_{}.tar.gz'.format(siteName,public.format_date("%Y%m%d_%H%M%S"))
        dfile = os.path.join(self._path,'site',fname)
        if not self.backup_path_to(spath,dfile,exclude,siteName=siteName):
            return False

        pdata = {
            'type': 0,
            'name': fname,
            'pid': pid,
            'filename': dfile,
            'addtime': public.format_date(),
            'size': os.path.getsize(dfile)
        }
        public.M('backup').insert(pdata)

        #清理多余备份     
        backups = public.M('backup').where('type=? and pid=? and filename!=? and filename!=? and filename!=? and filename!=? and filename!=?',('0',pid,'alioss','txcos','upyun','qiniu','ftp')).field('id,filename').select()
        self.delete_old(backups,save)
        self.echo_end()
        return dfile
            

    #备份所有站点
    def backup_site_all(self,save = 3):
        sites = public.M('sites').field('name').select()
        for site in sites:
            self.backup_site(site['name'],save)

    #配置
    def mypass(self,act):
        conf_file = '/etc/my.cnf'
        public.ExecShell("sed -i '/user=root/d' {}".format(conf_file))
        public.ExecShell("sed -i '/password=/d' {}".format(conf_file))
        if act:
            password = public.M('config').where('id=?',(1,)).getField('mysql_root')
            mycnf = public.readFile(conf_file)
            src_dump = "[mysqldump]\n"
            sub_dump = src_dump + "user=root\npassword=\"{}\"\n".format(password)
            if not mycnf: return False
            mycnf = mycnf.replace(src_dump,sub_dump)
            if len(mycnf) > 100: public.writeFile(conf_file,mycnf)
            return True
        return True

    #map to list
    def map_to_list(self,map_obj):
        try:
            if type(map_obj) != list and type(map_obj) != str: map_obj = list(map_obj)
            return map_obj
        except: return []

    #备份指定数据库
    def backup_database(self,db_name,dfile = None,save=3):
        self.echo_start()
        if not dfile:
            fname = 'db_{}_{}.sql.gz'.format(db_name,public.format_date("%Y%m%d_%H%M%S"))
            dfile = os.path.join(self._path,'database',fname)
        else:
            fname = os.path.basename(dfile)
        
        dpath = os.path.dirname(dfile)
        if not os.path.exists(dpath):
            os.makedirs(dpath,384)

        import panelMysql
        if not self._db_mysql:self._db_mysql = panelMysql.panelMysql()
        d_tmp = self._db_mysql.query("select sum(DATA_LENGTH)+sum(INDEX_LENGTH) from information_schema.tables where table_schema='%s'" % db_name)
        p_size = self.map_to_list(d_tmp)[0][0]
        
        if p_size == None:
            self.echo_error('指定数据库 `{}` 没有任何数据!'.format(db_name))
            return

        character = public.get_database_character(db_name)

        self.echo_info('备份数据库:{}'.format(db_name))
        self.echo_info("数据库大小:{}".format(public.to_size(p_size)))
        self.echo_info("数据库字符集:{}".format(character))
        disk_path,disk_free,disk_inode = self.get_disk_free(dfile)
        self.echo_info("分区{}可用磁盘空间为:{},可用Inode为:{}".format(disk_path,public.to_size(disk_free),disk_inode))
        if disk_path:
            if disk_free < p_size:
                self.echo_error("目标分区可用的磁盘空间小于{},无法完成备份,请增加磁盘容量,或在设置页面更改默认备份目录!".format(public.to_size(p_size)))
                return False

            if disk_inode < self._inode_min:
                self.echo_error("目标分区可用的Inode小于{},无法完成备份,请增加磁盘容量,或在设置页面更改默认备份目录!".format(self._inode_min))
                return False
        
        stime = time.time()
        self.echo_info("开始导出数据库:{}".format(public.format_date(times=stime)))
        if os.path.exists(dfile):
            os.remove(dfile)
        self.mypass(True)
        public.ExecShell("/www/server/mysql/bin/mysqldump --default-character-set="+ character +" --force --hex-blob --opt " + db_name + " 2>"+self._err_log+"| gzip > " + dfile)
        self.mypass(False)
        gz_size = os.path.getsize(dfile)
        if gz_size < 400:
            self.echo_error("数据库导出失败!")
            self.echo_info(public.readFile(self._err_log))
            return False
        self.echo_info("数据库备份完成,耗时{:.2f}秒,压缩包大小:{}".format(time.time() - stime,public.to_size(gz_size)))
        self.echo_info("数据库已备份到:{}".format(dfile))
        os.system("bash ./class/dropbox_uploader.sh upload {} /".format(dfile))

        if os.path.exists(self._err_log):
            os.remove(self._err_log)

        pid = public.M('databases').where('name=?',(db_name)).getField('id')
        pdata = {
            'type': '1',
            'name': fname,
            'pid': pid,
            'filename': dfile,
            'addtime': public.format_date(),
            'size': os.path.getsize(dfile)
        }
        public.M('backup').insert(pdata)

        backups = public.M('backup').where('type=? and pid=? and filename!=? and filename!=? and filename!=? and filename!=? and filename!=?',('1',pid,'alioss','txcos','upyun','qiniu','ftp')).field('id,filename').select()
        self.delete_old(backups,save)
        self.echo_end()
        return dfile
        

    #备份所有数据库
    def backup_database_all(self,save = 3):
        databases = public.M('databases').field('name').select()
        for database in databases:
            self.backup_database(database['name'],save=save)

    

  • 版权声明:除非注明,本博客均为北京SEO方法的原创文章,转载或引用请以超链接形式标明本文地址,否则会在SEO圈内公开此种不尊重版权的行为,谢谢合作!本文地址:https://seofangfa.com/python-note/bt-dropbox-uploader.html
  • 转载请注明:宝塔面板免费版 7.2.0版实现自动备份网站到Dropbox_ 【方法SEO顾问】

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: