mdserver-web/web/utils/site.py

2742 lines
105 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# coding:utf-8
# ---------------------------------------------------------------------------------
# MW-Linux面板
# ---------------------------------------------------------------------------------
# copyright (c) 2018-∞(https://github.com/midoks/mdserver-web) All rights reserved.
# ---------------------------------------------------------------------------------
# Author: midoks <midoks@163.com>
# ---------------------------------------------------------------------------------
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: 检测到配置文件有错误,请先排除后再操作<br><br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>'
return mw.returnData(False, msg)
mw.restartWeb()
mw.removeBackFile(path)
return rdata
def getRewriteConf(self, site_name):
return self.rewritePath + '/' + site_name + '.conf'
def getRedirectDataPath(self, site_name):
return "{}/{}/data.json".format(self.redirectPath, site_name)
def getRedirectPath(self, site_name):
return "{}/{}".format(self.redirectPath, site_name)
def getProxyDataPath(self, site_name):
return "{}/{}/data.json".format(self.proxyPath, site_name)
def getProxyPath(self, site_name):
return "{}/{}".format(self.proxyPath, site_name)
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()
self.runHook('site_cb', 'add')
return mw.returnData(True, '添加成功')
def stop(self, site_id):
site_info = thisdb.getSitesById(site_id)
path = self.setupPath + '/stop'
if not os.path.exists(path):
os.makedirs(path)
default_text = 'The website has been closed!!!'
mw.writeFile(path + '/index.html', default_text)
binding = thisdb.getBindingListBySiteId(site_id)
for b in binding:
bpath = path + '/' + b['path']
if not os.path.exists(bpath):
mw.execShell('mkdir -p ' + bpath)
mw.execShell('ln -sf ' + path +'/index.html ' + bpath + '/index.html')
# nginx
file = self.getHostConf(site_info['name'])
conf = mw.readFile(file)
if conf:
conf = conf.replace(site_info['path'], path)
mw.writeFile(file, conf)
thisdb.setSitesData(site_id, status='0')
msg = mw.getInfo('网站[{1}]已被停用!', (site_info['name'],))
mw.writeLog('网站管理', msg)
mw.restartWeb()
return mw.returnData(True, '站点已停用!')
def start(self, site_id):
site_info = thisdb.getSitesById(site_id)
path = self.setupPath + '/stop'
# nginx
file = self.getHostConf(site_info['name'])
conf = mw.readFile(file)
if conf:
conf = conf.replace(path, site_info['path'])
mw.writeFile(file, conf)
thisdb.setSitesData(site_id, status='1')
msg = mw.getInfo('网站[{1}]已被启用!', (site_info['name'],))
mw.writeLog('网站管理', msg)
mw.restartWeb()
return mw.returnData(True, '站点已启用!')
def nginxAddDomainFilter(self, site_id, site_name, domain, port):
self.nginxAddDomain(site_name, domain, port)
return 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: 检测到配置文件有错误,请先排除后再操作<br><br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>'
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.nginxAddDomainFilter(site_id, 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()
self.runHook('site_cb', 'delete')
return mw.returnData(True, '站点【%s】删除成功!' % webname)
def nginxAddConf(self):
source_tpl = self.getNgxTplDir() + '/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, '设置成功,站点到期后将自动停止!')
def setSsl(self, site_name, key, csr):
path = self.sslDir + '/' + site_name
if not os.path.exists(path):
mw.execShell('mkdir -p ' + path)
csrpath = path + "/fullchain.pem" # 生成证书路径
keypath = path + "/privkey.pem" # 密钥文件路径
if(key.find('KEY') == -1):
return mw.returnJson(False, '秘钥错误,请检查!')
if(csr.find('CERTIFICATE') == -1):
return mw.returnJson(False, '证书错误,请检查!')
tmp_cert = '/tmp/cert.pl'
mw.writeFile(tmp_cert, csr)
if not mw.checkCert(tmp_cert):
os.remove(tmp_cert)
return mw.returnData(False, '证书错误,请粘贴正确的PEM格式证书!')
os.remove(tmp_cert)
mw.backFile(keypath)
mw.backFile(csrpath)
mw.writeFile(keypath, key)
mw.writeFile(csrpath, csr)
# 写入配置文件
result = self.setSslConf(site_name)
if not result['status']:
return result
isError = mw.checkWebConfig()
if(type(isError) == str):
mw.restoreFile(keypath)
mw.restoreFile(csrpath)
msg = 'ERROR: <br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>'
return mw.returnData(False, msg)
mw.writeLog('网站管理', '证书已保存!')
mw.restartWeb()
return mw.returnData(True, '证书已保存!')
# ssl相关方法 start
def setSslConf(self, site_name):
file = self.getHostConf(site_name)
conf = mw.readFile(file)
if not conf:
return mw.returnData(False, '站点[%s]配置异常!'.format(site_name))
version = mw.getOpVer()
keyPath = self.sslDir + '/' + site_name + '/privkey.pem'
certPath = self.sslDir + '/' + site_name + '/fullchain.pem'
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';
# add_header Strict-Transport-Security "max-age=63072000" always;
http3Header = """
add_header Alt-Svc 'h3=":443";ma=86400' always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
"""
if not mw.isSupportHttp3(version):
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()
quic_conf = "quic reuseport"
if mw.isVhostHasReuseport(version):
quic_conf = "quic"
if mw.isSupportHttp3(version):
http_ssl = "\n\tlisten 443 ssl;"
http_ssl = http_ssl + "\n\tlisten [::]:443 ssl;"
http_ssl = http_ssl + "\n\tlisten 443 "+quic_conf+";"
http_ssl = http_ssl + "\n\tlisten [::]:443 "+quic_conf+";"
http_ssl = http_ssl + "\n\thttp3 on;"
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 not isError:
mw.restoreFile(file)
return mw.returnData(False, '证书错误: <br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>')
self.saveCert(site_name, 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, site_id):
data = thisdb.getDomainBySiteId(site_id)
return mw.returnData(True, 'ok', 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 getNgxRewriteDir(self):
return mw.getPanelDir() + '/web/misc/nginx/rewrite'
def getNgxTplDir(self):
return mw.getPanelDir() + '/web/misc/nginx/tpl'
# 获取模版名内容
def getRewriteTpl(self, name):
path = self.getNgxRewriteDir() +'/'+ name + ".conf"
if not os.path.exists(path):
return mw.returnData(False, '模版不存在!')
return mw.returnData(True, 'OK', path)
def setRewrite(self,path,data,encoding):
if not os.path.exists(path):
mw.writeFile(path, '')
mw.backFile(path)
mw.writeFile(path, data)
isError = mw.checkWebConfig()
if(type(isError) == str):
mw.restoreFile(path)
msg = 'ERROR: <br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>'
return mw.returnJson(False, msg)
mw.restartWeb()
return mw.returnData(True, '设置成功!')
def setRewriteTpl(self,name,data):
path = self.getNgxRewriteDir() +'/'+ name + ".conf"
if os.path.exists(path):
return mw.returnData(False, '模版已经存在!')
if data == "":
return mw.returnData(False, '模版内容不能为空!')
ok = mw.writeFile(path, data)
if not ok:
return mw.returnData(False, '模版保持失败!')
return mw.returnData(True, '设置模板成功!')
def getRewriteList(self):
rewriteList = {}
rewriteList['rewrite'] = []
rewriteList['rewrite'].append('0.当前')
rewrite_nginx_dir = self.getNgxRewriteDir()
for ds in os.listdir(rewrite_nginx_dir):
if ds.startswith('.'):
continue
if ds.endswith('conf'):
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('/')
if os.path.exists(site_path):
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 = self.getNgxTplDir() + '/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: <br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>'
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, "与原路径一致,无需修改!")
host_conf = self.getHostConf(info['name'])
content = mw.readFile(host_conf)
if content:
content = content.replace(info['path'], path)
mw.writeFile(host_conf, 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
site_host = self.getHostConf(site_name)
if os.path.exists(site_host):
content = mw.readFile(site_host)
rep = r'\s*root\s*(.+);'
path = re.search(rep, content).groups()[0]
content = content.replace(path, new_path)
mw.writeFile(site_host, content)
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.getDomainBySiteId(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 httpToHttps(self, site_name):
file = self.getHostConf(site_name)
conf = mw.readFile(file)
if not conf:
return mw.returnData(False, '站点[{}]配置异常!'.format(site_name))
if conf.find('ssl_certificate') == -1:
return mw.returnData(False, '当前未开启SSL')
to = "#error_page 404/404.html;\n\
#HTTP_TO_HTTPS_START\n\
if ($server_port !~ 443){\n\
rewrite ^(/.*)$ https://$host$1 permanent;\n\
}\n\
#HTTP_TO_HTTPS_END"
conf = conf.replace('#error_page 404/404.html;', to)
mw.writeFile(file, conf)
mw.restartWeb()
return mw.returnData(True, '设置成功!')
def closeToHttps(self, site_name):
file = self.getHostConf(site_name)
conf = mw.readFile(file)
if not conf:
return mw.returnData(False, '站点[{}]配置异常!'.format(site_name))
rep = r"\n\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END"
conf = re.sub(rep, '', conf)
rep = r"\s+if.+server_port.+\n.+\n\s+\s*}"
conf = re.sub(rep, '', conf)
mw.writeFile(file, conf)
mw.restartWeb()
return mw.returnData(True, '关闭HTTPS跳转成功!')
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.returnData(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, site_name):
redirect_file = self.getRedirectDataPath(site_name)
if not os.path.exists(redirect_file):
mw.execShell("mkdir " + self.getRedirectPath(site_name))
return mw.returnData(True, "no exists!", {"result": [], "count": 0})
content = mw.readFile(redirect_file)
data = json.loads(content)
for i in range(len(data)):
redirect_dir = self.getRedirectPath(site_name)
redirect_file = redirect_dir + '/' + data[i]['id'] + '.conf'
if os.path.exists(redirect_file):
data[i]['status'] = True
else:
data[i]['status'] = False
return mw.returnData(True, "ok", {"result": data, "count": len(data)})
def setRedirectStatus(self, site_name, redirect_id, status):
if status == '' or site_name == '' or redirect_id == '':
return mw.returnData(False, "必填项不能为空!")
conf_file = "{}/{}/{}.conf".format(self.redirectPath, site_name, redirect_id)
conf_txt = "{}/{}/{}.conf.txt".format(self.redirectPath, site_name, redirect_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 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, site_name, site_from, to, type, r_type, keep_path):
if site_name == '' or site_from == '' or to == '' or type == '' or r_type == '':
return mw.returnData(False, "必填项不能为空!")
redirect_file = self.getRedirectDataPath(site_name)
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:
domain_list = mw.M('domain').where("name=?", (site_name,)).field('id,pid,name,port,add_time').select()
site_domain_lists = mw.M('domain').where("pid=?", (domain_list[0]['pid'],)).field('name').select()
found = False
for item in site_domain_lists:
if item['name'] == site_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 _r_type == 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, site_name))
# 防止规则重复
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(site_name), _id), file_content)
self.operateRedirectConf(site_name, 'start')
mw.restartWeb()
return mw.returnData(True, "设置成功")
def getRedirectConf(self, site_name, redirect_id):
if redirect_id == '' or site_name == '':
return mw.returnData(False, "必填项不能为空!")
path = self.getRedirectPath(site_name)
conf = "{}/{}.conf".format(path, redirect_id)
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_cors, open_http3,open_cache, cache_time, proxy_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 Exception as e:
# return mw.returnData(False, "错误的目标地址")
# print(host)
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 proxy_id == "":
proxy_id = 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\
add_header X-Cache $upstream_cache_status;\n\
{cors}\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\
proxy_set_header User-Agent $http_user_agent;\n\
proxy_set_header X-Forwarded-Proto $scheme;\n\
proxy_set_header X-Forwarded-Host $http_host;\n\
proxy_set_header X-Forwarded-Port $server_port;\n\
\n\
#ws\n\
proxy_buffering off;\n\
proxy_read_timeout 300s;\n\
proxy_connect_timeout 75s;\n\
{proxy_cache}\n\
{http3}\n\
}\n\
# PROXY-END"
tpl_proxy_cache = "\n\
if ( $uri ~* \\.(gif|png|jpg|jpeg|css|js|svg|ttf|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_bak = "\n\
set $static_files_app 0; \n\
if ( $uri ~* \\.(gif|png|jpg|jpeg|css|js|svg|ttf|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\
"
tpl_proxy_nocache = "\n\
add_header Cache-Control no-cache;\n\
"
tpl_proxy_cors = "\n\
add_header Access-Control-Allow-Origin *;\n\
add_header Access-Control-Allow-Headers *;\n\
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';\n\
if ($request_method = 'OPTIONS') {\n\
return 204;\n\
}\n\
"
tpl_proxy_http3 = "\n\
add_header Alt-Svc 'h3=\":443\";ma=86400' always;\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)
if open_cors == 'on':
tpl = tpl.replace("{cors}", tpl_proxy_cors, 999)
else:
tpl = tpl.replace("{cors}", '', 999)
if open_http3 == 'on':
tpl = tpl.replace("{http3}", tpl_proxy_http3, 999)
else:
tpl = tpl.replace("{http3}", '', 999)
conf_proxy = "{}/{}.conf".format(self.getProxyPath(site_name), proxy_id)
conf_bk = "{}/{}.conf.txt".format(self.getProxyPath(site_name), proxy_id)
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":
# 添加代理
proxy_id = 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,
"cache_time": cache_time,
"open_proxy": open_proxy,
"open_cors": open_cors,
"open_http3": open_http3,
"id": proxy_id,
})
else:
# 修改代理
dindex = -1
for x in range(len(data)):
if data[x]["id"] == proxy_id:
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]['cache_time'] = cache_time
data[dindex]['open_proxy'] = open_proxy
data[dindex]['open_cors'] = open_cors
data[dindex]['open_http3'] = open_http3
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": proxy_id})
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_txt):
mw.execShell('mv ' + proxy_txt + ' ' + proxy_conf)
if len(self.close_proxy) > 0:
mw.restartWeb()
self.close_proxy = []
return True
def closeRedirectAll(self, site_name):
self.close_redirect = []
redirect_path = self.getRedirectDataPath(site_name)
if os.path.exists(redirect_path):
content = mw.readFile(redirect_path)
data = json.loads(content)
for redirect_data in data:
redirect_dir = "{}/{}".format(self.redirectPath, site_name)
redirect_conf = redirect_dir + '/' + redirect_data['id'] + '.conf'
redirect_txt = "{}/{}/{}.conf.txt".format(self.redirectPath, site_name, redirect_data['id'])
if os.path.exists(redirect_conf):
self.close_redirect.append(redirect_data['id'])
mw.execShell('mv ' + redirect_conf + ' ' + redirect_txt)
mw.restartWeb()
def openRedirectByOpen(self, site_name):
for redirect_id in self.close_redirect:
redirect_dir = "{}/{}".format(self.redirectPath, site_name)
redirect_conf = redirect_dir + '/' + redirect_id + '.conf'
redirect_txt = "{}/{}/{}.conf.txt".format(self.redirectPath, site_name, redirect_id)
if os.path.exists(redirect_txt):
mw.execShell('mv ' + redirect_txt + ' ' + redirect_conf)
if len(self.close_redirect) > 0:
mw.restartWeb()
self.close_redirect = []
return True
def saveRedirectConf(self, site_name, redirect_id, config):
if redirect_id == '' or site_name == '':
return mw.returnData(False, "必填项不能为空!")
_old_config = mw.readFile("{}/{}/{}.conf".format(self.redirectPath, site_name, redirect_id))
if _old_config == False:
return mw.returnData(False, "非法操作")
mw.writeFile("{}/{}/{}.conf".format(self.redirectPath, site_name, redirect_id), config)
rule_test = mw.checkWebConfig()
if rule_test != True:
mw.writeFile("{}/{}/{}.conf".format(self.redirectPath,site_name, redirect_id), _old_config)
return mw.returnData(False, "OpenResty 配置测试不通过, 请重试: {}".format(rule_test))
self.operateRedirectConf(site_name, 'start')
mw.restartWeb()
return mw.returnData(True, "ok")
def getProxyConf(self, site_name, proxy_id):
if proxy_id == '' 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 saveProxyConf(self, site_name, proxy_id, config):
if proxy_id == '' or site_name == '':
return mw.returnData(False, "必填项不能为空!")
proxy_file = "{}/{}/{}.conf".format(self.proxyPath, site_name, proxy_id)
mw.backFile(proxy_file)
mw.writeFile(proxy_file, config)
rule_test = mw.checkWebConfig()
if rule_test != True:
mw.restoreFile(proxy_file)
mw.removeBackFile(proxy_file)
return mw.returnData(False, "OpenResty 配置测试不通过, 请重试: {}".format(rule_test))
mw.removeBackFile(proxy_file)
self.operateRedirectConf(site_name, 'start')
mw.restartWeb()
return mw.returnData(True, "ok")
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('$server_port !~ 443') != -1:
return True
return False
def getSsl(self, site_name, ssl_type):
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)
site_info = thisdb.getSitesByName(site_name)
domains = thisdb.getDomainBySiteId(site_info['id'])
path = self.sslDir + '/' + site_name
if ssl_type == 'lets':
csr_path = self.sslLetsDir + '/' + site_name + '/fullchain.pem' # Let生成证书路径
key_path = self.sslLetsDir + '/' + site_name + '/privkey.pem' # Let密钥文件路径
elif ssl_type == 'acme':
csr_path = path + '/fullchain.pem'
key_path = path + '/privkey.pem'
# acme_dir = mw.getAcmeDomainDir(site_name)
# csr_path = acme_dir + '/fullchain.cer' # ACME生成证书路径
# key_path = acme_dir + '/' + site_name + '.key' # ACME密钥文件路径
else:
csr_path = path + '/fullchain.pem' # 生成证书路径
key_path = path + '/privkey.pem' # 密钥文件路径
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, site_name, keyPath, certPath):
try:
certInfo = mw.getCertName(certPath)
if not certInfo:
return mw.returnData(False, '证书解析失败!')
vpath = self.sslDir + '/' + site_name
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(d, 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+443\s+quic\s*reuseport default_server"
conf = re.sub(rep, 'listen 443 quic reuseport;', conf, 1)
rep = r"listen\s+\[\:\:\]\:443\s+quic\s*reuseport\s*default_server;"
conf = re.sub(rep, 'listen [::]:443 quic reuseport;', conf, 1)
rep = r"listen\s+80\s*default_server;"
conf = re.sub(rep, 'listen 80;', conf, 1)
rep = r"listen\s+\[\:\:\]\:80\s*default_server;"
conf = re.sub(rep, 'listen [::]:80;', conf, 1)
rep = r"listen\s+443\s*ssl\s*default_server;"
conf = re.sub(rep, 'listen 443 ssl;', conf, 1)
rep = r"listen\s+443\s*quic\*default_server;"
conf = re.sub(rep, 'listen 443 quic;', conf, 1)
rep = r"listen\s+\[\:\:\]\:443\s*ssl\s*default_server;"
conf = re.sub(rep, 'listen [::]:443 ssl;', conf, 1)
rep = r"listen\s+\[\:\:\]\:443\s*quic\s*default_server;"
conf = re.sub(rep, 'listen [::]:443 quic;', conf, 1)
mw.writeFile(path, conf)
path = self.getHostConf(name)
if os.path.exists(path):
conf = mw.readFile(path)
rep = r"listen\s+443\s*quic\s*reuseport;"
conf = re.sub(rep, 'listen 443 quic reuseport default_server;', conf, 1)
rep = r"listen\s+\[\:\:\]\:443\s*quic\s*reuseport;"
conf = re.sub(rep, 'listen [::]:443 quic reuseport default_server;', conf, 1)
rep = r"listen\s+80\s*;"
conf = re.sub(rep, 'listen 80 default_server;', conf, 1)
rep = r"listen\s+\[\:\:\]\:80;"
conf = re.sub(rep, 'listen [::]:80 default_server;', conf, 1)
rep = r"listen\s+443\s*ssl;"
conf = re.sub(rep, 'listen 443 ssl default_server;', conf, 1)
rep = r"listen\s+443\s*quic;"
conf = re.sub(rep, 'listen 443 quic default_server;', conf, 1)
rep = r"listen\s+\[\:\:\]\:443\s*ssl;"
conf = re.sub(rep, 'listen [::]:443 ssl default_server;', conf, 1)
rep = r"listen\s+\[\:\:\]\:443\s*quic;"
conf = re.sub(rep, 'listen [::]:443 quic 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_huaweicloud", "title":'华为云', 'key':'HUAWEICLOUD_Username:HUAWEICLOUD_Password:HUAWEICLOUD_DomainName'},
{"name":"dns_cf", "title":'cloudflare', 'key':'CF_Key:CF_Email:CF_Token:CF_Account_ID:CF_Zone_ID', '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)
rep = r"\s+http3\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, apply_ca, domains, email, force, renew):
site_conf = self.getHostConf(site_name)
if not os.path.exists(site_conf):
return mw.returnData(False, '配置异常!')
# 关闭反向代理
self.closeProxyAll(site_name)
# 关闭重定向
self.closeRedirectAll(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()
if apply_ca == "let":
cmd = cmd + " --server letsencrypt "
elif apply_ca == "zerossl":
cmd = cmd + " --server zerossl "
elif apply_ca == "buypass":
cmd = cmd + " --server buypass "
cmd = 'export ACCOUNT_EMAIL=' + email + ' && ' + cmd + ' >> ' + log_file
# print(cmd)
result = mw.execShell(cmd)
# 开启代理
self.openProxyByOpen(site_name)
# 开启重定向
self.openRedirectByOpen(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 = '签发失败,您尝试申请证书的失败次数已达上限!<p>1、检查域名是否绑定到对应站点</p>\
<p>2、检查域名是否正确解析到本服务器,或解析还未完全生效</p>\
<p>3、如果您的站点设置了反向代理,或使用了CDN,请先将其关闭</p>\
<p>4、如果您的站点设置了301重定向,请先将其关闭</p>\
<p>5、如果以上检查都确认没有问题请尝试更换DNS服务商</p>'
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('.',1)
# return s[1]
def getDomainRootName(self, domain):
import tldextract
extracted = tldextract.extract(domain)
# 组合注册域名和顶级域名
return f"{extracted.domain}.{extracted.suffix}"
def getDomainRootName_Old(self, domain):
s = domain.split('.')
count = len(s)
last_index = count - 1
top_domain = s[last_index-1]+'.'+s[last_index]
return top_domain
# 查找手动验证,需要改动域名dns的配置
# nslookup -q=txt _acme-challenge.xx.com
def findAcmeHandDnsNotice(self, top_domain):
log_file = self.acmeLogFile()
info = mw.readFile(log_file)
txt_rep = r"TXT value: \'(.*)\'"
txt_value = re.finditer(txt_rep, info)
rdata = []
for text in txt_value:
t = {}
t['domain'] = '_acme-challenge.'+top_domain
t['val'] = text.groups()[0]
t['type'] = 'TXT'
t['must'] = True
rdata.append(t)
return rdata
# acme手动申请方式
# https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode
def createAcmeDnsTypeNone(self, site_name, domains, email, dnspai, wildcard_domain, force, renew, dns_alias):
# print(site_name, domains, email, dnspai, wildcard_domain, force, renew)
acme_dir = mw.getAcmeDir()
log_file = self.acmeLogFile()
for d in domains:
top_domain = self.getDomainRootName(d)
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 += "acme.sh --register-account -m " + email + " \n"
if wildcard_domain == 'true':
cmd += 'acme.sh --issue -d '+top_domain+' -d "*.'+top_domain+'"'
d = top_domain
else:
cmd += "acme.sh --issue -d " + d + " "
cmd += " --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please"
if dns_alias != '':
cmd += ' --domain-alias '+str(dns_alias)
if renew == 'true':
cmd += " --renew"
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'
if not os.path.exists(src_cert):
info = self.findAcmeHandDnsNotice(top_domain)
if len(info) != 0:
return mw.returnData(True, '手动解析', info)
# 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 createAcmeDns(self, site_name, apply_ca, domains, email, dnspai, wildcard_domain, force, renew, dns_alias):
dnsapi_option = thisdb.getOptionByJson('dnsapi', default={})
log_file = self.acmeLogFile()
cmd = 'echo "..." > '+ log_file
mw.execShell(cmd)
# 手动方式申请
if dnspai == 'none':
return self.createAcmeDnsTypeNone(site_name, domains, email, dnspai, wildcard_domain, force, renew, dns_alias)
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 += "acme.sh --register-account -m "+email+" \n"
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
if dns_alias != '':
cmd += ' --domain-alias '+str(dns_alias)
if apply_ca == "let":
cmd = cmd + " --server letsencrypt "
elif apply_ca == "zerossl":
cmd = cmd + " --server zerossl "
elif apply_ca == "buypass":
cmd = cmd + " --server buypass "
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'
msg = '签发失败,您尝试申请证书的失败次数已达上限!\
<p>1、检查域名是否正确解析到本服务器,或解析还未完全生效</p>\
<p>2、如果以上检查都确认没有问题请尝试更换DNS服务商</p>'
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, apply_ca, dnspai, email, wildcard_domain, dns_alias):
domains = json.loads(domains)
if len(domains) < 1:
return mw.returnData(False, '请选择域名')
if email.strip() != '':
thisdb.setOption('ssl_email', email)
if email.strip() == '':
email = mw.getRandomString(10)+"."+mw.getRandomString(3) + '@gmail.com'
# 检测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失败,请通过以下命令尝试手动安装<p>安装命令: curl https://get.acme.sh | sh</p>')
# 避免频繁执行
checkAcmeRun = mw.execShell('ps -ef|grep acme.sh |grep -v grep')
if checkAcmeRun[0] != '':
return mw.returnData(False, '正在申请或更新SSL中...')
if apply_type == 'file':
return self.createAcmeFile(site_name, apply_ca, domains, email,force,renew)
elif apply_type == 'dns':
return self.createAcmeDns(site_name, apply_ca,domains, email, dnspai, wildcard_domain,force, renew, dns_alias)
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 + '<p><span>响应状态:</span>' + str(emsg['status']) + '</p><p><span>错误类型:</span>' + emsg[
'type'] + '</p><p><span>错误代码:</span>' + emsg['detail'] + '</p>'
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 setCertToSite(self, site_name, cert_name):
try:
path = self.sslDir + '/' + site_name.strip()
if not os.path.exists(path):
return mw.returnData(False, '证书不存在!')
result = self.setSslConf(site_name)
if not result['status']:
return result
mw.restartWeb()
mw.writeLog('网站管理', '证书已部署!')
return mw.returnData(True, '证书已部署!')
except Exception as ex:
return mw.returnData(False, '设置错误:' + str(ex))
def removeCert(self, cert_name):
try:
path = self.sslDir + '/' + cert_name
if not os.path.exists(path):
return mw.returnData(False, '证书已不存在!')
os.system("rm -rf " + path)
return mw.returnData(True, '证书已删除!')
except Exception as ex:
return mw.returnData(False, '证书删除失败:'+str(ex))
def getBackup(self,site_id,page=1,size=10):
site_info = thisdb.getSitesById(site_id)
info = thisdb.getBackupPage(site_id, page, size)
data = {}
data['data'] = info['list']
data['site'] = site_info
data['page'] = mw.getPage({'count':info['count'],'tojs':'getBackup','p':page, 'row':size})
return data
def toBackup(self, site_id):
site_info = thisdb.getSitesById(site_id)
filename = site_info['name'] + '_' + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.zip'
backup_path = mw.getBackupDir() + '/site'
zip_name = backup_path + '/' + filename
if not (os.path.exists(backup_path)):
os.makedirs(backup_path)
exec_log = mw.getPanelDir() + '/logs/panel_exec.log'
cmd = "cd '" + site_info['path'] + "' && zip '" + zip_name + "' -r ./* > " + exec_log + " 2>&1"
mw.execShell(cmd)
fsize = 0
if os.path.exists(zip_name):
fsize = os.path.getsize(zip_name)
thisdb.addBackup(site_id,filename,zip_name,fsize)
msg = mw.getInfo('备份网站[{1}]成功!', (site_info['name'],))
mw.writeLog('网站管理', msg)
return mw.returnData(True, '备份成功!')
def delBackup(self,backup_id):
info = thisdb.getBackupById(backup_id)
if os.path.exists(info['filename']):
os.remove(info['filename'])
msg = mw.getInfo('删除网站[{1}]的备份[{2}]成功!', (info['name'], info['filename']))
mw.writeLog('网站管理', msg)
thisdb.deleteBackupById(backup_id)
return mw.returnData(True, '站点删除成功!')
def getPhpVersion(self):
phpVersions = ('00', '52', '53', '54', '55', '56',
'70', '71', '72', '73', '74', '80',
'81', '82', '83', '84', '85')
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"
if not os.path.exists(conf_dir):
return data
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 mw.returnData(True, 'ok', data)
# return data