mirror of
https://github.com/midoks/mdserver-web.git
synced 2026-10-10 02:39:26 +08:00
@@ -162,9 +162,7 @@ plugins/my_*
|
||||
plugins/l2tp
|
||||
plugins/openlitespeed
|
||||
plugins/tamper_proof
|
||||
plugins/tamper_proof_*
|
||||
plugins/cryptocurrency_trade
|
||||
plugins/op_load_balance
|
||||
plugins/gdrive
|
||||
plugins/mtproxy
|
||||
plugins/zimg
|
||||
|
||||
@@ -99,9 +99,11 @@ docker run -itd --name mw-server --privileged=true -p 7200:7200 -p 80:80 -p 443:
|
||||
```
|
||||
|
||||
|
||||
### 版本更新 0.14.0
|
||||
### 版本更新 0.14.1
|
||||
|
||||
* 加入系统加固插件。
|
||||
* 安装优化。
|
||||
* 加入OP负载均衡插件。
|
||||
* 网站防篡改程序(测试中)。
|
||||
|
||||
### JSDelivr安装地址
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from flask import request
|
||||
|
||||
class config_api:
|
||||
|
||||
__version = '0.14.0'
|
||||
__version = '0.14.1'
|
||||
__api_addr = 'data/api.json'
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
## 指定共享内存
|
||||
lua_shared_dict healthcheck 10m;
|
||||
@@ -0,0 +1,64 @@
|
||||
location / {
|
||||
proxy_pass http://{$UPSTREAM_NAME};
|
||||
|
||||
client_max_body_size 10m;
|
||||
client_body_buffer_size 128k;
|
||||
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $proxy_host;
|
||||
proxy_connect_timeout 90;
|
||||
proxy_send_timeout 90;
|
||||
proxy_read_timeout 90;
|
||||
proxy_buffer_size 4k;
|
||||
proxy_buffers 4 32k;
|
||||
proxy_busy_buffers_size 64k;
|
||||
proxy_temp_file_write_size 64k;
|
||||
}
|
||||
|
||||
|
||||
#location /upstream_status {
|
||||
# allow 127.0.0.1;
|
||||
# deny all;
|
||||
# access_log off;
|
||||
# default_type text/plain;
|
||||
# content_by_lua_block {
|
||||
# local hc = require "resty.upstream.healthcheck"
|
||||
# ngx.say("OpenResty Worker PID: ", ngx.worker.pid())
|
||||
# ngx.print(hc.status_page())
|
||||
# }
|
||||
#}
|
||||
|
||||
location /upstream_status_{$UPSTREAM_NAME} {
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
access_log off;
|
||||
default_type text/plain;
|
||||
content_by_lua_block {
|
||||
local json = require "cjson"
|
||||
local ok, upstream = pcall(require, "ngx.upstream")
|
||||
if not ok then
|
||||
ngx.print("[]")
|
||||
return
|
||||
end
|
||||
|
||||
local get_primary_peers = upstream.get_primary_peers
|
||||
local get_backup_peers = upstream.get_backup_peers
|
||||
local upstream_name = "{$UPSTREAM_NAME}"
|
||||
|
||||
peers, err = get_primary_peers(upstream_name)
|
||||
if not peers then
|
||||
ngx.print("[]")
|
||||
return
|
||||
end
|
||||
|
||||
peers_backup, err = get_backup_peers(upstream_name)
|
||||
if peers_backup then
|
||||
for k, v in pairs(peers_backup) do
|
||||
table.insert(peers,v)
|
||||
end
|
||||
end
|
||||
|
||||
ngx.print(json.encode(peers))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
upstream {$UPSTREAM_NAME}
|
||||
{
|
||||
{$NODE_ALGO}
|
||||
{$NODE_SERVER_LIST}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 853 B |
Executable
+21
@@ -0,0 +1,21 @@
|
||||
<div class="bt-form">
|
||||
<div class="bt-w-main">
|
||||
<div class="bt-w-menu">
|
||||
<p class="bgw" onclick="pluginService('op_load_balance');">服务</p>
|
||||
<p onclick="loadBalanceList();">负载均衡</p>
|
||||
</div>
|
||||
<div class="bt-w-con pd15">
|
||||
<div class="soft-man-con"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="/plugins/file?name=op_load_balance&f=js/app.js"></script>
|
||||
<script type="text/javascript">
|
||||
resetPluginWinWidth(800);
|
||||
resetPluginWinHeight(300);
|
||||
$.getScript( "/plugins/file?name=op_load_balance&f=js/app.js", function(){
|
||||
pluginService('op_load_balance');
|
||||
});
|
||||
</script>
|
||||
Executable
+474
@@ -0,0 +1,474 @@
|
||||
# coding:utf-8
|
||||
|
||||
import sys
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
import json
|
||||
import re
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
|
||||
app_debug = False
|
||||
if mw.isAppleSystem():
|
||||
app_debug = True
|
||||
|
||||
|
||||
def getPluginName():
|
||||
return 'op_load_balance'
|
||||
|
||||
|
||||
def getPluginDir():
|
||||
return mw.getPluginDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getServerDir():
|
||||
return mw.getServerDir() + '/' + getPluginName()
|
||||
|
||||
|
||||
def getArgs():
|
||||
args = sys.argv[2:]
|
||||
tmp = {}
|
||||
args_len = len(args)
|
||||
|
||||
if args_len == 1:
|
||||
t = args[0].strip('{').strip('}')
|
||||
if t.strip() == '':
|
||||
tmp = []
|
||||
else:
|
||||
t = t.split(':')
|
||||
tmp[t[0]] = t[1]
|
||||
tmp[t[0]] = t[1]
|
||||
elif args_len > 1:
|
||||
for i in range(len(args)):
|
||||
t = args[i].split(':')
|
||||
tmp[t[0]] = t[1]
|
||||
return tmp
|
||||
|
||||
|
||||
def checkArgs(data, ck=[]):
|
||||
for i in range(len(ck)):
|
||||
if not ck[i] in data:
|
||||
return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!'))
|
||||
return (True, mw.returnJson(True, 'ok'))
|
||||
|
||||
|
||||
def getConf():
|
||||
path = getServerDir() + "/cfg.json"
|
||||
|
||||
if not os.path.exists(path):
|
||||
mw.writeFile(path, '[]')
|
||||
|
||||
c = mw.readFile(path)
|
||||
return json.loads(c)
|
||||
|
||||
|
||||
def writeConf(data):
|
||||
path = getServerDir() + "/cfg.json"
|
||||
mw.writeFile(path, json.dumps(data))
|
||||
|
||||
|
||||
def contentReplace(content):
|
||||
service_path = mw.getServerDir()
|
||||
content = content.replace('{$ROOT_PATH}', mw.getRootDir())
|
||||
content = content.replace('{$SERVER_PATH}', service_path)
|
||||
content = content.replace('{$APP_PATH}', app_path)
|
||||
return content
|
||||
|
||||
|
||||
def restartWeb():
|
||||
mw.opWeb('stop')
|
||||
mw.opWeb('start')
|
||||
|
||||
|
||||
def loadBalanceConf():
|
||||
path = mw.getServerDir() + '/web_conf/nginx/vhost/load_balance.conf'
|
||||
return path
|
||||
|
||||
|
||||
def initDreplace():
|
||||
|
||||
dst_conf_tpl = getPluginDir() + '/conf/load_balance.conf'
|
||||
dst_conf = loadBalanceConf()
|
||||
|
||||
if not os.path.exists(dst_conf):
|
||||
con = mw.readFile(dst_conf_tpl)
|
||||
mw.writeFile(dst_conf, con)
|
||||
|
||||
|
||||
def status():
|
||||
if not mw.getWebStatus():
|
||||
return 'stop'
|
||||
|
||||
dst_conf = loadBalanceConf()
|
||||
if not os.path.exists(dst_conf):
|
||||
return 'stop'
|
||||
|
||||
return 'start'
|
||||
|
||||
|
||||
def start():
|
||||
initDreplace()
|
||||
restartWeb()
|
||||
return 'ok'
|
||||
|
||||
|
||||
def stop():
|
||||
dst_conf = loadBalanceConf()
|
||||
os.remove(dst_conf)
|
||||
|
||||
deleteLoadBalanceAllCfg()
|
||||
restartWeb()
|
||||
return 'ok'
|
||||
|
||||
|
||||
def restart():
|
||||
restartWeb()
|
||||
return 'ok'
|
||||
|
||||
|
||||
def reload():
|
||||
restartWeb()
|
||||
return 'ok'
|
||||
|
||||
|
||||
def installPreInspection():
|
||||
check_op = mw.getServerDir() + "/openresty"
|
||||
if not os.path.exists(check_op):
|
||||
return "请先安装OpenResty"
|
||||
return 'ok'
|
||||
|
||||
|
||||
def deleteLoadBalanceAllCfg():
|
||||
cfg = getConf()
|
||||
upstream_dir = mw.getServerDir() + '/web_conf/nginx/upstream'
|
||||
lua_dir = mw.getServerDir() + '/web_conf/nginx/lua/init_worker_by_lua_file'
|
||||
rewrite_dir = mw.getServerDir() + '/web_conf/nginx/rewrite'
|
||||
vhost_dir = mw.getServerDir() + '/web_conf/nginx/vhost'
|
||||
|
||||
for conf in cfg:
|
||||
upstream_file = upstream_dir + '/' + conf['upstream_name'] + '.conf'
|
||||
if os.path.exists(upstream_file):
|
||||
os.remove(upstream_file)
|
||||
|
||||
lua_file = lua_dir + '/' + conf['upstream_name'] + '.lua'
|
||||
if os.path.exists(lua_file):
|
||||
os.remove(lua_file)
|
||||
|
||||
rewrite_file = rewrite_dir + '/' + conf['domain'] + '.conf'
|
||||
mw.writeFile(rewrite_file, '')
|
||||
|
||||
path = vhost_dir + '/' + conf['domain'] + '.conf'
|
||||
|
||||
content = mw.readFile(path)
|
||||
content = re.sub('include ' + upstream_file + ';' + "\n", '', content)
|
||||
mw.writeFile(path, content)
|
||||
|
||||
mw.opLuaInitWorkerFile()
|
||||
|
||||
|
||||
def makeConfServerList(data):
|
||||
slist = ''
|
||||
for x in data:
|
||||
slist += 'server '
|
||||
slist += x['ip'] + ':' + x['port']
|
||||
|
||||
if x['state'] == '0':
|
||||
slist += ' down;\n\t'
|
||||
continue
|
||||
|
||||
if x['state'] == '2':
|
||||
slist += ' backup;\n\t'
|
||||
continue
|
||||
|
||||
slist += ' weight=' + x['weight']
|
||||
slist += ' max_fails=' + x['max_fails']
|
||||
slist += ' fail_timeout=' + x['fail_timeout'] + "s;\n\t"
|
||||
return slist
|
||||
|
||||
|
||||
def makeLoadBalanceAllCfg(row):
|
||||
# 生成所有配置
|
||||
cfg = getConf()
|
||||
|
||||
upstream_dir = mw.getServerDir() + '/web_conf/nginx/upstream'
|
||||
rewrite_dir = mw.getServerDir() + '/web_conf/nginx/rewrite'
|
||||
vhost_dir = mw.getServerDir() + '/web_conf/nginx/vhost'
|
||||
upstream_tpl = getPluginDir() + '/conf/upstream.tpl.conf'
|
||||
rewrite_tpl = getPluginDir() + '/conf/rewrite.tpl.conf'
|
||||
|
||||
if not os.path.exists(upstream_dir):
|
||||
os.makedirs(upstream_dir)
|
||||
|
||||
conf = cfg[row]
|
||||
|
||||
# replace vhost start
|
||||
vhost_file = vhost_dir + '/' + conf['domain'] + '.conf'
|
||||
vcontent = mw.readFile(vhost_file)
|
||||
|
||||
vhost_find_str = 'upstream/' + conf['upstream_name'] + '.conf'
|
||||
vhead = 'include ' + mw.getServerDir() + '/web_conf/nginx/' + \
|
||||
vhost_find_str + ';'
|
||||
|
||||
vpos = vcontent.find(vhost_find_str)
|
||||
if vpos < 0:
|
||||
vcontent = vhead + "\n" + vcontent
|
||||
mw.writeFile(vhost_file, vcontent)
|
||||
# replace vhost end
|
||||
|
||||
# make upstream start
|
||||
upstream_file = upstream_dir + '/' + conf['upstream_name'] + '.conf'
|
||||
content = ''
|
||||
if len(conf['node_list']) > 0:
|
||||
content = mw.readFile(upstream_tpl)
|
||||
slist = makeConfServerList(conf['node_list'])
|
||||
content = content.replace('{$NODE_SERVER_LIST}', slist)
|
||||
content = content.replace('{$UPSTREAM_NAME}', conf['upstream_name'])
|
||||
if conf['node_algo'] != 'polling':
|
||||
content = content.replace('{$NODE_ALGO}', conf['node_algo'] + ';')
|
||||
else:
|
||||
content = content.replace('{$NODE_ALGO}', '')
|
||||
mw.writeFile(upstream_file, content)
|
||||
# make upstream end
|
||||
|
||||
# make rewrite start
|
||||
rewrite_file = rewrite_dir + '/' + conf['domain'] + '.conf'
|
||||
rcontent = ''
|
||||
if len(conf['node_list']) > 0:
|
||||
rcontent = mw.readFile(rewrite_tpl)
|
||||
rcontent = rcontent.replace('{$UPSTREAM_NAME}', conf['upstream_name'])
|
||||
mw.writeFile(rewrite_file, rcontent)
|
||||
# make rewrite end
|
||||
|
||||
# health check start
|
||||
lua_dir = mw.getServerDir() + '/web_conf/nginx/lua/init_worker_by_lua_file'
|
||||
lua_init_worker_file = lua_dir + '/' + conf['upstream_name'] + '.lua'
|
||||
if conf['node_health_check'] == 'ok':
|
||||
lua_dir_tpl = getPluginDir() + '/lua/health_check.lua.tpl'
|
||||
content = mw.readFile(lua_dir_tpl)
|
||||
content = content.replace('{$UPSTREAM_NAME}', conf['upstream_name'])
|
||||
content = content.replace('{$DOMAIN}', conf['domain'])
|
||||
mw.writeFile(lua_init_worker_file, content)
|
||||
else:
|
||||
if os.path.exists(lua_init_worker_file):
|
||||
os.remove(lua_init_worker_file)
|
||||
|
||||
mw.opLuaInitWorkerFile()
|
||||
# health check end
|
||||
return True
|
||||
|
||||
|
||||
def add_load_balance(args):
|
||||
|
||||
data = checkArgs(
|
||||
args, ['domain', 'upstream_name', 'node_algo', 'node_list', 'node_health_check'])
|
||||
if not data[0]:
|
||||
return data[1]
|
||||
|
||||
domain_json = args['domain']
|
||||
|
||||
tmp = json.loads(domain_json)
|
||||
domain = tmp['domain']
|
||||
|
||||
cfg = getConf()
|
||||
cfg_len = len(cfg)
|
||||
tmp = {}
|
||||
tmp['domain'] = domain
|
||||
tmp['data'] = args['domain']
|
||||
tmp['upstream_name'] = args['upstream_name']
|
||||
tmp['node_algo'] = args['node_algo']
|
||||
tmp['node_list'] = args['node_list']
|
||||
tmp['node_health_check'] = args['node_health_check']
|
||||
cfg.append(tmp)
|
||||
writeConf(cfg)
|
||||
|
||||
import site_api
|
||||
sobj = site_api.site_api()
|
||||
domain_path = mw.getWwwDir() + '/' + domain
|
||||
|
||||
ps = '负载均衡[' + domain + ']'
|
||||
data = sobj.add(domain_json, '80', ps, domain_path, '00')
|
||||
|
||||
makeLoadBalanceAllCfg(cfg_len)
|
||||
mw.restartWeb()
|
||||
return mw.returnJson(True, '添加成功', data)
|
||||
|
||||
|
||||
def edit_load_balance(args):
|
||||
data = checkArgs(
|
||||
args, ['row', 'node_algo', 'node_list', 'node_health_check'])
|
||||
if not data[0]:
|
||||
return data[1]
|
||||
|
||||
row = int(args['row'])
|
||||
|
||||
cfg = getConf()
|
||||
tmp = cfg[row]
|
||||
tmp['node_algo'] = args['node_algo']
|
||||
tmp['node_list'] = args['node_list']
|
||||
tmp['node_health_check'] = args['node_health_check']
|
||||
cfg[row] = tmp
|
||||
writeConf(cfg)
|
||||
|
||||
makeLoadBalanceAllCfg(row)
|
||||
mw.restartWeb()
|
||||
return mw.returnJson(True, '修改成功', data)
|
||||
|
||||
|
||||
def loadBalanceList():
|
||||
cfg = getConf()
|
||||
return mw.returnJson(True, 'ok', cfg)
|
||||
|
||||
|
||||
def loadBalanceDelete():
|
||||
args = getArgs()
|
||||
data = checkArgs(args, ['row'])
|
||||
if not data[0]:
|
||||
return data[1]
|
||||
|
||||
row = int(args['row'])
|
||||
|
||||
cfg = getConf()
|
||||
data = cfg[row]
|
||||
|
||||
import site_api
|
||||
sobj = site_api.site_api()
|
||||
|
||||
sid = mw.M('sites').where('name=?', (data['domain'],)).getField('id')
|
||||
|
||||
if type(sid) == list:
|
||||
del(cfg[row])
|
||||
writeConf(cfg)
|
||||
return mw.returnJson(False, '已经删除了!')
|
||||
|
||||
status = sobj.delete(sid, data['domain'], 1)
|
||||
status_data = json.loads(status)
|
||||
|
||||
if status_data['status']:
|
||||
del(cfg[row])
|
||||
writeConf(cfg)
|
||||
|
||||
upstream_dir = mw.getServerDir() + '/web_conf/nginx/upstream'
|
||||
rewrite_dir = mw.getServerDir() + '/web_conf/nginx/rewrite'
|
||||
|
||||
upstream_file = upstream_dir + '/' + data['upstream_name'] + '.conf'
|
||||
if os.path.exists(upstream_file):
|
||||
os.remove(upstream_file)
|
||||
|
||||
rewrite_file = rewrite_dir + '/' + data['domain'] + '.conf'
|
||||
if os.path.exists(rewrite_file):
|
||||
mw.writeFile(rewrite_file, '')
|
||||
|
||||
return mw.returnJson(status_data['status'], status_data['msg'])
|
||||
|
||||
|
||||
def http_get(url):
|
||||
ret = re.search(r'https://', url)
|
||||
if ret:
|
||||
try:
|
||||
from gevent import monkey
|
||||
monkey.patch_ssl()
|
||||
import requests
|
||||
ret = requests.get(url=str(url), verify=False, timeout=10)
|
||||
status = [200, 301, 302, 404, 403]
|
||||
if ret.status_code in status:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
if sys.version_info[0] == 2:
|
||||
import urllib2
|
||||
rec = urllib2.urlopen(url, timeout=3)
|
||||
else:
|
||||
import urllib.request
|
||||
rec = urllib.request.urlopen(url, timeout=3)
|
||||
status = [200, 301, 302, 404, 403]
|
||||
if rec.getcode() in status:
|
||||
return True
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def checkUrl():
|
||||
args = getArgs()
|
||||
data = checkArgs(args, ['ip', 'port', 'path'])
|
||||
if not data[0]:
|
||||
return data[1]
|
||||
|
||||
ip = args['ip']
|
||||
port = args['port']
|
||||
path = args['path']
|
||||
|
||||
if port == '443':
|
||||
url = 'https://' + str(ip) + ':' + str(port) + str(path.strip())
|
||||
else:
|
||||
url = 'http://' + str(ip) + ':' + str(port) + str(path.strip())
|
||||
ret = http_get(url)
|
||||
if not ret:
|
||||
return mw.returnJson(False, '访问节点[%s]失败' % url)
|
||||
return mw.returnJson(True, '访问节点[%s]成功' % url)
|
||||
|
||||
|
||||
def getHealthStatus():
|
||||
args = getArgs()
|
||||
data = checkArgs(args, ['row'])
|
||||
if not data[0]:
|
||||
return data[1]
|
||||
|
||||
row = int(args['row'])
|
||||
|
||||
cfg = getConf()
|
||||
data = cfg[row]
|
||||
|
||||
url = 'http://' + data['domain'] + \
|
||||
'/upstream_status_' + data['upstream_name']
|
||||
|
||||
url_data = mw.httpGet(url)
|
||||
return mw.returnJson(True, 'ok', json.loads(url_data))
|
||||
|
||||
|
||||
def getLogs():
|
||||
args = getArgs()
|
||||
data = checkArgs(args, ['domain'])
|
||||
if not data[0]:
|
||||
return data[1]
|
||||
|
||||
domain = args['domain']
|
||||
logs = mw.getLogsDir() + '/' + domain + '.log'
|
||||
return logs
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
if func == 'status':
|
||||
print(status())
|
||||
elif func == 'start':
|
||||
print(start())
|
||||
elif func == 'stop':
|
||||
print(stop())
|
||||
elif func == 'restart':
|
||||
print(restart())
|
||||
elif func == 'reload':
|
||||
print(reload())
|
||||
elif func == 'install_pre_inspection':
|
||||
print(installPreInspection())
|
||||
elif func == 'add_load_balance':
|
||||
print(addLoadBalance())
|
||||
elif func == 'load_balance_list':
|
||||
print(loadBalanceList())
|
||||
elif func == 'load_balance_delete':
|
||||
print(loadBalanceDelete())
|
||||
elif func == 'check_url':
|
||||
print(checkUrl())
|
||||
elif func == 'get_logs':
|
||||
print(getLogs())
|
||||
elif func == 'get_health_status':
|
||||
print(getHealthStatus())
|
||||
else:
|
||||
print('error')
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"title":"OP负载均衡",
|
||||
"tip":"soft",
|
||||
"name":"op_load_balance",
|
||||
"type":"其他插件",
|
||||
"install_pre_inspection":true,
|
||||
"ps":"基于OpenResty的负载均衡",
|
||||
"shell":"install.sh",
|
||||
"checks":"server/op_load_balance",
|
||||
"path":"server/op_load_balance",
|
||||
"author":"midoks",
|
||||
"home":"https://github.com/midoks",
|
||||
"date":"2023-02-02",
|
||||
"pid": "1",
|
||||
"versions": ["1.0"]
|
||||
}
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
|
||||
export PATH
|
||||
|
||||
curPath=`pwd`
|
||||
rootPath=$(dirname "$curPath")
|
||||
rootPath=$(dirname "$rootPath")
|
||||
serverPath=$(dirname "$rootPath")
|
||||
|
||||
install_tmp=${rootPath}/tmp/mw_install.pl
|
||||
|
||||
action=$1
|
||||
version=$2
|
||||
sys_os=`uname`
|
||||
|
||||
if [ -f ${rootPath}/bin/activate ];then
|
||||
source ${rootPath}/bin/activate
|
||||
fi
|
||||
|
||||
if [ "$sys_os" == "Darwin" ];then
|
||||
BAK='_bak'
|
||||
else
|
||||
BAK=''
|
||||
fi
|
||||
|
||||
Install_App(){
|
||||
echo '正在安装脚本文件...' > $install_tmp
|
||||
mkdir -p $serverPath/op_load_balance
|
||||
echo "${version}" > $serverPath/op_load_balance/version.pl
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/op_load_balance/index.py start
|
||||
echo 'install ok' > $install_tmp
|
||||
}
|
||||
|
||||
Uninstall_App(){
|
||||
cd ${rootPath} && python3 ${rootPath}/plugins/op_load_balance/index.py stop
|
||||
rm -rf $serverPath/op_load_balance
|
||||
}
|
||||
|
||||
|
||||
action=$1
|
||||
if [ "${1}" == 'install' ];then
|
||||
Install_App
|
||||
else
|
||||
Uninstall_App
|
||||
fi
|
||||
@@ -0,0 +1,606 @@
|
||||
function ooPost(method,args,callback){
|
||||
var _args = null;
|
||||
if (typeof(args) == 'string'){
|
||||
_args = JSON.stringify(toArrayObject(args));
|
||||
} else {
|
||||
_args = JSON.stringify(args);
|
||||
}
|
||||
|
||||
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
|
||||
$.post('/plugins/run', {name:'op_load_balance', func:method, args:_args}, function(data) {
|
||||
layer.close(loadT);
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof(callback) == 'function'){
|
||||
callback(data);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
function ooAsyncPost(method,args){
|
||||
var _args = null;
|
||||
if (typeof(args) == 'string'){
|
||||
_args = JSON.stringify(toArrayObject(args));
|
||||
} else {
|
||||
_args = JSON.stringify(args);
|
||||
}
|
||||
return syncPost('/plugins/run', {name:'op_load_balance', func:method, args:_args});
|
||||
}
|
||||
|
||||
function ooPostCallbak(method, args, callback){
|
||||
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
|
||||
|
||||
var req_data = {};
|
||||
req_data['name'] = 'op_load_balance';
|
||||
req_data['func'] = method;
|
||||
args['version'] = '1.0';
|
||||
|
||||
if (typeof(args) == 'string'){
|
||||
req_data['args'] = JSON.stringify(toArrayObject(args));
|
||||
} else {
|
||||
req_data['args'] = JSON.stringify(args);
|
||||
}
|
||||
|
||||
$.post('/plugins/callback', req_data, function(data) {
|
||||
layer.close(loadT);
|
||||
if (!data.status){
|
||||
layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof(callback) == 'function'){
|
||||
callback(data);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
|
||||
function addNode(){
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['450px','580px'],
|
||||
title: '添加节点',
|
||||
closeBtn: 1,
|
||||
shift: 5,
|
||||
shadeClose: true,
|
||||
btn:['提交','关闭'],
|
||||
content: "<form class='bt-form pd20'>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>IP地址</span>\
|
||||
<div class='info-r'>\
|
||||
<input name='ip' class='bt-input-text mr5' placeholder='负载名称,可以是英文字母和下划线,不能使用中文' type='text' style='width:250px' value='127.0.0.1'>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>端口</span>\
|
||||
<div class='info-r'>\
|
||||
<input name='port' class='bt-input-text mr5' type='text' style='width:250px' value='80'>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>验证文件路径</span>\
|
||||
<div class='info-r'>\
|
||||
<input name='path' class='bt-input-text mr5' type='text' style='width:250px' value='/'>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>节点状态</span>\
|
||||
<div class='info-r'>\
|
||||
<select name='state'>\
|
||||
<option value='1'>参与者</option>\
|
||||
<option value='2'>备份</option>\
|
||||
<option value='0'>停用</option>\
|
||||
</select>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>权重</span>\
|
||||
<div class='info-r'>\
|
||||
<input name='weight' class='bt-input-text mr5' type='text' style='width:250px' value='1'>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>阈值</span>\
|
||||
<div class='info-r'>\
|
||||
<input name='max_fails' class='bt-input-text mr5' type='text' style='width:250px' value='2'>次\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>恢复时间</span>\
|
||||
<div class='info-r'>\
|
||||
<input name='fail_timeout' class='bt-input-text mr5' type='text' style='width:250px' value='10'>秒\
|
||||
</div>\
|
||||
</div>\
|
||||
<ul style='margin-left:10px' class='help-info-text c7'>\
|
||||
<li>备份状态: 指当其它节点都无法使用时才会使用此节点</li>\
|
||||
<li>参与状态: 正常参与负载均衡,请至少添加1个普通节点</li>\
|
||||
<li>验证文件路径: 用于检查文件路径地址是否可用</li>\
|
||||
<li>IP地址: 仅支持IP地址,否则无法正常参与负载均衡</li>\
|
||||
<li>阈值: 在恢复时间的时间段内,如果OpenResty与节点通信尝试失败的次数达到此值,OpenResty就认为服务器不可用</li>\
|
||||
</ul>\
|
||||
</form>",
|
||||
success:function(){
|
||||
},
|
||||
yes:function(index) {
|
||||
|
||||
var ip = $('input[name="ip"]').val();
|
||||
var port = $('input[name="port"]').val();
|
||||
var path = $('input[name="path"]').val();
|
||||
var state = $('select[name="state"]').val();
|
||||
var weight = $('input[name="weight"]').val();
|
||||
var max_fails = $('input[name="max_fails"]').val();
|
||||
var fail_timeout = $('input[name="fail_timeout"]').val();
|
||||
|
||||
ooPost('check_url', {ip:ip,port:port,path:path},function(rdata){
|
||||
var rdata = $.parseJSON(rdata.data);
|
||||
showMsg(rdata.msg, function(){
|
||||
if (rdata.status){
|
||||
layer.close(index);
|
||||
$('#nodecon .nulltr').hide();
|
||||
|
||||
var tbody = '<tr>';
|
||||
tbody +='<td>'+ip+'</td>';
|
||||
tbody +='<td>'+port+'</td>';
|
||||
tbody +='<td>'+path+'</td>';
|
||||
|
||||
tbody +="<td><select name='state'>";
|
||||
var state_option_list = {
|
||||
'1':'参与者',
|
||||
'2':'备份',
|
||||
'0':'停用',
|
||||
}
|
||||
for (i in state_option_list) {
|
||||
if (i == state){
|
||||
tbody +="<option value='"+i+"' selected>"+state_option_list[i]+"</option>";
|
||||
} else{
|
||||
tbody +="<option value='"+i+"'>"+state_option_list[i]+"</option>";
|
||||
}
|
||||
}
|
||||
tbody +="</select></td>";
|
||||
|
||||
tbody +='<td><input type="number" name="weight" value="'+weight+'" style="width:50px"></td>';
|
||||
tbody +='<td><input type="number" name="max_fails" value="'+max_fails+'" style="width:50px"></td>';
|
||||
tbody +='<td><input type="number" name="fail_timeout" value="'+fail_timeout+'" style="width:50px"></td>';
|
||||
tbody +='<td class="text-right" width="50"><a class="btlink minus delete">删除</a></td>';
|
||||
tbody += '</tr>';
|
||||
$('#nodecon').append(tbody);
|
||||
|
||||
$('#nodecon .delete').click(function(){
|
||||
$(this).parent().parent().remove();
|
||||
if ($('#nodecon tr').length == 1 ){
|
||||
$('#nodecon .nulltr').show();
|
||||
}
|
||||
});
|
||||
}
|
||||
},{ icon: rdata.status ? 1 : 2 }, 2000);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addBalance(){
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['750px','460px'],
|
||||
title: '创建负载',
|
||||
closeBtn: 1,
|
||||
shift: 5,
|
||||
shadeClose: true,
|
||||
btn:['提交','关闭'],
|
||||
content: "<form class='bt-form pd20'>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>域名</span>\
|
||||
<div class='info-r'><textarea name='load_domain' class='bt-input-text mr5' placeholder='' style='width:95%;resize: none;height:90px;line-height:20px;'></textarea></div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>负载名称</span>\
|
||||
<div class='info-r'>\
|
||||
<input name='upstream_name' class='bt-input-text mr5' placeholder='负载名称,可以是英文字母和下划线,不能使用中文' type='text' style='width:95%' value=''>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>节点调度</span>\
|
||||
<div class='info-r'>\
|
||||
<select name='node_algo'>\
|
||||
<option value='polling'>轮询[默认]</option>\
|
||||
<option value='ip_hash'>ip_hash</option>\
|
||||
<option value='fair'>fair</option>\
|
||||
<option value='url_hash'>url_hash</option>\
|
||||
<option value='least_conn'>least_conn</option>\
|
||||
</select>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>节点健康检查</span>\
|
||||
<div class='info-r'>\
|
||||
<input type='checkbox' name='node_health_check' checked>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>节点</span>\
|
||||
<div class='info-r'>\
|
||||
<div class='table-con divtable' style='max-height:120px;overflow:auto; margin-bottom:8px;width:95%'>\
|
||||
<table class='table table-hover' id='fixTable3'>\
|
||||
<thead>\
|
||||
<tr>\
|
||||
<th width='120'>IP地址</th>\
|
||||
<th width='60'>端口</th>\
|
||||
<th width='120'>验证路径</th>\
|
||||
<th width='60'>状态</th>\
|
||||
<th width='60'>权重</th>\
|
||||
<th width='60'>阀值</th>\
|
||||
<th width='120'>恢复时间</th>\
|
||||
<th width='80' class='text-right'>操作</th>\
|
||||
</tr>\
|
||||
</thead>\
|
||||
<tbody id='nodecon'>\
|
||||
<tr class='nulltr'>\
|
||||
<td colspan='8' align='center'>当前节点为空,请至少添加一个普通节点</td>\
|
||||
</tr>\
|
||||
</tbody>\
|
||||
</table>\
|
||||
</div>\
|
||||
<span class='btn btn-success btn-sm add_node' style='vertical-align:0'>添加节点</span>\
|
||||
</div>\
|
||||
</div>\
|
||||
</form>",
|
||||
success:function(){
|
||||
$('textarea[name="load_domain"]').attr('placeholder','每行填写一个域名,默认为80端口。\n泛解析添加方法 *.domain.com\n如另加端口格式为 www.domain.com:88');
|
||||
var rval = getRandomString(6);
|
||||
$('input[name="upstream_name"]').val('load_balance_'+rval);
|
||||
|
||||
$('.add_node').click(function(){
|
||||
addNode();
|
||||
});
|
||||
},
|
||||
yes:function(index) {
|
||||
var data = {};
|
||||
|
||||
var upstream_name = $('input[name="upstream_name"]').val();
|
||||
if (upstream_name == ''){
|
||||
layer.msg('负载名称不能为空!',{icon:0,time:2000,shade: [0.3, '#000']});
|
||||
return;
|
||||
}
|
||||
|
||||
var domain = $('textarea[name="load_domain"]').val().replace('http://','').replace('https://','').split("\n");
|
||||
if (domain[0] == ''){
|
||||
layer.msg('域名不能为空!',{icon:0,time:2000,shade: [0.3, '#000']});
|
||||
return;
|
||||
}
|
||||
|
||||
var domainlist = '';
|
||||
for(var i=1; i<domain.length; i++){
|
||||
domainlist += '"'+domain[i]+'",';
|
||||
}
|
||||
domain ='{"domain":"'+domain[0]+'","domainlist":['+domainlist+'],"count":'+domain.length+'}';//拼接json
|
||||
data['domain'] = domain;
|
||||
data['upstream_name'] = upstream_name;
|
||||
|
||||
data['node_algo'] = $('select[name="node_algo"]').val();
|
||||
|
||||
data['node_health_check'] = 'fail';
|
||||
if ($('input[name="node_health_check"]').prop('checked')){
|
||||
data['node_health_check'] = 'ok';
|
||||
}
|
||||
|
||||
var node_list = [];
|
||||
$('#nodecon tr').each(function(){
|
||||
|
||||
var ip = $(this).find('td').eq(0).text();
|
||||
var port = $(this).find('td').eq(1).text();
|
||||
|
||||
if (port == ''){return;}
|
||||
|
||||
var path = $(this).find('td').eq(2).text();
|
||||
var state = $(this).find('select[name="state"]').val();
|
||||
var weight = $(this).find('input[name="weight"]').val();
|
||||
var max_fails = $(this).find('input[name="max_fails"]').val();
|
||||
var fail_timeout = $(this).find('input[name="fail_timeout"]').val();
|
||||
|
||||
var tmp = {
|
||||
ip:ip,
|
||||
port:port,
|
||||
path:path,
|
||||
state:state,
|
||||
weight:weight,
|
||||
max_fails:max_fails,
|
||||
fail_timeout:fail_timeout,
|
||||
}
|
||||
node_list.push(tmp);
|
||||
});
|
||||
data['node_list'] = node_list;
|
||||
ooPostCallbak('add_load_balance', data, function(rdata){
|
||||
var rdata = $.parseJSON(rdata.data);
|
||||
showMsg(rdata.msg, function(){
|
||||
layer.close(index);
|
||||
loadBalanceListRender();
|
||||
},{ icon: rdata.status ? 1 : 2 }, 2000);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function editBalance(data, row){
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['750px','400px'],
|
||||
title: '编辑负载',
|
||||
closeBtn: 1,
|
||||
shift: 5,
|
||||
shadeClose: true,
|
||||
btn:['提交','关闭'],
|
||||
content: "<form class='bt-form pd20'>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>负载名称</span>\
|
||||
<div class='info-r'><input name='upstream_name' class='bt-input-text mr5' type='text' style='width:95%;background: rgb(238, 238, 238);' value='' readonly></div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>节点调度</span>\
|
||||
<div class='info-r'>\
|
||||
<select name='node_algo'>\
|
||||
<option value='polling'>轮询[默认]</option>\
|
||||
<option value='ip_hash'>ip_hash</option>\
|
||||
<option value='fair'>fair</option>\
|
||||
<option value='url_hash'>url_hash</option>\
|
||||
<option value='least_conn'>least_conn</option>\
|
||||
</select>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>节点健康检查</span>\
|
||||
<div class='info-r'>\
|
||||
<input type='checkbox' name='node_health_check'>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class='line'>\
|
||||
<span class='tname'>节点</span>\
|
||||
<div class='info-r'>\
|
||||
<div class='table-con divtable' style='max-height:120px;overflow:auto; margin-bottom:8px;width:95%'>\
|
||||
<table class='table table-hover' id='fixTable3'>\
|
||||
<thead>\
|
||||
<tr>\
|
||||
<th width='120'>IP地址</th>\
|
||||
<th width='60'>端口</th>\
|
||||
<th width='120'>验证路径</th>\
|
||||
<th width='60'>状态</th>\
|
||||
<th width='60'>权重</th>\
|
||||
<th width='60'>阀值</th>\
|
||||
<th width='120'>恢复时间</th>\
|
||||
<th width='80' class='text-right'>操作</th>\
|
||||
</tr>\
|
||||
</thead>\
|
||||
<tbody id='nodecon'>\
|
||||
<tr class='nulltr'>\
|
||||
<td colspan='8' align='center'>当前节点为空,请至少添加一个普通节点</td>\
|
||||
</tr>\
|
||||
</tbody>\
|
||||
</table>\
|
||||
</div>\
|
||||
<span class='btn btn-success btn-sm add_node' style='vertical-align:0'>添加节点</span>\
|
||||
</div>\
|
||||
</div>\
|
||||
</form>",
|
||||
success:function(){
|
||||
$('input[name="upstream_name"]').val(data['upstream_name']);
|
||||
$('select[name="node_algo"]').val(data['node_algo']);
|
||||
|
||||
$('input[name="node_health_check"]').prop('checked',false);
|
||||
if (data['node_health_check'] == 'ok'){
|
||||
$('input[name="node_health_check"]').prop('checked',true);
|
||||
}
|
||||
|
||||
var node_list = data['node_list'];
|
||||
if (node_list.length>0){
|
||||
$('#nodecon .nulltr').hide();
|
||||
}
|
||||
|
||||
var state_option_list = {
|
||||
'1':'参与者',
|
||||
'2':'备份',
|
||||
'0':'停用',
|
||||
}
|
||||
|
||||
for (var n in node_list) {
|
||||
|
||||
var tbody = '<tr>';
|
||||
tbody +='<td>'+node_list[n]['ip']+'</td>';
|
||||
tbody +='<td>'+node_list[n]['port']+'</td>';
|
||||
tbody +='<td>'+node_list[n]['path']+'</td>';
|
||||
|
||||
tbody +="<td><select name='state'>";
|
||||
|
||||
for (i in state_option_list) {
|
||||
if (i == node_list[n]['state']){
|
||||
tbody +="<option value='"+i+"' selected>"+state_option_list[i]+"</option>";
|
||||
} else{
|
||||
tbody +="<option value='"+i+"'>"+state_option_list[i]+"</option>";
|
||||
}
|
||||
}
|
||||
tbody +="</select></td>";
|
||||
|
||||
tbody +='<td><input type="number" name="weight" value="'+node_list[n]['weight']+'" style="width:50px"></td>';
|
||||
tbody +='<td><input type="number" name="max_fails" value="'+node_list[n]['max_fails']+'" style="width:50px"></td>';
|
||||
tbody +='<td><input type="number" name="fail_timeout" value="'+node_list[n]['fail_timeout']+'" style="width:50px"></td>';
|
||||
tbody +='<td class="text-right" width="50"><a class="btlink minus delete">删除</a></td>';
|
||||
tbody += '</tr>';
|
||||
$('#nodecon').append(tbody);
|
||||
}
|
||||
|
||||
$('#nodecon .delete').click(function(){
|
||||
$(this).parent().parent().remove();
|
||||
if ($('#nodecon tr').length == 1 ){
|
||||
$('#nodecon .nulltr').show();
|
||||
}
|
||||
});
|
||||
|
||||
$('.add_node').click(function(){
|
||||
addNode();
|
||||
});
|
||||
},
|
||||
yes:function(index) {
|
||||
var data = {};
|
||||
|
||||
data['node_algo'] = $('select[name="node_algo"]').val();
|
||||
data['node_health_check'] = 'fail';
|
||||
if ($('input[name="node_health_check"]').prop('checked')){
|
||||
data['node_health_check'] = 'ok';
|
||||
}
|
||||
|
||||
var node_list = [];
|
||||
$('#nodecon tr').each(function(){
|
||||
|
||||
var ip = $(this).find('td').eq(0).text();
|
||||
var port = $(this).find('td').eq(1).text();
|
||||
|
||||
if (port == ''){return;}
|
||||
|
||||
var path = $(this).find('td').eq(2).text();
|
||||
var state = $(this).find('select[name="state"]').val();
|
||||
var weight = $(this).find('input[name="weight"]').val();
|
||||
var max_fails = $(this).find('input[name="max_fails"]').val();
|
||||
var fail_timeout = $(this).find('input[name="fail_timeout"]').val();
|
||||
|
||||
var tmp = {
|
||||
ip:ip,
|
||||
port:port,
|
||||
path:path,
|
||||
state:state,
|
||||
weight:weight,
|
||||
max_fails:max_fails,
|
||||
fail_timeout:fail_timeout,
|
||||
}
|
||||
node_list.push(tmp);
|
||||
});
|
||||
data['node_list'] = node_list;
|
||||
data['row'] = row;
|
||||
ooPostCallbak('edit_load_balance', data, function(rdata){
|
||||
var rdata = $.parseJSON(rdata.data);
|
||||
showMsg(rdata.msg, function(){
|
||||
layer.close(index);
|
||||
loadBalanceListRender();
|
||||
},{ icon: rdata.status ? 1 : 2 }, 2000);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadBalanceListRender(){
|
||||
ooPost('load_balance_list', {}, function(rdata){
|
||||
var rdata = $.parseJSON(rdata.data);
|
||||
var alist = rdata.data;
|
||||
|
||||
var tbody = '';
|
||||
for (var i = 0; i < alist.length; i++) {
|
||||
tbody += '<tr>';
|
||||
tbody += '<td>'+alist[i]['domain']+'</td>';
|
||||
tbody += '<td>'+alist[i]['upstream_name']+'</td>';
|
||||
tbody += '<td>'+alist[i]['node_list'].length+'</td>';
|
||||
tbody += '<td><a class="btlink log_look" data-row="'+i+'">查看</a></td>';
|
||||
tbody += '<td><a class="btlink health_status" data-row="'+i+'">查看</a></td>';
|
||||
tbody += '<td style="text-align: right;"><a class="btlink edit" data-row="'+i+'">修改</a> | <a class="btlink delete" data-row="'+i+'">删除</a></td>';
|
||||
tbody += '</tr>';
|
||||
}
|
||||
|
||||
$('#nodeTable').html(tbody);
|
||||
$('.nodeTablePage .Pcount').text('共'+alist.length+'条');
|
||||
$('#nodeTable .edit').click(function(){
|
||||
var row = $(this).data('row');
|
||||
editBalance(alist[row],row);
|
||||
});
|
||||
|
||||
$('#nodeTable .log_look').click(function(){
|
||||
var row = $(this).data('row');
|
||||
var args = {'domain':alist[row]['domain']};
|
||||
pluginRollingLogs('op_load_balance','','get_logs',JSON.stringify(args),20);
|
||||
});
|
||||
|
||||
$('#nodeTable .health_status').click(function(){
|
||||
var row = $(this).data('row');
|
||||
ooPost('get_health_status', {row:row}, function(rdata){
|
||||
var rdata = $.parseJSON(rdata.data);
|
||||
|
||||
var tval = '';
|
||||
for (var i = 0; i < rdata.data.length; i++) {
|
||||
tval += '<tr>';
|
||||
tval += '<td>'+rdata.data[i]['name']+'</td>';
|
||||
|
||||
if (typeof(rdata.data[i]['down']) != 'undefined' && rdata.data[i]['down']){
|
||||
tval += '<td><span style="color:red;">不正常</span></td>';
|
||||
} else{
|
||||
tval += '<td><span class="btlink">正常</span></td>';
|
||||
}
|
||||
tval += '</tr>';
|
||||
}
|
||||
|
||||
var tbody = "<div class='bt-form pd20'>\
|
||||
<div>\
|
||||
<div id='gitea_table' class='divtable' style='margin-top:5px;'>\
|
||||
<table class='table table-hover'>\
|
||||
<thead>\
|
||||
<tr>\
|
||||
<th width='120'>地址</th>\
|
||||
<th width='60'>状态</th>\
|
||||
</tr>\
|
||||
</thead>\
|
||||
<tbody>"+tval+"</tbody>\
|
||||
</table>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>";
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['500px','300px'],
|
||||
title: '节点状态',
|
||||
closeBtn: 1,
|
||||
shift: 5,
|
||||
shadeClose: true,
|
||||
btn:['提交','关闭'],
|
||||
content:tbody,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('#nodeTable .delete').click(function(){
|
||||
var row = $(this).data('row');
|
||||
ooPost('load_balance_delete', {row:row}, function(rdata){
|
||||
var rdata = $.parseJSON(rdata.data);
|
||||
showMsg(rdata.msg, function(){
|
||||
loadBalanceListRender();
|
||||
},{ icon: rdata.status ? 1 : 2 }, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function loadBalanceList() {
|
||||
var body = '<div class="bt-box active" style="display: block;">\
|
||||
<div class="mb10">\
|
||||
<button class="btn btn-success btn-sm" data-index="0" onclick="addBalance()">添加负载</button>\
|
||||
<div class="divtable mt10">\
|
||||
<table class="table table-hover">\
|
||||
<thead>\
|
||||
<tr>\
|
||||
<th>网站</th>\
|
||||
<th>负载名称</th>\
|
||||
<th>节点</th>\
|
||||
<th>日志</th>\
|
||||
<th>状态</th>\
|
||||
<th width="100" style="text-align: right;">操作</th>\
|
||||
</tr>\
|
||||
</thead>\
|
||||
<tbody id="nodeTable"></tbody>\
|
||||
</table>\
|
||||
<div class="nodeTablePage page" data-type="nodePage">\
|
||||
<div><span class="Pcount">共0条</span></div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>';
|
||||
$(".soft-man-con").html(body);
|
||||
loadBalanceListRender();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
local hc = require "resty.upstream.healthcheck"
|
||||
local ok, err = hc.spawn_checker {
|
||||
shm = "healthcheck",
|
||||
type = "http",
|
||||
upstream = "{$UPSTREAM_NAME}",
|
||||
http_req = "GET / HTTP/1.0\r\nHost: {$UPSTREAM_NAME}\r\n\r\n",
|
||||
interval = 2000,
|
||||
timeout = 6000,
|
||||
fall = 3,
|
||||
rise = 2,
|
||||
valid_statuses = {200, 302},
|
||||
concurrency = 20,
|
||||
}
|
||||
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "=======> load balance health checker error: ", err)
|
||||
return
|
||||
end
|
||||
@@ -1,19 +0,0 @@
|
||||
# plugins_system_safe
|
||||
|
||||
系统加固插件
|
||||
|
||||
# DEBUG
|
||||
```
|
||||
|
||||
https://code_hosting_007.midoks.me/midoks/plugins_system_safe
|
||||
|
||||
ln -s /www/git/midoks/plugins_system_safe /www/server/mdserver-web/plugins/system_safe
|
||||
|
||||
ln -s /www/wwwroot/midoks/plugins_system_safe /www/server/mdserver-web/plugins/system_safe
|
||||
|
||||
|
||||
python3 /www/server/mdserver-web/plugins/system_safe/index.py bg_start
|
||||
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"open": true,
|
||||
"excludePath": [ "cache", "threadcache", "log", "logs", "config", "runtime", "temp","tmp","caches","tmps"],
|
||||
"protectExt": [ "php", "html", "htm", "shtml", "tpl", "js", "css", "jsp", "do" ]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 556 B |
Executable
+1163
File diff suppressed because it is too large
Load Diff
Executable
+628
@@ -0,0 +1,628 @@
|
||||
# coding:utf-8
|
||||
|
||||
import sys
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import re
|
||||
import psutil
|
||||
from datetime import datetime
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
app_debug = False
|
||||
if mw.isAppleSystem():
|
||||
app_debug = True
|
||||
|
||||
|
||||
class App:
|
||||
|
||||
__total = 'total.json'
|
||||
__sites = []
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def getPluginName(self):
|
||||
return 'tamper_proof_py'
|
||||
|
||||
def getPluginDir(self):
|
||||
return mw.getPluginDir() + '/' + self.getPluginName()
|
||||
|
||||
def getServerDir(self):
|
||||
return mw.getServerDir() + '/' + self.getPluginName()
|
||||
|
||||
def getInitDFile(self):
|
||||
if app_debug:
|
||||
return '/tmp/' + self.getPluginName()
|
||||
return '/etc/init.d/' + self.getPluginName()
|
||||
|
||||
def getInitDTpl(self):
|
||||
path = self.getPluginDir() + "/init.d/" + self.getPluginName() + ".tpl"
|
||||
return path
|
||||
|
||||
def getArgs(self):
|
||||
args = sys.argv[2:]
|
||||
tmp = {}
|
||||
args_len = len(args)
|
||||
|
||||
if args_len == 1:
|
||||
t = args[0].strip('{').strip('}')
|
||||
if t.strip() == '':
|
||||
tmp = []
|
||||
else:
|
||||
t = t.split(':')
|
||||
tmp[t[0]] = t[1]
|
||||
elif args_len > 1:
|
||||
for i in range(len(args)):
|
||||
t = args[i].split(':')
|
||||
tmp[t[0]] = t[1]
|
||||
return tmp
|
||||
|
||||
def checkArgs(self, data, ck=[]):
|
||||
for i in range(len(ck)):
|
||||
if not ck[i] in data:
|
||||
return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!'))
|
||||
return (True, mw.returnJson(True, 'ok'))
|
||||
|
||||
def getTotal(self, siteName=None, day=None):
|
||||
defaultTotal = {"total": 0, "delete": 0,
|
||||
"create": 0, "modify": 0, "move": 0}
|
||||
if siteName:
|
||||
total = {}
|
||||
total_path = self.getServerDir() + '/sites/' + siteName + '/' + self.__total
|
||||
if not os.path.exists(total_path):
|
||||
total['site'] = defaultTotal
|
||||
else:
|
||||
total_data = mw.readFile(total_path)
|
||||
if total_data['site']:
|
||||
total['site'] = json.loads(total_data['site'])
|
||||
else:
|
||||
total['site'] = defaultTotal
|
||||
|
||||
if not day:
|
||||
day = time.strftime("%Y-%m-%d", time.localtime())
|
||||
total_day_path = self.getServerDir() + '/sites/' + siteName + '/day/total.json'
|
||||
if not os.path.exists(total_day_path):
|
||||
total['day'] = defaultTotal
|
||||
else:
|
||||
total['day'] = mw.readFile(total_day_path)
|
||||
if total['day']:
|
||||
total['day'] = json.loads(total['day'])
|
||||
else:
|
||||
total['day'] = defaultTotal
|
||||
else:
|
||||
filename = self.getServerDir() + '/sites/' + self.__total
|
||||
if os.path.exists(filename):
|
||||
total = json.loads(mw.readFile(filename))
|
||||
else:
|
||||
total = defaultTotal
|
||||
return total
|
||||
|
||||
def getSites(self):
|
||||
sites_path = self.getServerDir() + '/sites.json'
|
||||
t = mw.readFile(sites_path)
|
||||
if not os.path.exists(sites_path) or not t:
|
||||
mw.writeFile(sites_path, '[]')
|
||||
data = json.loads(mw.readFile(sites_path))
|
||||
|
||||
is_write = False
|
||||
rm_keys = ['lock', 'bak_open']
|
||||
for i in data:
|
||||
i_keys = i.keys()
|
||||
if not 'open' in i_keys:
|
||||
i['open'] = False
|
||||
for o in rm_keys:
|
||||
if o in i_keys:
|
||||
if i[o]:
|
||||
i['open'] = True
|
||||
i.pop(o)
|
||||
is_write = True
|
||||
if is_write:
|
||||
mw.writeFile(sites_path, json.dumps(data))
|
||||
|
||||
self.__sites = data
|
||||
return data
|
||||
|
||||
def writeSites(self, data):
|
||||
mw.writeFile(self.getServerDir() + '/sites.json', json.dumps(data))
|
||||
# mw.ExecShell('/etc/init.d/bt_tamper_proof reload')
|
||||
|
||||
def __getFind(self, siteName):
|
||||
data = self.getSites()
|
||||
for siteInfo in data:
|
||||
if siteName == siteInfo['siteName']:
|
||||
return siteInfo
|
||||
return None
|
||||
|
||||
def writeLog(self, log):
|
||||
mw.writeLog('防篡改程序', log)
|
||||
|
||||
def saveSiteConfig(self, siteInfo):
|
||||
data = self.getSites()
|
||||
for i in range(len(data)):
|
||||
if data[i]['siteName'] != siteInfo['siteName']:
|
||||
continue
|
||||
data[i] = siteInfo
|
||||
break
|
||||
self.writeSites(data)
|
||||
|
||||
def syncSites(self):
|
||||
data = self.getSites()
|
||||
sites = mw.M('sites').field('name,path').select()
|
||||
|
||||
config_path = self.getPluginDir() + '/conf/config.json'
|
||||
config = json.loads(mw.readFile(config_path))
|
||||
names = []
|
||||
n = 0
|
||||
|
||||
# print(config)
|
||||
for siteTmp in sites:
|
||||
names.append(siteTmp['name'])
|
||||
siteInfo = self.__getFind(siteTmp['name'])
|
||||
if siteInfo:
|
||||
if siteInfo['path'] != siteTmp['path']:
|
||||
siteInfo['path'] = siteTmp['path']
|
||||
self.saveSiteConfig(siteInfo)
|
||||
data = self.getSites()
|
||||
continue
|
||||
siteInfo = {}
|
||||
siteInfo['siteName'] = siteTmp['name']
|
||||
siteInfo['path'] = siteTmp['path']
|
||||
siteInfo['open'] = False
|
||||
siteInfo['excludePath'] = config['excludePath']
|
||||
siteInfo['protectExt'] = config['protectExt']
|
||||
data.append(siteInfo)
|
||||
n += 1
|
||||
|
||||
newData = []
|
||||
for siteInfoTmp in data:
|
||||
if siteInfoTmp['siteName'] in names:
|
||||
newData.append(siteInfoTmp)
|
||||
else:
|
||||
mw.execShell("rm -rf " + self.getServerDir() +
|
||||
'/sites/' + siteInfoTmp['siteName'])
|
||||
n += 1
|
||||
if n > 0:
|
||||
self.writeSites(newData)
|
||||
self.__sites = None
|
||||
|
||||
def initDreplace(self):
|
||||
file_tpl = self.getInitDTpl()
|
||||
service_path = self.getServerDir()
|
||||
|
||||
initD_path = service_path + '/init.d'
|
||||
if not os.path.exists(initD_path):
|
||||
os.mkdir(initD_path)
|
||||
|
||||
# init.d
|
||||
file_bin = initD_path + '/' + self.getPluginName()
|
||||
if not os.path.exists(file_bin):
|
||||
# initd replace
|
||||
content = mw.readFile(file_tpl)
|
||||
content = content.replace('{$SERVER_PATH}', service_path)
|
||||
mw.writeFile(file_bin, content)
|
||||
mw.execShell('chmod +x ' + file_bin)
|
||||
|
||||
# systemd
|
||||
# /usr/lib/systemd/system
|
||||
systemDir = mw.systemdCfgDir()
|
||||
systemService = systemDir + '/tamper_proof_py.service'
|
||||
systemServiceTpl = self.getPluginDir() + '/init.d/tamper_proof_py.service.tpl'
|
||||
if os.path.exists(systemDir) and not os.path.exists(systemService):
|
||||
se_content = mw.readFile(systemServiceTpl)
|
||||
se_content = se_content.replace('{$SERVER_PATH}', service_path)
|
||||
mw.writeFile(systemService, se_content)
|
||||
mw.execShell('systemctl daemon-reload')
|
||||
|
||||
return file_bin
|
||||
|
||||
def getDays(self, path):
|
||||
days = []
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
for dirname in os.listdir(path):
|
||||
if dirname == '..' or dirname == '.' or dirname == 'total.json':
|
||||
continue
|
||||
if not os.path.isdir(path + '/' + dirname):
|
||||
continue
|
||||
days.append(dirname)
|
||||
days = sorted(days, reverse=True)
|
||||
return days
|
||||
|
||||
def status(self):
|
||||
'''
|
||||
状态
|
||||
'''
|
||||
initd_file = self.getServerDir() + '/init.d/' + self.getPluginName()
|
||||
if not os.path.exists(initd_file):
|
||||
return 'stop'
|
||||
cmd = initd_file + ' status|grep already'
|
||||
data = mw.execShell(cmd)
|
||||
if data[0] != '':
|
||||
return 'start'
|
||||
return 'stop'
|
||||
|
||||
def tpOp(self, method):
|
||||
file = self.initDreplace()
|
||||
if not mw.isAppleSystem():
|
||||
cmd = 'systemctl ' + method + ' ' + self.getPluginName()
|
||||
data = mw.execShell(cmd)
|
||||
if data[1] == '':
|
||||
return mw.returnJson(True, '操作成功')
|
||||
return mw.returnJson(False, '操作失败')
|
||||
|
||||
cmd = file + ' ' + method
|
||||
data = mw.execShell(cmd)
|
||||
if data[1] == '':
|
||||
return mw.returnJson(True, '操作成功')
|
||||
return mw.returnJson(False, '操作失败')
|
||||
|
||||
def start(self):
|
||||
return self.tpOp('start')
|
||||
|
||||
def restart(self):
|
||||
return self.tpOp('restart')
|
||||
|
||||
def service_admin(self):
|
||||
if mw.isAppleSystem():
|
||||
return mw.returnJson(False, '仅支持Linux!')
|
||||
|
||||
args = self.getArgs()
|
||||
check = self.checkArgs(args, ['serviceStatus'])
|
||||
if not check[0]:
|
||||
return check[1]
|
||||
|
||||
method = args['serviceStatus']
|
||||
return self.tpOp(method)
|
||||
|
||||
def initd_status(self):
|
||||
if mw.isAppleSystem():
|
||||
return "Apple Computer does not support"
|
||||
shell_cmd = 'systemctl status %s | grep loaded | grep "enabled;"' % (
|
||||
self.getPluginName())
|
||||
data = mw.execShell(shell_cmd)
|
||||
if data[0] == '':
|
||||
return 'fail'
|
||||
return 'ok'
|
||||
|
||||
def initd_install(self):
|
||||
if mw.isAppleSystem():
|
||||
return "Apple Computer does not support"
|
||||
|
||||
mw.execShell('systemctl enable ' + self.getPluginName())
|
||||
return 'ok'
|
||||
|
||||
def initd_uninstall(self):
|
||||
if mw.isAppleSystem():
|
||||
return "Apple Computer does not support"
|
||||
|
||||
mw.execShell('systemctl disable ' + self.getPluginName())
|
||||
return 'ok'
|
||||
|
||||
def set_site_status(self):
|
||||
args = self.getArgs()
|
||||
check = self.checkArgs(args, ['siteName'])
|
||||
if not check[0]:
|
||||
return check[1]
|
||||
|
||||
siteName = args['siteName']
|
||||
siteInfo = self.__getFind(siteName)
|
||||
if not siteInfo:
|
||||
return mw.returnJson(False, '指定站点不存在!')
|
||||
try:
|
||||
siteInfo['open'] = not siteInfo['open']
|
||||
except:
|
||||
siteInfo['open'] = not siteInfo['open']
|
||||
|
||||
m_logs = {True: '开启', False: '关闭'}
|
||||
self.writeLog('%s站点[%s]防篡改保护' %
|
||||
(m_logs[siteInfo['open']], siteInfo['siteName']))
|
||||
self.siteReload(siteInfo)
|
||||
self.saveSiteConfig(siteInfo)
|
||||
self.restart()
|
||||
return mw.returnJson(True, '设置成功!')
|
||||
|
||||
def get_run_logs(self):
|
||||
log_file = self.getServerDir() + '/service.log'
|
||||
return mw.returnJson(True, mw.getLastLine(log_file, 200))
|
||||
|
||||
# 取文件指定尾行数
|
||||
def getNumLines(self, path, num, p=1):
|
||||
pyVersion = sys.version_info[0]
|
||||
try:
|
||||
import cgi
|
||||
if not os.path.exists(path):
|
||||
return ""
|
||||
start_line = (p - 1) * num
|
||||
count = start_line + num
|
||||
fp = open(path, 'rb')
|
||||
buf = ""
|
||||
fp.seek(-1, 2)
|
||||
if fp.read(1) == "\n":
|
||||
fp.seek(-1, 2)
|
||||
data = []
|
||||
b = True
|
||||
n = 0
|
||||
for i in range(count):
|
||||
while True:
|
||||
newline_pos = str.rfind(str(buf), "\n")
|
||||
pos = fp.tell()
|
||||
if newline_pos != -1:
|
||||
if n >= start_line:
|
||||
line = buf[newline_pos + 1:]
|
||||
try:
|
||||
data.append(json.loads(cgi.escape(line)))
|
||||
except:
|
||||
pass
|
||||
buf = buf[:newline_pos]
|
||||
n += 1
|
||||
break
|
||||
else:
|
||||
if pos == 0:
|
||||
b = False
|
||||
break
|
||||
to_read = min(4096, pos)
|
||||
fp.seek(-to_read, 1)
|
||||
t_buf = fp.read(to_read)
|
||||
if pyVersion == 3:
|
||||
if type(t_buf) == bytes:
|
||||
t_buf = t_buf.decode('utf-8')
|
||||
buf = t_buf + buf
|
||||
fp.seek(-to_read, 1)
|
||||
if pos - to_read == 0:
|
||||
buf = "\n" + buf
|
||||
if not b:
|
||||
break
|
||||
fp.close()
|
||||
except:
|
||||
return []
|
||||
if len(data) >= 2000:
|
||||
arr = []
|
||||
for d in data:
|
||||
arr.insert(0, json.dumps(d))
|
||||
mw.writeFile(path, "\n".join(arr))
|
||||
return data
|
||||
|
||||
def get_safe_logs(self):
|
||||
|
||||
args = self.getArgs()
|
||||
check = self.checkArgs(args, ['siteName'])
|
||||
if not check[0]:
|
||||
return check[1]
|
||||
|
||||
siteName = args['siteName']
|
||||
|
||||
data = {}
|
||||
path = self.getPluginDir() + '/sites/' + siteName + '/day'
|
||||
data['days'] = self.getDays(path)
|
||||
|
||||
if not data['days']:
|
||||
data['logs'] = []
|
||||
else:
|
||||
p = 1
|
||||
if hasattr(args, 'p'):
|
||||
p = args['p']
|
||||
|
||||
day = data['days'][0]
|
||||
if hasattr(args, 'day'):
|
||||
day = args['day']
|
||||
data['get_day'] = day
|
||||
logs_path = path + '/' + day + '/logs.json'
|
||||
data['logs'] = self.getNumLines(logs_path, 2000, int(p))
|
||||
return mw.returnJson(True, 'ok', data)
|
||||
|
||||
def get_site_find(self):
|
||||
args = self.getArgs()
|
||||
check = self.checkArgs(args, ['siteName'])
|
||||
if not check[0]:
|
||||
return check[1]
|
||||
|
||||
siteName = args['siteName']
|
||||
data = self.__getFind(siteName)
|
||||
return mw.returnJson(True, 'ok', data)
|
||||
|
||||
def siteReload(self, siteInfo):
|
||||
cmd = "python3 {} {}".format(
|
||||
mw.getPluginDir() + '/tamper_proof_service.py unlock', siteInfo['path'])
|
||||
mw.execShell(cmd)
|
||||
tip_file = mw.getServerDir() + '/tips/' + siteInfo['siteName'] + '.pl'
|
||||
if os.path.exists(tip_file):
|
||||
os.remove(tip_file)
|
||||
|
||||
def remove_protect_ext(self):
|
||||
args = self.getArgs()
|
||||
check = self.checkArgs(args, ['siteName', 'protectExt'])
|
||||
if not check[0]:
|
||||
return check[1]
|
||||
|
||||
siteName = args['siteName']
|
||||
protectExt = args['protectExt'].strip()
|
||||
|
||||
siteInfo = self.__getFind(siteName)
|
||||
|
||||
if not siteInfo:
|
||||
return mw.returnJson(False, '指定站点不存在!')
|
||||
if not protectExt:
|
||||
return mw.returnJson(False, '被删除的保护列表不能为空')
|
||||
|
||||
for protectExt in protectExt.split(','):
|
||||
if not protectExt in siteInfo['protectExt']:
|
||||
continue
|
||||
siteInfo['protectExt'].remove(protectExt)
|
||||
self.writeLog('站点[%s]从受保护列表中删除[.%s]' %
|
||||
(siteInfo['siteName'], protectExt))
|
||||
self.siteReload(siteInfo)
|
||||
self.saveSiteConfig(siteInfo)
|
||||
return mw.returnJson(True, '删除成功!')
|
||||
|
||||
def add_protect_ext(self):
|
||||
args = self.getArgs()
|
||||
check = self.checkArgs(args, ['siteName', 'protectExt'])
|
||||
if not check[0]:
|
||||
return check[1]
|
||||
|
||||
siteName = args['siteName']
|
||||
protectExt = args['protectExt'].strip()
|
||||
|
||||
siteInfo = self.__getFind(siteName)
|
||||
if not siteInfo:
|
||||
return mw.returnJson(False, '指定站点不存在!')
|
||||
protectExt = protectExt.lower()
|
||||
for protectExt in protectExt.split("\n"):
|
||||
if protectExt[0] == '/':
|
||||
if os.path.isdir(protectExt):
|
||||
continue
|
||||
if protectExt in siteInfo['protectExt']:
|
||||
continue
|
||||
siteInfo['protectExt'].insert(0, protectExt)
|
||||
self.writeLog('站点[%s]添加文件类型或文件名[.%s]到受保护列表' %
|
||||
(siteInfo['siteName'], protectExt))
|
||||
self.siteReload(siteInfo)
|
||||
self.saveSiteConfig(siteInfo)
|
||||
return mw.returnJson(True, '添加成功!')
|
||||
|
||||
def add_excloud(self):
|
||||
args = self.getArgs()
|
||||
check = self.checkArgs(args, ['siteName', 'excludePath'])
|
||||
if not check[0]:
|
||||
return check[1]
|
||||
|
||||
siteName = args['siteName']
|
||||
excludePath = args['excludePath'].strip()
|
||||
siteInfo = self.__getFind(siteName)
|
||||
if not siteInfo:
|
||||
return mw.returnJson(False, '指定站点不存在!')
|
||||
|
||||
if not excludePath:
|
||||
return mw.returnJson(False, '排除内容不能为空')
|
||||
|
||||
for excludePath in excludePath.split('\n'):
|
||||
if not excludePath:
|
||||
continue
|
||||
if excludePath.find('/') != -1:
|
||||
if not os.path.exists(excludePath):
|
||||
continue
|
||||
excludePath = excludePath.lower()
|
||||
if excludePath[-1] == '/':
|
||||
excludePath = excludePath[:-1]
|
||||
if excludePath in siteInfo['excludePath']:
|
||||
continue
|
||||
siteInfo['excludePath'].insert(0, excludePath)
|
||||
self.writeLog('站点[%s]添加排除目录名[%s]到排除列表' %
|
||||
(siteInfo['siteName'], excludePath))
|
||||
|
||||
self.siteReload(siteInfo)
|
||||
self.saveSiteConfig(siteInfo)
|
||||
return mw.returnJson(True, '添加成功!')
|
||||
|
||||
def remove_excloud(self):
|
||||
args = self.getArgs()
|
||||
check = self.checkArgs(args, ['siteName', 'excludePath'])
|
||||
if not check[0]:
|
||||
return check[1]
|
||||
|
||||
siteName = args['siteName']
|
||||
siteInfo = self.__getFind(siteName)
|
||||
excludePath = args['excludePath'].strip()
|
||||
if excludePath == '':
|
||||
return mw.returnJson(False, '排除文件或目录不能为空')
|
||||
if not siteInfo:
|
||||
return mw.returnJson(False, '指定站点不存在!')
|
||||
|
||||
for excludePath in excludePath.split(','):
|
||||
if not excludePath:
|
||||
continue
|
||||
if not excludePath in siteInfo['excludePath']:
|
||||
continue
|
||||
siteInfo['excludePath'].remove(excludePath)
|
||||
self.writeLog('站点[%s]从排除列表中删除目录名[%s]' %
|
||||
(siteInfo['siteName'], excludePath))
|
||||
self.siteReload(siteInfo)
|
||||
self.saveSiteConfig(siteInfo)
|
||||
return mw.returnJson(True, '删除成功!')
|
||||
|
||||
def sim_test(self):
|
||||
args = self.getArgs()
|
||||
check = self.checkArgs(args, ['path'])
|
||||
if not check[0]:
|
||||
return check[1]
|
||||
|
||||
path = args['path'].strip()
|
||||
if not os.path.exists(path):
|
||||
return mw.returnJson(False, "此目录不存在")
|
||||
|
||||
# 判断是否安装php
|
||||
import site_api
|
||||
php_version = site_api.site_api().getPhpVersion()
|
||||
if not php_version:
|
||||
return mw.returnJson(False, "未安装PHP测试失败")
|
||||
|
||||
php_path = '/www/server/php/' + php_version[1]['version'] + '/bin/php'
|
||||
php_name = path + "/" + str(int(time.time())) + ".php"
|
||||
if os.path.exists(php_name):
|
||||
mw.execShell("rm -rf %s" % php_name)
|
||||
# 写入
|
||||
cmd = php_path + \
|
||||
" -r \"file_put_contents('{}','{}');\"".format(php_name, php_name)
|
||||
mw.execShell(cmd)
|
||||
time.sleep(0.5)
|
||||
if os.path.exists(php_name):
|
||||
if os.path.exists(php_name):
|
||||
mw.execShell("rm -rf %s" % php_name)
|
||||
return mw.returnJson(False, "拦截失败,可能未开启防篡改")
|
||||
return mw.returnJson(True, "拦截成功")
|
||||
|
||||
def set_site_status_all(self):
|
||||
args = self.getArgs()
|
||||
check = self.checkArgs(args, ['siteNames', 'siteState'])
|
||||
if not check[0]:
|
||||
return check[1]
|
||||
|
||||
sites = self.getSites()
|
||||
siteState = True if args['siteState'] == '1' else False
|
||||
siteNames = json.loads(args['siteNames'])
|
||||
m_logs = {True: '开启', False: '关闭'}
|
||||
for i in range(len(sites)):
|
||||
if sites[i]['siteName'] in siteNames:
|
||||
sites[i]['open'] = siteState
|
||||
self.writeLog('%s站点[%s]防篡改保护' %
|
||||
(m_logs[siteState], sites[i]['siteName']))
|
||||
self.writeSites(sites)
|
||||
return mw.returnJson(True, '批量设置成功')
|
||||
|
||||
def get_index(self):
|
||||
self.syncSites()
|
||||
args = self.getArgs()
|
||||
day = None
|
||||
if 'day' in args:
|
||||
day = args['day']
|
||||
|
||||
ser_status = self.status()
|
||||
ser_status_bool = False
|
||||
if ser_status == 'start':
|
||||
ser_status_bool = True
|
||||
data = {}
|
||||
data['open'] = ser_status_bool
|
||||
data['total'] = self.getTotal()
|
||||
data['sites'] = self.getSites()
|
||||
for i in range(len(data['sites'])):
|
||||
data['sites'][i]['total'] = self.getTotal(
|
||||
data['sites'][i]['siteName'], day)
|
||||
return mw.returnJson(True, 'ok', data)
|
||||
|
||||
def get_speed(self):
|
||||
print("12")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
func = sys.argv[1]
|
||||
classApp = App()
|
||||
try:
|
||||
data = eval("classApp." + func + "()")
|
||||
print(data)
|
||||
except Exception as e:
|
||||
print(mw.getTracebackInfo())
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"title": "网站防篡改程序[PY]",
|
||||
"tip": "lib",
|
||||
"name": "tamper_proof_py",
|
||||
"type": "soft",
|
||||
"ps": "事件型防篡改程序,可有效保护网站重要文件不被木马篡改",
|
||||
"versions": "1.0",
|
||||
"shell": "install.sh",
|
||||
"checks": "server/tamper_proof_py",
|
||||
"path": "server/tamper_proof_py",
|
||||
"author": "midoks",
|
||||
"home": "",
|
||||
"date":"2022-01-18",
|
||||
"pid":"4"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=tamper_proof_py server daemon
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
ExecStart={$SERVER_PATH}/init.d/tamper_proof_py start
|
||||
ExecStop={$SERVER_PATH}/init.d/tamper_proof_py stop
|
||||
ExecReload={$SERVER_PATH}/init.d/tamper_proof_py reload
|
||||
ExecRestart={$SERVER_PATH}/init.d/tamper_proof_py restart
|
||||
KillMode=process
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
# chkconfig: 2345 55 25
|
||||
# description:tamper_proof_service
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: tamper_proof_service
|
||||
# Required-Start: $all
|
||||
# Required-Stop: $all
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: starts tamper_proof_service
|
||||
# Description: starts the tamper_proof_service
|
||||
### END INIT INFO
|
||||
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
||||
mw_path={$SERVER_PATH}
|
||||
rootPath=$(dirname "$mw_path")
|
||||
PATH=$PATH:$mw_path/bin
|
||||
|
||||
if [ -f $rootPath/mdserver-web/bin/activate ];then
|
||||
source $rootPath/mdserver-web/bin/activate
|
||||
fi
|
||||
|
||||
# cd /www/server/mdserver-web && python3 plugins/tamper_proof_py/tamper_proof_service.py start
|
||||
sys_start()
|
||||
{
|
||||
isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep |grep -v 'tamper_proof_py/tamper_proof_service.py start' | grep -v 'tamper_proof_py/tamper_proof_service.py reload' | grep -v 'tamper_proof_py/tamper_proof_service.py restart' | grep -v systemctl | grep -v '/bin/sh' | grep -v '/bin/bash' | awk '{print $2}'|xargs)
|
||||
if [ "$isStart" == '' ];then
|
||||
echo -e "Starting tamper_proof_service... \c"
|
||||
cd $rootPath/mdserver-web
|
||||
nohup python3 plugins/tamper_proof_py/tamper_proof_service.py start &> $mw_path/service.log &
|
||||
sleep 0.5
|
||||
isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|awk '{print $2}'|xargs)
|
||||
if [ "$isStart" == '' ];then
|
||||
echo -e "\033[31mfailed\033[0m"
|
||||
echo '------------------------------------------------------'
|
||||
cat $mw_path/service.log
|
||||
echo '------------------------------------------------------'
|
||||
echo -e "\033[31mError: tamper_proof_service startup failed.\033[0m"
|
||||
return;
|
||||
fi
|
||||
echo -e "\033[32mdone\033[0m"
|
||||
else
|
||||
echo "Starting tamper_proof_service (pid $isStart) already running"
|
||||
fi
|
||||
}
|
||||
|
||||
sys_stop()
|
||||
{
|
||||
echo -e "Stopping tamper_proof_service... \c";
|
||||
pids=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|grep -v '/bin/bash'|grep -v systemctl | grep -v 'tamper_proof_py/tamper_proof_service.py stop' | grep -v 'tamper_proof_py/tamper_proof_service.py reload' | grep -v 'tamper_proof_py/tamper_proof_service.py restart' |awk '{print $2}'|xargs)
|
||||
arr=($pids)
|
||||
for p in ${arr[@]}
|
||||
do
|
||||
kill -9 $p
|
||||
done
|
||||
cd $rootPath/mdserver-web
|
||||
python3 plugins/tamper_proof_py/tamper_proof_service.py stop
|
||||
echo -e "\033[32mdone\033[0m"
|
||||
}
|
||||
|
||||
sys_status()
|
||||
{
|
||||
isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|grep -v "init.d/tamper_proof_py"|grep -v systemctl|awk '{print $2}'|xargs)
|
||||
if [ "$isStart" != '' ];then
|
||||
echo -e "\033[32mtamper_proof_service (pid $isStart) already running\033[0m"
|
||||
else
|
||||
echo -e "\033[32mtamper_proof_service not running\033[0m"
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
'start')
|
||||
sys_start
|
||||
;;
|
||||
'stop')
|
||||
sys_stop
|
||||
;;
|
||||
'restart')
|
||||
sys_stop
|
||||
sleep 0.2
|
||||
sys_start
|
||||
;;
|
||||
'reload')
|
||||
sys_stop
|
||||
sleep 0.2
|
||||
sys_start
|
||||
;;
|
||||
'status')
|
||||
sys_status
|
||||
;;
|
||||
*)
|
||||
echo "Usage: systemctl {start|stop|restart|reload} tamper_proof_service"
|
||||
;;
|
||||
esac
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
|
||||
export PATH
|
||||
|
||||
curPath=`pwd`
|
||||
rootPath=$(dirname "$curPath")
|
||||
rootPath=$(dirname "$rootPath")
|
||||
serverPath=$(dirname "$rootPath")
|
||||
|
||||
pip install pyinotify
|
||||
if [ -f ${rootPath}/bin/activate ];then
|
||||
source ${rootPath}/bin/activate
|
||||
fi
|
||||
pip install pyinotify
|
||||
|
||||
install_tmp=${rootPath}/tmp/mw_install.pl
|
||||
SYSOS=`uname`
|
||||
VERSION=$2
|
||||
APP_NAME=tamper_proof_py
|
||||
|
||||
Install_App()
|
||||
{
|
||||
echo '正在安装脚本文件...' > $install_tmp
|
||||
mkdir -p $serverPath/tamper_proof_py
|
||||
echo "$VERSION" > $serverPath/tamper_proof_py/version.pl
|
||||
echo 'install complete' > $install_tmp
|
||||
|
||||
#初始化
|
||||
cd ${serverPath}/mdserver-web && python3 plugins/tamper_proof_py/index.py start $VERSION
|
||||
cd ${serverPath}/mdserver-web && python3 plugins/tamper_proof_py/index.py initd_install $VERSION
|
||||
}
|
||||
|
||||
Uninstall_App()
|
||||
{
|
||||
if [ -f /usr/lib/systemd/system/${APP_NAME}.service ] || [ -f /lib/systemd/system/${APP_NAME}.service ] ;then
|
||||
systemctl stop ${APP_NAME}
|
||||
systemctl disable ${APP_NAME}
|
||||
rm -rf /usr/lib/systemd/system/${APP_NAME}.service
|
||||
rm -rf /lib/systemd/system/${APP_NAME}.service
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
|
||||
rm -rf $serverPath/tamper_proof_py
|
||||
echo "uninstall completed" > $install_tmp
|
||||
}
|
||||
|
||||
action=$1
|
||||
if [ "${1}" == 'install' ];then
|
||||
Install_App
|
||||
else
|
||||
Uninstall_App
|
||||
fi
|
||||
@@ -0,0 +1,552 @@
|
||||
|
||||
# +--------------------------------------------------------------------
|
||||
# | 事件型防篡改
|
||||
# +--------------------------------------------------------------------
|
||||
import sys
|
||||
import os
|
||||
import pyinotify
|
||||
import json
|
||||
import shutil
|
||||
import time
|
||||
import psutil
|
||||
import threading
|
||||
import datetime
|
||||
|
||||
sys.path.append(os.getcwd() + "/class/core")
|
||||
import mw
|
||||
|
||||
|
||||
class MyEventHandler(pyinotify.ProcessEvent):
|
||||
_PLUGIN_PATH = '/www/server/tamper_proof_py'
|
||||
_CONFIG = '/config.json'
|
||||
_SITES = '/sites.json'
|
||||
_SITES_DATA = None
|
||||
_CONFIG_DATA = None
|
||||
_DONE_FILE = None
|
||||
bakcupChirdPath = []
|
||||
|
||||
def __init__(self):
|
||||
self._PLUGIN_PATH = self.getServerDir()
|
||||
|
||||
def getPluginName(self):
|
||||
return 'tamper_proof_py'
|
||||
|
||||
def getPluginDir(self):
|
||||
return mw.getPluginDir() + '/' + self.getPluginName()
|
||||
|
||||
def getServerDir(self):
|
||||
return mw.getServerDir() + '/' + self.getPluginName()
|
||||
|
||||
def rmdir(self, filename):
|
||||
try:
|
||||
shutil.rmtree(filename)
|
||||
except:
|
||||
pass
|
||||
|
||||
def check_site_logs(self, Stiename, datetime_time):
|
||||
ret = []
|
||||
cur_month = datetime_time.month
|
||||
cur_day = datetime_time.day
|
||||
cur_year = datetime_time.year
|
||||
cur_hour = datetime_time.hour
|
||||
cur_minute = datetime_time.minute
|
||||
cur_second = int(datetime_time.second)
|
||||
months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07',
|
||||
'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'}
|
||||
logs_data = self.get_site_logs(Stiename)
|
||||
if not logs_data:
|
||||
return False
|
||||
for i2 in logs_data:
|
||||
try:
|
||||
i = i2.split()
|
||||
# 判断状态码是否为200
|
||||
if int(i[8]) not in [200, 500]:
|
||||
continue
|
||||
# 判断是否为POST
|
||||
day_time = i[3].split('/')[0].split('[')[1]
|
||||
if int(cur_day) != int(day_time):
|
||||
continue
|
||||
month_time = i[3].split('/')[1]
|
||||
if int(months[month_time]) != int(cur_month):
|
||||
continue
|
||||
year_time = i[3].split('/')[2].split(':')[0]
|
||||
if int(year_time) != int(cur_year):
|
||||
continue
|
||||
hour_time = i[3].split('/')[2].split(':')[1]
|
||||
if int(hour_time) != int(cur_hour):
|
||||
continue
|
||||
minute_time = i[3].split('/')[2].split(':')[2]
|
||||
if int(minute_time) != int(cur_minute):
|
||||
continue
|
||||
second_time = int(i[3].split('/')[2].split(':')[3])
|
||||
if cur_second - second_time > 10:
|
||||
continue
|
||||
ret.append(i2)
|
||||
except:
|
||||
continue
|
||||
ret2 = []
|
||||
if len(ret) > 20:
|
||||
for i2 in logs_data:
|
||||
try:
|
||||
i = i2.split()
|
||||
if i[6] != 'POST':
|
||||
continue
|
||||
if int(i[8]) not in [200, 500]:
|
||||
continue
|
||||
# 判断是否为POST
|
||||
day_time = i[3].split('/')[0].split('[')[1]
|
||||
if int(cur_day) != int(day_time):
|
||||
continue
|
||||
month_time = i[3].split('/')[1]
|
||||
if int(months[month_time]) != int(cur_month):
|
||||
continue
|
||||
year_time = i[3].split('/')[2].split(':')[0]
|
||||
if int(year_time) != int(cur_year):
|
||||
continue
|
||||
hour_time = i[3].split('/')[2].split(':')[1]
|
||||
if int(hour_time) != int(cur_hour):
|
||||
continue
|
||||
minute_time = i[3].split('/')[2].split(':')[2]
|
||||
if int(minute_time) != int(cur_minute):
|
||||
continue
|
||||
ret2.append(i2)
|
||||
except:
|
||||
continue
|
||||
if ret2:
|
||||
ret = ret2
|
||||
if len(ret) > 20:
|
||||
return ret[0:20]
|
||||
return ret
|
||||
|
||||
def get_site_logs(self, Stiename):
|
||||
try:
|
||||
pythonV = sys.version_info[0]
|
||||
path = '/www/wwwlogs/' + Stiename + '.log'
|
||||
num = 500
|
||||
if not os.path.exists(path):
|
||||
return []
|
||||
p = 1
|
||||
start_line = (p - 1) * num
|
||||
count = start_line + num
|
||||
fp = open(path, 'rb')
|
||||
buf = ""
|
||||
try:
|
||||
fp.seek(-1, 2)
|
||||
except:
|
||||
return []
|
||||
if fp.read(1) == "\n":
|
||||
fp.seek(-1, 2)
|
||||
data = []
|
||||
b = True
|
||||
n = 0
|
||||
c = 0
|
||||
while c < count:
|
||||
while True:
|
||||
newline_pos = str.rfind(buf, "\n")
|
||||
pos = fp.tell()
|
||||
if newline_pos != -1:
|
||||
if n >= start_line:
|
||||
line = buf[newline_pos + 1:]
|
||||
if line:
|
||||
try:
|
||||
data.append(line)
|
||||
except:
|
||||
c -= 1
|
||||
n -= 1
|
||||
pass
|
||||
else:
|
||||
c -= 1
|
||||
n -= 1
|
||||
buf = buf[:newline_pos]
|
||||
n += 1
|
||||
c += 1
|
||||
break
|
||||
else:
|
||||
if pos == 0:
|
||||
b = False
|
||||
break
|
||||
to_read = min(4096, pos)
|
||||
fp.seek(-to_read, 1)
|
||||
t_buf = fp.read(to_read)
|
||||
if pythonV == 3:
|
||||
t_buf = t_buf.decode('utf-8', errors="ignore")
|
||||
buf = t_buf + buf
|
||||
fp.seek(-to_read, 1)
|
||||
if pos - to_read == 0:
|
||||
buf = "\n" + buf
|
||||
if not b:
|
||||
break
|
||||
fp.close()
|
||||
except:
|
||||
data = []
|
||||
return data
|
||||
|
||||
def process_IN_CREATE(self, event):
|
||||
siteInfo = self.get_SITE_CONFIG(event.pathname)
|
||||
if not self.check_FILE(event, siteInfo, True):
|
||||
return False
|
||||
self._DONE_FILE = event.pathname
|
||||
if event.dir:
|
||||
if os.path.exists(event.pathname):
|
||||
self.rmdir(event.pathname)
|
||||
self.write_LOG('create', siteInfo[
|
||||
'siteName'], event.pathname, datetime.datetime.now())
|
||||
|
||||
else:
|
||||
if os.path.exists(event.pathname):
|
||||
try:
|
||||
src_path = os.path.dirname(event.pathname)
|
||||
os.system("chattr -a {}".format(src_path))
|
||||
os.remove(event.pathname)
|
||||
os.system("chattr +a {}".format(src_path))
|
||||
self.write_LOG('create', siteInfo[
|
||||
'siteName'], event.pathname, datetime.datetime.now())
|
||||
except:
|
||||
pass
|
||||
|
||||
def process_IN_MOVED_TO(self, event):
|
||||
# 检查是否受保护
|
||||
siteInfo = self.get_SITE_CONFIG(event.pathname)
|
||||
if not self.check_FILE(event, siteInfo):
|
||||
return False
|
||||
|
||||
if not getattr(event, 'src_pathname', None):
|
||||
if os.path.isdir(event.pathname):
|
||||
self.rmdir(event.pathname)
|
||||
else:
|
||||
os.remove(event.pathname)
|
||||
self.write_LOG('move', siteInfo[
|
||||
'siteName'], '未知 -> ' + event.pathname)
|
||||
return True
|
||||
|
||||
# 是否为标记文件
|
||||
if event.src_pathname == self._DONE_FILE:
|
||||
return False
|
||||
|
||||
if not os.path.exists(event.src_pathname):
|
||||
# 标记
|
||||
self._DONE_FILE = event.pathname
|
||||
# 还原
|
||||
os.renames(event.pathname, event.src_pathname)
|
||||
|
||||
# 记录日志
|
||||
self.write_LOG('move', siteInfo['siteName'],
|
||||
event.src_pathname + ' -> ' + event.pathname)
|
||||
|
||||
def check_FILE(self, event, siteInfo, create=False):
|
||||
if not siteInfo:
|
||||
return False
|
||||
if self.exclude_PATH(event.pathname):
|
||||
return False
|
||||
if event.dir and create:
|
||||
return True
|
||||
if not event.dir:
|
||||
if not self.protect_EXT(event.pathname):
|
||||
return False
|
||||
return True
|
||||
|
||||
def protect_EXT(self, pathname):
|
||||
if pathname.find('.') == -1:
|
||||
return False
|
||||
extName = pathname.split('.')[-1].lower()
|
||||
siteData = self.get_SITE_CONFIG(pathname)
|
||||
if siteData:
|
||||
if extName in siteData['protectExt']:
|
||||
return True
|
||||
return False
|
||||
|
||||
def exclude_PATH(self, pathname):
|
||||
if pathname.find('/') == -1:
|
||||
return False
|
||||
siteData = self.get_SITE_CONFIG(pathname)
|
||||
return self.exclude_PATH_OF_SITE(pathname, siteData['excludePath'])
|
||||
|
||||
def exclude_PATH_OF_SITE(self, pathname, excludePath):
|
||||
pathname = pathname.lower()
|
||||
dirNames = pathname.split('/')
|
||||
if excludePath:
|
||||
if pathname in excludePath:
|
||||
return True
|
||||
if pathname + '/' in excludePath:
|
||||
return True
|
||||
for ePath in excludePath:
|
||||
if ePath in dirNames:
|
||||
return True
|
||||
if pathname.find(ePath) == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_SITE_CONFIG(self, pathname):
|
||||
if not self._SITES_DATA:
|
||||
self._SITES_DATA = json.loads(
|
||||
mw.readFile(self._PLUGIN_PATH + self._SITES))
|
||||
for site in self._SITES_DATA:
|
||||
length = len(site['path'])
|
||||
if len(pathname) < length:
|
||||
continue
|
||||
if site['path'] != pathname[:length]:
|
||||
continue
|
||||
return site
|
||||
return None
|
||||
|
||||
def get_CONFIG(self):
|
||||
if self._CONFIG_DATA:
|
||||
return self._CONFIG_DATA
|
||||
self._CONFIG_DATA = json.loads(
|
||||
mw.readFile(self._PLUGIN_PATH + self._CONFIG))
|
||||
|
||||
def list_DIR(self, path, siteInfo): # path 站点路径
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
lock_files = []
|
||||
lock_dirs = []
|
||||
explode_a = ['log', 'logs', 'cache', 'templates', 'template', 'upload', 'img',
|
||||
'image', 'images', 'public', 'static', 'js', 'css', 'tmp', 'temp', 'update', 'data']
|
||||
for name in os.listdir(path):
|
||||
try:
|
||||
filename = "{}/{}".format(path, name).replace('//', '/')
|
||||
lower_name = name.lower()
|
||||
lower_filename = filename.lower()
|
||||
if os.path.isdir(filename): # 是否为目录
|
||||
if lower_name in siteInfo['excludePath']:
|
||||
continue # 是否为排除的文件名
|
||||
# 是否为排除目录
|
||||
if not self.exclude_PATH_OF_SITE(filename, siteInfo['excludePath']):
|
||||
if not lower_name in explode_a: # 是否为固定不锁定目录
|
||||
lock_dirs.append('"' + name + '"')
|
||||
self.list_DIR(filename, siteInfo)
|
||||
continue
|
||||
|
||||
# 是否为受保护的文件名或文件全路径
|
||||
if not lower_name in siteInfo['protectExt'] and not lower_filename in siteInfo['protectExt']:
|
||||
if not self.get_EXT_NAME(lower_name) in siteInfo['protectExt']:
|
||||
continue # 是否为受保护文件类型
|
||||
|
||||
if lower_filename in siteInfo['excludePath']:
|
||||
continue # 是否为排除文件
|
||||
if lower_name in siteInfo['excludePath']:
|
||||
continue # 是否为排除的文件名
|
||||
lock_files.append('"' + name + '"')
|
||||
except:
|
||||
print(mw.getTracebackInfo())
|
||||
if lock_files:
|
||||
self.thread_exec(lock_files, path, 'i')
|
||||
if lock_dirs:
|
||||
self.thread_exec(lock_dirs, path, 'a')
|
||||
|
||||
_thread_count = 0
|
||||
_thread_max = 2 * psutil.cpu_count()
|
||||
|
||||
def thread_exec(self, file_list, cwd, i='i'):
|
||||
while self._thread_count > self._thread_max:
|
||||
time.sleep(0.1)
|
||||
|
||||
self._thread_count += 1
|
||||
cmd = "cd {} && chattr +{} {} > /dev/null".format(
|
||||
cwd, i, ' '.join(file_list))
|
||||
p = threading.Thread(target=self.run_thread, args=(cmd,))
|
||||
p.start()
|
||||
|
||||
def run_thread(self, cmd):
|
||||
os.system(cmd)
|
||||
self._thread_count -= 1
|
||||
|
||||
def get_EXT_NAME(self, fileName):
|
||||
return fileName.split('.')[-1]
|
||||
|
||||
def write_LOG(self, eventType, siteName, pathname, datetime):
|
||||
# 获取网站时间的top100 记录
|
||||
site_log = '/www/wwwlogs/%s.log' % siteName
|
||||
logs_data = []
|
||||
if os.path.exists(site_log):
|
||||
logs_data = self.check_site_logs(siteName, datetime)
|
||||
dateDay = time.strftime("%Y-%m-%d", time.localtime())
|
||||
logPath = self._PLUGIN_PATH + '/sites/' + \
|
||||
siteName + '/day/' + dateDay
|
||||
if not os.path.exists(logPath):
|
||||
os.makedirs(logPath)
|
||||
logFile = os.path.join(logPath, 'logs.json')
|
||||
logVar = [int(time.time()), eventType, pathname, logs_data]
|
||||
fp = open(logFile, 'a+')
|
||||
fp.write(json.dumps(logVar) + "\n")
|
||||
fp.close()
|
||||
logFiles = [
|
||||
logPath + '/total.json',
|
||||
self._PLUGIN_PATH + '/sites/' + siteName + '/day/total.json',
|
||||
self._PLUGIN_PATH + '/sites/total.json'
|
||||
]
|
||||
|
||||
for totalLogFile in logFiles:
|
||||
if not os.path.exists(totalLogFile):
|
||||
totalData = {"total": 0, "delete": 0,
|
||||
"create": 0, "modify": 0, "move": 0}
|
||||
else:
|
||||
dataTmp = mw.readFile(totalLogFile)
|
||||
if dataTmp:
|
||||
totalData = json.loads(dataTmp)
|
||||
else:
|
||||
totalData = {"total": 0, "delete": 0,
|
||||
"create": 0, "modify": 0, "move": 0}
|
||||
|
||||
totalData['total'] += 1
|
||||
totalData[eventType] += 1
|
||||
mw.writeFile(totalLogFile, json.dumps(totalData))
|
||||
|
||||
# 设置.user.ini
|
||||
def set_user_ini(self, path, up=0):
|
||||
os.chdir(path)
|
||||
useriniPath = path + '/.user.ini'
|
||||
if os.path.exists(useriniPath):
|
||||
os.system('chattr +i ' + useriniPath)
|
||||
for p1 in os.listdir(path):
|
||||
try:
|
||||
npath = path + '/' + p1
|
||||
if not os.path.isdir(npath):
|
||||
continue
|
||||
useriniPath = npath + '/.user.ini'
|
||||
if os.path.exists(useriniPath):
|
||||
os.system('chattr +i ' + useriniPath)
|
||||
if up < 3:
|
||||
self.set_user_ini(npath, up + 1)
|
||||
except:
|
||||
continue
|
||||
return True
|
||||
|
||||
def unlock(self, path):
|
||||
os.system('chattr -R -i {} &> /dev/null'.format(path))
|
||||
os.system('chattr -R -a {} &> /dev/null'.format(path))
|
||||
self.set_user_ini(path)
|
||||
|
||||
def close(self, reload=False):
|
||||
# 解除锁定
|
||||
sites = self.get_sites()
|
||||
print("")
|
||||
print("=" * 60)
|
||||
print("【{}】正在关闭防篡改,请稍候...".format(mw.formatDate()))
|
||||
print("-" * 60)
|
||||
for siteInfo in sites:
|
||||
tip = self._PLUGIN_PATH + '/tips/' + siteInfo['siteName'] + '.pl'
|
||||
if not siteInfo['open'] and not os.path.exists(tip):
|
||||
continue
|
||||
if reload and siteInfo['open']:
|
||||
continue
|
||||
if sys.version_info[0] == 2:
|
||||
print(
|
||||
"【{}】|-解锁网站[{}]".format(mw.formatDate(), siteInfo['siteName'])),
|
||||
else:
|
||||
os.system(
|
||||
"echo -e '【{}】|-解锁网站[{}]\c'".format(mw.formatDate(), siteInfo['siteName']))
|
||||
#print("【{}】|-解锁网站[{}]".format(mw.format_date(),siteInfo['siteName']),end=" ")
|
||||
self.unlock(siteInfo['path'])
|
||||
if os.path.exists(tip):
|
||||
os.remove(tip)
|
||||
print("\t=> 完成")
|
||||
print("-" * 60)
|
||||
print('|-防篡改已关闭')
|
||||
print("=" * 60)
|
||||
print(">>>>>>>>>>END<<<<<<<<<<")
|
||||
|
||||
# 获取网站配置列表
|
||||
def get_sites(self):
|
||||
siteconf = self._PLUGIN_PATH + '/sites.json'
|
||||
d = mw.readFile(siteconf)
|
||||
if not os.path.exists(siteconf) or not d:
|
||||
mw.writeFile(siteconf, "[]")
|
||||
data = json.loads(mw.readFile(siteconf))
|
||||
|
||||
# 处理多余字段开始 >>>>>>>>>>
|
||||
is_write = False
|
||||
rm_keys = ['lock', 'bak_open']
|
||||
for i in data:
|
||||
i_keys = i.keys()
|
||||
if not 'open' in i_keys:
|
||||
i['open'] = False
|
||||
for o in rm_keys:
|
||||
if o in i_keys:
|
||||
if i[o]:
|
||||
i['open'] = True
|
||||
i.pop(o)
|
||||
is_write = True
|
||||
if is_write:
|
||||
mw.writeFile(siteconf, json.dumps(data))
|
||||
# 处理多余字段结束 <<<<<<<<<<<<<
|
||||
return data
|
||||
|
||||
def __enter__(self):
|
||||
self.close()
|
||||
|
||||
def __exit__(self, a, b, c):
|
||||
self.close()
|
||||
|
||||
|
||||
def run():
|
||||
# 初始化inotify对像
|
||||
event = MyEventHandler()
|
||||
watchManager = pyinotify.WatchManager()
|
||||
starttime = time.time()
|
||||
mode = pyinotify.IN_CREATE | pyinotify.IN_MOVED_TO
|
||||
|
||||
# 处理网站属性
|
||||
sites = event.get_sites()
|
||||
print("=" * 60)
|
||||
print("【{}】正在启动防篡改,请稍候...".format(mw.formatDate()))
|
||||
print("-" * 60)
|
||||
tip_path = event._PLUGIN_PATH + '/tips/'
|
||||
if not os.path.exists(tip_path):
|
||||
os.makedirs(tip_path)
|
||||
speed_file = event._PLUGIN_PATH + '/speed.pl'
|
||||
for siteInfo in sites:
|
||||
s = time.time()
|
||||
tip = tip_path + siteInfo['siteName'] + '.pl'
|
||||
if not siteInfo['open']:
|
||||
continue
|
||||
if sys.version_info[0] == 2:
|
||||
print("【{}】|-网站[{}]".format(mw.formatDate(),
|
||||
siteInfo['siteName'])),
|
||||
else:
|
||||
os.system(
|
||||
"echo -e '【{}】|-网站[{}]\c'".format(mw.formatDate(), siteInfo['siteName']))
|
||||
# print("【{}】|-网站[{}]".format(public.format_date(),siteInfo['siteName']),end=" ")
|
||||
mw.writeFile(speed_file, "正在处理网站[{}],请稍候...".format(
|
||||
siteInfo['siteName']))
|
||||
if not os.path.exists(tip):
|
||||
event.list_DIR(siteInfo['path'], siteInfo)
|
||||
try:
|
||||
watchManager.add_watch(
|
||||
siteInfo['path'], mode, auto_add=True, rec=True)
|
||||
except:
|
||||
print(mw.getTracebackInfo())
|
||||
tout = round(time.time() - s, 2)
|
||||
mw.writeFile(tip, '1')
|
||||
print("\t\t=> 完成,耗时 {} 秒".format(tout))
|
||||
|
||||
# 启动服务
|
||||
endtime = round(time.time() - starttime, 2)
|
||||
mw.writeLog('防篡改程序', "网站防篡改服务已成功启动,耗时[%s]秒" % endtime)
|
||||
notifier = pyinotify.Notifier(watchManager, event)
|
||||
print("-" * 60)
|
||||
print('|-防篡改服务已启动')
|
||||
print("=" * 60)
|
||||
end_tips = ">>>>>>>>>>END<<<<<<<<<<"
|
||||
print(end_tips)
|
||||
mw.writeFile(speed_file, end_tips)
|
||||
notifier.loop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
if 'stop' in sys.argv:
|
||||
event = MyEventHandler()
|
||||
event.close()
|
||||
elif 'start' in sys.argv:
|
||||
run()
|
||||
elif 'unlock' in sys.argv:
|
||||
event = MyEventHandler()
|
||||
event.unlock(sys.argv[2])
|
||||
elif 'reload' in sys.argv:
|
||||
event = MyEventHandler()
|
||||
event.close(True)
|
||||
else:
|
||||
print('error')
|
||||
else:
|
||||
run()
|
||||
@@ -41,6 +41,11 @@ def send_msg(bot, tag='ad', trigger_time=300):
|
||||
mw.writeFile(lock_file, json.dumps(lock_data))
|
||||
# 信号只在一个周期内执行一次|end
|
||||
|
||||
# https://t.me/gjgzs2022 | 19/m
|
||||
# ♻️CMS导航网♻️/💰流量变现💰 | 28/m
|
||||
# CK资源采集 |29/m
|
||||
# 高防服务器CDN请联系玥玥 |3/m
|
||||
|
||||
keyboard = [
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
|
||||
@@ -82,11 +82,6 @@ def send_msg(bot, tag='ad', trigger_time=300):
|
||||
|
||||
msg_notice = "由于在解决的问题的时候,不给信息,无法了解情况。以后不再群里回答技术问题。全部去论坛提问。在解决问题的过程中,可能需要面板信息,和SSH信息,如无法提供请不要提问。为了让群里都知晓。轮播一年!\n"
|
||||
msg_notice += "为了不打扰双方,私聊解决问题先转100U,否则无视!\n"
|
||||
msg_notice += "现阶段3件事:\n"
|
||||
msg_notice += "* 炒币\n"
|
||||
msg_notice += "* 量化交易\n"
|
||||
msg_notice += "* MW-VIP系统开发\n"
|
||||
msg_notice += "至少1年时间都没空。非诚勿扰!"
|
||||
msg = bot.send_message(chat_id, msg_notice, reply_markup=markup)
|
||||
|
||||
# print(msg.message_id)
|
||||
|
||||
+2
-2
@@ -7,6 +7,8 @@ urllib3>=1.21.1
|
||||
flask==2.0.3
|
||||
flask-session==0.3.2
|
||||
flask-helper==0.19
|
||||
flask-bcrypt==1.0.1
|
||||
flask-caching>=1.10.1
|
||||
cache==1.0.3
|
||||
gevent==21.12.0
|
||||
gevent-websocket==0.10.1
|
||||
@@ -25,8 +27,6 @@ paramiko>=2.8.0
|
||||
pymongo
|
||||
pillow
|
||||
Jinja2>=2.11.2
|
||||
flask-caching>=1.10.1
|
||||
bcrypt==3.1.3
|
||||
PyMySQL==1.0.2
|
||||
whitenoise==5.3.0
|
||||
pyTelegramBotAPI
|
||||
|
||||
Reference in New Issue
Block a user