# coding:utf-8 # --------------------------------------------------------------------------------- # MW-Linux面板 # --------------------------------------------------------------------------------- # copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved. # --------------------------------------------------------------------------------- # Author: midoks # --------------------------------------------------------------------------------- import os import sys import re import json import time import threading import multiprocessing import core.mw as mw import thisdb class sites(object): # lock _instance_lock = threading.Lock() @classmethod def instance(cls, *args, **kwargs): if not hasattr(sites, "_instance"): with sites._instance_lock: if not hasattr(sites, "_instance"): sites._instance = sites(*args, **kwargs) return sites._instance def __init__(self): # nginx conf self.setupPath = mw.getServerDir() + '/web_conf' self.logsPath = mw.getLogsDir() self.vhostPath = vhost = self.setupPath + '/nginx/vhost' if not os.path.exists(vhost): mw.execShell("mkdir -p " + vhost + " && chmod -R 755 " + vhost) self.rewritePath = rewrite = self.setupPath + '/nginx/rewrite' if not os.path.exists(rewrite): mw.execShell("mkdir -p " + rewrite + " && chmod -R 755 " + rewrite) self.passPath = passwd = self.setupPath + '/nginx/pass' if not os.path.exists(passwd): mw.execShell("mkdir -p " + passwd + " && chmod -R 755 " + passwd) self.redirectPath = redirect = self.setupPath + '/nginx/redirect' if not os.path.exists(redirect): mw.execShell("mkdir -p " + redirect +" && chmod -R 755 " + redirect) self.proxyPath = proxy = self.setupPath + '/nginx/proxy' if not os.path.exists(proxy): mw.execShell("mkdir -p " + proxy + " && chmod -R 755 " + proxy) # ssl conf self.sslDir = self.setupPath + '/ssl' self.sslLetsDir = self.setupPath + '/letsencrypt' if not os.path.exists(self.sslLetsDir): mw.execShell("mkdir -p " + self.sslLetsDir +" && chmod -R 755 " + self.sslLetsDir) def runHook(self, hook_name, func_name): # 站点操作Hook hook_cfg = thisdb.getOptionByJson('hook_site_cb',type='hook',default=[]) hook_num = len(hook_cfg) if hook_num == 0: return from utils.plugin import plugin as MwPlugin pa = MwPlugin.instance() for x in range(hook_num): hook_data = hook_cfg[x] if func_name in hook_data: app_name = hook_data["name"] run_func = hook_data[func_name]['func'] pa.run(app_name, run_func) return True # 域名编码转换 def toPunycode(self, domain): if sys.version_info[0] == 2: domain = domain.encode('utf8') tmp = domain.split('.') newdomain = '' for dkey in tmp: # 匹配非ascii字符 match = re.search(u"[\x80-\xff]+", dkey) if not match: newdomain += dkey + '.' else: newdomain += 'xn--' + dkey.decode('utf-8').encode('punycode') + '.' return newdomain[0:-1] # 中文路径处理 def toPunycodePath(self, path): if sys.version_info[0] == 2: path = path.encode('utf-8') if os.path.exists(path): return path match = re.search(u"[\x80-\xff]+", path) if not match: return path npath = '' for ph in path.split('/'): npath += '/' + self.toPunycode(ph) return npath.replace('//', '/') def getHostConf(self, siteName): return self.vhostPath + '/' + siteName + '.conf' def saveHostConf(self, path, data, encoding): import utils.file as file mw.backFile(path) rdata = file.saveBody(path, data, encoding) if rdata['status']: isError = mw.checkWebConfig() if isError != True: mw.restoreFile(path) msg = 'ERROR: 检测到配置文件有错误,请先排除后再操作

' + isError.replace("\n", '
') + '
' return mw.returnData(False, msg) mw.restartWeb() mw.removeBackFile(path) return rdata def getRewriteConf(self, siteName): return self.rewritePath + '/' + siteName + '.conf' def getRedirectDataPath(self, siteName): return "{}/{}/data.json".format(self.redirectPath, siteName) def getRedirectPath(self, siteName): return "{}/{}".format(self.redirectPath, siteName) def getProxyDataPath(self, siteName): return "{}/{}/data.json".format(self.proxyPath, siteName) def getProxyPath(self, siteName): return "{}/{}".format(self.proxyPath, siteName) def getDirBindRewrite(self, site_name, dir_name): return self.rewritePath + '/' + site_name + '_' + dir_name + '.conf' def getIndexConf(self): return mw.getServerDir() + '/openresty/nginx/conf/nginx.conf' # 路径处理 def getPath(self, path): if path[-1] == '/': return path[0:-1] return path def getSitePath(self, site_name): file = self.getHostConf(site_name) if os.path.exists(file): conf = mw.readFile(file) rep = r'\s*root\s*(.+);' find_cnf = re.search(rep, conf) if not find_cnf: return '' path = find_cnf.groups()[0] return path return '' def createRootDir(self, path): autoInit = False if not os.path.exists(path): autoInit = True os.makedirs(path) if not mw.isAppleSystem(): mw.execShell('chown -R www:www ' + path) if autoInit: mw.writeFile(path + '/index.html', 'Work has started!!!') mw.execShell('chmod -R 755 ' + path) def add(self, site_info, port, ps, path, version): site_root_dir = mw.getWwwDir() if site_root_dir == path.rstrip('/'): return mw.returnData(False, '不要以网站根目录创建站点!') site_info = json.loads(site_info) self.siteName = self.toPunycode(site_info['domain'].strip().split(':')[0]).strip() self.sitePath = self.toPunycodePath(self.getPath(path.replace(' ', ''))) self.sitePort = port.strip().replace(' ', '') self.phpVersion = version if thisdb.isSitesExist(self.siteName): return mw.returnData(False, '您添加的站点[%s]已存在!' % self.siteName) site_id = thisdb.addSites(self.siteName, self.sitePath) if site_id < 1: return mw.returnData(False, '添加失败!') self.createRootDir(self.sitePath) self.nginxAddConf() # 主域名配置 thisdb.addDomain(site_id, self.siteName, self.sitePort) # 添加更多域名 for domain in site_info['domainlist']: self.addDomain(site_id, self.siteName, domain) mw.restartWeb() return mw.returnData(True, '添加成功') def nginxAddDomain(self, site_name, domain, port): file = self.getHostConf(site_name) conf = mw.readFile(file) if not conf: return # 添加域名 rep = r"server_name\s*(.*);" tmp = re.search(rep, conf).group() domains = tmp.split(' ') if not mw.inArray(domains, domain): newServerName = tmp.replace(';', ' ' + domain + ';') conf = conf.replace(tmp, newServerName) # 添加端口 rep = r"listen\s+([0-9]+)\s*[default_server]*\s*;" tmp = re.findall(rep, conf) if not mw.inArray(tmp, port): listen = re.search(rep, conf).group() conf = conf.replace( listen, listen + "\n\tlisten " + port + ';') # 保存配置文件 mw.writeFile(file, conf) return True def addDomain(self, site_id, site_name, domain): isError = mw.checkWebConfig() if isError != True: msg = 'ERROR: 检测到配置文件有错误,请先排除后再操作

' + isError.replace("\n", '
') + '
' return mw.returnData(False, msg) domains = domain.split(',') for d in domains: if d == '': continue d = d.split(':') name = self.toPunycode(d[0]) port = '80' if len(d) == 2: port = d[1] if not mw.checkPort(port): return mw.returnData(False, '端口范围不合法!') reg = r"^([\w\-\*]{1,100}\.){1,4}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$" if not re.match(reg, name): return mw.returnData(False, '域名格式不正确!') if thisdb.checkSitesDomainIsExist(name, port): return mw.returnData(False, '您添加的域名[{}:{}],已使用。请仔细检查!'.format(name, port)) self.nginxAddDomain(site_name, name, port) msg = mw.getInfo('网站[{1}]添加域名[{2}]成功!', (site_name, name)) mw.writeLog('网站管理', msg) thisdb.addDomain(site_id, name, port) mw.restartWeb() self.runHook('site_cb', 'add') return mw.returnData(True, '域名添加成功!') def delDomain(self, site_id, site_name, domain, port): domain_nums = thisdb.getDomainCountBySiteId(site_id) if domain_nums == 1: return mw.returnData(False, '最后一个域名不能删除!') info = mw.M('domain').field('id,name').where("pid=? AND name=? AND port=?",(site_id, domain, port)).find() file = self.getHostConf(site_name) conf = mw.readFile(file) if conf: # 删除域名 rep = r"server_name\s+(.+);" tmp = re.search(rep, conf).group() newServerName = tmp.replace(' ' + domain + ';', ';') newServerName = newServerName.replace(' ' + domain + ' ', ' ') conf = conf.replace(tmp, newServerName) # 删除端口 rep = r"listen\s+([0-9]+);" tmp = re.findall(rep, conf) port_nums = mw.M('domain').where('pid=? AND port=?', (site_id, port)).count() if mw.inArray(tmp, port) == True and port_nums < 2: rep = r"\n*\s+listen\s+" + port + ";" conf = re.sub(rep, '', conf) # 保存配置 mw.writeFile(file, conf) thisdb.deleteDomainId(info['id']) msg = mw.getInfo('网站[{1}]删除域名[{2}:{3}]成功!', (site_name, domain, port)) mw.writeLog('网站管理', msg) mw.restartWeb() return mw.returnData(True, '站点删除成功!') def deleteALlLogs(self, webname): assLogPath = mw.getLogsDir() + '/' + webname + '.log' errLogPath = mw.getLogsDir() + '/' + webname + '.error.log' confFile = self.setupPath + '/nginx/vhost/' + webname + '.conf' rewriteFile = self.setupPath + '/nginx/rewrite/' + webname + '.conf' passFile = self.setupPath + '/nginx/pass/' + webname + '.conf' keyPath = self.sslDir + webname + '/privkey.pem' certPath = self.sslDir + webname + '/fullchain.pem' logs = [assLogPath, errLogPath, confFile, rewriteFile, passFile, keyPath, certPath] for i in logs: mw.deleteFile(i) # 重定向目录 redirectDir = self.setupPath + '/nginx/redirect/' + webname if os.path.exists(redirectDir): mw.execShell('rm -rf ' + redirectDir) # 代理目录 proxyDir = self.setupPath + '/nginx/proxy/' + webname if os.path.exists(proxyDir): mw.execShell('rm -rf ' + proxyDir) def delete(self, site_id, path): info = thisdb.getSitesById(site_id) webname = info['name'] self.deleteALlLogs(webname) if path == '1': web_root_path = mw.getWwwDir() + '/' + webname mw.execShell('rm -rf ' + web_root_path) # ssl ssl_dir = self.sslDir + '/' + webname if os.path.exists(ssl_dir): mw.execShell('rm -rf ' + ssl_dir) ssl_lets_dir = self.sslLetsDir + '/' + webname if os.path.exists(ssl_lets_dir): mw.execShell('rm -rf ' + ssl_lets_dir) ssl_acme_dir = mw.getAcmeDir() + '/' + webname if os.path.exists(ssl_acme_dir): mw.execShell('rm -rf ' + ssl_acme_dir) thisdb.deleteSitesById(site_id) thisdb.deleteDomainBySiteId(site_id) binding_list = thisdb.getBindingListBySiteId(site_id) for x in binding_list: tag = mw.getLogsDir() + "/" + webname + "_" + x['domain'] site_log = tag + ".log" site_error = tag + ".error.log" if os.path.exists(site_log): mw.execShell('rm -rf ' + site_log) if os.path.exists(site_error): mw.execShell('rm -rf ' + site_error) thisdb.deleteBindingBySiteId(site_id) mw.restartWeb() return mw.returnData(True, '站点【%s】删除成功!' % webname) def nginxAddConf(self): source_tpl = mw.getPanelDir() + '/data/tpl/nginx.conf' vhost_file = self.getHostConf(self.siteName) content = mw.readFile(source_tpl) content = content.replace('{$PORT}', self.sitePort) content = content.replace('{$SERVER_NAME}', self.siteName) content = content.replace('{$ROOT_DIR}', self.sitePath) content = content.replace('{$PHP_DIR}', self.setupPath + '/php') content = content.replace('{$PHPVER}', self.phpVersion) content = content.replace('{$OR_REWRITE}', self.rewritePath) # content = content.replace('{$OR_REDIRECT}', self.redirectPath) # content = content.replace('{$OR_PROXY}', self.proxyPath) logsPath = mw.getLogsDir() content = content.replace('{$LOGPATH}', logsPath) mw.writeFile(vhost_file, content) # 和反代配置冲突 && 默认伪静态为空 # rewrite_content = ''' # location /{ # if ($PHP_ENV != "1"){ # break; # } # if (!-e $request_filename) { # rewrite ^(.*)$ /index.php/$1 last; # break; # } # } # ''' rewrite_file = self.getRewriteConf(self.siteName) mw.writeFile(rewrite_file, '') # 设置网站过期 def setEndDate(self, site_id, end_date): info = thisdb.getSitesById(site_id) thisdb.setSitesData(site_id, edate=end_date) mw.writeLog('网站管理', '设置成功,站点【{1}】到期【{2}】后将自动停止!', (info['name'], end_date,)) return mw.returnData(True, '设置成功,站点到期后将自动停止!') # ssl相关方法 start def setSslConf(self, site_name): file = self.getHostConf(site_name) conf = mw.readFile(file) version = mw.getOpVer() keyPath = self.sslDir + '/' + site_name + '/privkey.pem' certPath = self.sslDir + '/' + site_name + '/fullchain.pem' if conf: if conf.find('ssl_certificate') == -1: # ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; # add_header Alt-Svc 'h3=":443";ma=86400,h3-29=":443";ma=86400'; http3Header = """ add_header Strict-Transport-Security "max-age=63072000"; add_header Alt-Svc 'h3=":443";ma=86400'; """ if not version.startswith('1.25'): http3Header = ''; sslStr = """#error_page 404/404.html; ssl_certificate %s; ssl_certificate_key %s; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; %s error_page 497 https://$host$request_uri;""" % (certPath, keyPath, http3Header) if(conf.find('ssl_certificate') != -1): return mw.returnData(True, 'SSL开启成功!') conf = conf.replace('#error_page 404/404.html;', sslStr) rep = r"listen\s+([0-9]+)\s*[default_server|reuseport]*;" tmp = re.findall(rep, conf) if not mw.inArray(tmp, '443'): listen = re.search(rep, conf).group() if version.startswith('1.25'): http_ssl = "\n\tlisten 443 ssl;" http_ssl = http_ssl + "\n\tlisten [::]:443 ssl;" http_ssl = http_ssl + "\n\tlisten 443 quic;" http_ssl = http_ssl + "\n\tlisten [::]:443 quic;" http_ssl = http_ssl + "\n\thttp2 on;" else: http_ssl = "\n\tlisten 443 ssl http2;" http_ssl = http_ssl + "\n\tlisten [::]:443 ssl http2;" conf = conf.replace(listen, listen + http_ssl) mw.backFile(file) mw.writeFile(file, conf) isError = mw.checkWebConfig() if(isError != True): mw.restoreFile(file) return mw.returnData(False, '证书错误:
' + isError.replace("\n", '
') + '
') self.saveCert(keyPath, certPath) msg = mw.getInfo('网站[{1}]开启SSL成功!', (site_name,)) mw.writeLog('网站管理', msg) mw.restartWeb() return mw.returnData(True, 'SSL开启成功!') # 设置网站备注 def setPs(self, site_id, ps): if thisdb.setSitesData(site_id, ps=ps): return mw.returnData(True, '修改成功!') return mw.returnData(False, '修改失败!') # 获取默认站点 def getDefaultSite(self): data = {} data['sites'] = mw.M('sites').field('name').order('id desc').select() data['default_site'] = thisdb.getOption('default_site', default='') return data # 获取域名列表 def getDomain(self, pid): data = thisdb.getDomainByPid(pid) return data # 获取日志内容 def getLogs(self, siteName): logPath = mw.getLogsDir() + '/' + siteName + '.log' if not os.path.exists(logPath): return mw.returnData(False, '日志为空') return mw.returnData(True, mw.getLastLine(logPath, 100)) # 获取错误日志内容 def getErrorLogs(self, siteName): logPath = mw.getLogsDir() + '/' + siteName + '.error.log' if not os.path.exists(logPath): return mw.returnData(False, '日志为空') return mw.returnData(True, mw.getLastLine(logPath, 100)) # 获取模版名内容 def getRewriteTpl(self, tplname): file = mw.getPanelDir() + '/rewrite/nginx/' + tplname + '.conf' if not os.path.exists(file): return mw.returnData(False, '模版不存在!') return mw.returnData(True, 'OK', file) def getRewriteList(self): rewriteList = {} rewriteList['rewrite'] = [] rewriteList['rewrite'].append('0.当前') rewrite_nginx_dir = mw.getPanelDir() + '/rewrite/nginx' for ds in os.listdir(rewrite_nginx_dir): rewriteList['rewrite'].append(ds[0:len(ds) - 5]) rewriteList['rewrite'] = sorted(rewriteList['rewrite']) return rewriteList # 取日志状态 def getLogsStatus(self, siteName): filename = self.getHostConf(siteName) conf = mw.readFile(filename) if conf.find('#ErrorLog') != -1: return False if conf.find("access_log off") != -1: return False return True # 取目录加密状态 def getHasPwd(self, siteName): filename = self.getHostConf(siteName) conf = mw.readFile(filename) if conf.find('#AUTH_START') != -1: return True return False # 取当站点前运行目录 def getSiteRunPath(self, site_name, site_path): filename = self.getHostConf(site_name) if os.path.exists(filename): conf = mw.readFile(filename) rep = r'\s*root\s*(.+);' path = re.search(rep, conf).groups()[0] data = {} if site_path == path: data['path'] = '/' else: data['path'] = path.replace(site_path, '') dirnames = [] dirnames.append('/') for filename in os.listdir(site_path): try: file_path = site_path + '/' + filename if os.path.islink(file_path): continue if os.path.isdir(file_path): dirnames.append('/' + filename) except: pass data['dirs'] = dirnames return data def getDirUserIni(self, site_id): info = thisdb.getSitesById(site_id) path = info['path'] name = info['name'] data = {} data['logs'] = self.getLogsStatus(name) data['run_path'] = self.getSiteRunPath(name, path) data['user_ini'] = False if os.path.exists(path + '/.user.ini'): data['user_ini'] = True if data['run_path']['path'] != '/': user_ini = path + data['run_path']['path'] + '/.user.ini' if os.path.exists(user_ini): data['userini'] = True data['pass'] = self.getHasPwd(name) data['path'] = path data['name'] = name return mw.returnData(True, 'OK', data) # 清除多余user.ini def delUserInI(self, path, up=0): filename = path + '/.user.ini' if os.path.exists(filename): mw.execShell("which chattr && chattr -i " + filename) os.remove(filename) for f in os.listdir(path): try: npath = path + '/' + f if os.path.isdir(npath): if up < 100: self.delUserInI(npath, up + 1) user_ini = npath + '/.user.ini' print('ff:',user_ini) if not os.path.exists(user_ini): continue mw.execShell('which chattr && chattr -i ' + user_ini) os.remove(user_ini) except: continue return True # 设置目录防御 def addDirUserIni(self, site_path, run_path): new_path = site_path + run_path filename = new_path + '/.user.ini' if os.path.exists(filename): return mw.returnData(True, '已打开防跨站设置!') open_path = 'open_basedir={}/:{}/'.format(new_path, site_path) if run_path == '/' or run_path == '': open_path = 'open_basedir={}/'.format(site_path) mw.writeFile(filename, open_path + ':/www/server/php:/tmp/:/proc/') mw.execShell("which chattr && chattr +i " + filename) def setDirUserIni(self, site_path, run_path): filename = site_path + '/.user.ini' if os.path.exists(filename): self.delUserInI(site_path) return mw.returnData(True, '已清除防跨站设置!') self.addDirUserIni(site_path, run_path) return mw.returnData(True, '已打开防跨站设置!') def getDirBinding(self, site_id): info = thisdb.getSitesById(site_id) path = info['path'] if not os.path.exists(path): checks = ['/', '/usr', '/etc'] if path in checks: data = {} data['dirs'] = [] data['binding'] = [] return mw.returnData(True, 'OK', data) os.system('mkdir -p ' + path) os.system('chmod 755 ' + path) os.system('chown www:www ' + path) siteName = info['name'] mw.writeLog('网站管理', '站点[' + siteName + '],根目录[' + path + ']不存在,已重新创建!') dirnames = [] for filename in os.listdir(path): try: filePath = path + '/' + filename if os.path.islink(filePath): continue if os.path.isdir(filePath): dirnames.append(filename) except: pass data = {} data['dirs'] = dirnames data['binding'] = thisdb.getBindingListBySiteId(site_id) return mw.returnJson(True, 'OK', data) def addDirBind(self, site_id, domain, dir_name): domain_split = domain.split(':') domain = domain_split[0] port = '80' if len(domain_split) > 1: port = domain_split[1] if dir_name == '': mw.returnData(False, '目录不能为空!') reg = r"^([\w\-\*]{1,100}\.){1,4}(\w{1,10}|\w{1,10}\.\w{1,10})$" if not re.match(reg, domain): return mw.returnData(False, '主域名格式不正确!') info = thisdb.getSitesById(site_id) webdir = info['path'] + '/' + dir_name if thisdb.getBindingCountByDomain(domain): return mw.returnData(False, '您添加的域名在子目录已存在!') if thisdb.getDomainCountByName(domain) > 0: return mw.returnData(False, '您添加的域名已存在!') filename = self.getHostConf(info['name']) conf = mw.readFile(filename) if conf: rep = r"enable-php-([0-9]{2,3})\.conf" domain_split = re.search(rep, conf).groups() version = domain_split[0] source_dirbind_tpl = mw.getPanelDir() + '/data/tpl/nginx_dirbind.conf' content = mw.readFile(source_dirbind_tpl) content = content.replace('{$PORT}', port) content = content.replace('{$PHPVER}', version) content = content.replace('{$DIRBIND}', domain) content = content.replace('{$ROOT_DIR}', webdir) content = content.replace('{$SERVER_MAIN}', info['name']) content = content.replace('{$OR_REWRITE}', self.rewritePath) content = content.replace('{$PHP_DIR}', self.setupPath + '/php') content = content.replace('{$LOGPATH}', mw.getLogsDir()) conf += "\r\n" + content mw.backFile(filename) mw.writeFile(filename, conf) conf = mw.readFile(filename) # 检查配置是否有误 isError = mw.checkWebConfig() if isError != True: mw.restoreFile(filename) msg = 'ERROR:
' + isError.replace("\n", '
') + '
' return mw.returnData(False, msg) thisdb.addBinding(site_id,domain,port,dir_name) msg = mw.getInfo('网站[{1}]子目录[{2}]绑定到[{3}]',(info['name'], dir_name, domain)) mw.writeLog('网站管理', msg) mw.restartWeb() mw.removeBackFile(filename) return mw.returnData(True, '添加成功!') # 取子目录Rewrite def getDirBindingRewrite(self, binding_id, add): binding_info = thisdb.getBindingById(binding_id) info = thisdb.getSitesById(binding_info['pid']) filename = self.getDirBindRewrite(info['name'], binding_info['path']) if add == '1': mw.writeFile(filename, '') file = self.getHostConf(info['name']) conf = mw.readFile(file) domain = binding_info['domain'] rep = "\n#BINDING-" + domain + "-START(.|\n)+BINDING-" + domain + "-END" tmp = re.search(rep, conf).group() dirConf = tmp.replace('rewrite/' + info['name'] + '.conf;', 'rewrite/' + info['name'] + '_' + binding_info['path'] + '.conf;') conf = conf.replace(tmp, dirConf) mw.writeFile(file, conf) data = {} data['rewrite_dir'] = self.rewritePath data['status'] = False if os.path.exists(filename): data['status'] = True data['data'] = mw.readFile(filename) data['rlist'] = [] for ds in os.listdir(self.rewritePath): if ds[0:1] == '.': continue if ds == 'list.txt': continue data['rlist'].append(ds[0:len(ds) - 5]) data['filename'] = filename return data def delDirBinding(self, binding_id): binding_info = thisdb.getBindingById(binding_id) info = thisdb.getSitesById(binding_info['pid']) filename = self.getHostConf(info['name']) conf = mw.readFile(filename) if conf: rep = r"\s*.+BINDING-" + binding_info['domain'] + "-START(.|\n)+BINDING-" + binding_info['domain'] + "-END" conf = re.sub(rep, '', conf) mw.writeFile(filename, conf) filename = self.getDirBindRewrite(info['name'], binding_info['path']) if os.path.exists(filename): os.remove(filename) msg = mw.getInfo('删除网站[{1}]子目录[{2}]绑定',(info['name'], binding_info['path'])) mw.writeLog('网站管理', msg) mw.restartWeb() thisdb.deleteBindingById(binding_id) return mw.returnJson(True, '删除成功!') def logsOpen(self, site_id): info = thisdb.getSitesById(site_id) name = info['name'] filename = self.getHostConf(name) if os.path.exists(filename): conf = mw.readFile(filename) rep = self.logsPath + '/' + name + '.log' if conf.find(rep) != -1: conf = conf.replace(rep + ' main', 'off') else: conf = conf.replace('access_log off', 'access_log ' + rep + ' main') mw.writeFile(filename, conf) mw.restartWeb() return mw.returnData(True, '操作成功!') def setSitePath(self, site_id, path): path = self.getPath(path) if path == "" or site_id == '0': return mw.returnData(False, "目录不能为空!") import utils.file as file if not file.checkDir(path): return mw.returnData(False, "不能以系统关键目录作为站点目录") info = thisdb.getSitesById(site_id) if info['path'] == path: return mw.returnData(False, "与原路径一致,无需修改!") conf_file = self.getHostConf(info['name']) content = mw.readFile(conf_file) if content: content = content.replace(info['path'], path) mw.writeFile(file, content) thisdb.setSitesData(site_id, path=path) msg = mw.getInfo('修改网站[{1}]物理路径成功!', (info['name'],)) mw.writeLog('网站管理', msg) mw.restartWeb() return mw.returnData(True, "设置成功!") # 设置当前站点运行目录 def setSiteRunPath(self, site_id, run_path): info = thisdb.getSitesById(site_id) site_name = info['name'] site_path = info['path'] new_path = site_path + run_path # 处理Nginx filename = self.getHostConf(site_name) if os.path.exists(filename): conf = mw.readFile(filename) rep = r'\s*root\s*(.+);' path = re.search(rep, conf).groups()[0] conf = conf.replace(path, new_path) mw.writeFile(filename, conf) self.setSitePath(site_path, run_path) mw.restartWeb() return mw.returnData(True, '设置成功!') # 设置目录加密 def setHasPwd(self, site_id, username, password): if len(username.strip()) == 0 or len(password.strip()) == 0: return mw.returnData(False, '用户名或密码不能为空!') info = thisdb.getSitesById(site_id) siteName = info['name'] filename = self.passPath + '/' + siteName + '.pass' passconf = username + ':' + mw.hasPwd(password) configFile = self.getHostConf(siteName) # 处理Nginx配置 conf = mw.readFile(configFile) if conf: rep = '#error_page 404 /404.html;' if conf.find(rep) == -1: rep = '#error_page 404/404.html;' data = ''' #AUTH_START auth_basic "Authorization"; auth_basic_user_file %s; #AUTH_END''' % (filename,) conf = conf.replace(rep, rep + data) mw.writeFile(configFile, conf) # 写密码配置 passDir = self.passPath if not os.path.exists(passDir): mw.execShell('mkdir -p ' + passDir) mw.writeFile(filename, passconf) msg = mw.getInfo('设置网站[{1}]为需要密码认证!', (siteName,)) mw.writeLog("网站管理", msg) mw.restartWeb() return mw.returnData(True, '设置成功!') # 取消目录加密 def closeHasPwd(self, site_id): info = thisdb.getSitesById(site_id) siteName = info['name'] configFile = self.getHostConf(siteName) if os.path.exists(configFile): conf = mw.readFile(configFile) rep = r"\n\s*#AUTH_START(.|\n){1,200}#AUTH_END" conf = re.sub(rep, '', conf) mw.writeFile(configFile, conf) msg = mw.getInfo('清除网站[{1}]的密码认证!', (siteName,)) mw.writeLog("网站管理", msg) mw.restartWeb() return mw.returnData(True, '设置成功!') def getSecurity(self, site_id): info = thisdb.getSitesById(site_id) name = info['name'] filename = self.getHostConf(name) conf = mw.readFile(filename) data = {} if conf.find('SECURITY-START') != -1: rep = "#SECURITY-START(\n|.){1,500}#SECURITY-END" tmp = re.search(rep, conf).group() data['fix'] = re.search(r"\(.+\)\$", tmp).group().replace('(', '').replace(')$', '').replace('|', ',') data['status'] = False data['none'] = False valid_referers = re.search(r"valid_referers\s+(.+);\n", tmp) valid_referers_none = re.search(r"valid_referers\s+none\s+blocked\s+(.+);\n", tmp) if valid_referers or valid_referers_none: data['status'] = True if valid_referers_none: domain_t = valid_referers_none.groups()[0].split() data['domains'] = ','.join(domain_t) data['none'] = True elif valid_referers: domain_t = valid_referers.groups()[0].split() data['domains'] = ','.join(domain_t) data['none'] = False else: data['fix'] = 'gif|jpg|jpeg|png|bmp|swf|js|css|ttf|woff2' domains = thisdb.getDomainByPid(site_id) tmp = [] for domain in domains: tmp.append(domain['name']) data['domains'] = ','.join(tmp) data['status'] = False data['none'] = False return data def setSecurity(self, site_id, fix, domains, status, none=''): info = thisdb.getSitesById(site_id) name = info['name'] if len(fix) < 2: return mw.returnData(False, 'URL后缀不能为空!') file = self.getHostConf(name) if os.path.exists(file): conf = mw.readFile(file) if status == 'false': rep = r"\s{0,4}#SECURITY-START(\n|.){1,500}#SECURITY-END\n?" conf = re.sub(rep, '', conf) mw.writeLog('网站管理', '站点[' + name + ']已关闭防盗链设置!') else: rep = r"\s{0,4}#SECURITY-START(\n|.){1,500}#SECURITY-END\n?" conf = re.sub(rep, '', conf) valid_referers = domains.strip().replace(',', ' ') if none == 'true': valid_referers = 'none blocked ' + valid_referers pre_path = self.setupPath + "/php/conf" re_path = r"include\s+" + pre_path + "/enable-php-" rconf = r'''#SECURITY-START 防盗链配置 location ~ .*\.(%s)$ { expires 30d; access_log /dev/null; valid_referers %s; if ($invalid_referer){ return 404; } } #SECURITY-END include %s/enable-php-''' % (fix.strip().replace(',', '|'), valid_referers, pre_path) conf = re.sub(re_path, rconf, conf) mw.writeLog('网站管理', '站点[' + name + ']已开启防盗链!') mw.writeFile(file, conf) mw.restartWeb() return mw.returnData(True, '设置成功!') def getSitePhpVersion(self, siteName): conf = mw.readFile(self.getHostConf(siteName)) rep = r"enable-php-(.*)\.conf" find_php_cnf = re.search(rep, conf) def_pver = '00' if find_php_cnf: tmp = find_php_cnf.groups() def_pver = tmp[0] data = {} data['phpversion'] = def_pver return data def getIndex(self, site_id): info = thisdb.getSitesById(site_id) file = self.getHostConf(info['name']) conf = mw.readFile(file) rep = r"\s+index\s+(.+);" tmp = re.search(rep, conf).groups() return tmp[0].replace(' ', ',') def setIndex(self, site_id, index): if index.find('.') == -1: return mw.returnData(False, '默认文档格式不正确,例:index.html') index = index.replace(' ', '') index = index.replace(',,', ',') if len(index) < 3: return mw.returnData(False, '默认文档不能为空!') info = thisdb.getSitesById(site_id) siteName = info['name'] index_l = index.replace(",", " ") file = self.getHostConf(siteName) conf = mw.readFile(file) if conf: rep = r"\s+index\s+.+;" conf = re.sub(rep, "\n\tindex " + index_l + ";", conf) mw.writeFile(file, conf) mw.writeLog('网站管理', '站点[{1}]设置{2}成功', (siteName, index_l)) return mw.returnJson(True, '设置成功!') def getLimitNet(self, site_id): info = thisdb.getSitesById(site_id) siteName = info['name'] filename = self.getHostConf(siteName) # 站点总并发 data = {} conf = mw.readFile(filename) try: rep = r"\s+limit_conn\s+perserver\s+([0-9]+);" tmp = re.search(rep, conf).groups() data['perserver'] = int(tmp[0]) # IP并发限制 rep = r"\s+limit_conn\s+perip\s+([0-9]+);" tmp = re.search(rep, conf).groups() data['perip'] = int(tmp[0]) # 请求并发限制 rep = r"\s+limit_rate\s+([0-9]+)\w+;" tmp = re.search(rep, conf).groups() data['limit_rate'] = int(tmp[0]) except: data['perserver'] = 0 data['perip'] = 0 data['limit_rate'] = 0 return data def setLimitNet(self, site_id, perserver, perip, limit_rate): str_perserver = 'limit_conn perserver ' + perserver + ';' str_perip = 'limit_conn perip ' + perip + ';' str_limit_rate = 'limit_rate ' + limit_rate + 'k;' info = thisdb.getSitesById(site_id) siteName = info['name'] filename = self.getHostConf(siteName) conf = mw.readFile(filename) if(conf.find('limit_conn perserver') != -1): # 替换总并发 rep = r"limit_conn\s+perserver\s+([0-9]+);" conf = re.sub(rep, str_perserver, conf) # 替换IP并发限制 rep = r"limit_conn\s+perip\s+([0-9]+);" conf = re.sub(rep, str_perip, conf) # 替换请求流量限制 rep = r"limit_rate\s+([0-9]+)\w+;" conf = re.sub(rep, str_limit_rate, conf) else: conf = conf.replace('#error_page 404/404.html;', "#error_page 404/404.html;\n " + str_perserver + "\n " + str_perip + "\n " + str_limit_rate) mw.writeFile(filename, conf) mw.restartWeb() mw.writeLog('网站管理', '网站[{1}]流量限制已开启!', (siteName,)) return mw.returnData(True, '设置成功!') def closeLimitNet(self, site_id): info = thisdb.getSitesById(site_id) siteName = info['name'] filename = self.getHostConf(siteName) conf = mw.readFile(filename) # 清理总并发 rep = r"\s+limit_conn\s+perserver\s+([0-9]+);" conf = re.sub(rep, '', conf) # 清理IP并发限制 rep = r"\s+limit_conn\s+perip\s+([0-9]+);" conf = re.sub(rep, '', conf) # 清理请求流量限制 rep = r"\s+limit_rate\s+([0-9]+)\w+;" conf = re.sub(rep, '', conf) mw.writeFile(filename, conf) mw.restartWeb() mw.writeLog('网站管理', '网站[{1}]流量限制已关闭!', (siteName,)) return mw.returnData(True, '已关闭流量限制!') # 获取重定向配置 def getRedirect(self, siteName): redirect_file = self.getRedirectDataPath(siteName) if not os.path.exists(redirect_file): mw.execShell("mkdir {}/{}".format(self.redirectPath, siteName)) return mw.returnData(True, "no exists!", {"result": [], "count": 0}) content = mw.readFile(redirect_file) data = json.loads(content) # 处理301信息 return mw.returnData(True, "ok", {"result": data, "count": len(data)}) # 操作 重定向配置 def operateRedirectConf(self, siteName, method='start'): vhost_file = self.getHostConf(siteName) content = mw.readFile(vhost_file) cnf_301 = '''#301-START include %s/*.conf; #301-END''' % (self.getRedirectPath( siteName)) cnf_301_source = '#301-START' # print('operateRedirectConf', content.find('#301-END')) if content.find('#301-END') != -1: if method == 'stop': rep = '#301-START(\n|.){1,500}#301-END' content = re.sub(rep, '#301-START', content) else: if method == 'start': content = re.sub(cnf_301_source, cnf_301, content) mw.writeFile(vhost_file, content) # get redirect status def setRedirect(self, siteName, site_from, to, type, r_type, keep_path): if siteName == '' or site_from == '' or to == '' or type == '' or r_type == '': return mw.returnData(False, "必填项不能为空!") redirect_file = self.getRedirectDataPath(siteName) content = mw.readFile(redirect_file) if os.path.exists(redirect_file) else "" data = json.loads(content) if content != "" else [] _r_type = 0 if r_type == "301" else 1 _type_code = 0 if type == "path" else 1 _keep_path = 1 if keep_path == "1" else 0 # check if domain exists in site if _type_code == 1: pid = mw.M('domain').where("name=?", (_siteName,)).field('id,pid,name,port,addtime').select() site_domain_lists = mw.M('domain').where("pid=?", (pid[0]['pid'],)).field('name').select() found = False for item in site_domain_lists: if item['name'] == _from: found = True break if found == False: return mw.returnData(False, "域名不存在!") file_content = "" # path if _type_code == 0: redirect_type = "permanent" if _r_type == 0 else "redirect" if not site_from.startswith("/"): site_from = "/{}".format(site_from) if _keep_path == 1: to = "{}$1".format(to) site_from = "{}(.*)".format(site_from) file_content = "rewrite ^{} {} {};".format(site_from, to, redirect_type) # domain else: if _keep_path == 1: _to = "{}$request_uri".format(_to) redirect_type = "301" if _type_code == 0 else "302" _if = "if ($host ~ '^{}')".format(site_from) _return = "return {} {}; ".format(redirect_type, to) file_content = _if + "{\r\n " + _return + "\r\n}" _id = mw.md5("{}+{}".format(file_content, siteName)) # 防止规则重复 for item in data: if item["r_from"] == site_from: return mw.returnData(False, "重复的规则!") rep = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" if not re.match(rep, to): return mw.returnData(False, "错误的目标地址") # write data json file data.append({"r_from": site_from, "type": _type_code, "r_type": _type_code,"r_to": to, 'keep_path': _keep_path, 'id': _id}) mw.writeFile(redirect_file, json.dumps(data)) mw.writeFile("{}/{}.conf".format(self.getRedirectPath(siteName), _id), file_content) self.operateRedirectConf(siteName, 'start') mw.restartWeb() return mw.returnData(True, "设置成功") def getRedirectConf(self, siteName, rid): if rid == '' or siteName == '': return mw.returnData(False, "必填项不能为空!") path = self.getRedirectPath(siteName) conf = "{}/{}.conf".format(path, rid) data = mw.readFile(conf) if data == False: return mw.returnData(False, "获取失败!") return mw.returnData(True, "ok", {"result": data}) # 删除指定重定向 def delRedirect(self,siteName, rid): if rid == '' or siteName == '': return mw.returnData(False, "必填项不能为空!") try: data_path = self.getRedirectDataPath(siteName) data_content = mw.readFile(data_path) if os.path.exists(data_path) else "" data = json.loads(data_content) if data_content != "" else [] for item in data: if item["id"] == rid: data.remove(item) break # write database mw.writeFile(data_path, json.dumps(data)) # data is empty ,should stop if len(data) == 0: self.operateRedirectConf(siteName, 'stop') # remove conf file mw.execShell("rm -rf {}/{}.conf".format(self.getRedirectPath(siteName), rid)) except Exception as e: return mw.returnData(False, "删除失败:"+str(e)) return mw.returnData(True, "删除成功!") # 读取 网站 反向代理列表 def getProxyList(self, site_name): data_path = self.getProxyDataPath(site_name) if not os.path.exists(data_path): mw.execShell("mkdir {}/{}".format(self.proxyPath, site_name)) return mw.returnData(True, "", {"result": [], "count": 0}) content = mw.readFile(data_path) data = json.loads(content) tmp = [] for proxy in data: proxy_dir = "{}/{}".format(self.proxyPath, site_name) proxy_dir_file = proxy_dir + '/' + proxy['id'] + '.conf' if os.path.exists(proxy_dir_file): proxy['status'] = True else: proxy['status'] = False tmp.append(proxy) return mw.returnData(True, "ok", {"result": data, "count": len(data)}) # 操作 反向代理配置 def operateProxyConf(self, site_name, method='start'): vhost_file = self.getHostConf(site_name) content = mw.readFile(vhost_file) proxy_cnf = '''#PROXY-START include %s/*.conf; #PROXY-END''' % (self.getProxyPath(site_name)) proxy_cnf_source = '#PROXY-START' if content.find('#PROXY-END') != -1: if method == 'stop': rep = '#PROXY-START(\n|.){1,500}#PROXY-END' content = re.sub(rep, '#PROXY-START', content) else: if method == 'start': content = re.sub(proxy_cnf_source, proxy_cnf, content) mw.writeFile(vhost_file, content) # 设置 网站 反向代理列表 def setProxy(self, site_name, site_from, to, host, name, open_proxy, open_cache, cache_time, site_id): from urllib.parse import urlparse if site_name == "" or site_from == "" or to == "" or host == "" or name == "": return mw.returnData(False, "必填项不能为空") rep = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" if not re.match(rep, to): return mw.returnData(False, "错误的目标地址!") # get host from url try: if host == "$host": host_tmp = urlparse(_to) host = host_tmp.netloc except: return mw.returnData(False, "错误的目标地址") proxy_site_path = self.getProxyDataPath(site_name) data_content = mw.readFile(proxy_site_path) if os.path.exists(proxy_site_path) else "" data = json.loads(data_content) if data_content != "" else [] proxy_action = 'add' if pid == "": pid = mw.md5("{}".format(name)) else: proxy_action = 'edit' if proxy_action == "add": for item in data: if item["name"] == name: return mw.returnData(False, "名称重复!!") if item["from"] == site_from: return mw.returnData(False, "代理目录已存在!!") tpl = "#PROXY-START\n\ location ^~ {from} {\n\ proxy_pass {to};\n\ proxy_set_header Host {host};\n\ proxy_ssl_server_name on;\n\ proxy_set_header X-Real-IP $remote_addr;\n\ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\ proxy_set_header REMOTE-HOST $remote_addr;\n\ proxy_set_header Upgrade $http_upgrade;\n\ proxy_set_header Connection $connection_upgrade;\n\ proxy_http_version 1.1;\n\ \n\ add_header X-Cache $upstream_cache_status;\n\ \n\ {proxy_cache}\n\ }\n\ # PROXY-END" tpl_proxy_cache = "\n\ if ( $uri ~* \\.(gif|png|jpg|css|js|woff|woff2)$\" )\n\ {\n\ expires {cache_time}m;\n\ }\n\ proxy_ignore_headers Set-Cookie Cache-Control expires;\n\ proxy_cache mw_cache;\n\ proxy_cache_key \"$host$uri$is_args$args\";\n\ proxy_cache_valid 200 304 301 302 {cache_time}m;\n\ " tpl_proxy_nocache = "\n\ set $static_files_app 0; \n\ if ( $uri ~* \\.(gif|png|jpg|css|js|woff|woff2)$\" )\n\ {\n\ set $static_files_app 1;\n\ expires 12h;\n\ }\n\ if ( $static_files_app = 0 )\n\ {\n\ add_header Cache-Control no-cache;\n\ }\n\ " # replace if site_from[0] != '/': site_from = '/' + site_from tpl = tpl.replace("{from}", site_from, 999) tpl = tpl.replace("{to}", to) tpl = tpl.replace("{host}", host, 999) tpl = tpl.replace("{cache_time}", cache_time, 999) if open_cache == 'on': tpl_proxy_cache = tpl_proxy_cache.replace("{cache_time}", cache_time, 999) tpl = tpl.replace("{proxy_cache}", tpl_proxy_cache, 999) else: tpl = tpl.replace("{proxy_cache}", tpl_proxy_nocache, 999) conf_proxy = "{}/{}.conf".format(self.getProxyPath(site_name), pid) conf_bk = "{}/{}.conf.txt".format(self.getProxyPath(site_name), pid) mw.writeFile(conf_proxy, tpl) rule_test = mw.checkWebConfig() if rule_test != True: os.remove(conf_proxy) return mw.returnData(False, "OpenResty配置测试不通过, 请重试: {}".format(rule_test)) if proxy_action == "add": # 添加代理 pid = mw.md5("{}".format(name)) for item in data: if item["name"] == name: return mw.returnData(False, "名称重复!") if item["from"] == site_from: return mw.returnData(False, "代理目录已存在!") data.append({ "name": name, "from": site_from, "to": to, "host": host, "open_cache": open_cache, "open_proxy": open_proxy, "cache_time": cache_time, "id": pid, }) else: # 修改代理 dindex = -1 for x in range(len(data)): if data[x]["id"] == pid: dindex = x break if dindex < 0: return mw.returnData(False, "异常请求") data[dindex]['from'] = site_from data[dindex]['to'] = to data[dindex]['host'] = host data[dindex]['open_cache'] = open_cache data[dindex]['open_proxy'] = open_proxy data[dindex]['cache_time'] = cache_time if open_proxy != 'on': os.rename(conf_proxy, conf_bk) mw.writeFile(proxy_site_path, json.dumps(data)) self.operateProxyConf(site_name, 'start') mw.restartWeb() return mw.returnData(True, "ok", {"hash": pid}) def setProxyStatus(self, site_name, proxy_id, status): if status == '' or site_name == '' or proxy_id == '': return mw.returnData(False, "必填项不能为空!") conf_file = "{}/{}/{}.conf".format(self.proxyPath, site_name, proxy_id) conf_txt = "{}/{}/{}.conf.txt".format(self.proxyPath, site_name, proxy_id) if status == '1': mw.execShell('mv ' + conf_txt + ' ' + conf_file) else: mw.execShell('mv ' + conf_file + ' ' + conf_txt) mw.restartWeb() return mw.returnData(True, "OK") def closeProxyAll(self, site_name): self.close_proxy = [] proxy_path = self.getProxyDataPath(site_name) if os.path.exists(proxy_path): content = mw.readFile(proxy_path) data = json.loads(content) for proxy in data: proxy_dir = "{}/{}".format(self.proxyPath, site_name) proxy_conf = proxy_dir + '/' + proxy['id'] + '.conf' proxy_txt = "{}/{}/{}.conf.txt".format(self.proxyPath, site_name, proxy['id']) if os.path.exists(proxy_conf): self.close_proxy.append(proxy['id']) mw.execShell('mv ' + proxy_conf + ' ' + proxy_txt) mw.restartWeb() return True def openProxyByOpen(self, site_name): for proxy_id in self.close_proxy: proxy_dir = "{}/{}".format(self.proxyPath, site_name) proxy_conf = proxy_dir + '/' + proxy_id + '.conf' proxy_txt = "{}/{}/{}.conf.txt".format(self.proxyPath, site_name, proxy_id) if os.path.exists(proxy_conf): mw.execShell('mv ' + proxy_txt + ' ' + proxy_conf) if len(self.close_proxy) > 0: mw.restartWeb() self.close_proxy = [] return True def getProxyConf(self, site_name, proxy_id): if pid == '' or site_name == '': return mw.returnData(False, "必填项不能为空!") conf_file = "{}/{}/{}.conf".format(self.proxyPath, site_name, proxy_id) if not os.path.exists(conf_file): conf_file = "{}/{}/{}.conf.txt".format(self.proxyPath, site_name, proxy_id) if not os.path.exists(conf_file): return mw.returnData(False, "获取失败!") data = mw.readFile(conf_file) return mw.returnData(True, "ok", {"result": data}) def delProxy(self, site_name, proxy_id): if proxy_id == '' or site_name == '': return mw.returnData(False, "必填项不能为空!") try: data_path = self.getProxyDataPath(site_name) data_content = mw.readFile(data_path) if os.path.exists(data_path) else "" data = json.loads(data_content) if data_content != "" else [] for item in data: if item["id"] == proxy_id: data.remove(item) break # write database mw.writeFile(data_path, json.dumps(data)) # data is empty,should stop if len(data) == 0: self.operateProxyConf(site_name, 'stop') # remove conf file cmd = "rm -rf {}/{}.conf*".format(self.getProxyPath(site_name), proxy_id) mw.execShell(cmd) except: return mw.returnData(False, "删除反代失败!") mw.restartWeb() return mw.returnData(True, "删除反代成功!") # 是否跳转到https def isToHttps(self, site_name): file = self.getHostConf(site_name) conf = mw.readFile(file) if conf: # if conf.find('HTTP_TO_HTTPS_START') != -1: # return True if conf.find('$server_port !~ 443') != -1: return True return False def getSsl(self, site_name, ssl_type): path = self.sslDir + '/' + site_name file = self.getHostConf(site_name) content = mw.readFile(file) key_text = 'ssl_certificate' status = True stype = 0 if content.find(key_text) == -1: status = False stype = -1 to_https = self.isToHttps(site_name) info = thisdb.getSitesByName(site_name) domains = mw.M('domain').field('name').where("pid=?", (info['id'],)).select() csr_path = path + '/fullchain.pem' # 生成证书路径 key_path = path + '/privkey.pem' # 密钥文件路径 cert_data = None if ssl_type == 'lets': csr_path = self.sslLetsDir + '/' + site_name + '/fullchain.pem' # 生成证书路径 key_path = self.sslLetsDir + '/' + site_name + '/privkey.pem' # 密钥文件路径 elif ssl_type == 'acme': acme_dir = mw.getAcmeDomainDir(site_name) csr_path = acme_dir + '/fullchain.cer' # ACME生成证书路径 key_path = acme_dir + '/' + site_name + '.key' # ACME密钥文件路径 key = '' if os.path.exists(key_path): key = mw.readFile(key_path) csr = '' if os.path.exists(csr_path): csr = mw.readFile(csr_path) cert_data = mw.getCertName(csr_path) # print(csr_path,cert_data) data = { 'status': status, 'domain': domains, 'key': key, 'csr': csr, 'type': stype, 'httpTohttps': to_https, 'cert_data': cert_data, } return mw.returnData(True, 'OK', data) def saveCert(self, keyPath, certPath): try: certInfo = mw.getCertName(certPath) if not certInfo: return mw.returnData(False, '证书解析失败!') vpath = self.sslDir + '/' + certInfo['subject'].strip() if not os.path.exists(vpath): os.system('mkdir -p ' + vpath) mw.writeFile(vpath + '/privkey.pem', mw.readFile(keyPath)) mw.writeFile(vpath + '/fullchain.pem', mw.readFile(certPath)) mw.writeFile(vpath + '/info.json', json.dumps(certInfo)) return mw.returnData(True, '证书保存成功!') except Exception as e: return mw.returnData(False, '证书保存失败!') def getCertList(self): try: vpath = self.sslDir if not os.path.exists(vpath): os.system('mkdir -p ' + vpath) data = [] for d in os.listdir(vpath): # keyPath = self.sslDir + siteName + '/privkey.pem' # certPath = self.sslDir + siteName + '/fullchain.pem' keyPath = vpath + '/' + d + '/privkey.pem' certPath = vpath + '/' + d + '/fullchain.pem' if os.path.exists(keyPath) and os.path.exists(certPath): self.saveCert(keyPath, certPath) mpath = vpath + '/' + d + '/info.json' if not os.path.exists(mpath): continue tmp = mw.readFile(mpath) if not tmp: continue tmp1 = json.loads(tmp) data.append(tmp1) return mw.returnData(True, 'OK', data) except: return mw.returnData(True, 'OK', []) def setPhpVersion(self, siteName, version): # nginx file = self.getHostConf(siteName) conf = mw.readFile(file) if conf: rep = r"enable-php-(.*)\.conf" tmp = re.search(rep, conf).group() conf = conf.replace(tmp, 'enable-php-' + version + '.conf') mw.writeFile(file, conf) msg = mw.getInfo('成功切换网站[{1}]的PHP版本为PHP-{2}', (siteName, version)) mw.writeLog("网站管理", msg) mw.restartWeb() return mw.returnData(True, msg) def setDefaultSite(self, name): # 清理旧的 default_site = thisdb.getOption('default_site', default='') if default_site: path = self.getHostConf(default_site) if os.path.exists(path): conf = mw.readFile(path) rep = r"listen\s+80.+;" conf = re.sub(rep, 'listen 80;', conf, 1) rep = r"listen\s+443.+;" conf = re.sub(rep, 'listen 443 ssl;', conf, 1) mw.writeFile(path, conf) path = self.getHostConf(name) if os.path.exists(path): conf = mw.readFile(path) rep = r"listen\s+80\s*;" conf = re.sub(rep, 'listen 80 default_server;', conf, 1) rep = r"listen\s+443\s*ssl\s*\w*\s*;" conf = re.sub(rep, 'listen 443 ssl default_server;', conf, 1) mw.writeFile(path, conf) thisdb.setOption('default_site', name) mw.restartWeb() return mw.returnData(True, '设置成功!') def setCliPhpVersion(self, version): php_bin = '/usr/bin/php' php_bin_src = "/www/server/php/%s/bin/php" % version php_ize = '/usr/bin/phpize' php_ize_src = "/www/server/php/%s/bin/phpize" % version php_fpm = '/usr/bin/php-fpm' php_fpm_src = "/www/server/php/%s/sbin/php-fpm" % version php_pecl = '/usr/bin/pecl' php_pecl_src = "/www/server/php/%s/bin/pecl" % version php_pear = '/usr/bin/pear' php_pear_src = "/www/server/php/%s/bin/pear" % version if not os.path.exists(php_bin_src): return mw.returnData(False, '指定PHP版本未安装!') is_chattr = mw.execShell('lsattr /usr|grep /usr/bin')[0].find('-i-') if is_chattr != -1: mw.execShell('chattr -i /usr/bin') mw.execShell("rm -f " + php_bin + ' ' + php_ize + ' ' + php_fpm + ' ' + php_pecl + ' ' + php_pear) mw.execShell("ln -sf %s %s" % (php_bin_src, php_bin)) mw.execShell("ln -sf %s %s" % (php_ize_src, php_ize)) mw.execShell("ln -sf %s %s" % (php_fpm_src, php_fpm)) mw.execShell("ln -sf %s %s" % (php_pecl_src, php_pecl)) mw.execShell("ln -sf %s %s" % (php_pear_src, php_pear)) if is_chattr != -1: mw.execShell('chattr +i /usr/bin') mw.writeLog('面板设置', '设置PHP-CLI版本为: %s' % version) return mw.returnData(True, '设置成功!') def addSiteTypes(name): if not name: return mw.returnData(False, "分类名称不能为空") if len(name) > 18: return mw.returnData(False, "分类名称长度不能超过6个汉字或18位字母") all_count = thisdb.getSiteTypesCount() if all_count >= 10: return mw.returnData(False, '最多添加10个分类!') name_count = thisdb.getSiteTypesCountByName(name) if name_count > 0: return mw.returnData(False, "指定分类名称已存在!") thisdb.addSiteTypes(name) return mw.returnData(True, '添加成功!') def getDnsapi(self): dnsapi_data = thisdb.getOptionByJson('dnsapi', default={}) dnsapi_option = [ {"name":"none", "title":'手动解析', 'key':'', 'help':''}, {"name":"dns_ali", "title":'Aliyun', 'key':'Ali_Key:Ali_Secret', 'help':'阿里云控制台》用户头像》accesskeys按指引获取AccessKey/SecretKey'}, {"name":"dns_cf", "title":'cloudflare', 'key':'CF_Key:CF_Email', 'help':'CloudFlare后台获取Global API Key'}, {"name":"dns_dp", "title":'dnspod/国内', 'key':'DP_Id:DP_Key','help':'DnsPod后台》用户中心》安全设置,开启API Token'}, {"name":"dns_dpi", "title":'dnspod/国际', 'key':'DPI_Id:DPI_Key','help':'DnsPod后台》用户中心》安全设置,开启API Token'}, {"name":"dns_tencent", "title":"腾讯云DNS", 'key':'Tencent_SecretId:Tencent_SecretKey', 'help':'腾讯云后台获取通行证'}, # {"name":"dns_gd", "title":'GoDaddy', 'key':'GD_Key:GD_Secret'}, # {"name":"dns_pdns", "title":'PowerDNS', 'key':'PDNS_Url:PDNS_ServerId:PDNS_Token:PDNS_Ttl'}, # {"name":"dns_lua", "title":'LuaDNS', 'key':'LUA_Key:LUA_Email'}, # {"name":"dns_me", "title":'DNSMadeEasy', 'key':'ME_Key:ME_Secret'}, # {"name":"dns_aws", "title":'Amazon Route53', 'key':'AWS_ACCESS_KEY_ID:AWS_SECRET_ACCESS_KEY'}, # {"name":"dns_ispconfig", "title":'ISPConfig', 'key':'ISPC_User:ISPC_Password:ISPC_Api:ISPC_Api_Insecure'}, # {"name":"dns_ad", "title":'Alwaysdata', 'key':'AD_API_KEY'}, # {"name":"dns_linode_v4", "title":'Linode', 'key':'LINODE_V4_API_KEY'}, # {"name":"dns_freedns", "title":'FreeDNS', 'key':'FREEDNS_User:FREEDNS_Password'}, # {"name":"dns_cyon", "title":'cyon.ch', 'key':'CY_Username:CY_Password:CY_OTP_Secret'}, # {"name":"dns_gandi_livedns", "title":'LiveDNS', 'key':'GANDI_LIVEDNS_TOKEN'}, # {"name":"dns_knot", "title":'Knot', 'key':'KNOT_SERVER:KNOT_KEY'}, # {"name":"dns_dgon", "title":'DigitalOcean', 'key':'DO_API_KEY'}, # {"name":"dns_cloudns", "title":'ClouDNS.net', 'key':'CLOUDNS_SUB_AUTH_ID:CLOUDNS_AUTH_PASSWORD'}, # {"name":"dns_namesilo", "title":'Namesilo', 'key':'Namesilo_Key'}, # {"name":"dns_azure", "title":'Azure', 'key':'AZUREDNS_SUBSCRIPTIONID:AZUREDNS_TENANTID:AZUREDNS_APPID:AZUREDNS_CLIENTSECRET'}, # {"name":"dns_selectel", "title":'selectel.com', 'key':'SL_Key'}, # {"name":"dns_zonomi", "title":'zonomi.com', 'key':'ZM_Key'}, # {"name":"dns_kinghost", "title":'KingHost', 'key':'KINGHOST_Username:KINGHOST_Password'}, # {"name":"dns_zilore", "title":'Zilore', 'key':'Zilore_Key'}, # {"name":"dns_gcloud", "title":'Google Cloud DNS', 'key':'CLOUDSDK_ACTIVE_CONFIG_NAME'}, # {"name":"dns_mydnsjp", "title":'MyDNS.JP', 'key':'MYDNSJP_MasterID:MYDNSJP_Password'}, # {"name":"dns_doapi", "title":'do.de', 'key':'DO_LETOKEN'}, # {"name":"dns_online", "title":'Online', 'key':'ONLINE_API_KEY'}, # {"name":"dns_cn", "title":'Core-Networks', 'key':'CN_User:CN_Password'}, # {"name":"dns_ultra", "title":'UltraDNS', 'key':'ULTRA_USR:ULTRA_PWD'}, # {"name":"dns_hetzner", "title":'Hetzner', 'key':'HETZNER_Token'}, # {"name":"dns_ddnss", "title":'DDNSS.de', 'key':'DDNSS_Token'}, ]; for i in range(len(dnsapi_option)): dval = dnsapi_option[i]['key'] dname = dnsapi_option[i]['name'] if dname == 'none': continue keys = dval.split(':') data = {} if dname in dnsapi_data: dnsapi_option[i]['data'] = dnsapi_data[dname] dnsapi_option[i]['title'] = dnsapi_option[i]['title'] + '[已配置]' else: t = {} for field in keys: t[field] = '' dnsapi_option[i]['data'] = t return dnsapi_option def setDnsapi(self, type, data): dnsapi_data = thisdb.getOptionByJson('dnsapi', default={}) dnsapi_data[type] = json.loads(data) thisdb.setOption('dnsapi',json.dumps(dnsapi_data)) return mw.returnData(True, '设置成功!') def acmeLogFile(self): return mw.getPanelDir() + '/logs/acme.log' def writeAcmeLog(self,msg): log_file = self.acmeLogFile() mw.writeFile(log_file, msg+"\n", 'w+') return True def letLogFile(self): return mw.getPanelDir() + '/logs/letsencrypt.log' def writeLetLog(self,msg): log_file = self.letLogFile() mw.writeFile(log_file, msg+"\n", "wb+") return True def closeSslConf(self, site_name): file = self.getHostConf(site_name) conf = mw.readFile(file) if conf: rep = "\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" conf = re.sub(rep, '', conf) rep = "\\s+ssl_certificate\\s+.+;\\s+ssl_certificate_key\\s+.+;" conf = re.sub(rep, '', conf) rep = "\\s+ssl_protocols\\s+.+;\n" conf = re.sub(rep, '', conf) rep = "\\s+ssl_ciphers\\s+.+;\n" conf = re.sub(rep, '', conf) rep = "\\s+ssl_prefer_server_ciphers\\s+.+;\n" conf = re.sub(rep, '', conf) rep = "\\s+ssl_session_cache\\s+.+;\n" conf = re.sub(rep, '', conf) rep = r"\s+ssl_session_timeout\s+.+;\n" conf = re.sub(rep, '', conf) rep = r"\s+ssl_ecdh_curve\s+.+;\n" conf = re.sub(rep, '', conf) rep = r"\s+ssl_session_tickets\s+.+;\n" conf = re.sub(rep, '', conf) rep = r"\s+ssl_stapling\s+.+;\n" conf = re.sub(rep, '', conf) rep = r"\s+ssl_stapling_verify\s+.+;\n" conf = re.sub(rep, '', conf) rep = r"\s+add_header\s+.+;\n" conf = re.sub(rep, '', conf) rep = r"\s+add_header\s+.+;\n" conf = re.sub(rep, '', conf) rep = r"\s+ssl\s+on;" conf = re.sub(rep, '', conf) rep = r"\s+error_page\s497.+;" conf = re.sub(rep, '', conf) rep = r"\s+if.+server_port.+\n.+\n\s+\s*}" conf = re.sub(rep, '', conf) rep = r"\s+listen\s+443.*;" conf = re.sub(rep, '', conf) rep = r"\s+listen\s+\[\:\:\]\:443.*;" conf = re.sub(rep, '', conf) rep = r"\s+http2\s+on;" conf = re.sub(rep, '', conf) mw.writeFile(file, conf) msg = mw.getInfo('网站[{1}]关闭SSL成功!', (site_name,)) mw.writeLog('网站管理', msg) mw.restartWeb() return mw.returnData(True, 'SSL已关闭!') def deleteSsl(self,site_name,ssl_type): path = self.sslDir + '/' + site_name csr_path = path + '/fullchain.pem' file = self.getHostConf(site_name) content = mw.readFile(file) key_text = 'ssl_certificate' status = True if content.find(key_text) == -1: status = False if ssl_type == 'now': if status: return mw.returnData(False, '使用中,先关闭再删除') if os.path.exists(path): mw.execShell('rm -rf ' + path) else: return mw.returnData(False, '还未申请!') elif ssl_type == 'lets': ssl_lets_dir = self.sslLetsDir + '/' + site_name csr_lets_path = ssl_lets_dir + '/fullchain.pem' # 生成证书路径 if mw.md5(mw.readFile(csr_lets_path)) == mw.md5(mw.readFile(csr_path)): return mw.returnData(False, '使用中,先关闭再删除') mw.execShell('rm -rf ' + ssl_lets_dir) elif ssl_type == 'acme': ssl_acme_dir = mw.getAcmeDomainDir(site_name) csr_acme_path = ssl_acme_dir + '/fullchain.cer' # 生成证书路径 if mw.md5(mw.readFile(csr_acme_path)) == mw.md5(mw.readFile(csr_path)): return mw.returnData(False, '使用中,先关闭再删除') mw.execShell('rm -rf ' + ssl_acme_dir) mw.restartWeb() return mw.returnData(True, '删除成功') def createAcmeFile(self, site_name, domains, email, force, renew): print(site_name, domains,force, renew, email) site_conf = self.getHostConf(site_name) if not os.path.exists(site_conf): return mw.returnData(False, '配置异常!') content = mw.readFile(site_conf) if content.find('301-END') != -1: return mw.returnData(False, '检测到您的站点做了301重定向设置,请先关闭重定向!') # 关闭反向代理 self.closeProxyAll(site_name) site_info = thisdb.getSitesByName(site_name) path = self.getSitePath(site_name) if path == '': return mw.returnData(False, '【'+site_name+'】配置文件,异常!') src_path = site_info['path'] acme_dir = mw.getAcmeDir() if force == 'true': force_bool = True if renew == 'true': cmd = acme_dir + "/acme.sh --renew --yes-I-know-dns-manual-mode-enough-go-ahead-please" else: cmd = acme_dir + "/acme.sh --issue --force" # 确定主域名顺序 t = [] if site_name in domains: t.append(site_name) for dd in domains: if dd == site_name: continue t.append(dd) domains = t domain_nums = 0 for d in domains: if mw.checkIp(d): continue if d.find('*.') != -1: return mw.returnData(False, '泛域名不能使用【文件验证】的方式申请证书!') cmd += ' -w ' + path cmd += ' -d ' + d domain_nums += 1 if domain_nums == 0: return mw.returnData(False, '请选择域名(不包括IP地址与泛域名)!') self.writeAcmeLog('开始ACME申请...') log_file = self.acmeLogFile() cmd = 'export ACCOUNT_EMAIL=' + email + ' && ' + cmd + ' >> ' + log_file result = mw.execShell(cmd) # 开启代理 self.openProxyByOpen(site_name) src_path = mw.getAcmeDomainDir(domains[0]) src_cert = src_path + '/fullchain.cer' src_key = src_path + '/' + domains[0] + '.key' src_cert.replace("\\*", "*") msg = '签发失败,您尝试申请证书的失败次数已达上限!

1、检查域名是否绑定到对应站点

\

2、检查域名是否正确解析到本服务器,或解析还未完全生效

\

3、如果您的站点设置了反向代理,或使用了CDN,请先将其关闭

\

4、如果您的站点设置了301重定向,请先将其关闭

\

5、如果以上检查都确认没有问题,请尝试更换DNS服务商

' if not os.path.exists(src_cert): data = {} data['err'] = result data['out'] = result[0] data['msg'] = msg data['result'] = {} if result[1].find('new-authz error:') != -1: data['result'] = json.loads(re.search("{.+}", result[1]).group()) if data['result']['status'] == 429: data['msg'] = msg data['status'] = False return data dst_path = self.sslDir + '/' + site_name dst_cert = dst_path + "/fullchain.pem" # 生成证书路径 dst_key = dst_path + "/privkey.pem" # 密钥文件路径 if not os.path.exists(dst_path): mw.execShell("mkdir -p " + dst_path) mw.buildSoftLink(src_cert, dst_cert, True) mw.buildSoftLink(src_key, dst_key, True) mw.execShell('echo "acme" > "' + dst_path + '/README"') # 写入配置文件 result = self.setSslConf(site_name) if not result['status']: return result result['csr'] = mw.readFile(src_cert) result['key'] = mw.readFile(src_key) mw.restartWeb() return mw.returnData(True, '证书已更新!', result) def getDnsapiExportVar(self, data): def_var = '' for k in data: def_var += 'export '+k+'="'+data[k]+'"\n' return def_var def getDomainRootName(self, domain): s = domain.split('.') count = len(s) last_index = count - 1 top_domain = s[last_index-1]+'.'+s[last_index] return top_domain def createAcmeDns(self, site_name, domains, dnspai, wildcard_domain, force, renew): dnsapi_option = thisdb.getOptionByJson('dnsapi', default={}) if not dnspai in dnsapi_option: return mw.returnData(False, dnspai+'未设置') dnsapi_data = dnsapi_option[dnspai] for k in dnsapi_data: if dnsapi_data[k] == '': return mw.returnData(False, k+'为空!') acme_dir = mw.getAcmeDir() for d in domains: cmd = ''' #!/bin/bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin:/opt/homebrew/bin:%s export PATH ''' % (acme_dir,) cmd += self.getDnsapiExportVar(dnsapi_data) if wildcard_domain == 'true': top_domain = self.getDomainRootName(d) cmd += 'acme.sh --issue --dns '+str(dnspai)+' -d '+top_domain+' -d "*.'+top_domain+'"' d = top_domain else: cmd += 'acme.sh --issue --dns '+str(dnspai)+' -d '+d log_file = self.acmeLogFile() cmd += ' >> ' + log_file print(cmd) result = mw.execShell(cmd) print(result) # acme源的ssl证书 src_path = mw.getAcmeDomainDir(d) src_cert = src_path + '/fullchain.cer' src_key = src_path + '/' + d + '.key' src_cert.replace("\\*", "*") msg = '签发失败,您尝试申请证书的失败次数已达上限!\

1、检查域名是否正确解析到本服务器,或解析还未完全生效

\

2、如果以上检查都确认没有问题,请尝试更换DNS服务商

' if not os.path.exists(src_cert): data = {} data['err'] = result data['out'] = result[0] data['msg'] = msg data['result'] = {} if result[1].find('new-authz error:') != -1: data['result'] = json.loads(re.search("{.+}", result[1]).group()) if data['result']['status'] == 429: data['msg'] = msg data['status'] = False return data # acme源建立软链接(目标) dst_path = self.sslDir + '/' + site_name dst_cert = dst_path + "/fullchain.pem" # 生成证书路径 dst_key = dst_path + "/privkey.pem" # 密钥文件路径 if not os.path.exists(dst_path): mw.execShell("mkdir -p " + dst_path) mw.buildSoftLink(src_cert, dst_cert, True) mw.buildSoftLink(src_key, dst_key, True) mw.execShell('echo "acme" > "' + dst_path + '/README"') # 写入配置文件 result = self.setSslConf(site_name) if not result['status']: return result result['csr'] = mw.readFile(src_cert) result['key'] = mw.readFile(src_key) mw.restartWeb() return mw.returnData(True, '证书已更新!', result) def createAcme(self, site_name, domains,force,renew, apply_type, dnspai, email, wildcard_domain): domains = json.loads(domains) if len(domains) < 1: return mw.returnData(False, '请选择域名') if email.strip() != '': thisdb.setOption('ssl_email', email) # 检测acme是否安装 acme_dir = mw.getAcmeDir() if not os.path.exists(acme_dir): try: mw.execShell("curl -sS curl https://get.acme.sh | sh") except: pass if not os.path.exists(acme_dir): return mw.returnData(False, '尝试自动安装ACME失败,请通过以下命令尝试手动安装

安装命令: curl https://get.acme.sh | sh

') # 避免频繁执行 checkAcmeRun = mw.execShell('ps -ef|grep acme.sh |grep -v grep') if checkAcmeRun[0] != '': return mw.returnData(False, '正在申请或更新SSL中...') if apply_type == 'file': print('apply_type',apply_type) return self.createAcmeFile(site_name, domains, email,force,renew) elif apply_type == 'dns': return self.createAcmeDns(site_name, domains, dnspai, wildcard_domain,force, renew) return mw.returnData(False, '异常请求') def createLet(self, site_name, domains, force, renew, apply_type, dnspai, email, wildcard_domain): domains = json.loads(domains) if len(domains) < 1: return mw.returnData(False, '请选择域名') if email.strip() != '': thisdb.setOption('ssl_email', email) host_conf_file = self.getHostConf(site_name) if os.path.exists(host_conf_file): siteConf = mw.readFile(host_conf_file) if siteConf.find('301-END') != -1: return mw.returnJson(False, '检测到您的站点做了301重定向设置,请先关闭重定向!') # 检测存在反向代理 data_path = self.getProxyDataPath(site_name) data_content = mw.readFile(data_path) if data_content != False: try: data = json.loads(data_content) except: pass for proxy in data: proxy_dir = "{}/{}".format(self.proxyPath, site_name) proxy_dir_file = proxy_dir + '/' + proxy['id'] + '.conf' if os.path.exists(proxy_dir_file): return mw.returnJson(False, '检测到您的站点做了反向代理设置,请先关闭反向代理!') # fix binddir domain ssl apply question mw.backFile(host_conf_file) auth_to = self.getSitePath(site_name) rep = r"\s*root\s*(.+);" replace_root = "\n\troot " + auth_to + ";" siteConf = re.sub(rep, replace_root, siteConf) mw.writeFile(host_conf_file, siteConf) mw.restartWeb() to_args = { 'domains': domains, 'auth_type': 'http', 'auth_to': auth_to, } src_letpath = mw.getServerDir() + '/web_conf/letsencrypt/' + site_name src_csrpath = src_letpath + "/fullchain.pem" # 生成证书路径 src_keypath = src_letpath + "/privkey.pem" # 密钥文件路径 dst_letpath = self.sslDir + '/' + site_name dst_csrpath = dst_letpath + '/fullchain.pem' dst_keypath = dst_letpath + '/privkey.pem' if not os.path.exists(src_letpath): import cert_api data = cert_api.cert_api().applyCertApi(to_args) mw.restoreFile(host_conf_file) if not data['status']: msg = data['msg'] if type(data['msg']) != str: msg = data['msg'][0] emsg = data['msg'][1]['challenges'][0]['error'] msg = msg + '

响应状态:' + str(emsg['status']) + '

错误类型:' + emsg[ 'type'] + '

错误代码:' + emsg['detail'] + '

' return mw.returnData(data['status'], msg, data['msg']) mw.execShell('mkdir -p ' + dst_letpath) mw.buildSoftLink(src_csrpath, dst_csrpath, True) mw.buildSoftLink(src_keypath, dst_keypath, True) mw.execShell('echo "lets" > "' + dst_letpath + '/README"') # 写入配置文件 result = self.setSslConf(site_name) if not result['status']: return result result['csr'] = mw.readFile(src_csrpath) result['key'] = mw.readFile(src_keypath) mw.restartWeb() return mw.returnData(data['status'], data['msg'], result) def getPhpVersion(self): phpVersions = ('00', '52', '53', '54', '55', '56', '70', '71', '72', '73', '74', '80', '81', '82', '83', '84') data = [] for val in phpVersions: tmp = {} if val == '00': tmp['version'] = '00' tmp['name'] = '纯静态' data.append(tmp) # 标准判断 checkPath = mw.getServerDir() + '/php/' + val + '/bin/php' if os.path.exists(checkPath): tmp['version'] = val tmp['name'] = 'PHP-' + val data.append(tmp) # 其他PHP安装类型 conf_dir = mw.getServerDir() + "/web_conf/php/conf" conf_list = os.listdir(conf_dir) l = len(conf_list) rep = r"enable-php-(.*?)\.conf" for name in conf_list: tmp = {} try: matchVer = re.search(rep, name).groups()[0] except Exception as e: continue if matchVer in phpVersions: continue tmp['version'] = matchVer tmp['name'] = 'PHP-' + matchVer data.append(tmp) return data