插件位置:/www/server/panel/plugin/txcos/txcos_main.py

修改后重启宝塔服务,新代码如下:

#!/usr/bin/python
# coding: utf-8
# -----------------------------
# 宝塔Linux面板网站备份工具 - TXCOS
# -----------------------------
import sys, os,re
if sys.version_info[0] == 2:
    reload(sys)
    sys.setdefaultencoding('utf-8')
os.chdir('/www/server/panel')
sys.path.insert(0,"class/")
import public, db, time,re,json
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client


# 腾讯云oss 的类
class txcos_main:
    __oss = None
    __bucket_path = None
    __error_count = 0
    __secret_id = 'None'
    __secret_key = 'None'
    __region = 'None'
    __Bucket = 'None'
    __oss_path=None
    __error_msg = "ERROR: 无法连接腾讯云COS !"
    __setupPath = '/www/server/panel/plugin/txcos'
    __exclude = ""
    
    '''
     __oss         : COS 客户端的对象
    __bucket_path : COS 的路径 例如:/   /data
    __error_count : 错误次数
    __secret_id  : 腾讯云COS 的secret_id 
    __secret_key : 腾讯云COS 的secret_key
    __region     : 腾讯云COS 的地区
    __Bucket     : COS 的Bucket
    __error_msg  : 错误次数
    __error_msg   : 错误信息
    '''

    def __init__(self):
        self.__conn()
        self.get_exclode()

    def set_cos(self):
        cfile = 'plugin/txcos/config.conf'
        fp = open(cfile, 'r')
        keys = fp.read().split('|')
        self.__secret_id = keys[0]
        self.__secret_key = keys[1]
        self.__region = keys[2]
        self.__Bucket = keys[3]
        self.__bucket_path = self.get_path(keys[4]) 
        try:
            config = CosConfig(Region=self.__region, SecretId=self.__secret_id, SecretKey=self.__secret_key, Token=None,Scheme='http')
            self.__oss = CosS3Client(config)
        except:
            if self.__oss == None:
                self.__conn()
            return json.dumps(self.__error_msg)


    def __conn(self):
        cfile = 'plugin/txcos/config.conf'
        if not os.path.exists(cfile): cfile = 'data/txcos.conf';
        if not os.path.exists(cfile): public.writeFile(cfile, '');
        fp = open(cfile, 'r')
        if not fp:
            print('ERROR: 请检查atxcos.conf文件中是否有腾讯云secret_id相关信息!')
        keys = fp.read().split('|')
        if len(keys) < 4:
            keys = ['', '', '', '', '/']
        if len(keys) < 5: keys.append('/');

        self.__secret_id = keys[0]
        self.__secret_key = keys[1]
        self.__region = keys[2]
        self.__Bucket = keys[3]
        self.__bucket_path = self.get_path(keys[4]) 
        try:
            config = CosConfig(Region=self.__region, SecretId=self.__secret_id, SecretKey=self.__secret_key, Token=None,Scheme='http')
            self.__oss = CosS3Client(config)
        except:
            if self.__oss==None:return json.dumps(self.__error_msg)

    def GetConfig(self, get):
        path = self.__setupPath + '/config.conf'
        if not os.path.exists(path):
            if os.path.exists('conf/atxcos.conf'): public.writeFile(path, public.readFile('conf/atxcos.conf'));
        if not os.path.exists(path): return ['', '', '', '', '/'];
        conf = public.readFile(path)
        if not conf: return ['', '', '', '', '/']
        result = conf.split('|')
        if len(result) < 5: result.append('/');
        return result

    def SetConfig(self, get):
        path = self.__setupPath + '/config.conf'
        conf = get.secret_id.strip() + '|' + get.secret_key.strip() + '|' + get.region.strip() + '|' + get.Bucket.strip() + '|' + get.bucket_path.strip()
        public.writeFile(path, conf)
        return public.returnMsg(True, '设置成功!')

    # 上传文件
    def upload_file(self, filename):
        if not self.__oss:
            self.set_cos()
            if not self.__oss:
                return False
        try:
            # 断点续传
            filepath,key= os.path.split(filename)
            print(key)
            key = self.__bucket_path + key
            print(key)
            # 短点续传
            response = self.__oss.upload_file(
                Bucket=self.__Bucket,
                Key=key,
                MAXThread=10,
                PartSize=5,
                LocalFilePath=filename)
        except:
            time.sleep(1)
            self.__error_count += 1
            if self.__error_count < 2:  # 重试2次
                self.sync_date()
                self.upload_file(filename)
            print(self.__error_msg)
            return None

    def create_dir(self, get):
        if not self.__oss:
            self.set_cos()
            if not self.__oss:
                return False
        path = self.get_path(get.path + get.dirname)
        filename = '/tmp/dirname.pl'
        public.writeFile(filename, '')
        response = self.__oss.put_object(
            Bucket=self.__Bucket,
            Body=b'',
            Key=path,
        )
        os.remove(filename)
        return public.returnMsg(True, '创建成功!')

    def get_list(self, get):
        
        try:
            data = []
            dir_list = []
            #path = self.get_path(get.path) ant修改
            path = self.__bucket_path+self.get_path(get.path) 
            if 'Contents'  in self.__oss.list_objects(Bucket=self.__Bucket, MaxKeys=100, Delimiter='/', Prefix=path):
                for b in self.__oss.list_objects(Bucket=self.__Bucket, MaxKeys=100, Delimiter='/', Prefix=path)['Contents']:
                    tmp = {}
                    b['Key'] = b['Key'].replace(path, '')
                    if not b['Key']: continue
                    tmp['name'] = b['Key']
                    tmp['size'] = b['Size']
                    tmp['type'] = b['StorageClass']
                    tmp['download'] = self.download_file(path + b['Key'])
                    tmp['time'] = b['LastModified']
                    data.append(tmp)
            else:
                pass
            if 'CommonPrefixes' in self.__oss.list_objects(Bucket=self.__Bucket, MaxKeys=100, Delimiter='/', Prefix=path):
                for i in self.__oss.list_objects(Bucket=self.__Bucket, MaxKeys=100, Delimiter='/', Prefix=path)['CommonPrefixes']:
                    if not i['Prefix']: continue
                    dir_dir=i['Prefix'].split('/')[-2]+'/'
                    dir_list.append(dir_dir)
            else:
                pass
            mlist = {}
            mlist['path'] = get.path 
            mlist['list'] = data
            mlist['dir'] = dir_list
            return mlist
        except:
            mlist = {}
            if self.__oss:
                mlist['status']=True
            else:
                mlist['status']=False
            mlist['path'] = get.path
            mlist['list'] = data
            mlist['dir'] = dir_list
            return mlist

    def sync_date(self):
        public.ExecShell("ntpdate 0.asia.pool.ntp.org")

    def download_file(self, filename, Expired=300):
        if not self.__oss:
            self.set_cos()
            if self.__oss:
                return False
        try:
            response = self.__oss.get_presigned_download_url(
                Bucket=self.__Bucket,
                Key=filename
            )
            response = re.findall('([^?]*)?.*', response)[0]
            return response
        except:
            print(self.__error_msg)
            return None

    def get_path(self, path):
        if path == '/': path = '';
        if path[:1] == '/':
            path = path[1:]
            if path[-1:] != '/': path += '/';
        return path

    def delete_file(self, filename):
        if not self.__oss:
            self.set_cos()
            if not self.__oss:
                return False
        try:
            response = self.__oss.delete_object(
                Bucket=self.__Bucket,
                Key=filename
            )
            return response
        except Exception as ex:
            self.__error_count += 1
            if self.__error_count < 2:
                self.sync_date()
                self.delete_file(filename)
            print(self.__error_msg)
            return None

    # 删除文件
    def remove_file(self, get):
        if not self.__oss:
            self.set_cos()
            if not self.__oss:
                return False
        path = self.get_path(get.path)
        filename = path + get.filename
        self.delete_file(filename)
        return public.returnMsg(True, '删除文件成功!')

    # 备份网站
    def backupSite(self, name, count):
        # self.set_cos()
        # self.__conn();
        if not self.__oss:
            self.set_cos()
            if not self.__oss:
                return False
        sql = db.Sql();
        path = sql.table('sites').where('name=?', (name,)).getField('path');
        startTime = time.time();
        if not path:
            endDate = time.strftime('%Y/%m/%d %X', time.localtime())
            log = "网站[" + name + "]不存在!"
            print("★[" + endDate + "] " + log)
            print("----------------------------------------------------------------------------")
            return;

        backup_path = sql.table('config').where("id=?", (1,)).getField('backup_path') + '/site';
        if not os.path.exists(backup_path): public.ExecShell("mkdir -p " + backup_path);

        filename = backup_path + "/Web_" + name + "_" + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.tar.gz'
        public.ExecShell("cd " + os.path.dirname(path) + " && tar zcvf '" + filename + "' '" + os.path.basename(path) + "'" + self.__exclude + " > /dev/null")
        endDate = time.strftime('%Y/%m/%d %X', time.localtime())

        time.sleep(1)
        if not os.path.exists(filename):
            log = "网站[" + name + "]备份失败!"
            print("★[" + endDate + "] " + log)
            print("----------------------------------------------------------------------------")
            return;

        #ant修改 
        # if self.__bucket_path != '': self.__bucket_path = 'backup/site' + '/';
           self.__bucket_path = self.__bucket_path +'backup/site/';

        # 上传文件
        self.upload_file(filename);

        outTime = time.time() - startTime
        pid = sql.table('sites').where('name=?', (name,)).getField('id');
        sql.table('backup').add('type,name,pid,filename,addtime,size',
                                ('0', os.path.basename(filename), pid, 'txcos', endDate, os.path.getsize(filename)))
        log = "网站[" + name + "]已成功备份到腾讯云COS,用时[" + str(round(outTime, 2)) + "]秒";
        public.WriteLog('计划任务', log)
        print("★[" + endDate + "] " + log)
        print("|---保留最新的[" + count + "]份备份")
        print("|---文件名:" + os.path.basename(filename))
        if self.__exclude: print(u"|---排除规则: " + self.__exclude)

        # 清理本地文件
        public.ExecShell("rm -f " + filename)

        # 清理多余备份
        backups = sql.table('backup').where('type=? and pid=? and filename=?', ('0', pid,'txcos')).field('id,name,filename').select();

        num = len(backups) - int(count)
        if num > 0:
            for backup in backups:
                if os.path.exists(backup['filename']):
                    public.ExecShell("rm -f " + backup['filename']);
                self.delete_file(self.__bucket_path + backup['name']);
                sql.table('backup').where('id=?', (backup['id'],)).delete();
                num -= 1;
                print("|---已清理过期备份文件:" + backup['name'])
                if num < 1: break;
        return None
    # 备份数据库

    def backupDatabase(self, name, count):
        if not self.__oss:
            self.set_cos()
            if not self.__oss:
                return False
        sql = db.Sql();
        path = sql.table('databases').where('name=?', (name,)).getField('id');
        startTime = time.time();
        if not path:
            endDate = time.strftime('%Y/%m/%d %X', time.localtime())
            log = "数据库[" + name + "]不存在!"
            print("★[" + endDate + "] " + log)
            print("----------------------------------------------------------------------------")
            return;

        backup_path = sql.table('config').where("id=?", (1,)).getField('backup_path') + '/database';
        if not os.path.exists(backup_path): public.ExecShell("mkdir -p " + backup_path);

        filename = backup_path + "/Db_" + name + "_" + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + ".sql.gz"

        import re
        mysql_root = sql.table('config').where("id=?", (1,)).getField('mysql_root')
        mycnf = public.readFile('/etc/my.cnf');
        rep = "\[mysqldump\]\nuser=root"
        sea = "[mysqldump]\n"
        subStr = sea + "user=root\npassword=" + mysql_root + "\n";
        mycnf = mycnf.replace(sea, subStr)
        if len(mycnf) > 100:
            public.writeFile('/etc/my.cnf', mycnf);

        public.ExecShell("/www/server/mysql/bin/mysqldump --default-character-set="+ self.get_database_character(name) +" --force --opt " + name + " | gzip > " + filename)

        if not os.path.exists(filename):
            endDate = time.strftime('%Y/%m/%d %X', time.localtime())
            log = "数据库[" + name + "]备份失败!"
            print("★[" + endDate + "] " + log)
            print("----------------------------------------------------------------------------")
            return;

        mycnf = public.readFile('/etc/my.cnf');
        mycnf = mycnf.replace(subStr, sea)
        if len(mycnf) > 100:
            public.writeFile('/etc/my.cnf', mycnf);

        # 上传 ant修改
        #if self.__bucket_path != '': self.__bucket_path = '' + 'backup/databases' + '/';
        self.__bucket_path = self.__bucket_path + 'backup/databases/';
        self.upload_file(filename);

        endDate = time.strftime('%Y/%m/%d %X', time.localtime())
        outTime = time.time() - startTime
        pid = sql.table('databases').where('name=?', (name,)).getField('id');

        sql.table('backup').add('type,name,pid,filename,addtime,size',
                                (1, os.path.basename(filename), pid, 'txcos', endDate, os.path.getsize(filename)))
        log = "数据库[" + name + "]已成功备份到腾讯云COS,用时[" + str(round(outTime, 2)) + "]秒";
        public.WriteLog('计划任务', log)
        print("★[" + endDate + "] " + log)
        print("|---保留最新的[" + count + "]份备份")
        print("|---文件名:" + os.path.basename(filename))

        # 清理本地文件
        public.ExecShell("rm -f " + filename)

        # 清理多余备份
        backups = sql.table('backup').where('type=? and pid=? and filename=?', ('1', pid,'txcos')).field('id,name,filename').select();

        num = len(backups) - int(count)
        if num > 0:
            for backup in backups:
                if os.path.exists(backup['filename']):
                    public.ExecShell("rm -f " + backup['filename']);

                self.delete_file(self.__bucket_path + backup['name']);
                sql.table('backup').where('id=?', (backup['id'],)).delete();
                num -= 1;
                print("|---已清理过期备份文件:" + backup['name'])
                if num < 1: break;
        return None

        # 备份指定目录
    def backupPath(self, path, count):
        if not self.__oss:
            self.set_cos()
            if not self.__oss:
                return False
        sql = db.Sql();
        startTime = time.time();
        if path[-1:] == '/': path = path[:-1]
        name = os.path.basename(path)
        backup_path = sql.table('config').where("id=?", (1,)).getField('backup_path') + '/path';
        if not os.path.exists(backup_path): os.makedirs(backup_path);
        filename = backup_path + "/Path_" + name + "_" + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.tar.gz'
        os.system(
            "cd " + os.path.dirname(path) + " && tar zcvf '" + filename + "' '" + os.path.basename(path) + "'" + self.__exclude + " > /dev/null")

        endDate = time.strftime('%Y/%m/%d %X', time.localtime())
        if not os.path.exists(filename):
            log = u"目录[" + path + "]备份失败"
            print(u"★[" + endDate + "] " + log)
            print(u"----------------------------------------------------------------------------")
            return;

        # 上传文件 ant修改
        #if self.__bucket_path != '': self.__bucket_path = 'path/' + 'backup/path' + '/';
        self.__bucket_path = self.__bucket_path  + 'backup/path/';
        self.upload_file(filename);
        outTime = time.time() - startTime
        sql.table('backup').add('type,name,pid,filename,addtime,size',
                                ('2', path, '0', filename, endDate, os.path.getsize(filename)))
        log = u"目录[" + path + "]备份成功,用时[" + str(round(outTime, 2)) + "]秒";
        public.WriteLog(u'计划任务', log)
        print(u"★[" + endDate + "] " + log)
        print(u"|---保留最新的[" + count + u"]份备份")
        print(u"|---文件名:" + filename)
        if self.__exclude: print(u"|---排除规则: " + self.__exclude)

        # 清理多余备份
        backups = sql.table('backup').where('type=? and pid=? and name=?',('2',0,path)).field('id,filename').select();

        # 清理本地文件
        if os.path.exists(filename): os.remove(filename)
        num = len(backups) - int(count)
        if num > 0:
            for backup in backups:
                if os.path.exists(backup['filename']): os.remove(backup['filename'])
                self.delete_file(self.__bucket_path + backup['filename']);
                sql.table('backup').where('id=?', (backup['id'],)).delete();
                num -= 1;
                print(u"|---已清理过期备份文件:" + backup['filename'])
                if num < 1: break;


    def backupSiteAll(self, save):
        if not self.__oss:
            self.set_cos()
            if not self.__oss:
                return False
        self.__conn()
        sites = public.M('sites').field('name').select()
        for site in sites:
            self.backupSite(site['name'], save)


    def backupDatabaseAll(self, save):
        if not self.__oss:
            self.set_cos()
            if self.__oss:
                return False
        self.__conn()
        databases = public.M('databases').field('name').select()
        for database in databases:
            self.backupDatabase(database['name'], save)

    def set_cos(self):
        cfile = 'plugin/txcos/config.conf'
        fp = open(cfile, 'r')
        keys = fp.read().split('|')
        self.__secret_id = keys[0]
        self.__secret_key = keys[1]
        self.__region = keys[2]
        self.__Bucket = keys[3] 
        self.__bucket_path = self.get_path(keys[4]) 
        try:
            config = CosConfig(Region=self.__region, SecretId=self.__secret_id, SecretKey=self.__secret_key, Token=None,Scheme='http', Timeout=1)
            self.__oss = CosS3Client(config)
        except:
            if self.__oss == None:
                time.sleep(1)
                self.__conn()
            return json.dumps(self.__error_msg)

    def get_lib(self):
        import json
        list={
            "name":"腾讯云COS",
            "type":"计划任务",
            "ps":"将网站或数据库打包备份到腾讯云COS对象存储空间,, <a class='link' href='https://portal.qiniu.com/signup?code=3liz7nbopjd5e' target='_blank'>点击申请</a>",
            "status":'false',
            "opt":"txcos",
            "module":"qcloud_cos",
            "script":"txcos",
            "help":"https://www.bt.cn/bbs/thread-17442-1-1.html",
            "SecretId":"SecretId|请输入SecretId|腾讯云COS的SecretId",
            "SecretKey":"SecretKey|请输入SecretKey|腾讯云COS  SecretKey",
            "region":"存储地区|请输入对象存储地区|例如 ap-chengdu",
            "Bucket":"存储名称|请输入绑定的存储名称",
            "check":["/usr/lib/python2.6/site-packages/qcloud_cos/cos_auth.py","/usr/lib/python2.7/site-packages/qcloud_cos/cos_auth.py"]
        }
        lib='/www/server/panel/data/libList.conf'
        lib_dic = json.loads(public.readFile(lib))
        for i in lib_dic:
            if list['name'] in i['name']:
                return True
            else:
                pass
        lib_dic.append(list)
        public.writeFile(lib, json.dumps(lib_dic))
        return lib_dic
    
    def get_exclode(self):
        tmp_exclude = os.getenv('BT_EXCLUDE')
        if not tmp_exclude: return ""
        for ex in tmp_exclude.split(','):
            self.__exclude += " --exclude=\"" + ex + "\""
        self.__exclude += " "
        return self.__exclude
    
    #取数据库字符集
    def get_database_character(self,db_name):
        try:
            import panelMysql
            tmp = panelMysql.panelMysql().query("show create database `%s`" % db_name.strip())
            c_type = str(re.findall("SET\s+([\w\d-]+)\s",tmp[0][1])[0])
            c_types = ['utf8','utf-8','gbk','big5','utf8mb4']
            if not c_type.lower() in c_types: return 'utf8'
            return c_type
        except:
            return 'utf8'



if __name__ == "__main__":
    import json

    data = None
    q = txcos_main()
    type = sys.argv[1]
    if type == 'site':
        if sys.argv[2] == 'ALL':
            q.backupSiteAll(sys.argv[3])
        else:
            q.backupSite(sys.argv[2], sys.argv[3])
        exit()
    elif type == 'database':
        if sys.argv[2] == 'ALL':
            q.backupDatabaseAll(sys.argv[3])
        else:
            q.backupDatabase(sys.argv[2], sys.argv[3])
        exit()
    elif type == 'path':
        q.backupPath(sys.argv[2], sys.argv[3])
    elif type == 'upload':
        data = q.upload_file(sys.argv[2])
    elif type == 'download':
        data = q.download_file(sys.argv[2])
    elif type == 'get':
        data = q.get_files(sys.argv[2])
    elif type == 'list':
        data = q.get_list()
    elif type == 'delete_file':
        data = q.delete_file(sys.argv[2])
    elif type == 'lib':
        data = q.get_lib()
    else:
        data = 'ERROR: 参数不正确!'
    print(json.dumps(data))