mirror of https://github.com/midoks/mdserver-web
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2504 lines
95 KiB
2504 lines
95 KiB
# coding: utf-8
|
|
|
|
import time
|
|
import os
|
|
import sys
|
|
from urllib.parse import urlparse
|
|
import mw
|
|
import re
|
|
import json
|
|
import shutil
|
|
|
|
|
|
from flask import request
|
|
|
|
|
|
class site_api:
|
|
siteName = None # 网站名称
|
|
sitePath = None # 根目录
|
|
sitePort = None # 端口
|
|
phpVersion = None # PHP版本
|
|
|
|
setupPath = None # 安装路径
|
|
vhostPath = None
|
|
logsPath = None
|
|
passPath = None
|
|
rewritePath = None
|
|
redirectPath = None
|
|
sslDir = None # ssl目录
|
|
|
|
def __init__(self):
|
|
# nginx conf
|
|
self.setupPath = mw.getServerDir() + '/web_conf'
|
|
self.vhostPath = vh = self.setupPath + '/nginx/vhost'
|
|
if not os.path.exists(vh):
|
|
mw.execShell("mkdir -p " + vh + " && chmod -R 755 " + vh)
|
|
self.rewritePath = rw = self.setupPath + '/nginx/rewrite'
|
|
if not os.path.exists(rw):
|
|
mw.execShell("mkdir -p " + rw + " && chmod -R 755 " + rw)
|
|
self.passPath = self.setupPath + '/nginx/pass'
|
|
# if not os.path.exists(pp):
|
|
# mw.execShell("mkdir -p " + rw + " && chmod -R 755 " + rw)
|
|
|
|
self.redirectPath = self.setupPath + '/nginx/redirect'
|
|
if not os.path.exists(self.redirectPath):
|
|
mw.execShell("mkdir -p " + self.redirectPath +
|
|
" && chmod -R 755 " + self.redirectPath)
|
|
|
|
self.proxyPath = self.setupPath + '/nginx/proxy'
|
|
if not os.path.exists(self.proxyPath):
|
|
mw.execShell("mkdir -p " + self.proxyPath +
|
|
" && chmod -R 755 " + self.proxyPath)
|
|
|
|
self.logsPath = mw.getRootDir() + '/wwwlogs'
|
|
# ssl conf
|
|
if mw.isAppleSystem():
|
|
self.sslDir = self.setupPath + '/letsencrypt/'
|
|
else:
|
|
self.sslDir = '/etc/letsencrypt/live/'
|
|
|
|
##### ----- start ----- ###
|
|
def listApi(self):
|
|
limit = request.form.get('limit', '10')
|
|
p = request.form.get('p', '1')
|
|
type_id = request.form.get('type_id', '')
|
|
|
|
start = (int(p) - 1) * (int(limit))
|
|
|
|
siteM = mw.M('sites')
|
|
if type_id != '' and type_id == '-1' and type_id == '0':
|
|
siteM.where('type_id=?', (type_id))
|
|
|
|
_list = siteM.field('id,name,path,status,ps,addtime,edate').limit(
|
|
(str(start)) + ',' + limit).order('id desc').select()
|
|
|
|
for i in range(len(_list)):
|
|
_list[i]['backup_count'] = mw.M('backup').where(
|
|
"pid=? AND type=?", (_list[i]['id'], 0)).count()
|
|
|
|
_ret = {}
|
|
_ret['data'] = _list
|
|
|
|
count = siteM.count()
|
|
_page = {}
|
|
_page['count'] = count
|
|
_page['tojs'] = 'getWeb'
|
|
_page['p'] = p
|
|
_page['row'] = limit
|
|
|
|
_ret['page'] = mw.getPage(_page)
|
|
return mw.getJson(_ret)
|
|
|
|
def setDefaultSiteApi(self):
|
|
name = request.form.get('name', '')
|
|
import time
|
|
# 清理旧的
|
|
default_site = mw.readFile('data/default_site.pl')
|
|
if default_site:
|
|
path = self.getHostConf(default_site)
|
|
if os.path.exists(path):
|
|
conf = mw.readFile(path)
|
|
rep = "listen\s+80.+;"
|
|
conf = re.sub(rep, 'listen 80;', conf, 1)
|
|
rep = "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 = "listen\s+80\s*;"
|
|
conf = re.sub(rep, 'listen 80 default_server;', conf, 1)
|
|
rep = "listen\s+443\s*ssl\s*\w*\s*;"
|
|
conf = re.sub(rep, 'listen 443 ssl default_server;', conf, 1)
|
|
mw.writeFile(path, conf)
|
|
|
|
mw.writeFile('data/default_site.pl', name)
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, '设置成功!')
|
|
|
|
def getDefaultSiteApi(self):
|
|
data = {}
|
|
data['sites'] = mw.M('sites').field(
|
|
'name').order('id desc').select()
|
|
data['default_site'] = mw.readFile('data/default_site.pl')
|
|
return mw.getJson(data)
|
|
|
|
def setPsApi(self):
|
|
mid = request.form.get('id', '')
|
|
ps = request.form.get('ps', '')
|
|
if mw.M('sites').where("id=?", (mid,)).setField('ps', ps):
|
|
return mw.returnJson(True, '修改成功!')
|
|
return mw.returnJson(False, '修改失败!')
|
|
|
|
def stopApi(self):
|
|
mid = request.form.get('id', '')
|
|
name = request.form.get('name', '')
|
|
|
|
return self.stop(mid, name)
|
|
|
|
def stop(self, mid, name):
|
|
path = self.setupPath + '/stop'
|
|
if not os.path.exists(path):
|
|
os.makedirs(path)
|
|
mw.writeFile(path + '/index.html',
|
|
'The website has been closed!!!')
|
|
|
|
binding = mw.M('binding').where('pid=?', (mid,)).field(
|
|
'id,pid,domain,path,port,addtime').select()
|
|
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')
|
|
|
|
sitePath = mw.M('sites').where("id=?", (mid,)).getField('path')
|
|
|
|
# nginx
|
|
file = self.getHostConf(name)
|
|
conf = mw.readFile(file)
|
|
if conf:
|
|
conf = conf.replace(sitePath, path)
|
|
mw.writeFile(file, conf)
|
|
|
|
mw.M('sites').where("id=?", (mid,)).setField('status', '0')
|
|
mw.restartWeb()
|
|
msg = mw.getInfo('网站[{1}]已被停用!', (name,))
|
|
mw.writeLog('网站管理', msg)
|
|
return mw.returnJson(True, '站点已停用!')
|
|
|
|
def startApi(self):
|
|
mid = request.form.get('id', '')
|
|
name = request.form.get('name', '')
|
|
path = self.setupPath + '/stop'
|
|
sitePath = mw.M('sites').where("id=?", (mid,)).getField('path')
|
|
|
|
# nginx
|
|
file = self.getHostConf(name)
|
|
conf = mw.readFile(file)
|
|
if conf:
|
|
conf = conf.replace(path, sitePath)
|
|
mw.writeFile(file, conf)
|
|
|
|
mw.M('sites').where("id=?", (mid,)).setField('status', '1')
|
|
mw.restartWeb()
|
|
msg = mw.getInfo('网站[{1}]已被启用!', (name,))
|
|
mw.writeLog('网站管理', msg)
|
|
return mw.returnJson(True, '站点已启用!')
|
|
|
|
def getBackupApi(self):
|
|
limit = request.form.get('limit', '')
|
|
p = request.form.get('p', '')
|
|
mid = request.form.get('search', '')
|
|
|
|
find = mw.M('sites').where("id=?", (mid,)).field(
|
|
"id,name,path,status,ps,addtime,edate").find()
|
|
|
|
start = (int(p) - 1) * (int(limit))
|
|
_list = mw.M('backup').where('pid=?', (mid,)).field('id,type,name,pid,filename,size,addtime').limit(
|
|
(str(start)) + ',' + limit).order('id desc').select()
|
|
_ret = {}
|
|
_ret['data'] = _list
|
|
|
|
count = mw.M('backup').where("id=?", (mid,)).count()
|
|
info = {}
|
|
info['count'] = count
|
|
info['tojs'] = 'getBackup'
|
|
info['p'] = p
|
|
info['row'] = limit
|
|
_ret['page'] = mw.getPage(info)
|
|
_ret['site'] = find
|
|
return mw.getJson(_ret)
|
|
|
|
def toBackupApi(self):
|
|
mid = request.form.get('id', '')
|
|
find = mw.M('sites').where(
|
|
"id=?", (mid,)).field('name,path,id').find()
|
|
fileName = find['name'] + '_' + \
|
|
time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.zip'
|
|
backupPath = mw.getBackupDir() + '/site'
|
|
zipName = backupPath + '/' + fileName
|
|
if not (os.path.exists(backupPath)):
|
|
os.makedirs(backupPath)
|
|
tmps = mw.getRunDir() + '/tmp/panelExec.log'
|
|
execStr = "cd '" + find['path'] + "' && zip '" + \
|
|
zipName + "' -r ./* > " + tmps + " 2>&1"
|
|
# print execStr
|
|
mw.execShell(execStr)
|
|
|
|
if os.path.exists(zipName):
|
|
fsize = os.path.getsize(zipName)
|
|
else:
|
|
fsize = 0
|
|
sql = mw.M('backup').add('type,name,pid,filename,size,addtime',
|
|
(0, fileName, find['id'], zipName, fsize, mw.getDate()))
|
|
|
|
msg = mw.getInfo('备份网站[{1}]成功!', (find['name'],))
|
|
mw.writeLog('网站管理', msg)
|
|
return mw.returnJson(True, '备份成功!')
|
|
|
|
def delBackupApi(self):
|
|
mid = request.form.get('id', '')
|
|
filename = mw.M('backup').where(
|
|
"id=?", (mid,)).getField('filename')
|
|
if os.path.exists(filename):
|
|
os.remove(filename)
|
|
name = mw.M('backup').where("id=?", (mid,)).getField('name')
|
|
msg = mw.getInfo('删除网站[{1}]的备份[{2}]成功!', (name, filename))
|
|
mw.writeLog('网站管理', msg)
|
|
mw.M('backup').where("id=?", (mid,)).delete()
|
|
return mw.returnJson(True, '站点删除成功!')
|
|
|
|
def getPhpVersionApi(self):
|
|
return self.getPhpVersion()
|
|
|
|
def setPhpVersionApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
version = request.form.get('version', '')
|
|
|
|
# nginx
|
|
file = self.getHostConf(siteName)
|
|
conf = mw.readFile(file)
|
|
if conf:
|
|
rep = "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.returnJson(True, msg)
|
|
|
|
def getDomainApi(self):
|
|
pid = request.form.get('pid', '')
|
|
return self.getDomain(pid)
|
|
|
|
# 获取站点所有域名
|
|
def getSiteDomainsApi(self):
|
|
pid = request.form.get('id', '')
|
|
|
|
data = {}
|
|
domains = mw.M('domain').where(
|
|
'pid=?', (pid,)).field('name,id').select()
|
|
binding = mw.M('binding').where(
|
|
'pid=?', (pid,)).field('domain,id').select()
|
|
if type(binding) == str:
|
|
return binding
|
|
for b in binding:
|
|
tmp = {}
|
|
tmp['name'] = b['domain']
|
|
tmp['id'] = b['id']
|
|
domains.append(tmp)
|
|
data['domains'] = domains
|
|
data['email'] = mw.M('users').getField('email')
|
|
if data['email'] == 'midoks@163.com':
|
|
data['email'] = ''
|
|
return mw.returnJson(True, 'OK', data)
|
|
|
|
def getDirBindingApi(self):
|
|
mid = request.form.get('id', '')
|
|
|
|
path = mw.M('sites').where('id=?', (mid,)).getField('path')
|
|
if not os.path.exists(path):
|
|
checks = ['/', '/usr', '/etc']
|
|
if path in checks:
|
|
data = {}
|
|
data['dirs'] = []
|
|
data['binding'] = []
|
|
return mw.returnJson(True, 'OK', data)
|
|
os.system('mkdir -p ' + path)
|
|
os.system('chmod 755 ' + path)
|
|
os.system('chown www:www ' + path)
|
|
siteName = mw.M('sites').where(
|
|
'id=?', (get.id,)).getField('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'] = mw.M('binding').where('pid=?', (mid,)).field(
|
|
'id,pid,domain,path,port,addtime').select()
|
|
return mw.returnJson(True, 'OK', data)
|
|
|
|
def getDirUserIniApi(self):
|
|
mid = request.form.get('id', '')
|
|
|
|
path = mw.M('sites').where('id=?', (mid,)).getField('path')
|
|
name = mw.M('sites').where("id=?", (mid,)).getField('name')
|
|
data = {}
|
|
data['logs'] = self.getLogsStatus(name)
|
|
data['runPath'] = self.getSiteRunPath(mid)
|
|
|
|
data['userini'] = False
|
|
if os.path.exists(path + '/.user.ini'):
|
|
data['userini'] = True
|
|
|
|
if data['runPath']['runPath'] != '/':
|
|
if os.path.exists(path + data['runPath']['runPath'] + '/.user.ini'):
|
|
data['userini'] = True
|
|
|
|
data['pass'] = self.getHasPwd(name)
|
|
data['path'] = path
|
|
data['name'] = name
|
|
return mw.returnJson(True, 'OK', data)
|
|
|
|
def setDirUserIniApi(self):
|
|
path = request.form.get('path', '')
|
|
runPath = request.form.get('runPath', '')
|
|
filename = path + '/.user.ini'
|
|
|
|
if os.path.exists(filename):
|
|
self.delUserInI(path)
|
|
mw.execShell("which chattr && chattr -i " + filename)
|
|
os.remove(filename)
|
|
return mw.returnJson(True, '已清除防跨站设置!')
|
|
|
|
self.setDirUserINI(path, runPath)
|
|
mw.execShell("which chattr && chattr +i " + filename)
|
|
|
|
return mw.returnJson(True, '已打开防跨站设置!')
|
|
|
|
def setRewriteApi(self):
|
|
data = request.form.get('data', '')
|
|
path = request.form.get('path', '')
|
|
encoding = request.form.get('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)
|
|
return mw.returnJson(False, 'ERROR: <br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>')
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, '设置成功!')
|
|
|
|
def setRewriteTplApi(self):
|
|
data = request.form.get('data', '')
|
|
name = request.form.get('name', '')
|
|
path = mw.getRunDir() + "/rewrite/nginx/" + name + ".conf"
|
|
if os.path.exists(path):
|
|
return mw.returnJson(False, '模版已经存在!')
|
|
|
|
if data == "":
|
|
return mw.returnJson(False, '模版内容不能为空!')
|
|
ok = mw.writeFile(path, data)
|
|
if not ok:
|
|
return mw.returnJson(False, '模版保持失败!')
|
|
|
|
return mw.returnJson(True, '设置模板成功!')
|
|
|
|
def logsOpenApi(self):
|
|
mid = request.form.get('id', '')
|
|
name = mw.M('sites').where("id=?", (mid,)).getField('name')
|
|
|
|
# NGINX
|
|
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.returnJson(True, '操作成功!')
|
|
|
|
def getCertListApi(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.returnJson(True, 'OK', data)
|
|
except:
|
|
return mw.returnJson(True, 'OK', [])
|
|
|
|
def getSslApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
|
|
path = self.sslDir + siteName
|
|
csrpath = path + "/fullchain.pem" # 生成证书路径
|
|
keypath = path + "/privkey.pem" # 密钥文件路径
|
|
key = mw.readFile(keypath)
|
|
csr = mw.readFile(csrpath)
|
|
|
|
file = self.getHostConf(siteName)
|
|
conf = mw.readFile(file)
|
|
|
|
keyText = 'ssl_certificate'
|
|
status = True
|
|
stype = 0
|
|
if(conf.find(keyText) == -1):
|
|
status = False
|
|
stype = -1
|
|
|
|
toHttps = self.isToHttps(siteName)
|
|
id = mw.M('sites').where("name=?", (siteName,)).getField('id')
|
|
domains = mw.M('domain').where(
|
|
"pid=?", (id,)).field('name').select()
|
|
data = {'status': status, 'domain': domains, 'key': key,
|
|
'csr': csr, 'type': stype, 'httpTohttps': toHttps}
|
|
return mw.returnJson(True, 'OK', data)
|
|
|
|
def setSslApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
key = request.form.get('key', '')
|
|
csr = request.form.get('csr', '')
|
|
|
|
path = self.sslDir + siteName
|
|
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, '证书错误,请检查!')
|
|
|
|
mw.writeFile('/tmp/cert.pl', csr)
|
|
if not mw.checkCert('/tmp/cert.pl'):
|
|
return mw.returnJson(False, '证书错误,请粘贴正确的PEM格式证书!')
|
|
|
|
mw.execShell('\\cp -a ' + keypath + ' /tmp/backup1.conf')
|
|
mw.execShell('\\cp -a ' + csrpath + ' /tmp/backup2.conf')
|
|
|
|
# 清理旧的证书链
|
|
if os.path.exists(path + '/README'):
|
|
mw.execShell('rm -rf ' + path)
|
|
mw.execShell('rm -rf ' + path + '-00*')
|
|
mw.execShell('rm -rf /etc/letsencrypt/archive/' + siteName)
|
|
mw.execShell(
|
|
'rm -rf /etc/letsencrypt/archive/' + siteName + '-00*')
|
|
mw.execShell(
|
|
'rm -f /etc/letsencrypt/renewal/' + siteName + '.conf')
|
|
mw.execShell('rm -rf /etc/letsencrypt/renewal/' +
|
|
siteName + '-00*.conf')
|
|
mw.execShell('rm -rf ' + path + '/README')
|
|
mw.execShell('mkdir -p ' + path)
|
|
|
|
mw.writeFile(keypath, key)
|
|
mw.writeFile(csrpath, csr)
|
|
|
|
# 写入配置文件
|
|
result = self.setSslConf(siteName)
|
|
# print result['msg']
|
|
if not result['status']:
|
|
return mw.getJson(result)
|
|
|
|
isError = mw.checkWebConfig()
|
|
if(type(isError) == str):
|
|
mw.execShell('\\cp -a /tmp/backup1.conf ' + keypath)
|
|
mw.execShell('\\cp -a /tmp/backup2.conf ' + csrpath)
|
|
return mw.returnJson(False, 'ERROR: <br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>')
|
|
|
|
mw.writeLog('网站管理', '证书已保存!')
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, '证书已保存!')
|
|
|
|
def setCertToSiteApi(self):
|
|
certName = request.form.get('certName', '')
|
|
siteName = request.form.get('siteName', '')
|
|
try:
|
|
path = self.sslDir + siteName.strip()
|
|
if not os.path.exists(path):
|
|
return mw.returnJson(False, '证书不存在!')
|
|
|
|
result = self.setSslConf(siteName)
|
|
if not result['status']:
|
|
return mw.getJson(result)
|
|
|
|
mw.restartWeb()
|
|
mw.writeLog('网站管理', '证书已部署!')
|
|
return mw.returnJson(True, '证书已部署!')
|
|
except Exception as ex:
|
|
return mw.returnJson(False, '设置错误,' + str(ex))
|
|
|
|
def removeCertApi(self):
|
|
certName = request.form.get('certName', '')
|
|
try:
|
|
path = self.sslDir + certName
|
|
if not os.path.exists(path):
|
|
return mw.returnJson(False, '证书已不存在!')
|
|
os.system("rm -rf " + path)
|
|
return mw.returnJson(True, '证书已删除!')
|
|
except:
|
|
return mw.returnJson(False, '删除失败!')
|
|
|
|
def closeSslConfApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
|
|
file = self.getHostConf(siteName)
|
|
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 = "\s+ssl_session_timeout\s+.+;\n"
|
|
conf = re.sub(rep, '', conf)
|
|
rep = "\s+ssl_ecdh_curve\s+.+;\n"
|
|
conf = re.sub(rep, '', conf)
|
|
rep = "\s+ssl_session_tickets\s+.+;\n"
|
|
conf = re.sub(rep, '', conf)
|
|
rep = "\s+ssl_stapling\s+.+;\n"
|
|
conf = re.sub(rep, '', conf)
|
|
rep = "\s+ssl_stapling_verify\s+.+;\n"
|
|
conf = re.sub(rep, '', conf)
|
|
rep = "\s+add_header\s+.+;\n"
|
|
conf = re.sub(rep, '', conf)
|
|
rep = "\s+add_header\s+.+;\n"
|
|
conf = re.sub(rep, '', conf)
|
|
rep = "\s+ssl\s+on;"
|
|
conf = re.sub(rep, '', conf)
|
|
rep = "\s+error_page\s497.+;"
|
|
conf = re.sub(rep, '', conf)
|
|
rep = "\s+if.+server_port.+\n.+\n\s+\s*}"
|
|
conf = re.sub(rep, '', conf)
|
|
rep = "\s+listen\s+443.*;"
|
|
conf = re.sub(rep, '', conf)
|
|
mw.writeFile(file, conf)
|
|
|
|
msg = mw.getInfo('网站[{1}]关闭SSL成功!', (siteName,))
|
|
mw.writeLog('网站管理', msg)
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, 'SSL已关闭!')
|
|
|
|
def getLetLogsApi(self):
|
|
log_file = mw.getRunDir() + '/logs/letsencrypt.log'
|
|
if not os.path.exists(log_file):
|
|
mw.execShell('touch ' + log_file)
|
|
return mw.returnJson(True, 'OK', log_file)
|
|
|
|
def createLetApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
updateOf = request.form.get('updateOf', '')
|
|
domains = request.form.get('domains', '')
|
|
force = request.form.get('force', '')
|
|
renew = request.form.get('renew', '')
|
|
email_args = request.form.get('email', '')
|
|
|
|
domains = json.loads(domains)
|
|
email = mw.M('users').getField('email')
|
|
if email_args.strip() != '':
|
|
mw.M('users').setField('email', email_args)
|
|
email = email_args
|
|
|
|
if not len(domains):
|
|
return mw.returnJson(False, '请选择域名')
|
|
|
|
file = self.getHostConf(siteName)
|
|
if os.path.exists(file):
|
|
siteConf = mw.readFile(file)
|
|
if siteConf.find('301-END') != -1:
|
|
return mw.returnJson(False, '检测到您的站点做了301重定向设置,请先关闭重定向!')
|
|
|
|
# 检测存在反向代理
|
|
data_path = self.getProxyDataPath(siteName)
|
|
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, siteName)
|
|
proxy_dir_file = proxy_dir + '/' + proxy['id'] + '.conf'
|
|
if os.path.exists(proxy_dir_file):
|
|
return mw.returnJson(False, '检测到您的站点做了反向代理设置,请先关闭反向代理!')
|
|
|
|
auth_to = self.getSitePath(siteName)
|
|
to_args = {
|
|
'domains': domains,
|
|
'auth_type': 'http',
|
|
'auth_to': auth_to,
|
|
}
|
|
|
|
# print(to_args)
|
|
import cert_request
|
|
data = cert_request.cert_request().applyCertApi(to_args)
|
|
|
|
# letpath = self.sslDir + siteName
|
|
# csrpath = letpath + "/fullchain.pem" # 生成证书路径
|
|
# keypath = letpath + "/privkey.pem" # 密钥文件路径
|
|
|
|
# # 写入配置文件
|
|
# result = self.setSslConf(siteName)
|
|
# if not result['status']:
|
|
# return mw.getJson(result)
|
|
# result['csr'] = mw.readFile(csrpath)
|
|
# result['key'] = mw.readFile(keypath)
|
|
|
|
# mw.restartWeb()
|
|
return mw.returnJson(data['status'], '', data['msg'])
|
|
|
|
def getAcmeLogsApi(self):
|
|
log_file = mw.getRunDir() + '/logs/acme.log'
|
|
if not os.path.exists(log_file):
|
|
mw.execShell('touch ' + log_file)
|
|
return mw.returnJson(True, 'OK', log_file)
|
|
|
|
def createAcmeApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
updateOf = request.form.get('updateOf', '')
|
|
domains = request.form.get('domains', '')
|
|
force = request.form.get('force', '')
|
|
renew = request.form.get('renew', '')
|
|
email_args = request.form.get('email', '')
|
|
|
|
domains = json.loads(domains)
|
|
email = mw.M('users').getField('email')
|
|
if email_args.strip() != '':
|
|
mw.M('users').setField('email', email_args)
|
|
email = email_args
|
|
|
|
if not len(domains):
|
|
return mw.returnJson(False, '请选择域名')
|
|
|
|
file = self.getHostConf(siteName)
|
|
if os.path.exists(file):
|
|
siteConf = mw.readFile(file)
|
|
if siteConf.find('301-END') != -1:
|
|
return mw.returnJson(False, '检测到您的站点做了301重定向设置,请先关闭重定向!')
|
|
|
|
# 检测存在反向代理
|
|
data_path = self.getProxyDataPath(siteName)
|
|
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, siteName)
|
|
proxy_dir_file = proxy_dir + '/' + proxy['id'] + '.conf'
|
|
if os.path.exists(proxy_dir_file):
|
|
return mw.returnJson(False, '检测到您的站点做了反向代理设置,请先关闭反向代理!')
|
|
|
|
letpath = self.sslDir + siteName
|
|
csrpath = letpath + "/fullchain.pem" # 生成证书路径
|
|
keypath = letpath + "/privkey.pem" # 密钥文件路径
|
|
|
|
actionstr = updateOf
|
|
siteInfo = mw.M('sites').where(
|
|
'name=?', (siteName,)).field('id,name,path').find()
|
|
path = self.getSitePath(siteName)
|
|
srcPath = siteInfo['path']
|
|
|
|
# 检测acme是否安装
|
|
if mw.isAppleSystem():
|
|
user = mw.execShell(
|
|
"who | sed -n '2, 1p' |awk '{print $1}'")[0].strip()
|
|
acem = '/Users/' + user + '/.acme.sh/acme.sh'
|
|
else:
|
|
acem = '/root/.acme.sh/acme.sh'
|
|
if not os.path.exists(acem):
|
|
acem = '/.acme.sh/acme.sh'
|
|
if not os.path.exists(acem):
|
|
try:
|
|
mw.execShell("curl -sS curl https://get.acme.sh | sh")
|
|
except:
|
|
return mw.returnJson(False, '尝试自动安装ACME失败,请通过以下命令尝试手动安装<p>安装命令: curl https://get.acme.sh | sh</p>' + acem)
|
|
if not os.path.exists(acem):
|
|
return mw.returnJson(False, '尝试自动安装ACME失败,请通过以下命令尝试手动安装<p>安装命令: curl https://get.acme.sh | sh</p>' + acem)
|
|
|
|
# 避免频繁执行
|
|
checkAcmeRun = mw.execShell('ps -ef|grep acme.sh |grep -v grep')
|
|
if checkAcmeRun[0] != '':
|
|
return mw.returnJson(False, '正在申请或更新SSL中...')
|
|
|
|
if force == 'true':
|
|
force_bool = True
|
|
|
|
if renew == 'true':
|
|
execStr = acem + " --renew --yes-I-know-dns-manual-mode-enough-go-ahead-please"
|
|
else:
|
|
execStr = acem + " --issue --force"
|
|
|
|
# 确定主域名顺序
|
|
domainsTmp = []
|
|
if siteName in domains:
|
|
domainsTmp.append(siteName)
|
|
for domainTmp in domains:
|
|
if domainTmp == siteName:
|
|
continue
|
|
domainsTmp.append(domainTmp)
|
|
domains = domainsTmp
|
|
|
|
domainCount = 0
|
|
for domain in domains:
|
|
if mw.checkIp(domain):
|
|
continue
|
|
if domain.find('*.') != -1:
|
|
return mw.returnJson(False, '泛域名不能使用【文件验证】的方式申请证书!')
|
|
execStr += ' -w ' + path
|
|
execStr += ' -d ' + domain
|
|
domainCount += 1
|
|
if domainCount == 0:
|
|
return mw.returnJson(False, '请选择域名(不包括IP地址与泛域名)!')
|
|
|
|
home_path = '/root/.acme.sh/' + domains[0]
|
|
home_cert = home_path + '/fullchain.cer'
|
|
home_key = home_path + '/' + domains[0] + '.key'
|
|
|
|
if not os.path.exists(home_cert):
|
|
home_path = '/.acme.sh/' + domains[0]
|
|
home_cert = home_path + '/fullchain.cer'
|
|
home_key = home_path + '/' + domains[0] + '.key'
|
|
|
|
if mw.isAppleSystem():
|
|
user = mw.execShell(
|
|
"who | sed -n '2, 1p' |awk '{print $1}'")[0].strip()
|
|
acem = '/Users/' + user + '/.acme.sh/'
|
|
if not os.path.exists(home_cert):
|
|
home_path = acem + domains[0]
|
|
home_cert = home_path + '/fullchain.cer'
|
|
home_key = home_path + '/' + domains[0] + '.key'
|
|
|
|
# print home_cert
|
|
cmd = 'export ACCOUNT_EMAIL=' + email + ' && ' + \
|
|
execStr + ' >> ' + mw.getRunDir() + '/logs/acme.log'
|
|
# print(domains)
|
|
# print(cmd)
|
|
result = mw.execShell(cmd)
|
|
|
|
if not os.path.exists(home_cert.replace("\*", "*")):
|
|
data = {}
|
|
data['err'] = result
|
|
data['out'] = result[0]
|
|
data['msg'] = '签发失败,我们无法验证您的域名:<p>1、检查域名是否绑定到对应站点</p>\
|
|
<p>2、检查域名是否正确解析到本服务器,或解析还未完全生效</p>\
|
|
<p>3、如果您的站点设置了反向代理,或使用了CDN,请先将其关闭</p>\
|
|
<p>4、如果您的站点设置了301重定向,请先将其关闭</p>\
|
|
<p>5、如果以上检查都确认没有问题,请尝试更换DNS服务商</p>'
|
|
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'] = '签发失败,您尝试申请证书的失败次数已达上限!<p>1、检查域名是否绑定到对应站点</p>\
|
|
<p>2、检查域名是否正确解析到本服务器,或解析还未完全生效</p>\
|
|
<p>3、如果您的站点设置了反向代理,或使用了CDN,请先将其关闭</p>\
|
|
<p>4、如果您的站点设置了301重定向,请先将其关闭</p>\
|
|
<p>5、如果以上检查都确认没有问题,请尝试更换DNS服务商</p>'
|
|
data['status'] = False
|
|
return mw.getJson(data)
|
|
|
|
if not os.path.exists(letpath):
|
|
mw.execShell("mkdir -p " + letpath)
|
|
mw.execShell("ln -sf \"" + home_cert + "\" \"" + csrpath + '"')
|
|
mw.execShell("ln -sf \"" + home_key + "\" \"" + keypath + '"')
|
|
mw.execShell('echo "let" > "' + letpath + '/README"')
|
|
if(actionstr == '2'):
|
|
return mw.returnJson(True, '证书已更新!')
|
|
|
|
# 写入配置文件
|
|
result = self.setSslConf(siteName)
|
|
if not result['status']:
|
|
return mw.getJson(result)
|
|
result['csr'] = mw.readFile(csrpath)
|
|
result['key'] = mw.readFile(keypath)
|
|
mw.restartWeb()
|
|
|
|
return mw.returnJson(True, 'OK', result)
|
|
|
|
def httpToHttpsApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
file = self.getHostConf(siteName)
|
|
conf = mw.readFile(file)
|
|
if conf:
|
|
if conf.find('ssl_certificate') == -1:
|
|
return mw.returnJson(False, '当前未开启SSL')
|
|
to = """#error_page 404/404.html;
|
|
# HTTP_TO_HTTPS_START
|
|
if ($server_port !~ 443){
|
|
rewrite ^(/.*)$ https://$host$1 permanent;
|
|
}
|
|
# HTTP_TO_HTTPS_END"""
|
|
conf = conf.replace('#error_page 404/404.html;', to)
|
|
mw.writeFile(file, conf)
|
|
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, '设置成功!证书也要设置好哟!')
|
|
|
|
def closeToHttpsApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
file = self.getHostConf(siteName)
|
|
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+if.+server_port.+\n.+\n\s+\s*}"
|
|
conf = re.sub(rep, '', conf)
|
|
mw.writeFile(file, conf)
|
|
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, '关闭HTTPS跳转成功!')
|
|
|
|
def getIndexApi(self):
|
|
sid = request.form.get('id', '')
|
|
data = {}
|
|
index = self.getIndex(sid)
|
|
data['index'] = index
|
|
return mw.getJson(data)
|
|
|
|
def setIndexApi(self):
|
|
sid = request.form.get('id', '')
|
|
index = request.form.get('index', '')
|
|
return self.setIndex(sid, index)
|
|
|
|
def getLimitNetApi(self):
|
|
sid = request.form.get('id', '')
|
|
return self.getLimitNet(sid)
|
|
|
|
def saveLimitNetApi(self):
|
|
sid = request.form.get('id', '')
|
|
perserver = request.form.get('perserver', '')
|
|
perip = request.form.get('perip', '')
|
|
limit_rate = request.form.get('limit_rate', '')
|
|
return self.saveLimitNet(sid, perserver, perip, limit_rate)
|
|
|
|
def closeLimitNetApi(self):
|
|
sid = request.form.get('id', '')
|
|
return self.closeLimitNet(sid)
|
|
|
|
def getSecurityApi(self):
|
|
sid = request.form.get('id', '')
|
|
name = request.form.get('name', '')
|
|
return self.getSecurity(sid, name)
|
|
|
|
def setSecurityApi(self):
|
|
fix = request.form.get('fix', '')
|
|
domains = request.form.get('domains', '')
|
|
status = request.form.get('status', '')
|
|
name = request.form.get('name', '')
|
|
sid = request.form.get('id', '')
|
|
return self.setSecurity(sid, name, fix, domains, status)
|
|
|
|
def getLogsApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
return self.getLogs(siteName)
|
|
|
|
def getErrorLogsApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
return self.getErrorLogs(siteName)
|
|
|
|
def getSitePhpVersionApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
return self.getSitePhpVersion(siteName)
|
|
|
|
def getHostConfApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
host = self.getHostConf(siteName)
|
|
return mw.getJson({'host': host})
|
|
|
|
def getRewriteConfApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
rewrite = self.getRewriteConf(siteName)
|
|
return mw.getJson({'rewrite': rewrite})
|
|
|
|
def getRewriteTplApi(self):
|
|
tplname = request.form.get('tplname', '')
|
|
file = mw.getRunDir() + '/rewrite/nginx/' + tplname + '.conf'
|
|
if not os.path.exists(file):
|
|
return mw.returnJson(False, '模版不存在!')
|
|
return mw.returnJson(True, 'OK', file)
|
|
|
|
def getRewriteListApi(self):
|
|
rlist = self.getRewriteList()
|
|
return mw.getJson(rlist)
|
|
|
|
def getRootDirApi(self):
|
|
data = {}
|
|
data['dir'] = mw.getWwwDir()
|
|
return mw.getJson(data)
|
|
|
|
def setEndDateApi(self):
|
|
sid = request.form.get('id', '')
|
|
edate = request.form.get('edate', '')
|
|
return self.setEndDate(sid, edate)
|
|
|
|
def addApi(self):
|
|
webname = request.form.get('webinfo', '')
|
|
ps = request.form.get('ps', '')
|
|
path = request.form.get('path', '')
|
|
version = request.form.get('version', '')
|
|
port = request.form.get('port', '')
|
|
return self.add(webname, port, ps, path, version)
|
|
|
|
def checkWebStatusApi(self):
|
|
'''
|
|
创建站点检查web服务
|
|
'''
|
|
if not mw.isInstalledWeb():
|
|
return mw.returnJson(False, '请安装并启动OpenResty服务!')
|
|
|
|
# 这个快点
|
|
pid = mw.getServerDir() + '/openresty/nginx/logs/nginx.pid'
|
|
if not os.path.exists(pid):
|
|
return mw.returnJson(False, '请启动OpenResty服务!')
|
|
|
|
# path = mw.getServerDir() + '/openresty/init.d/openresty'
|
|
# data = mw.execShell(path + " status")
|
|
# if data[0].strip().find('stopped') != -1:
|
|
# return mw.returnJson(False, '请启动OpenResty服务!')
|
|
|
|
# import plugins_api
|
|
# data = plugins_api.plugins_api().run('openresty', 'status')
|
|
# if data[0].strip() == 'stop':
|
|
# return mw.returnJson(False, '请启动OpenResty服务!')
|
|
|
|
return mw.returnJson(True, 'OK')
|
|
|
|
def addDomainApi(self):
|
|
isError = mw.checkWebConfig()
|
|
if isError != True:
|
|
return mw.returnJson(False, 'ERROR: 检测到配置文件有错误,请先排除后再操作<br><br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>')
|
|
|
|
domain = request.form.get('domain', '')
|
|
webname = request.form.get('webname', '')
|
|
pid = request.form.get('id', '')
|
|
return self.addDomain(domain, webname, pid)
|
|
|
|
def addDomain(self, domain, webname, pid):
|
|
if len(domain) < 3:
|
|
return mw.returnJson(False, '域名不能为空!')
|
|
domains = domain.split(',')
|
|
for domain in domains:
|
|
if domain == "":
|
|
continue
|
|
domain = domain.split(':')
|
|
# print domain
|
|
domain_name = self.toPunycode(domain[0])
|
|
domain_port = '80'
|
|
|
|
reg = "^([\w\-\*]{1,100}\.){1,4}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$"
|
|
if not re.match(reg, domain_name):
|
|
return mw.returnJson(False, '域名格式不正确!')
|
|
|
|
if len(domain) == 2:
|
|
domain_port = domain[1]
|
|
if domain_port == "":
|
|
domain_port = "80"
|
|
|
|
if not mw.checkPort(domain_port):
|
|
return mw.returnJson(False, '端口范围不合法!')
|
|
|
|
opid = mw.M('domain').where(
|
|
"name=? AND (port=? OR pid=?)", (domain, domain_port, pid)).getField('pid')
|
|
if opid:
|
|
if mw.M('sites').where('id=?', (opid,)).count():
|
|
return mw.returnJson(False, '指定域名已绑定过!')
|
|
mw.M('domain').where('pid=?', (opid,)).delete()
|
|
|
|
if mw.M('binding').where('domain=?', (domain,)).count():
|
|
return mw.returnJson(False, '您添加的域名已存在!')
|
|
|
|
self.nginxAddDomain(webname, domain_name, domain_port)
|
|
|
|
mw.restartWeb()
|
|
msg = mw.getInfo('网站[{1}]添加域名[{2}]成功!', (webname, domain_name))
|
|
mw.writeLog('网站管理', msg)
|
|
mw.M('domain').add('pid,name,port,addtime',
|
|
(pid, domain_name, domain_port, mw.getDate()))
|
|
|
|
return mw.returnJson(True, '域名添加成功!')
|
|
|
|
def addDirBindApi(self):
|
|
pid = request.form.get('id', '')
|
|
domain = request.form.get('domain', '')
|
|
dirName = request.form.get('dirName', '')
|
|
tmp = domain.split(':')
|
|
domain = tmp[0]
|
|
port = '80'
|
|
if len(tmp) > 1:
|
|
port = tmp[1]
|
|
if dirName == '':
|
|
mw.returnJson(False, '目录不能为空!')
|
|
|
|
reg = "^([\w\-\*]{1,100}\.){1,4}(\w{1,10}|\w{1,10}\.\w{1,10})$"
|
|
if not re.match(reg, domain):
|
|
return mw.returnJson(False, '主域名格式不正确!')
|
|
|
|
siteInfo = mw.M('sites').where(
|
|
"id=?", (pid,)).field('id,path,name').find()
|
|
webdir = siteInfo['path'] + '/' + dirName
|
|
|
|
if mw.M('binding').where("domain=?", (domain,)).count() > 0:
|
|
return mw.returnJson(False, '您添加的域名已存在!')
|
|
if mw.M('domain').where("name=?", (domain,)).count() > 0:
|
|
return mw.returnJson(False, '您添加的域名已存在!')
|
|
|
|
filename = self.getHostConf(siteInfo['name'])
|
|
conf = mw.readFile(filename)
|
|
if conf:
|
|
rep = "enable-php-([0-9]{2,3})\.conf"
|
|
tmp = re.search(rep, conf).groups()
|
|
version = tmp[0]
|
|
|
|
source_dirbind_tpl = mw.getRunDir() + '/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}', siteInfo['name'])
|
|
content = content.replace('{$OR_REWRITE}', self.rewritePath)
|
|
content = content.replace('{$LOGPATH}', mw.getLogsDir())
|
|
|
|
conf += "\r\n" + content
|
|
shutil.copyfile(filename, '/tmp/backup.conf')
|
|
mw.writeFile(filename, conf)
|
|
conf = mw.readFile(filename)
|
|
|
|
# 检查配置是否有误
|
|
isError = mw.checkWebConfig()
|
|
if isError != True:
|
|
shutil.copyfile('/tmp/backup.conf', filename)
|
|
return mw.returnJson(False, 'ERROR: <br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>')
|
|
|
|
mw.M('binding').add('pid,domain,port,path,addtime',
|
|
(pid, domain, port, dirName, mw.getDate()))
|
|
|
|
msg = mw.getInfo('网站[{1}]子目录[{2}]绑定到[{3}]',
|
|
(siteInfo['name'], dirName, domain))
|
|
mw.writeLog('网站管理', msg)
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, '添加成功!')
|
|
|
|
def delDirBindApi(self):
|
|
mid = request.form.get('id', '')
|
|
binding = mw.M('binding').where(
|
|
"id=?", (mid,)).field('id,pid,domain,path').find()
|
|
siteName = mw.M('sites').where(
|
|
"id=?", (binding['pid'],)).getField('name')
|
|
|
|
filename = self.getHostConf(siteName)
|
|
conf = mw.readFile(filename)
|
|
if conf:
|
|
rep = "\s*.+BINDING-" + \
|
|
binding['domain'] + \
|
|
"-START(.|\n)+BINDING-" + binding['domain'] + "-END"
|
|
conf = re.sub(rep, '', conf)
|
|
mw.writeFile(filename, conf)
|
|
|
|
mw.M('binding').where("id=?", (mid,)).delete()
|
|
|
|
filename = self.getDirBindRewrite(siteName, binding['path'])
|
|
if os.path.exists(filename):
|
|
os.remove(filename)
|
|
mw.restartWeb()
|
|
msg = mw.getInfo('删除网站[{1}]子目录[{2}]绑定',
|
|
(siteName, binding['path']))
|
|
mw.writeLog('网站管理', msg)
|
|
return mw.returnJson(True, '删除成功!')
|
|
|
|
# 取子目录Rewrite
|
|
def getDirBindRewriteApi(self):
|
|
mid = request.form.get('id', '')
|
|
add = request.form.get('add', '0')
|
|
find = mw.M('binding').where(
|
|
"id=?", (mid,)).field('id,pid,domain,path').find()
|
|
site = mw.M('sites').where(
|
|
"id=?", (find['pid'],)).field('id,name,path').find()
|
|
|
|
filename = self.getDirBindRewrite(site['name'], find['path'])
|
|
if add == '1':
|
|
mw.writeFile(filename, '')
|
|
file = self.getHostConf(site['name'])
|
|
conf = mw.readFile(file)
|
|
domain = find['domain']
|
|
rep = "\n#BINDING-" + domain + \
|
|
"-START(.|\n)+BINDING-" + domain + "-END"
|
|
tmp = re.search(rep, conf).group()
|
|
dirConf = tmp.replace('rewrite/' + site['name'] + '.conf;', 'rewrite/' + site[
|
|
'name'] + '_' + find['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 == 'list.txt':
|
|
continue
|
|
data['rlist'].append(ds[0:len(ds) - 5])
|
|
data['filename'] = filename
|
|
return mw.getJson(data)
|
|
|
|
# 修改物理路径
|
|
def setPathApi(self):
|
|
mid = request.form.get('id', '')
|
|
path = request.form.get('path', '')
|
|
|
|
path = self.getPath(path)
|
|
if path == "" or mid == '0':
|
|
return mw.returnJson(False, "目录不能为空!")
|
|
|
|
import files_api
|
|
if not files_api.files_api().checkDir(path):
|
|
return mw.returnJson(False, "不能以系统关键目录作为站点目录")
|
|
|
|
siteFind = mw.M("sites").where(
|
|
"id=?", (mid,)).field('path,name').find()
|
|
if siteFind["path"] == path:
|
|
return mw.returnJson(False, "与原路径一致,无需修改!")
|
|
file = self.getHostConf(siteFind['name'])
|
|
conf = mw.readFile(file)
|
|
if conf:
|
|
conf = conf.replace(siteFind['path'], path)
|
|
mw.writeFile(file, conf)
|
|
|
|
# 创建basedir
|
|
# userIni = path + '/.user.ini'
|
|
# if os.path.exists(userIni):
|
|
# mw.execShell("chattr -i " + userIni)
|
|
# mw.writeFile(userIni, 'open_basedir=' + path + '/:/tmp/:/proc/')
|
|
# mw.execShell('chmod 644 ' + userIni)
|
|
# mw.execShell('chown root:root ' + userIni)
|
|
# mw.execShell('chattr +i ' + userIni)
|
|
|
|
mw.restartWeb()
|
|
mw.M("sites").where("id=?", (mid,)).setField('path', path)
|
|
msg = mw.getInfo('修改网站[{1}]物理路径成功!', (siteFind['name'],))
|
|
mw.writeLog('网站管理', msg)
|
|
return mw.returnJson(True, "设置成功!")
|
|
|
|
# 设置当前站点运行目录
|
|
def setSiteRunPathApi(self):
|
|
mid = request.form.get('id', '')
|
|
runPath = request.form.get('runPath', '')
|
|
siteName = mw.M('sites').where('id=?', (mid,)).getField('name')
|
|
sitePath = mw.M('sites').where('id=?', (mid,)).getField('path')
|
|
|
|
newPath = sitePath + runPath
|
|
|
|
# 处理Nginx
|
|
filename = self.getHostConf(siteName)
|
|
if os.path.exists(filename):
|
|
conf = mw.readFile(filename)
|
|
rep = '\s*root\s*(.+);'
|
|
path = re.search(rep, conf).groups()[0]
|
|
conf = conf.replace(path, newPath)
|
|
mw.writeFile(filename, conf)
|
|
|
|
self.setDirUserINI(sitePath, runPath)
|
|
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, '设置成功!')
|
|
|
|
# 设置目录加密
|
|
def setHasPwdApi(self):
|
|
username = request.form.get('username', '')
|
|
password = request.form.get('password', '')
|
|
siteName = request.form.get('siteName', '')
|
|
mid = request.form.get('id', '')
|
|
|
|
if len(username.strip()) == 0 or len(password.strip()) == 0:
|
|
return mw.returnJson(False, '用户名或密码不能为空!')
|
|
|
|
if siteName == '':
|
|
siteName = mw.M('sites').where('id=?', (mid,)).getField('name')
|
|
|
|
# self.closeHasPwd(get)
|
|
filename = self.passPath + '/' + siteName + '.pass'
|
|
# print(filename)
|
|
passconf = username + ':' + mw.hasPwd(password)
|
|
|
|
if siteName == 'phpmyadmin':
|
|
configFile = self.getHostConf('phpmyadmin')
|
|
else:
|
|
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)
|
|
|
|
mw.restartWeb()
|
|
msg = mw.getInfo('设置网站[{1}]为需要密码认证!', (siteName,))
|
|
mw.writeLog("网站管理", msg)
|
|
return mw.returnJson(True, '设置成功!')
|
|
|
|
# 取消目录加密
|
|
def closeHasPwdApi(self):
|
|
siteName = request.form.get('siteName', '')
|
|
mid = request.form.get('id', '')
|
|
if siteName == '':
|
|
siteName = mw.M('sites').where('id=?', (mid,)).getField('name')
|
|
|
|
if siteName == 'phpmyadmin':
|
|
configFile = self.getHostConf('phpmyadmin')
|
|
else:
|
|
configFile = self.getHostConf(siteName)
|
|
|
|
if os.path.exists(configFile):
|
|
conf = mw.readFile(configFile)
|
|
rep = "\n\s*#AUTH_START(.|\n){1,200}#AUTH_END"
|
|
conf = re.sub(rep, '', conf)
|
|
mw.writeFile(configFile, conf)
|
|
|
|
mw.restartWeb()
|
|
msg = mw.getInfo('清除网站[{1}]的密码认证!', (siteName,))
|
|
mw.writeLog("网站管理", msg)
|
|
return mw.returnJson(True, '设置成功!')
|
|
|
|
def delDomainApi(self):
|
|
domain = request.form.get('domain', '')
|
|
webname = request.form.get('webname', '')
|
|
port = request.form.get('port', '')
|
|
pid = request.form.get('id', '')
|
|
|
|
find = mw.M('domain').where("pid=? AND name=?",
|
|
(pid, domain)).field('id,name').find()
|
|
|
|
domain_count = mw.M('domain').where("pid=?", (pid,)).count()
|
|
if domain_count == 1:
|
|
return mw.returnJson(False, '最后一个域名不能删除!')
|
|
|
|
file = self.getHostConf(webname)
|
|
conf = mw.readFile(file)
|
|
if conf:
|
|
# 删除域名
|
|
rep = "server_name\s+(.+);"
|
|
tmp = re.search(rep, conf).group()
|
|
newServerName = tmp.replace(' ' + domain + ';', ';')
|
|
newServerName = newServerName.replace(' ' + domain + ' ', ' ')
|
|
conf = conf.replace(tmp, newServerName)
|
|
|
|
# 删除端口
|
|
rep = "listen\s+([0-9]+);"
|
|
tmp = re.findall(rep, conf)
|
|
port_count = mw.M('domain').where(
|
|
'pid=? AND port=?', (pid, port)).count()
|
|
if mw.inArray(tmp, port) == True and port_count < 2:
|
|
rep = "\n*\s+listen\s+" + port + ";"
|
|
conf = re.sub(rep, '', conf)
|
|
# 保存配置
|
|
mw.writeFile(file, conf)
|
|
|
|
mw.M('domain').where("id=?", (find['id'],)).delete()
|
|
msg = mw.getInfo('网站[{1}]删除域名[{2}]成功!', (webname, domain))
|
|
mw.writeLog('网站管理', msg)
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, '站点删除成功!')
|
|
|
|
def deleteApi(self):
|
|
sid = request.form.get('id', '')
|
|
webname = request.form.get('webname', '')
|
|
path = request.form.get('path', '0')
|
|
return self.delete(sid, webname, path)
|
|
|
|
# 操作 重定向配置
|
|
def operateRedirectConf(self, siteName, method='start'):
|
|
vhost_file = self.vhostPath + '/' + siteName + '.conf'
|
|
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 getRedirectApi(self):
|
|
_siteName = request.form.get("siteName", '')
|
|
|
|
# read data base
|
|
data_path = self.getRedirectDataPath(_siteName)
|
|
data_content = mw.readFile(data_path)
|
|
if data_content == False:
|
|
mw.execShell("mkdir {}/{}".format(self.redirectPath, _siteName))
|
|
return mw.returnJson(True, "", {"result": [], "count": 0})
|
|
# get
|
|
# conf_path = "{}/{}/*.conf".format(self.redirectPath, siteName)
|
|
# conf_list = glob.glob(conf_path)
|
|
# if conf_list == []:
|
|
# return mw.returnJson(True, "", {"result": [], "count": 0})
|
|
try:
|
|
data = json.loads(data_content)
|
|
except:
|
|
mw.execShell("rm -rf {}/{}".format(self.redirectPath, _siteName))
|
|
return mw.returnJson(True, "", {"result": [], "count": 0})
|
|
|
|
# 处理301信息
|
|
return mw.returnJson(True, "ok", {"result": data, "count": len(data)})
|
|
|
|
def getRedirectConfApi(self):
|
|
_siteName = request.form.get("siteName", '')
|
|
_id = request.form.get("id", '')
|
|
if _id == '' or _siteName == '':
|
|
return mw.returnJson(False, "必填项不能为空!")
|
|
|
|
data = mw.readFile(
|
|
"{}/{}/{}.conf".format(self.redirectPath, _siteName, _id))
|
|
if data == False:
|
|
return mw.returnJson(False, "获取失败!")
|
|
return mw.returnJson(True, "ok", {"result": data})
|
|
|
|
def saveRedirectConfApi(self):
|
|
_siteName = request.form.get("siteName", '')
|
|
_id = request.form.get("id", '')
|
|
_config = request.form.get("config", "")
|
|
if _id == '' or _siteName == '':
|
|
return mw.returnJson(False, "必填项不能为空!")
|
|
|
|
_old_config = mw.readFile(
|
|
"{}/{}/{}.conf".format(self.redirectPath, _siteName, _id))
|
|
if _old_config == False:
|
|
return mw.returnJson(False, "非法操作")
|
|
|
|
mw.writeFile("{}/{}/{}.conf".format(self.redirectPath,
|
|
_siteName, _id), _config)
|
|
rule_test = mw.checkWebConfig()
|
|
if rule_test != True:
|
|
mw.writeFile("{}/{}/{}.conf".format(self.redirectPath,
|
|
_siteName, _id), _old_config)
|
|
return mw.returnJson(False, "OpenResty 配置测试不通过, 请重试: {}".format(rule_test))
|
|
|
|
self.operateRedirectConf(_siteName, 'start')
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, "ok")
|
|
|
|
# get redirect status
|
|
def setRedirectApi(self):
|
|
_siteName = request.form.get("siteName", '')
|
|
# from (example.com / /test/)
|
|
_from = request.form.get("from", '')
|
|
_to = request.form.get("to", '') # redirect to
|
|
_type = request.form.get("type", '') # path / domain
|
|
_rType = request.form.get("r_type", '') # redirect type
|
|
_keepPath = request.form.get("keep_path", '') # keep path
|
|
|
|
if _siteName == '' or _from == '' or _to == '' or _type == '' or _rType == '':
|
|
return mw.returnJson(False, "必填项不能为空!")
|
|
|
|
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 []
|
|
|
|
_rTypeCode = 0 if _rType == "301" else 1
|
|
_typeCode = 0 if _type == "path" else 1
|
|
_keepPath = 1 if _keepPath == "1" else 0
|
|
|
|
# check if domain exists in site
|
|
if _typeCode == 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.returnJson(False, "域名不存在!")
|
|
|
|
file_content = ""
|
|
# path
|
|
if _typeCode == 0:
|
|
redirect_type = "permanent" if _rTypeCode == 0 else "redirect"
|
|
if not _from.startswith("/"):
|
|
_from = "/{}".format(_from)
|
|
if _keepPath == 1:
|
|
_to = "{}$1".format(_to)
|
|
_from = "{}(.*)".format(_from)
|
|
file_content = "rewrite ^{} {} {};".format(
|
|
_from, _to, redirect_type)
|
|
# domain
|
|
else:
|
|
if _keepPath == 1:
|
|
_to = "{}$request_uri".format(_to)
|
|
|
|
redirect_type = "301" if _rTypeCode == 0 else "302"
|
|
_if = "if ($host ~ '^{}')".format(_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"] == _from:
|
|
return mw.returnJson(False, "重复的规则!")
|
|
|
|
rep = "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.returnJson(False, "错误的目标地址")
|
|
|
|
# write data json file
|
|
data.append({"r_from": _from, "type": _typeCode, "r_type": _rTypeCode,
|
|
"r_to": _to, 'keep_path': _keepPath, 'id': _id})
|
|
mw.writeFile(data_path, json.dumps(data))
|
|
mw.writeFile(
|
|
"{}/{}.conf".format(self.getRedirectPath(_siteName), _id), file_content)
|
|
|
|
self.operateRedirectConf(_siteName, 'start')
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, "ok")
|
|
|
|
# 删除指定重定向
|
|
def delRedirectApi(self):
|
|
_siteName = request.form.get("siteName", '')
|
|
_id = request.form.get("id", '')
|
|
if _id == '' or _siteName == '':
|
|
return mw.returnJson(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"] == _id:
|
|
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), _id))
|
|
except:
|
|
return mw.returnJson(False, "删除失败!")
|
|
return mw.returnJson(True, "删除成功!")
|
|
|
|
# 操作 反向代理配置
|
|
def operateProxyConf(self, siteName, method='start'):
|
|
vhost_file = self.vhostPath + '/' + siteName + '.conf'
|
|
content = mw.readFile(vhost_file)
|
|
|
|
proxy_cnf = '''
|
|
#PROXY-START
|
|
include %s/*.conf;
|
|
#PROXY-END
|
|
''' % (self.getProxyPath(siteName))
|
|
|
|
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 getProxyConfApi(self):
|
|
_siteName = request.form.get("siteName", '')
|
|
_id = request.form.get("id", '')
|
|
if _id == '' or _siteName == '':
|
|
return mw.returnJson(False, "必填项不能为空!")
|
|
|
|
conf_file = "{}/{}/{}.conf".format(self.proxyPath, _siteName, _id)
|
|
if not os.path.exists(conf_file):
|
|
conf_file = "{}/{}/{}.conf.txt".format(
|
|
self.proxyPath, _siteName, _id)
|
|
|
|
data = mw.readFile(conf_file)
|
|
if data == False:
|
|
return mw.returnJson(False, "获取失败!")
|
|
return mw.returnJson(True, "ok", {"result": data})
|
|
|
|
def setProxyStatusApi(self):
|
|
_siteName = request.form.get("siteName", '')
|
|
_status = request.form.get("status", '')
|
|
_id = request.form.get("id", '')
|
|
if _status == '' or _siteName == '' or _id == '':
|
|
return mw.returnJson(False, "必填项不能为空!")
|
|
|
|
conf_file = "{}/{}/{}.conf".format(self.proxyPath, _siteName, _id)
|
|
conf_txt = "{}/{}/{}.conf.txt".format(self.proxyPath, _siteName, _id)
|
|
|
|
if _status == '1':
|
|
mw.execShell('mv ' + conf_txt + ' ' + conf_file)
|
|
else:
|
|
mw.execShell('mv ' + conf_file + ' ' + conf_txt)
|
|
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, "OK")
|
|
|
|
def saveProxyConfApi(self):
|
|
_siteName = request.form.get("siteName", '')
|
|
_id = request.form.get("id", '')
|
|
_config = request.form.get("config", "")
|
|
if _id == '' or _siteName == '':
|
|
return mw.returnJson(False, "必填项不能为空!")
|
|
|
|
_old_config = mw.readFile(
|
|
"{}/{}/{}.conf".format(self.proxyPath, _siteName, _id))
|
|
if _old_config == False:
|
|
return mw.returnJson(False, "非法操作")
|
|
|
|
mw.writeFile("{}/{}/{}.conf".format(self.proxyPath,
|
|
_siteName, _id), _config)
|
|
rule_test = mw.checkWebConfig()
|
|
if rule_test != True:
|
|
mw.writeFile("{}/{}/{}.conf".format(self.proxyPath,
|
|
_siteName, _id), _old_config)
|
|
return mw.returnJson(False, "OpenResty 配置测试不通过, 请重试: {}".format(rule_test))
|
|
|
|
self.operateRedirectConf(_siteName, 'start')
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, "ok")
|
|
|
|
# 读取 网站 反向代理列表
|
|
def getProxyListApi(self):
|
|
_siteName = request.form.get('siteName', '')
|
|
|
|
data_path = self.getProxyDataPath(_siteName)
|
|
data_content = mw.readFile(data_path)
|
|
|
|
# not exists
|
|
if data_content == False:
|
|
mw.execShell("mkdir {}/{}".format(self.proxyPath, _siteName))
|
|
return mw.returnJson(True, "", {"result": [], "count": 0})
|
|
|
|
try:
|
|
data = json.loads(data_content)
|
|
except:
|
|
mw.execShell("rm -rf {}/{}".format(self.proxyPath, _siteName))
|
|
return mw.returnJson(True, "", {"result": [], "count": 0})
|
|
|
|
tmp = []
|
|
for proxy in data:
|
|
proxy_dir = "{}/{}".format(self.proxyPath, _siteName)
|
|
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.returnJson(True, "ok", {"result": data, "count": len(data)})
|
|
|
|
# 设置 网站 反向代理列表
|
|
def setProxyApi(self):
|
|
_siteName = request.form.get('siteName', '')
|
|
_from = request.form.get('from', '')
|
|
_to = request.form.get('to', '')
|
|
_host = request.form.get('host', '')
|
|
_open_proxy = request.form.get('open_proxy', '')
|
|
|
|
if _siteName == "" or _from == "" or _to == "" or _host == "":
|
|
return mw.returnJson(False, "必填项不能为空")
|
|
|
|
data_path = self.getProxyDataPath(_siteName)
|
|
data_content = mw.readFile(
|
|
data_path) if os.path.exists(data_path) else ""
|
|
data = json.loads(data_content) if data_content != "" else []
|
|
|
|
rep = "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.returnJson(False, "错误的目标地址!")
|
|
|
|
# _to = _to.strip("/")
|
|
# get host from url
|
|
try:
|
|
if _host == "$host":
|
|
host_tmp = urlparse(_to)
|
|
_host = host_tmp.netloc
|
|
except:
|
|
return mw.returnJson(False, "错误的目标地址")
|
|
|
|
# location ~* ^{from}(.*)$ {
|
|
tpl = """
|
|
#PROXY-START/
|
|
location ^~ {from} {
|
|
proxy_pass {to};
|
|
proxy_set_header Host {host};
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header REMOTE-HOST $remote_addr;
|
|
|
|
add_header X-Cache $upstream_cache_status;
|
|
proxy_ignore_headers Set-Cookie Cache-Control expires;
|
|
add_header Cache-Control no-cache;
|
|
|
|
set $static_files_app 0;
|
|
if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
|
|
{
|
|
set $static_files_app 1;
|
|
expires 12h;
|
|
}
|
|
if ( $static_files_app = 0 )
|
|
{
|
|
add_header Cache-Control no-cache;
|
|
}
|
|
}
|
|
#PROXY-END/
|
|
"""
|
|
|
|
# replace
|
|
if _from[0] != '/':
|
|
_from = '/' + _from
|
|
tpl = tpl.replace("{from}", _from, 999)
|
|
tpl = tpl.replace("{to}", _to)
|
|
tpl = tpl.replace("{host}", _host, 999)
|
|
|
|
_id = mw.md5("{}+{}+{}".format(_from, _to, _siteName))
|
|
for item in data:
|
|
if item["id"] == _id:
|
|
return mw.returnJson(False, "已存在该规则!")
|
|
if item["from"] == _from:
|
|
return mw.returnJson(False, "代理目录已存在!")
|
|
data.append({
|
|
"from": _from,
|
|
"to": _to,
|
|
"host": _host,
|
|
"id": _id
|
|
})
|
|
|
|
conf_file = "{}/{}.conf".format(self.getProxyPath(_siteName), _id)
|
|
if _open_proxy != 'on':
|
|
conf_file = "{}/{}.conf.txt".format(
|
|
self.getProxyPath(_siteName), _id)
|
|
|
|
mw.writeFile(data_path, json.dumps(data))
|
|
mw.writeFile(conf_file, tpl)
|
|
|
|
self.operateProxyConf(_siteName, 'start')
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, "ok", {"hash": _id})
|
|
|
|
def delProxyApi(self):
|
|
_siteName = request.form.get("siteName", '')
|
|
_id = request.form.get("id", '')
|
|
if _id == '' or _siteName == '':
|
|
return mw.returnJson(False, "必填项不能为空!")
|
|
|
|
try:
|
|
data_path = self.getProxyDataPath(_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"] == _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(_siteName, 'stop')
|
|
# remove conf file
|
|
cmd = "rm -rf {}/{}.conf*".format(
|
|
self.getProxyPath(_siteName), _id)
|
|
mw.execShell(cmd)
|
|
except:
|
|
return mw.returnJson(False, "删除失败!")
|
|
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, "删除成功!")
|
|
|
|
def getSiteTypesApi(self):
|
|
# 取网站分类
|
|
data = mw.M("site_types").field("id,name").order("id asc").select()
|
|
data.insert(0, {"id": 0, "name": "默认分类"})
|
|
return mw.getJson(data)
|
|
|
|
def getSiteDocApi(self):
|
|
stype = request.form.get('type', '0').strip()
|
|
vlist = []
|
|
vlist.append('')
|
|
vlist.append(mw.getServerDir() +
|
|
'/openresty/nginx/html/index.html')
|
|
vlist.append(mw.getServerDir() + '/openresty/nginx/html/404.html')
|
|
vlist.append(mw.getServerDir() +
|
|
'/openresty/nginx/html/index.html')
|
|
vlist.append(mw.getServerDir() + '/web_conf/stop/index.html')
|
|
data = {}
|
|
data['path'] = vlist[int(stype)]
|
|
return mw.returnJson(True, 'ok', data)
|
|
|
|
def addSiteTypeApi(self):
|
|
name = request.form.get('name', '').strip()
|
|
if not name:
|
|
return mw.returnJson(False, "分类名称不能为空")
|
|
if len(name) > 18:
|
|
return mw.returnJson(False, "分类名称长度不能超过6个汉字或18位字母")
|
|
if mw.M('site_types').count() >= 10:
|
|
return mw.returnJson(False, '最多添加10个分类!')
|
|
if mw.M('site_types').where('name=?', (name,)).count() > 0:
|
|
return mw.returnJson(False, "指定分类名称已存在!")
|
|
mw.M('site_types').add("name", (name,))
|
|
return mw.returnJson(True, '添加成功!')
|
|
|
|
def removeSiteTypeApi(self):
|
|
mid = request.form.get('id', '')
|
|
if mw.M('site_types').where('id=?', (mid,)).count() == 0:
|
|
return mw.returnJson(False, "指定分类不存在!")
|
|
mw.M('site_types').where('id=?', (mid,)).delete()
|
|
mw.M("sites").where("type_id=?", (mid,)).save("type_id", (0,))
|
|
return mw.returnJson(True, "分类已删除!")
|
|
|
|
def modifySiteTypeNameApi(self):
|
|
# 修改网站分类名称
|
|
name = request.form.get('name', '').strip()
|
|
mid = request.form.get('id', '')
|
|
if not name:
|
|
return mw.returnJson(False, "分类名称不能为空")
|
|
if len(name) > 18:
|
|
return mw.returnJson(False, "分类名称长度不能超过6个汉字或18位字母")
|
|
if mw.M('site_types').where('id=?', (mid,)).count() == 0:
|
|
return mw.returnJson(False, "指定分类不存在!")
|
|
mw.M('site_types').where('id=?', (mid,)).setField('name', name)
|
|
return mw.returnJson(True, "修改成功!")
|
|
|
|
def setSiteTypeApi(self):
|
|
# 设置指定站点的分类
|
|
site_ids = request.form.get('site_ids', '')
|
|
mid = request.form.get('id', '')
|
|
site_ids = json.loads(site_ids)
|
|
for sid in site_ids:
|
|
print(mw.M('sites').where('id=?', (sid,)).setField('type_id', mid))
|
|
return mw.returnJson(True, "设置成功!")
|
|
|
|
##### ----- end ----- ###
|
|
|
|
# 域名编码转换
|
|
def toPunycode(self, domain):
|
|
import re
|
|
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
|
|
import re
|
|
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 getPath(self, path):
|
|
if path[-1] == '/':
|
|
return path[0:-1]
|
|
return path
|
|
|
|
def getSitePath(self, siteName):
|
|
file = self.getHostConf(siteName)
|
|
if os.path.exists(file):
|
|
conf = mw.readFile(file)
|
|
rep = '\s*root\s*(.+);'
|
|
path = re.search(rep, conf).groups()[0]
|
|
return path
|
|
return ''
|
|
|
|
# 取当站点前运行目录
|
|
def getSiteRunPath(self, mid):
|
|
siteName = mw.M('sites').where('id=?', (mid,)).getField('name')
|
|
sitePath = mw.M('sites').where('id=?', (mid,)).getField('path')
|
|
path = sitePath
|
|
|
|
filename = self.getHostConf(siteName)
|
|
if os.path.exists(filename):
|
|
conf = mw.readFile(filename)
|
|
rep = '\s*root\s*(.+);'
|
|
path = re.search(rep, conf).groups()[0]
|
|
|
|
data = {}
|
|
if sitePath == path:
|
|
data['runPath'] = '/'
|
|
else:
|
|
data['runPath'] = path.replace(sitePath, '')
|
|
|
|
dirnames = []
|
|
dirnames.append('/')
|
|
for filename in os.listdir(sitePath):
|
|
try:
|
|
filePath = sitePath + '/' + filename
|
|
if os.path.islink(filePath):
|
|
continue
|
|
if os.path.isdir(filePath):
|
|
dirnames.append('/' + filename)
|
|
except:
|
|
pass
|
|
|
|
data['dirs'] = dirnames
|
|
return data
|
|
|
|
def getHostConf(self, siteName):
|
|
return self.vhostPath + '/' + siteName + '.conf'
|
|
|
|
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, siteName, dirname):
|
|
return self.rewritePath + '/' + siteName + '_' + dirname + '.conf'
|
|
|
|
def getIndexConf(self):
|
|
return mw.getServerDir() + '/openresty/nginx/conf/nginx.conf'
|
|
|
|
def getDomain(self, pid):
|
|
_list = mw.M('domain').where("pid=?", (pid,)).field(
|
|
'id,pid,name,port,addtime').select()
|
|
return mw.getJson(_list)
|
|
|
|
def getLogs(self, siteName):
|
|
logPath = mw.getLogsDir() + '/' + siteName + '.log'
|
|
if not os.path.exists(logPath):
|
|
return mw.returnJson(False, '日志为空')
|
|
return mw.returnJson(True, mw.getLastLine(logPath, 100))
|
|
|
|
def getErrorLogs(self, siteName):
|
|
logPath = mw.getLogsDir() + '/' + siteName + '.error.log'
|
|
if not os.path.exists(logPath):
|
|
return mw.returnJson(False, '日志为空')
|
|
return mw.returnJson(True, mw.getLastLine(logPath, 100))
|
|
|
|
# 取日志状态
|
|
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 getSitePhpVersion(self, siteName):
|
|
conf = mw.readFile(self.getHostConf(siteName))
|
|
rep = "enable-php-(.*)\.conf"
|
|
tmp = re.search(rep, conf).groups()
|
|
data = {}
|
|
data['phpversion'] = tmp[0]
|
|
return mw.getJson(data)
|
|
|
|
def getIndex(self, sid):
|
|
siteName = mw.M('sites').where("id=?", (sid,)).getField('name')
|
|
file = self.getHostConf(siteName)
|
|
conf = mw.readFile(file)
|
|
rep = "\s+index\s+(.+);"
|
|
tmp = re.search(rep, conf).groups()
|
|
return tmp[0].replace(' ', ',')
|
|
|
|
def setIndex(self, sid, index):
|
|
if index.find('.') == -1:
|
|
return mw.returnJson(False, '默认文档格式不正确,例:index.html')
|
|
|
|
index = index.replace(' ', '')
|
|
index = index.replace(',,', ',')
|
|
|
|
if len(index) < 3:
|
|
return mw.returnJson(False, '默认文档不能为空!')
|
|
|
|
siteName = mw.M('sites').where("id=?", (sid,)).getField('name')
|
|
index_l = index.replace(",", " ")
|
|
file = self.getHostConf(siteName)
|
|
conf = mw.readFile(file)
|
|
if conf:
|
|
rep = "\s+index\s+.+;"
|
|
conf = re.sub(rep, "\n\tindex " + index_l + ";", conf)
|
|
mw.writeFile(file, conf)
|
|
|
|
mw.writeLog('TYPE_SITE', 'SITE_INDEX_SUCCESS', (siteName, index_l))
|
|
return mw.returnJson(True, '设置成功!')
|
|
|
|
def getLimitNet(self, sid):
|
|
siteName = mw.M('sites').where("id=?", (sid,)).getField('name')
|
|
filename = self.getHostConf(siteName)
|
|
# 站点总并发
|
|
data = {}
|
|
conf = mw.readFile(filename)
|
|
try:
|
|
rep = "\s+limit_conn\s+perserver\s+([0-9]+);"
|
|
tmp = re.search(rep, conf).groups()
|
|
data['perserver'] = int(tmp[0])
|
|
|
|
# IP并发限制
|
|
rep = "\s+limit_conn\s+perip\s+([0-9]+);"
|
|
tmp = re.search(rep, conf).groups()
|
|
data['perip'] = int(tmp[0])
|
|
|
|
# 请求并发限制
|
|
rep = "\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 mw.getJson(data)
|
|
|
|
def checkIndexConf(self):
|
|
limit = self.getIndexConf()
|
|
nginxConf = mw.readFile(limit)
|
|
limitConf = "limit_conn_zone $binary_remote_addr zone=perip:10m;\n\t\tlimit_conn_zone $server_name zone=perserver:10m;"
|
|
nginxConf = nginxConf.replace(
|
|
"#limit_conn_zone $binary_remote_addr zone=perip:10m;", limitConf)
|
|
mw.writeFile(limit, nginxConf)
|
|
|
|
def saveLimitNet(self, sid, perserver, perip, limit_rate):
|
|
|
|
str_perserver = 'limit_conn perserver ' + perserver + ';'
|
|
str_perip = 'limit_conn perip ' + perip + ';'
|
|
str_limit_rate = 'limit_rate ' + limit_rate + 'k;'
|
|
|
|
siteName = mw.M('sites').where("id=?", (sid,)).getField('name')
|
|
filename = self.getHostConf(siteName)
|
|
|
|
conf = mw.readFile(filename)
|
|
if(conf.find('limit_conn perserver') != -1):
|
|
# 替换总并发
|
|
rep = "limit_conn\s+perserver\s+([0-9]+);"
|
|
conf = re.sub(rep, str_perserver, conf)
|
|
|
|
# 替换IP并发限制
|
|
rep = "limit_conn\s+perip\s+([0-9]+);"
|
|
conf = re.sub(rep, str_perip, conf)
|
|
|
|
# 替换请求流量限制
|
|
rep = "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('TYPE_SITE', 'SITE_NETLIMIT_OPEN_SUCCESS', (siteName,))
|
|
return mw.returnJson(True, '设置成功!')
|
|
|
|
def closeLimitNet(self, sid):
|
|
siteName = mw.M('sites').where("id=?", (sid,)).getField('name')
|
|
filename = self.getHostConf(siteName)
|
|
conf = mw.readFile(filename)
|
|
# 清理总并发
|
|
rep = "\s+limit_conn\s+perserver\s+([0-9]+);"
|
|
conf = re.sub(rep, '', conf)
|
|
|
|
# 清理IP并发限制
|
|
rep = "\s+limit_conn\s+perip\s+([0-9]+);"
|
|
conf = re.sub(rep, '', conf)
|
|
|
|
# 清理请求流量限制
|
|
rep = "\s+limit_rate\s+([0-9]+)\w+;"
|
|
conf = re.sub(rep, '', conf)
|
|
mw.writeFile(filename, conf)
|
|
mw.restartWeb()
|
|
mw.writeLog(
|
|
'TYPE_SITE', 'SITE_NETLIMIT_CLOSE_SUCCESS', (siteName,))
|
|
return mw.returnJson(True, '已关闭流量限制!')
|
|
|
|
def getSecurity(self, sid, 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(
|
|
"\(.+\)\$", tmp).group().replace('(', '').replace(')$', '').replace('|', ',')
|
|
data['domains'] = ','.join(re.search(
|
|
"valid_referers\s+none\s+blocked\s+(.+);\n", tmp).groups()[0].split())
|
|
data['status'] = True
|
|
else:
|
|
data['fix'] = 'jpg,jpeg,gif,png,js,css'
|
|
domains = mw.M('domain').where(
|
|
'pid=?', (sid,)).field('name').select()
|
|
tmp = []
|
|
for domain in domains:
|
|
tmp.append(domain['name'])
|
|
data['domains'] = ','.join(tmp)
|
|
data['status'] = False
|
|
return mw.getJson(data)
|
|
|
|
def setSecurity(self, sid, name, fix, domains, status):
|
|
if len(fix) < 2:
|
|
return mw.returnJson(False, 'URL后缀不能为空!')
|
|
file = self.getHostConf(name)
|
|
if os.path.exists(file):
|
|
conf = mw.readFile(file)
|
|
if conf.find('SECURITY-START') != -1:
|
|
rep = "\s{0,4}#SECURITY-START(\n|.){1,500}#SECURITY-END\n?"
|
|
conf = re.sub(rep, '', conf)
|
|
mw.writeLog('网站管理', '站点[' + name + ']已关闭防盗链设置!')
|
|
else:
|
|
pre_path = self.setupPath + "/php/conf"
|
|
re_path = "include\s+" + pre_path + "/enable-php-"
|
|
rconf = '''#SECURITY-START 防盗链配置
|
|
location ~ .*\.(%s)$
|
|
{
|
|
expires 30d;
|
|
access_log /dev/null;
|
|
valid_referers none blocked %s;
|
|
if ($invalid_referer){
|
|
return 404;
|
|
}
|
|
}
|
|
#SECURITY-END
|
|
include %s/enable-php-''' % (fix.strip().replace(',', '|'), domains.strip().replace(',', ' '), pre_path)
|
|
conf = re.sub(re_path, rconf, conf)
|
|
mw.writeLog('网站管理', '站点[' + name + ']已开启防盗链!')
|
|
mw.writeFile(file, conf)
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, '设置成功!')
|
|
|
|
def getPhpVersion(self):
|
|
phpVersions = ('00', '52', '53', '54', '55',
|
|
'56', '70', '71', '72', '73', '74', '80', '81', '82')
|
|
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 = "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.getJson(data)
|
|
|
|
# 是否跳转到https
|
|
def isToHttps(self, siteName):
|
|
file = self.getHostConf(siteName)
|
|
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 getRewriteList(self):
|
|
rewriteList = {}
|
|
rewriteList['rewrite'] = []
|
|
rewriteList['rewrite'].append('0.当前')
|
|
for ds in os.listdir('rewrite/nginx'):
|
|
rewriteList['rewrite'].append(ds[0:len(ds) - 5])
|
|
rewriteList['rewrite'] = sorted(rewriteList['rewrite'])
|
|
return rewriteList
|
|
|
|
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 nginxAddDomain(self, webname, domain, port):
|
|
file = self.getHostConf(webname)
|
|
conf = mw.readFile(file)
|
|
if not conf:
|
|
return
|
|
|
|
# 添加域名
|
|
rep = "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 = "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 nginxAddConf(self):
|
|
source_tpl = mw.getRunDir() + '/data/tpl/nginx.conf'
|
|
vhost_file = self.vhostPath + '/' + self.siteName + '.conf'
|
|
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.rewritePath + '/' + self.siteName + '.conf'
|
|
mw.writeFile(rewrite_file, '')
|
|
|
|
def add(self, webname, port, ps, path, version):
|
|
siteMenu = json.loads(webname)
|
|
self.siteName = self.toPunycode(
|
|
siteMenu['domain'].strip().split(':')[0]).strip()
|
|
self.sitePath = self.toPunycodePath(
|
|
self.getPath(path.replace(' ', '')))
|
|
self.sitePort = port.strip().replace(' ', '')
|
|
self.phpVersion = version
|
|
|
|
if mw.M('sites').where("name=?", (self.siteName,)).count():
|
|
return mw.returnJson(False, '您添加的站点已存在!')
|
|
|
|
# 写入数据库
|
|
pid = mw.M('sites').add('name,path,status,ps,edate,addtime,type_id',
|
|
(self.siteName, self.sitePath, '1', ps, '0000-00-00', mw.getDate(), 0,))
|
|
opid = mw.M('domain').where(
|
|
"name=?", (self.siteName,)).getField('pid')
|
|
if opid:
|
|
if mw.M('sites').where('id=?', (opid,)).count():
|
|
return mw.returnJson(False, '您添加的域名已存在!')
|
|
mw.M('domain').where('pid=?', (opid,)).delete()
|
|
|
|
self.createRootDir(self.sitePath)
|
|
self.nginxAddConf()
|
|
|
|
# 添加更多域名
|
|
for domain in siteMenu['domainlist']:
|
|
self.addDomain(domain, self.siteName, pid)
|
|
|
|
mw.M('domain').add('pid,name,port,addtime',
|
|
(pid, self.siteName, self.sitePort, mw.getDate()))
|
|
|
|
data = {}
|
|
data['siteStatus'] = False
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, '添加成功')
|
|
|
|
def deleteWSLogs(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, sid, webname, path):
|
|
self.deleteWSLogs(webname)
|
|
if path == '1':
|
|
rootPath = mw.getWwwDir() + '/' + webname
|
|
mw.execShell('rm -rf ' + rootPath)
|
|
|
|
mw.M('sites').where("id=?", (sid,)).delete()
|
|
mw.restartWeb()
|
|
return mw.returnJson(True, '站点删除成功!')
|
|
|
|
def setEndDate(self, sid, edate):
|
|
result = mw.M('sites').where(
|
|
'id=?', (sid,)).setField('edate', edate)
|
|
siteName = mw.M('sites').where('id=?', (sid,)).getField('name')
|
|
mw.writeLog('TYPE_SITE', '设置成功,站点到期后将自动停止!', (siteName, edate))
|
|
return mw.returnJson(True, '设置成功,站点到期后将自动停止!')
|
|
|
|
# ssl相关方法 start
|
|
def setSslConf(self, siteName):
|
|
file = self.getHostConf(siteName)
|
|
conf = mw.readFile(file)
|
|
|
|
keyPath = self.sslDir + siteName + '/privkey.pem'
|
|
certPath = self.sslDir + siteName + '/fullchain.pem'
|
|
if conf:
|
|
if conf.find('ssl_certificate') == -1:
|
|
sslStr = """#error_page 404/404.html;
|
|
ssl_certificate %s;
|
|
ssl_certificate_key %s;
|
|
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
|
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
|
|
ssl_prefer_server_ciphers on;
|
|
ssl_session_cache shared:SSL:10m;
|
|
ssl_session_timeout 10m;
|
|
error_page 497 https://$host$request_uri;
|
|
""" % (certPath, keyPath)
|
|
if(conf.find('ssl_certificate') != -1):
|
|
return mw.returnData(True, 'SSL开启成功!')
|
|
|
|
conf = conf.replace('#error_page 404/404.html;', sslStr)
|
|
|
|
rep = "listen\s+([0-9]+)\s*[default_server]*;"
|
|
tmp = re.findall(rep, conf)
|
|
if not mw.inArray(tmp, '443'):
|
|
listen = re.search(rep, conf).group()
|
|
conf = conf.replace(
|
|
listen, listen + "\n\tlisten 443 ssl http2;")
|
|
shutil.copyfile(file, '/tmp/backup.conf')
|
|
|
|
mw.writeFile(file, conf)
|
|
isError = mw.checkWebConfig()
|
|
if(isError != True):
|
|
shutil.copyfile('/tmp/backup.conf', file)
|
|
return mw.returnData(False, '证书错误: <br><a style="color:red;">' + isError.replace("\n", '<br>') + '</a>')
|
|
|
|
mw.restartWeb()
|
|
self.saveCert(keyPath, certPath)
|
|
|
|
msg = mw.getInfo('网站[{1}]开启SSL成功!', siteName)
|
|
mw.writeLog('网站管理', msg)
|
|
return mw.returnData(True, 'SSL开启成功!')
|
|
|
|
def saveCert(self, keyPath, certPath):
|
|
try:
|
|
certInfo = self.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 getCertName(self, certPath):
|
|
try:
|
|
openssl = '/usr/local/openssl/bin/openssl'
|
|
if not os.path.exists(openssl):
|
|
openssl = 'openssl'
|
|
result = mw.execShell(
|
|
openssl + " x509 -in " + certPath + " -noout -subject -enddate -startdate -issuer")
|
|
tmp = result[0].split("\n")
|
|
data = {}
|
|
data['subject'] = tmp[0].split('=')[-1]
|
|
data['notAfter'] = self.strfToTime(tmp[1].split('=')[1])
|
|
data['notBefore'] = self.strfToTime(tmp[2].split('=')[1])
|
|
data['issuer'] = tmp[3].split('O=')[-1].split(',')[0]
|
|
if data['issuer'].find('/') != -1:
|
|
data['issuer'] = data['issuer'].split('/')[0]
|
|
result = mw.execShell(
|
|
openssl + " x509 -in " + certPath + " -noout -text|grep DNS")
|
|
data['dns'] = result[0].replace(
|
|
'DNS:', '').replace(' ', '').strip().split(',')
|
|
return data
|
|
except:
|
|
return None
|
|
|
|
# 清除多余user.ini
|
|
def delUserInI(self, path, up=0):
|
|
for p1 in os.listdir(path):
|
|
try:
|
|
npath = path + '/' + p1
|
|
if os.path.isdir(npath):
|
|
if up < 100:
|
|
self.delUserInI(npath, up + 1)
|
|
else:
|
|
continue
|
|
useriniPath = npath + '/.user.ini'
|
|
if not os.path.exists(useriniPath):
|
|
continue
|
|
mw.execShell('which chattr && chattr -i ' + useriniPath)
|
|
mw.execShell('rm -f ' + useriniPath)
|
|
except:
|
|
continue
|
|
return True
|
|
|
|
# 设置目录防御
|
|
def setDirUserINI(self, sitePath, runPath):
|
|
newPath = sitePath + runPath
|
|
|
|
filename = newPath + '/.user.ini'
|
|
if os.path.exists(filename):
|
|
mw.execShell("chattr -i " + filename)
|
|
os.remove(filename)
|
|
return mw.returnJson(True, '已清除防跨站设置!')
|
|
|
|
self.delUserInI(newPath)
|
|
openPath = 'open_basedir={}/:{}/'.format(newPath, sitePath)
|
|
if runPath == '/':
|
|
openPath = 'open_basedir={}/'.format(sitePath)
|
|
|
|
mw.writeFile(filename, openPath + ':/www/server/php:/tmp/:/proc/')
|
|
mw.execShell("chattr +i " + filename)
|
|
|
|
return mw.returnJson(True, '已打开防跨站设置!')
|
|
|
|
# 转换时间
|
|
def strfToTime(self, sdate):
|
|
import time
|
|
return time.strftime('%Y-%m-%d', time.strptime(sdate, '%b %d %H:%M:%S %Y %Z'))
|
|
# ssl相关方法 end
|
|
|