# coding:utf-8 # --------------------------------------------------------------------------------- # MW-Linux面板 # --------------------------------------------------------------------------------- # copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. # --------------------------------------------------------------------------------- # Author: midoks # --------------------------------------------------------------------------------- import os import sys import json import threading import multiprocessing import core.mw as mw import thisdb class pg_thread(threading.Thread): def __init__(self, func, args, name=''): threading.Thread.__init__(self) self.name = name self.func = func self.args = args self.result = self.func(*self.args) def getResult(self): try: return self.result except Exception: return None class plugin(object): def_plugin_type = [ { "title":"全部", "type":0, "ps":"" }, { "title":"已安装", "type":-1, "ps":"" }, { "title":"运行环境", "type":1, "ps":"" }, { "title":"数据软件", "type":2, "ps":"" }, { "title":"代码管理", "type":3, "ps":"" }, { "title":"系统工具", "type":4, "ps":"" }, { "title":"其他插件", "type":5, "ps":"" }, { "title":"辅助插件", "type":6, "ps":"" } ] __plugin_dir = 'plugins' __tasks = None __plugin_status_cachekey = 'plugin_list_status' __plugin_status_data = None # lock _instance_lock = threading.Lock() @classmethod def instance(cls, *args, **kwargs): if not hasattr(plugin, "_instance"): with plugin._instance_lock: if not hasattr(plugin, "_instance"): plugin._instance = plugin(*args, **kwargs) return plugin._instance """插件类初始化""" def __init__(self): self.__plugin_dir = mw.getPluginDir() def getIndexList(self): indexList = thisdb.getOptionByJson('display_index') plist = [] for i in indexList: tmp = i.split('-') tmp_len = len(tmp) plugin_name = tmp[0] plugin_ver = tmp[1] if tmp_len > 2: tmpArr = tmp[0:tmp_len - 1] plugin_name = '-'.join(tmpArr) plugin_ver = tmp[tmp_len - 1] read_json_file = self.__plugin_dir + '/' + plugin_name + '/info.json' if os.path.exists(read_json_file): content = mw.readFile(read_json_file) try: data = json.loads(content) data = self.makeCoexistList(data) for index in range(len(data)): if data[index]['coexist']: if data[index]['versions'] == plugin_ver or plugin_ver in data[index]['versions']: data[index]['display'] = True plist.append(data[index]) continue else: data[index]['display'] = True plist.append(data[index]) except Exception as e: print('getIndexList:', mw.getTracebackInfo()) plist = self.checkStatusMThreadsByCache(plist) return mw.returnData(True, 'ok', plist) def init(self): plugin_names = { 'openresty': '1.27.1', 'php': '56', 'swap': '1.1', 'mysql': '5.7', 'phpmyadmin': '4.4.15', } pn_dir = mw.getPluginDir() pn_server_dir = mw.getServerDir() pn_list = [] for pn in plugin_names: info = {} pn_json = pn_dir + '/' + pn + '/info.json' pn_server = pn_server_dir + '/' + pn if not os.path.exists(pn_server): tmp = mw.readFile(pn_json) tmp = json.loads(tmp) info['title'] = tmp['title'] info['name'] = tmp['name'] info['versions'] = tmp['versions'] info['default_ver'] = plugin_names[pn] pn_list.append(info) else: return mw.returnData(False, 'ok') return mw.returnData(True, 'ok', pn_list) def initInstall(self, plugin_list): try: pn_list = json.loads(plugin_list) for pn in pn_list: name = pn['name'] version = pn['version'] info_file = self.__plugin_dir + '/' + name + '/' + 'info.json' pluginInfo = json.loads(mw.readFile(info_file)) self.hookInstall(pluginInfo) cmd = 'cd {0} && bash {1} install {2}'.format( mw.getPluginDir() + '/'+name, pluginInfo['shell'], version ) title = '安装[' + name + '-' + version + ']' thisdb.addTask(name=title,cmd=cmd) os.mkdir(mw.getServerDir() + '/php') # 任务执行相关 mw.triggerTask() return mw.returnData(True, '添加成功') except Exception as e: return mw.returnData(False, mw.getTracebackInfo()) def menuGetAbsPath(self, tag, path): if path[0:1] == '/': return path else: return mw.getPluginDir() + '/' + tag + '/' + path def addIndex(self, name, version): vname = name + '-' + version indexList = thisdb.getOptionByJson('display_index',default=[]) if vname in indexList: return mw.returnData(False, '请不要重复添加!') if len(indexList) > 12: return mw.returnData(False, '首页最多只能显示12个软件!') indexList.append(vname) thisdb.setOption('display_index', json.dumps(indexList)) return mw.returnData(True, '添加成功!') def removeIndex(self, name, version): vname = name + '-' + version indexList = thisdb.getOptionByJson('display_index') if not vname in indexList: return mw.returnData(True, '删除成功!!') indexList.remove(vname) thisdb.setOption('display_index', json.dumps(indexList)) return mw.returnData(True, '删除成功!') def hookInstallOption(self, hook_name, info): hn_name = 'hook_'+hook_name src_data = thisdb.getOptionByJson(hn_name,type='hook',default=[]) isNeedAdd = True for x in range(len(src_data)): if src_data[x]['title'] == info['title'] and src_data[x]['name'] == info['name']: isNeedAdd = False if isNeedAdd: src_data.append(info) thisdb.setOption(hn_name, json.dumps(src_data), type='hook') return True def hookUninstallOption(self, hook_name, info): hn_name = 'hook_'+hook_name src_data = thisdb.getOptionByJson(hn_name,type='hook',default=[]) for idx in range(len(src_data)): if src_data[idx]['name'] == info['name']: src_data.remove(src_data[idx]) break thisdb.setOption(hn_name, json.dumps(src_data), type='hook') return True def hookInstall(self, info): valid_hook = ['backup', 'database'] valid_list_hook = ['menu', 'global_static', 'site_cb'] if 'hook' in info: hooks = info['hook'] for h in hooks: hooks_type = type(h) if hooks_type == dict: tag = h['tag'] if tag in valid_list_hook: self.hookInstallOption(tag, h[tag]) elif hooks_type == str: for x in hooks: if x in valid_hook: self.hookInstallOption(x, info) return True return False def hookUninstall(self, info): valid_hook = ['backup', 'database'] valid_list_hook = ['menu', 'global_static', 'site_cb'] if 'hook' in info: hooks = info['hook'] for h in hooks: hooks_type = type(h) if hooks_type == dict: tag = h['tag'] if tag in valid_list_hook: self.hookUninstallOption(tag, h[tag]) elif hooks_type == str: for x in hooks: if x in valid_hook: self.hookUninstallOption(x, info) return True return False def install(self, name, version, upgrade = None ): if name.strip() == '': return mw.returnData(False, '缺少插件名称!', ()) if version.strip() == '': return mw.returnData(False, '缺少版本信息!', ()) msg_head = '安装' if upgrade is not None and upgrade is True: mtype = 'update' msg_head = '更新' info_file = self.__plugin_dir + '/' + name + '/' + 'info.json' if not os.path.exists(info_file): return mw.returnData(False, "配置文件不存在!", ()) info_data = json.loads(mw.readFile(info_file)) exec_bash = 'cd {0} && bash {1} install {2}'.format( mw.getPluginDir() + '/'+name, info_data['shell'], version ) self.hookInstall(info_data) title = '{0}[{1}-{2}]'.format(msg_head,name,version) thisdb.addTask(name=title,cmd=exec_bash, status=0) mw.triggerTask() # 调式日志 mw.debugLog(exec_bash) return mw.returnData(True, '已将安装任务添加到队列!') # 卸载插件 def uninstall(self, name, version): rundir = mw.getRunDir() if name.strip() == '': return mw.returnData(False, "缺少插件名称!", ()) if version.strip() == '': return mw.returnData(False, "缺少版本信息!", ()) info_file = self.__plugin_dir + '/' + name + '/' + 'info.json' if not os.path.exists(info_file): return mw.returnData(False, "配置文件不存在!", ()) info_data = json.loads(mw.readFile(info_file)) exec_bash = "cd {0} && /bin/bash {1} uninstall {2}".format( mw.getPluginDir() + '/'+name, info_data['shell'], version ) self.hookUninstall(info_data) data = mw.execShell(exec_bash) self.removeIndex(name, version) mw.debugLog(exec_bash, data) return mw.returnData(True, '卸载执行成功!') # 插件搜索匹配 def searchKey(self, info, keyword = None, ): if keyword == None: return True try: if info['title'].lower().find(keyword) > -1: return True if info['ps'].lower().find(keyword) > -1: return True if info['name'].lower().find(keyword) > -1: return True except Exception as e: return False def getVersion(self, path): version_pl = path + '/version.pl' if os.path.exists(version_pl): return mw.readFile(version_pl).strip() return '' def checkIndexList(self, name, version): indexList = thisdb.getOptionByJson('display_index',default=[]) for i in indexList: t = i.split('-') if t[0] == name: return True return False def checkSetupTask(self, name, version, coexist): self.__tasks = thisdb.getTaskRunAll() isTask = '1' for task in self.__tasks: tmpt = mw.getStrBetween('[', ']', task['name']) if not tmpt: continue task_sign = tmpt.split('-') task_len = len(task_sign) task_name = task_sign[0].lower() task_ver = task_sign[1] if task_len > 2: nameArr = task_sign[0:task_len - 1] task_name = '-'.join(nameArr).lower() task_ver = task_sign[task_len - 1] if coexist: if task_name == name and task_ver == version: isTask = task['status'] else: if task_name == name: isTask = task['status'] return isTask def checkDisplayIndex(self, name, version, coexist): indexList = thisdb.getOptionByJson('display_index',default=[]) if coexist: if type(version) == list: for index in range(len(version)): vname = name + '-' + version[index] if vname in indexList: return True else: vname = name + '-' + version if vname in indexList: return True else: if type(version) == list: for index in range(len(version)): return self.checkIndexList(name, version) else: return self.checkIndexList(name, version) return False def makeCoexist(self, data): plugins_info = [] for index in range(len(data['versions'])): data_t = data.copy() data_t['title'] = data_t['title'] + '-' + data['versions'][index] data_t['versions'] = data['versions'][index] pg = self.makePluginInfo(data_t) plugins_info.append(pg) return plugins_info # 构造插件基本信息 def makePluginInfo(self, info): checks = '' path = '' coexist = False if info["checks"].startswith('/'): checks = info["checks"] else: checks = mw.getFatherDir() + '/' + info['checks'] if 'path' in info: path = info['path'] if not path.startswith('/'): path = mw.getFatherDir() + '/' + path if 'coexist' in info and info['coexist']: coexist = True pInfo = { "id": 10000, "pid": info['pid'], "type": 1000, "name": info['name'], "title": info['title'], "ps": info['ps'], "dependnet": "", "mutex": "", "icon": "", "path": path, "install_checks": checks, "uninsatll_checks": checks, "coexist": coexist, "versions": info['versions'], # "updates": info['updates'], "task": True, "display": False, "setup": False, "setup_version": "", "status": False, "install_pre_inspection": False, "uninstall_pre_inspection": False, } if 'icon' in info: pInfo['icon'] = info['icon'] if checks.find('VERSION') > -1: pInfo['install_checks'] = checks.replace('VERSION', info['versions']) if path.find('VERSION') > -1: pInfo['path'] = path.replace('VERSION', info['versions']) pInfo['task'] = self.checkSetupTask(pInfo['name'], info['versions'], coexist) pInfo['display'] = self.checkDisplayIndex(info['name'], pInfo['versions'], coexist) pInfo['setup'] = os.path.exists(pInfo['install_checks']) if coexist and pInfo['setup']: pInfo['setup_version'] = info['versions'] else: pInfo['setup_version'] = self.getVersion(pInfo['install_checks']) if 'install_pre_inspection' in info: pInfo['install_pre_inspection'] = info['install_pre_inspection'] if 'uninstall_pre_inspection' in info: pInfo['uninstall_pre_inspection'] = info['uninstall_pre_inspection'] return pInfo def makeCoexistData(self, data): plugins = [] if type(data['versions']) == list and 'coexist' in data and data['coexist']: data_t = self.makeCoexist(data) for index in range(len(data_t)): plugins.append(data_t[index]) else: pg = self.makePluginInfo(data) plugins.append(pg) return plugins def makeCoexistDataInstalled(self, data): plugins = [] if type(data['versions']) == list and 'coexist' in data and data['coexist']: data_t = self.makeCoexist(data) for index in range(len(data_t)): if data_t[index]['setup']: plugins.append(data_t[index]) else: pg = self.makePluginInfo(data) if pg['setup']: plugins.append(pg) return plugins # 对多版本共存进行处理 def makeCoexistList(self, data, plugin_type = None, ): plugins_t = [] # 返回指定类型 if plugin_type != None and data['pid'] == plugin_type: return self.makeCoexistData(data) # 全部 if plugin_type == None or plugin_type == '0': return self.makeCoexistData(data) # 已经安装 if str(plugin_type) == '-1': return self.makeCoexistDataInstalled(data) return plugins_t def getPluginInfo(self, name): info = {} path = self.__plugin_dir + '/' + name info_path = path + '/info.json' if not os.path.exists(info_path): return info try: data = json.loads(mw.readFile(info_path)) return data except Exception as e: return info def getPluginList(self, name, keyword = None, type = None, ): infos = [] data = self.getPluginInfo(name) if data is None or len(data) == 0: return infos # 判断是否搜索 if keyword != '' and not self.searchKey(data, keyword): return infos plugin_t = self.makeCoexistList(data, type) for index in range(len(plugin_t)): infos.append(plugin_t[index]) return infos # 检查插件状态 def checkStatusThreads(self, info, i): if not info['setup']: return False data = self.run(info['name'], 'status', info['setup_version']) if data[0].strip() == 'start': return True else: return False # 检查插件状态 def checkStatusThreadsByCache(self, info, i): # 初始化db if not info['setup']: return False plugin_list_status = self.__plugin_status_data if plugin_list_status is not None: k = info['name'] if 'coexist' in info and info['coexist']: k = info['title'] # print(k) if k in plugin_list_status: if plugin_list_status[k]: return True else: return False data = self.run(info['name'], 'status', info['setup_version']) if data[0].strip() == 'start': return True else: return False # 多线程检查插件状态[cache] def checkStatusMThreadsByCache(self, info): try: self.__plugin_status_data = thisdb.getOptionByJson(self.__plugin_status_cachekey, default=None) threads = [] ntmp_list = range(len(info)) for i in ntmp_list: t = pg_thread(self.checkStatusThreadsByCache,(info[i], i)) threads.append(t) for i in ntmp_list: threads[i].start() for i in ntmp_list: threads[i].join() for i in ntmp_list: t = threads[i].getResult() k = info[i]['name'] self.__plugin_status_data[k] = t info[i]['status'] = t thisdb.setOption(self.__plugin_status_cachekey, json.dumps(self.__plugin_status_data)) except Exception as e: print(mw.getTracebackInfo()) print('checkStatusMThreadsByCache:', str(e)) return info def autoCachePluginStatus(self): info = [] for name in os.listdir(self.__plugin_dir): if name.startswith('.'): continue t = self.getPluginList(name) for index in range(len(t)): info.append(t[index]) self.__plugin_status_data = {} for x in info: if not x['setup']: continue data = self.run(x['name'], 'status', x['setup_version']) k = x['name'] if 'coexist' in x and x['coexist']: k = x['title'] if data[0].strip() == 'start': self.__plugin_status_data[k] = True else: self.__plugin_status_data[k] = False thisdb.setOption(self.__plugin_status_cachekey, json.dumps(self.__plugin_status_data)) return True # 多线程检查插件状态 def checkStatusMThreads(self, info): try: threads = [] ntmp_list = range(len(info)) for i in ntmp_list: t = pg_thread(self.checkStatusThreads,(info[i], i)) threads.append(t) for i in ntmp_list: threads[i].start() for i in ntmp_list: threads[i].join() for i in ntmp_list: t = threads[i].getResult() info[i]['status'] = t except Exception as e: print('checkStatusMThreads:', str(e)) return info def getAllPluginList( self, type = None, keyword = None, page = 1, size = 10, ): info = [] for name in os.listdir(self.__plugin_dir): if name.startswith('.'): continue t = self.getPluginList(name, keyword, type=type) for index in range(len(t)): info.append(t[index]) start = (page - 1) * size end = start + size x = info[start:end] x = self.checkStatusMThreadsByCache(x) return (x, len(info)) def getList( self, type = None, keyword = None, page = 1, size = 10, ) -> object: ''' # print(type,keyword,page,size) ''' rdata = {} rdata['type'] = self.def_plugin_type data = self.getAllPluginList(type, keyword, page, size) rdata['data'] = data[0] rdata['list'] = mw.getPage({'count':data[1],'p':page,'tojs':'getSList','row':size}) return rdata def updateZip(self, request_zip): tmp_path = mw.getPanelDir() + '/temp' if not os.path.exists(tmp_path): os.makedirs(tmp_path) mw.execShell("rm -rf " + tmp_path + '/*') tmp_file = tmp_path + '/plugin_tmp.zip' if request_zip.filename[-4:] != '.zip': return mw.returnData(False, '仅支持zip文件!') request_zip.save(tmp_file) mw.execShell('cd ' + tmp_path + ' && unzip ' + tmp_file) os.remove(tmp_file) p_info = tmp_path + '/info.json' if not os.path.exists(p_info): d_path = None for df in os.walk(tmp_path): if len(df[2]) < 3: continue if not 'info.json' in df[2]: continue if not 'install.sh' in df[2]: continue if not os.path.exists(df[0] + '/info.json'): continue d_path = df[0] if d_path: tmp_path = d_path p_info = tmp_path + '/info.json' try: data = json.loads(mw.readFile(p_info)) data['size'] = mw.getPathSize(tmp_path) if not 'author' in data: data['author'] = '未知' if not 'home' in data: data['home'] = 'https://github.com/midoks/mdserver-web' plugin_path = mw.getPluginDir() + data['name'] + '/info.json' data['old_version'] = '0' data['tmp_path'] = tmp_path if os.path.exists(plugin_path): try: old_info = json.loads(mw.readFile(plugin_path)) data['old_version'] = old_info['versions'] except: pass except: mw.execShell("rm -rf " + tmp_path) return mw.returnData(False, '在压缩包中没有找到插件信息,请检查插件包!') protectPlist = ('openresty', 'mysql', 'php', 'redis', 'memcached' 'mongodb', 'swap', 'gogs', 'pureftp') if data['name'] in protectPlist: return mw.returnData(False, '[' + data['name'] + '],重要插件不可修改!') return data def inputZipApi(self, plugin_name,tmp_path): if not os.path.exists(tmp_path): return mw.returnData(False, '临时文件不存在,请重新上传!') plugin_path = mw.getPluginDir() + '/' + plugin_name if not os.path.exists(plugin_path): print(mw.execShell('mkdir -p ' + plugin_path)) mw.execShell("cp -rf " + tmp_path + '/* ' + plugin_path + '/') mw.execShell('chmod -R 755 ' + plugin_path) p_info = mw.readFile(plugin_path + '/info.json') if p_info: mw.writeLog('软件管理', '安装第三方插件[%s]' %json.loads(p_info)['title']) return mw.returnData(True, '安装成功!') mw.execShell("rm -rf " + plugin_path) return mw.returnData(False, '安装失败!') # [start|stop]操作,删除缓存! def runByCache(self, name, func, version): ppos = mw.getServerDir()+'/'+name if not os.path.exists(ppos): return data = thisdb.getOptionByJson(self.__plugin_status_cachekey, default={}) info = self.getPluginInfo(name) if 'coexist' in info and info['coexist']: name = info['title'] + '-'+ version if name in data: del(data[name]) thisdb.setOption(self.__plugin_status_cachekey, json.dumps(data)) # shell/bash方式调用 def run(self, name, func, version = '', args = '', script = 'index', ): path = self.__plugin_dir + '/' + name + '/' + script + '.py' if not os.path.exists(path): path = self.__plugin_dir + '/' + name + '/' + name + '.py' py = 'python3 ' + path if args == '': py_cmd = py + ' ' + func + ' ' + version else: py_cmd = py + ' ' + func + ' ' + version + ' ' + args if not os.path.exists(path): return ('', '') py_cmd = 'cd ' + mw.getPanelDir() + " && "+ py_cmd data = mw.execShell(py_cmd) if mw.inArray(['start','stop'], func): self.runByCache(name, func, version) # print(data) if mw.isDebugMode(): print('run:', py_cmd) print(data) # print os.path.exists(py_cmd) return (data[0].strip(), data[1].strip()) # 映射包调用 def callback(self, name, func, args = '', script = 'index', ): package = self.__plugin_dir + '/' + name if not os.path.exists(package): return (False, "插件不存在!") if not package in sys.path: sys.path.append(package) cmd = "__import__('" + script + "')." + func + '(' + args + ')' if mw.isDebugMode(): print('callback', cmd) data = None try: data = eval(cmd) except Exception as e: print(mw.getTracebackInfo()) return (False, mw.getTracebackInfo()) return (True, data)