diff --git a/.gitignore b/.gitignore
index 466df64ae1..dc5119205f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/README.md b/README.md
index 742d1f8488..751db92651 100644
--- a/README.md
+++ b/README.md
@@ -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安装地址
diff --git a/class/core/config_api.py b/class/core/config_api.py
index c1cb0dfb68..0fa7efba0f 100755
--- a/class/core/config_api.py
+++ b/class/core/config_api.py
@@ -27,7 +27,7 @@
class config_api:
- __version = '0.14.0'
+ __version = '0.14.1'
__api_addr = 'data/api.json'
def __init__(self):
diff --git a/plugins/op_load_balance/conf/load_balance.conf b/plugins/op_load_balance/conf/load_balance.conf
new file mode 100644
index 0000000000..0dff636c31
--- /dev/null
+++ b/plugins/op_load_balance/conf/load_balance.conf
@@ -0,0 +1,2 @@
+## 指定共享内存
+lua_shared_dict healthcheck 10m;
\ No newline at end of file
diff --git a/plugins/op_load_balance/conf/rewrite.tpl.conf b/plugins/op_load_balance/conf/rewrite.tpl.conf
new file mode 100644
index 0000000000..1554bf2c79
--- /dev/null
+++ b/plugins/op_load_balance/conf/rewrite.tpl.conf
@@ -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))
+ }
+}
\ No newline at end of file
diff --git a/plugins/op_load_balance/conf/upstream.tpl.conf b/plugins/op_load_balance/conf/upstream.tpl.conf
new file mode 100644
index 0000000000..711bbec01d
--- /dev/null
+++ b/plugins/op_load_balance/conf/upstream.tpl.conf
@@ -0,0 +1,5 @@
+upstream {$UPSTREAM_NAME}
+{
+ {$NODE_ALGO}
+ {$NODE_SERVER_LIST}
+}
\ No newline at end of file
diff --git a/plugins/op_load_balance/ico.png b/plugins/op_load_balance/ico.png
new file mode 100644
index 0000000000..57850d8d48
Binary files /dev/null and b/plugins/op_load_balance/ico.png differ
diff --git a/plugins/op_load_balance/index.html b/plugins/op_load_balance/index.html
new file mode 100755
index 0000000000..c789b699c3
--- /dev/null
+++ b/plugins/op_load_balance/index.html
@@ -0,0 +1,21 @@
+
+
+
+
\ No newline at end of file
diff --git a/plugins/op_load_balance/index.py b/plugins/op_load_balance/index.py
new file mode 100755
index 0000000000..59a646fb26
--- /dev/null
+++ b/plugins/op_load_balance/index.py
@@ -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')
diff --git a/plugins/op_load_balance/info.json b/plugins/op_load_balance/info.json
new file mode 100755
index 0000000000..1855728783
--- /dev/null
+++ b/plugins/op_load_balance/info.json
@@ -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"]
+}
\ No newline at end of file
diff --git a/plugins/op_load_balance/install.sh b/plugins/op_load_balance/install.sh
new file mode 100755
index 0000000000..d78bf73fbf
--- /dev/null
+++ b/plugins/op_load_balance/install.sh
@@ -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
diff --git a/plugins/op_load_balance/js/app.js b/plugins/op_load_balance/js/app.js
new file mode 100644
index 0000000000..3ffca22926
--- /dev/null
+++ b/plugins/op_load_balance/js/app.js
@@ -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: "",
+ 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 = '';
+ tbody +=''+ip+' | ';
+ tbody +=''+port+' | ';
+ tbody +=''+path+' | ';
+
+ tbody +=" | ";
+
+ tbody +=' | ';
+ tbody +=' | ';
+ tbody +=' | ';
+ tbody +='删除 | ';
+ tbody += '
';
+ $('#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: "",
+ 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\
+ \
+ \
+
节点调度\
+
\
+ \
+
\
+
\
+ \
+
节点健康检查\
+
\
+ \
+
\
+
\
+ \
+
节点\
+
\
+
\
+
\
+ \
+ \
+ IP地址 | \
+ 端口 | \
+ 验证路径 | \
+ 状态 | \
+ 权重 | \
+ 阀值 | \
+ 恢复时间 | \
+ 操作 | \
+
\
+ \
+ \
+ \
+ 当前节点为空,请至少添加一个普通节点 | \
+
\
+ \
+
\
+
\
+
添加节点\
+
\
+
\
+ ",
+ 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 = '';
+ tbody +=''+node_list[n]['ip']+' | ';
+ tbody +=''+node_list[n]['port']+' | ';
+ tbody +=''+node_list[n]['path']+' | ';
+
+ tbody +=" | ";
+
+ tbody +=' | ';
+ tbody +=' | ';
+ tbody +=' | ';
+ tbody +='删除 | ';
+ tbody += '
';
+ $('#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 += '';
+ tbody += ''+alist[i]['domain']+' | ';
+ tbody += ''+alist[i]['upstream_name']+' | ';
+ tbody += ''+alist[i]['node_list'].length+' | ';
+ tbody += '查看 | ';
+ tbody += '查看 | ';
+ tbody += '修改 | 删除 | ';
+ tbody += '
';
+ }
+
+ $('#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 += '';
+ tval += ''+rdata.data[i]['name']+' | ';
+
+ if (typeof(rdata.data[i]['down']) != 'undefined' && rdata.data[i]['down']){
+ tval += '不正常 | ';
+ } else{
+ tval += '正常 | ';
+ }
+ tval += '
';
+ }
+
+ var tbody = "";
+
+ 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 = '\
+
\
+
\
+
\
+
\
+ \
+ \
+ 网站 | \
+ 负载名称 | \
+ 节点 | \
+ 日志 | \
+ 状态 | \
+ 操作 | \
+
\
+ \
+ \
+
\
+
\
+
\
+
\
+
\
+ ';
+ $(".soft-man-con").html(body);
+ loadBalanceListRender();
+}
diff --git a/plugins/op_load_balance/lua/health_check.lua.tpl b/plugins/op_load_balance/lua/health_check.lua.tpl
new file mode 100644
index 0000000000..94411c942d
--- /dev/null
+++ b/plugins/op_load_balance/lua/health_check.lua.tpl
@@ -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
\ No newline at end of file
diff --git a/plugins/system_safe/README.md b/plugins/system_safe/README.md
deleted file mode 100644
index 18671a83b2..0000000000
--- a/plugins/system_safe/README.md
+++ /dev/null
@@ -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
-```
-
diff --git a/plugins/tamper_proof_py/conf/config.json b/plugins/tamper_proof_py/conf/config.json
new file mode 100644
index 0000000000..8a372031a3
--- /dev/null
+++ b/plugins/tamper_proof_py/conf/config.json
@@ -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" ]
+}
\ No newline at end of file
diff --git a/plugins/tamper_proof_py/ico.png b/plugins/tamper_proof_py/ico.png
new file mode 100644
index 0000000000..3dede4e917
Binary files /dev/null and b/plugins/tamper_proof_py/ico.png differ
diff --git a/plugins/tamper_proof_py/index.html b/plugins/tamper_proof_py/index.html
new file mode 100755
index 0000000000..cb5de195f3
--- /dev/null
+++ b/plugins/tamper_proof_py/index.html
@@ -0,0 +1,1163 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/tamper_proof_py/index.py b/plugins/tamper_proof_py/index.py
new file mode 100755
index 0000000000..446dee0c36
--- /dev/null
+++ b/plugins/tamper_proof_py/index.py
@@ -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())
diff --git a/plugins/tamper_proof_py/info.json b/plugins/tamper_proof_py/info.json
new file mode 100644
index 0000000000..4ed8adda46
--- /dev/null
+++ b/plugins/tamper_proof_py/info.json
@@ -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"
+}
\ No newline at end of file
diff --git a/plugins/tamper_proof_py/init.d/tamper_proof_py.service.tpl b/plugins/tamper_proof_py/init.d/tamper_proof_py.service.tpl
new file mode 100644
index 0000000000..75d25b0725
--- /dev/null
+++ b/plugins/tamper_proof_py/init.d/tamper_proof_py.service.tpl
@@ -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
\ No newline at end of file
diff --git a/plugins/tamper_proof_py/init.d/tamper_proof_py.tpl b/plugins/tamper_proof_py/init.d/tamper_proof_py.tpl
new file mode 100644
index 0000000000..ae38bab4cc
--- /dev/null
+++ b/plugins/tamper_proof_py/init.d/tamper_proof_py.tpl
@@ -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
\ No newline at end of file
diff --git a/plugins/tamper_proof_py/install.sh b/plugins/tamper_proof_py/install.sh
new file mode 100755
index 0000000000..efa2d12f77
--- /dev/null
+++ b/plugins/tamper_proof_py/install.sh
@@ -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
diff --git a/plugins/tamper_proof_py/tamper_proof_service.py b/plugins/tamper_proof_py/tamper_proof_service.py
new file mode 100644
index 0000000000..61645db3a7
--- /dev/null
+++ b/plugins/tamper_proof_py/tamper_proof_service.py
@@ -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()
diff --git a/plugins/tgbot/startup/extend/push_ad.py b/plugins/tgbot/startup/extend/push_ad.py
index 8ef7a7d030..e86cff6f95 100644
--- a/plugins/tgbot/startup/extend/push_ad.py
+++ b/plugins/tgbot/startup/extend/push_ad.py
@@ -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(
diff --git a/plugins/tgbot/startup/extend/push_notice_msg.py b/plugins/tgbot/startup/extend/push_notice_msg.py
index a2bcbf7b07..be99cfec7a 100644
--- a/plugins/tgbot/startup/extend/push_notice_msg.py
+++ b/plugins/tgbot/startup/extend/push_notice_msg.py
@@ -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)
diff --git a/requirements.txt b/requirements.txt
index 211761a52f..faa281bd3f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -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