Compare commits

...
61 Commits
Author SHA1 Message Date
Mr Chen fe04e02e88 Merge pull request #405 from midoks/dev
0.14.2
2023-05-02 12:11:48 +08:00
midoks 93554332a7 Update README.md 2023-05-02 12:10:37 +08:00
midoks b541c7e1b7 Update config_api.py 2023-05-02 12:09:02 +08:00
midoks 1dec1a286a Update docker.js 2023-05-02 12:07:51 +08:00
midoks b17510104d up 2023-05-02 11:55:32 +08:00
midoks c2c8ea0ce6 Update install.sh 2023-05-02 11:52:56 +08:00
midoks acbdb013ae Update install.sh 2023-05-02 11:42:16 +08:00
midoks b025f21765 Update index.py 2023-05-02 11:36:28 +08:00
midoks 1afc8a69e5 Update index.py 2023-05-02 11:35:21 +08:00
midoks 743fc9e8d3 Update index.py 2023-05-02 11:35:06 +08:00
midoks 8d692d2b3b Update install.sh 2023-05-02 11:34:00 +08:00
midoks a0b362e691 Update index.py 2023-05-02 11:31:25 +08:00
midoks 98e1c2ae8f Update install.sh 2023-05-02 11:26:27 +08:00
midoks c0e5915285 Update index.html 2023-05-02 11:23:46 +08:00
midoks 667a0fd0fd Update install.sh 2023-05-02 11:19:03 +08:00
midoks 8ed5f283b0 Update client_check_member.py 2023-05-01 17:30:03 +08:00
midoks 643c370d30 up 2023-05-01 17:26:02 +08:00
midoks 4601303065 up 2023-05-01 16:05:54 +08:00
midoks 14940d6734 Delete mdioks.session 2023-05-01 15:09:14 +08:00
midoks fdc2b0f5c4 Update client_holding.py 2023-05-01 15:08:40 +08:00
midoks cf0b233f63 Update client_check_member.py 2023-05-01 14:51:54 +08:00
midoks b3a7ef2206 Create client_holding.py 2023-05-01 14:49:50 +08:00
midoks 9a3ca3e833 Update index.html 2023-04-30 10:17:28 +08:00
midoks 9afff8df3f Update index.py 2023-04-30 01:13:00 +08:00
midoks ccd0b06223 Update tgclient.py 2023-04-30 01:04:16 +08:00
midoks 7b495fe9bf Update tgclient.py 2023-04-29 16:24:19 +08:00
midoks 2c3fb75e8f Update tgclient.js 2023-04-29 16:11:09 +08:00
midoks 53b606602f up 2023-04-29 16:10:30 +08:00
midoks ecef0cf994 up 2023-04-29 16:01:10 +08:00
midoks 3691d41b23 Update client_change_username.py 2023-04-28 21:47:13 +08:00
midoks 1f66457a3e Update client_change_username.py 2023-04-28 17:25:38 +08:00
midoks 8e05beccfd Update tgclient.py 2023-04-28 17:22:56 +08:00
midoks 35424eae1f up 2023-04-28 17:21:13 +08:00
midoks ed6df6d527 up 2023-04-28 17:03:41 +08:00
midoks 4366c4afde Update tgclient.py 2023-04-24 22:05:34 +08:00
midoks 4bb059f62f Update info.json 2023-04-24 21:23:44 +08:00
midoks aa8f618d9c Update tgclient.py 2023-04-24 21:15:47 +08:00
midoks dfd753e4c1 up 2023-04-24 21:07:25 +08:00
midoks e38daf3c89 Update tgclient.py 2023-04-24 20:51:54 +08:00
midoks 535838fc5e Update tgclient.py 2023-04-24 20:44:28 +08:00
midoks 797853bd61 Update tgclient.py 2023-04-24 20:36:49 +08:00
midoks 0de3aff045 up 2023-04-24 20:32:41 +08:00
midoks 0b7b48ab23 up 2023-04-24 20:31:31 +08:00
midoks 5ff0a0bfbc Update index.py 2023-04-21 21:03:30 +08:00
midoks 9131f44d67 Update install.sh 2023-04-21 13:02:42 +08:00
midoks 6b86bb9d2b Update docker.js 2023-04-21 13:00:05 +08:00
midoks dc06d6beda Update install.sh 2023-04-21 12:58:50 +08:00
midoks 2c8ea2498e Update install.sh 2023-04-21 12:55:38 +08:00
midoks 6b01173b61 Update push_notice_msg.py 2023-04-20 12:41:56 +08:00
midoks 883c554850 up 2023-04-20 12:19:33 +08:00
midoks d267da351c Update push_ad.py 2023-04-16 23:48:24 +08:00
midoks 13a39f4d6a up 2023-04-16 20:50:30 +08:00
Mr Chen cff1d5ee89 Merge pull request #403 from midoks/dev
0.14.1
2023-04-16 20:42:39 +08:00
midoks bf5274c313 0.14.1
* 安装优化。
* 加入OP负载均衡插件。
* 网站防篡改程序(测试中)。
2023-04-16 20:41:39 +08:00
midoks 5623e3efac Update requirements.txt 2023-04-16 20:38:03 +08:00
midoks b93bb6c64c Update requirements.txt 2023-04-16 20:37:24 +08:00
midoks c9962f169e Update requirements.txt 2023-04-16 20:36:01 +08:00
midoks 080f343ee2 Update push_ad.py 2023-04-15 10:57:19 +08:00
midoks 5aeaf13f16 负载均衡插件 2023-04-13 15:42:01 +08:00
midoks 528094cfa7 网站防篡改程序 2023-04-13 15:33:15 +08:00
midoks ed3e3336b2 Update push_notice_msg.py 2023-04-13 15:28:38 +08:00
46 changed files with 5113 additions and 36 deletions
+2 -2
View File
@@ -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
@@ -179,3 +177,5 @@ debug.out
mdioks.session-journal
*.session
+4 -2
View File
@@ -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.2
* 加入系统加固插件。
* 加入TgClient插件
* 网站防篡改程序(测试中)
* Docker(测试中)
### JSDelivr安装地址
+1 -1
View File
@@ -27,7 +27,7 @@ from flask import request
class config_api:
__version = '0.14.0'
__version = '0.14.2'
__api_addr = 'data/api.json'
def __init__(self):
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

+20
View File
@@ -0,0 +1,20 @@
<div class="bt-form">
<div class='plugin_version'></div>
<div class="bt-w-main">
<div class="bt-w-menu">
<p class="bgw" onclick="pluginService('docker');">服务</p>
<p onclick="pluginInitD('docker');">自启动</p>
<p onclick="dockerList();">容器列表</p>
</div>
<div class="bt-w-con pd15">
<div class="soft-man-con"></div>
</div>
</div>
</div>
<script type="text/javascript">
resetPluginWinWidth(700);
resetPluginWinHeight(400);
$.getScript( "/plugins/file?name=docker&f=js/docker.js", function(){
pluginService('docker', $('.plugin_version').attr('version'));
});
</script>
+169
View File
@@ -0,0 +1,169 @@
# coding:utf-8
import sys
import io
import os
import time
import re
sys.path.append(os.getcwd() + "/class/core")
import mw
app_debug = False
if mw.isAppleSystem():
app_debug = True
def getPluginName():
return 'docker'
def getPluginDir():
return mw.getPluginDir() + '/' + getPluginName()
def getServerDir():
return mw.getServerDir() + '/' + getPluginName()
def getInitDFile():
if app_debug:
return '/tmp/' + getPluginName()
return '/etc/init.d/' + getPluginName()
def getConf():
path = getServerDir() + "/redis.conf"
return path
def getConfTpl():
path = getPluginDir() + "/config/redis.conf"
return path
def getInitDTpl():
path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl"
return path
def getArgs():
args = sys.argv[3:]
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 status():
data = mw.execShell(
"ps -ef|grep dockerd |grep -v grep | grep -v python | grep -v mdserver-web | awk '{print $2}'")
if data[0] == '':
return 'stop'
return 'start'
def initDreplace():
return ''
def dockerOp(method):
file = initDreplace()
if not mw.isAppleSystem():
data = mw.execShell('systemctl ' + method + ' docker')
if data[1] == '':
return 'ok'
return data[1]
return 'fail'
def start():
return dockerOp('start')
def stop():
return dockerOp('stop')
def restart():
status = dockerOp('restart')
log_file = runLog()
mw.execShell("echo '' > " + log_file)
return status
def reload():
return dockerOp('reload')
def initdStatus():
if mw.isAppleSystem():
return "Apple Computer does not support"
shell_cmd = 'systemctl status ' + \
getPluginName() + ' | grep loaded | grep "enabled;"'
data = mw.execShell(shell_cmd)
if data[0] == '':
return 'fail'
return 'ok'
def initdInstall():
if mw.isAppleSystem():
return "Apple Computer does not support"
mw.execShell('systemctl enable ' + getPluginName())
return 'ok'
def initdUinstall():
if mw.isAppleSystem():
return "Apple Computer does not support"
mw.execShell('systemctl disable ' + getPluginName())
return 'ok'
def runLog():
return getServerDir() + '/data/redis.log'
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 == 'initd_status':
print(initdStatus())
elif func == 'initd_install':
print(initdInstall())
elif func == 'initd_uninstall':
print(initdUinstall())
elif func == 'conf':
print(getConf())
elif func == 'run_log':
print(runLog())
else:
print('error')
+18
View File
@@ -0,0 +1,18 @@
{
"sort": 7,
"ps": "Docker 是一个开源的应用容器引擎(开发测试中)",
"name": "docker",
"title": "Docker",
"shell": "install.sh",
"versions":["1.0"],
"updates":["1.0"],
"tip": "soft",
"checks": "server/docker",
"path": "server/docker",
"display": 1,
"author": "Zend",
"date": "2017-04-01",
"home": "https://docker.io",
"type": 0,
"pid": "2"
}
+77
View File
@@ -0,0 +1,77 @@
#!/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")
# cd /www/server/mdserver-web/plugins/docker && /bin/bash install.sh uninstall 1.0
# cd /www/server/mdserver-web/plugins/docker && /bin/bash install.sh install 1.0
install_tmp=${rootPath}/tmp/mw_install.pl
VERSION=$2
Install_Docker()
{
which docker
if [ "$?" == "0" ];then
echo '安装已经完成docker' > $install_tmp
exit 0
fi
echo '正在安装脚本文件...' > $install_tmp
mkdir -p $serverPath/source
if [ ! -d $serverPath/docker ];then
curl -fsSL https://get.docker.com | bash
mkdir -p $serverPath/docker
fi
if [ -d $serverPath/docker ];then
echo "${VERSION}" > $serverPath/docker/version.pl
echo '安装完成' > $install_tmp
cd ${rootPath} && python3 ${rootPath}/plugins/docker/index.py start
cd ${rootPath} && python3 ${rootPath}/plugins/docker/index.py initd_install
fi
}
Uninstall_Docker()
{
CMD=yum
which apt
if [ "$?" == "0" ];then
CMD=apt
fi
if [ -f /usr/lib/systemd/system/docker.service ];then
systemctl stop docker
systemctl disable docker
rm -rf /usr/lib/systemd/system/docker.service
systemctl daemon-reload
fi
$CMD remove -y docker docker-ce-cli containerd.io
# docker-client \
# docker-client-latest \
# docker-common \
# docker-latest \
# docker-latest-logrotate \
# docker-logrotate \
# docker-selinux \
# docker-engine-selinux \
# docker-engine \
# docker-ce
rm -rf $serverPath/docker
echo "Uninstall_Docker" > $install_tmp
}
action=$1
if [ "${1}" == 'install' ];then
Install_Docker
else
Uninstall_Docker
fi
+60
View File
@@ -0,0 +1,60 @@
function dPost(method, version, args,callback){
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
var req_data = {};
req_data['name'] = 'docker';
req_data['func'] = method;
req_data['version'] = version;
if (typeof(args) == 'string'){
req_data['args'] = JSON.stringify(toArrayObject(args));
} else {
req_data['args'] = JSON.stringify(args);
}
$.post('/plugins/run', req_data, function(data) {
layer.close(loadT);
if (!data.status){
//错误展示10S
layer.msg(data.msg,{icon:0,time:2000,shade: [10, '#000']});
return;
}
if(typeof(callback) == 'function'){
callback(data);
}
},'json');
}
function dPostCallbak(method, version, args,callback){
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
var req_data = {};
req_data['name'] = 'docker';
req_data['func'] = method;
args['version'] = version;
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 dockerList(){
$(".soft-man-con").html('');
}
@@ -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

+21
View File
@@ -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>
+474
View File
@@ -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')
+16
View File
@@ -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"]
}
+45
View File
@@ -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
+606
View File
@@ -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
-19
View File
@@ -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
```
+5
View File
@@ -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

+1163
View File
File diff suppressed because it is too large Load Diff
+628
View File
@@ -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())
+15
View File
@@ -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
+52
View File
@@ -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()
+1 -1
View File
@@ -11,7 +11,7 @@
"install_pre_inspection":true,
"uninstall_pre_inspection":true,
"display": 1,
"author": "midokos",
"author": "midoks",
"date": "2023-03-06",
"home": "https://core.telegram.org/bots/api",
"depend_doc1":"https://github.com/eternnoir/pyTelegramBotAPI",
+12 -1
View File
@@ -41,6 +41,13 @@ 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
# https://zhaoziyuan.la/ | 15/m | 2m | next,5/15
keyboard = [
[
types.InlineKeyboardButton(
@@ -63,6 +70,8 @@ def send_msg(bot, tag='ad', trigger_time=300):
text="🙎‍♂️代实名🙍‍♀️过人脸🅾️国际阿里云腾讯云(赞助商)", url='https://t.me/gjgzs2022')
],
[
types.InlineKeyboardButton(
text="倩倩CDN服务器", url='https://t.me/KLT_12'),
types.InlineKeyboardButton(
text="💎DigitalVirt(赞助商)", url='https://digitalvirt.com/aff.php?aff=154')
],
@@ -72,7 +81,9 @@ def send_msg(bot, tag='ad', trigger_time=300):
types.InlineKeyboardButton(
text="搜索", url='https://bbs.midoks.me/search.php'),
types.InlineKeyboardButton(
text="@ME", url='tg://user?id=5568699210')
text="@ME", url='tg://user?id=5568699210'),
types.InlineKeyboardButton(
text="100RMB/M", url='tg://user?id=5568699210')
]
]
markup = types.InlineKeyboardMarkup(keyboard)
@@ -66,6 +66,8 @@ def send_msg(bot, tag='ad', trigger_time=300):
text="🙎‍♂️代实名🙍‍♀️过人脸🅾️国际阿里云腾讯云(赞助商)", url='https://t.me/gjgzs2022')
],
[
types.InlineKeyboardButton(
text="倩倩CDN服务器", url='https://t.me/KLT_12'),
types.InlineKeyboardButton(
text="💎DigitalVirt(赞助商)", url='https://digitalvirt.com/aff.php?aff=154')
],
@@ -75,22 +77,19 @@ def send_msg(bot, tag='ad', trigger_time=300):
types.InlineKeyboardButton(
text="搜索", url='https://bbs.midoks.me/search.php'),
types.InlineKeyboardButton(
text="@ME", url='tg://user?id=5568699210')
text="@ME", url='tg://user?id=5568699210'),
types.InlineKeyboardButton(
text="100RMB/M", url='tg://user?id=5568699210')
]
]
markup = types.InlineKeyboardMarkup(keyboard)
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)
time.sleep(60)
time.sleep(90)
try:
bot.delete_message(
chat_id=chat_id, message_id=msg.message_id)
-1
View File
@@ -1,4 +1,3 @@
# coding:utf-8
import sys
Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

+27
View File
@@ -0,0 +1,27 @@
<div class="bt-form">
<div class='plugin_version'></div>
<div class="bt-w-main">
<div class="bt-w-menu">
<p class="bgw" onclick="pluginService('tgclient');">服务</p>
<p onclick="pluginInitD('tgclient');">自启动</p>
<p onclick="clientConf();">配置</p>
<p onclick="botExtList();">扩展列表</p>
<p onclick="pluginLogs('tgclient','','run_log');">日志</p>
<p onclick="readme();">说明</p>
</div>
<div class="bt-w-con pd15">
<div class="soft-man-con"></div>
</div>
</div>
</div>
<style>
.conf_p span{
width: 70px;
}
</style>
<script type="text/javascript">
resetPluginWinHeight(350);
$.getScript( "/plugins/file?name=tgclient&f=js/tgclient.js", function(){
pluginService('tgclient');
});
</script>
+371
View File
@@ -0,0 +1,371 @@
# coding:utf-8
import sys
import io
import os
import time
import re
import json
import base64
sys.path.append(os.getcwd() + "/class/core")
import mw
app_debug = False
if mw.isAppleSystem():
app_debug = True
def getPluginName():
return 'tgclient'
def getPluginDir():
return mw.getPluginDir() + '/' + getPluginName()
def getServerDir():
return mw.getServerDir() + '/' + getPluginName()
def getInitDFile():
if app_debug:
return '/tmp/' + getPluginName()
return '/etc/init.d/' + getPluginName()
def getConfigData():
cfg_path = getServerDir() + "/data.cfg"
if not os.path.exists(cfg_path):
mw.writeFile(cfg_path, '{}')
t = mw.readFile(cfg_path)
return json.loads(t)
def writeConf(data):
cfg_path = getServerDir() + "/data.cfg"
mw.writeFile(cfg_path, json.dumps(data))
return True
def getExtCfg():
cfg_path = getServerDir() + "/extend.cfg"
if not os.path.exists(cfg_path):
mw.writeFile(cfg_path, '{}')
t = mw.readFile(cfg_path)
return json.loads(t)
def writeExtCfg(data):
cfg_path = getServerDir() + "/extend.cfg"
return mw.writeFile(cfg_path, json.dumps(data))
def getInitDTpl():
path = getPluginDir() + "/init.d/" + getPluginName() + ".tpl"
return path
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 status():
data = mw.execShell(
"ps -ef|grep tgclient |grep -v grep | grep -v mdserver-web | awk '{print $2}'")
if data[0] == '':
return 'stop'
return 'start'
def initDreplace():
file_tpl = getInitDTpl()
service_path = mw.getServerDir()
app_path = service_path + '/' + getPluginName()
initD_path = getServerDir() + '/init.d'
if not os.path.exists(initD_path):
os.mkdir(initD_path)
file_bin = initD_path + '/' + getPluginName()
# initd replace
# if not os.path.exists(file_bin):
content = mw.readFile(file_tpl)
content = content.replace('{$SERVER_PATH}', service_path + '/mdserver-web')
content = content.replace('{$APP_PATH}', app_path)
mw.writeFile(file_bin, content)
mw.execShell('chmod +x ' + file_bin)
pyMainTplContent = mw.readFile(getPluginDir() + '/startup/tgclient.py')
toPyMainPath = mw.getServerDir() + '/tgclient.py'
mw.writeFile(toPyMainPath, pyMainTplContent)
# systemd
systemDir = mw.systemdCfgDir()
systemService = systemDir + '/tgclient.service'
systemServiceTpl = getPluginDir() + '/init.d/tgclient.service.tpl'
if os.path.exists(systemDir) and not os.path.exists(systemService):
service_path = mw.getServerDir()
se_content = mw.readFile(systemServiceTpl)
se_content = se_content.replace('{$APP_PATH}', app_path)
mw.writeFile(systemService, se_content)
mw.execShell('systemctl daemon-reload')
return file_bin
def tbOp(method):
file = initDreplace()
if not mw.isAppleSystem():
data = mw.execShell('systemctl ' + method + ' ' + getPluginName())
if data[1] == '':
return 'ok'
return data[1]
data = mw.execShell(file + ' ' + method)
# print(data)
if data[1] == '':
return 'ok'
return 'ok'
def start():
return tbOp('start')
def stop():
return tbOp('stop')
def restart():
status = tbOp('restart')
return status
def reload():
tgbot_tpl = getPluginDir() + '/startup/tgclient.py'
tgbot_dst = getServerDir() + '/tgclient.py'
content = mw.readFile(tgbot_tpl)
mw.writeFile(tgbot_dst, content)
ext_src = getPluginDir() + '/startup/extend'
ext_dst = getServerDir()
mw.execShell('cp -rf ' + ext_src + ' ' + ext_dst)
return tbOp('restart')
def initdStatus():
if mw.isAppleSystem():
return "Apple Computer does not support"
shell_cmd = 'systemctl status ' + \
getPluginName() + ' | grep loaded | grep "enabled;"'
data = mw.execShell(shell_cmd)
if data[0] == '':
return 'fail'
return 'ok'
def initdInstall():
if mw.isAppleSystem():
return "Apple Computer does not support"
mw.execShell('systemctl enable ' + getPluginName())
return 'ok'
def initdUinstall():
if mw.isAppleSystem():
return "Apple Computer does not support"
mw.execShell('systemctl disable ' + getPluginName())
return 'ok'
def getClientConf():
data = getConfigData()
if 'bot' in data:
return mw.returnJson(True, 'ok', data['bot'])
return mw.returnJson(False, 'ok', {})
def setClientConf():
args = getArgs()
data_args = checkArgs(args, ['api_id', 'api_hash'])
if not data_args[0]:
return data_args[1]
data = getConfigData()
args['api_id'] = base64.b64decode(args['api_id']).decode('ascii')
args['api_hash'] = base64.b64decode(args['api_hash']).decode('ascii')
data['bot'] = args
writeConf(data)
return mw.returnJson(True, '保存成功!', [])
def installPreInspection():
i = sys.version_info
if i[0] < 3 or i[1] < 7:
return "telebot在python小于3.7无法正常使用"
return 'ok'
def uninstallPreInspection():
stop()
return "请手动删除<br/> rm -rf {}".format(getServerDir())
def getExtCfgByName(name):
elist = getExtCfg()
for x in elist:
if x['name'] == name:
return x
return None
def clientExtList():
args = getArgs()
data_args = checkArgs(args, ['p'])
if not data_args[0]:
return data_args[1]
ext_path = getServerDir() + '/extend'
if not os.path.exists(ext_path):
return mw.returnJson(False, 'ok', [])
elist_source = os.listdir(ext_path)
elist = []
for e in elist_source:
if e.endswith('py'):
elist.append(e)
page = int(args['p'])
page_size = 5
make_ext_list = []
for ex in elist:
tmp = {}
tmp['name'] = ex
edata = getExtCfgByName(ex)
if edata:
tmp['status'] = edata['status']
else:
tmp['status'] = 'stop'
tmp['tag'] = ex.split('_')[0]
make_ext_list.append(tmp)
writeExtCfg(make_ext_list)
dlist_sum = len(make_ext_list)
page_start = int((page - 1) * page_size)
page_end = page_start + page_size
if page_end >= dlist_sum:
ret_data = make_ext_list[page_start:]
else:
ret_data = make_ext_list[page_start:page_end]
data = {}
data['data'] = ret_data
data['args'] = args
data['list'] = mw.getPage(
{'count': dlist_sum, 'p': page, 'row': page_size, 'tojs': 'botExtListP'})
return mw.returnJson(True, 'ok', data)
def setExtStatus():
args = getArgs()
data_args = checkArgs(args, ['name', 'status'])
if not data_args[0]:
return data_args[1]
elist = getExtCfg()
name = args['name']
status = args['status']
for x in range(len(elist)):
if elist[x]['name'] == name:
elist[x]['status'] = status
break
writeExtCfg(elist)
action = '开启'
if status == 'stop':
action = '关闭'
return mw.returnJson(True, action + '[' + name + ']扩展成功')
def runLog():
p = getServerDir() + '/task.log'
return p
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 == 'initd_status':
print(initdStatus())
elif func == 'initd_install':
print(initdInstall())
elif func == 'initd_uninstall':
print(initdUinstall())
elif func == 'install_pre_inspection':
print(installPreInspection())
elif func == 'uninstall_pre_inspection':
print(uninstallPreInspection())
elif func == 'get_client_conf':
print(getClientConf())
elif func == 'set_client_conf':
print(setClientConf())
elif func == 'client_ext_list':
print(clientExtList())
elif func == 'set_ext_status':
print(setExtStatus())
elif func == 'run_log':
print(runLog())
else:
print('error')
+20
View File
@@ -0,0 +1,20 @@
{
"sort": 7,
"ps": "简单Telegram客服端管理",
"name": "tgclient",
"title": "tgclient",
"shell": "install.sh",
"versions":["1.1"],
"tip": "soft",
"checks": "server/tgclient",
"path": "server/tgclient",
"install_pre_inspection":true,
"uninstall_pre_inspection":true,
"display": 1,
"author": "midoks",
"date": "2023-03-06",
"home": "https://my.telegram.org/apps",
"depend_doc1":"https://docs.telethon.dev/en/stable/basic/installation.html",
"type": 0,
"pid": "5"
}
@@ -0,0 +1,14 @@
[Unit]
Description=Tgbot Service
After=network.target
[Service]
Type=forking
ExecStart={$APP_PATH}/init.d/tgclient start
ExecStop={$APP_PATH}/init.d/tgclient stop
ExecReload={$APP_PATH}/init.d/tgclient reload
KillMode=process
Restart=on-failure
[Install]
WantedBy=multi-user.target
+86
View File
@@ -0,0 +1,86 @@
#!/bin/sh
# chkconfig: 2345 55 25
# description: Tgbot Service
### BEGIN INIT INFO
# Provides: Tgbot
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts Tgbot
# Description: starts the MDW-Web
### END INIT INFO
# Simple Tgbot init.d script conceived to work on Linux systems
# as it does use of the /proc filesystem.
PATH=/usr/local/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export LANG=en_US.UTF-8
mw_path={$SERVER_PATH}
PATH=$PATH:$mw_path/bin
if [ -f $mw_path/bin/activate ];then
source $mw_path/bin/activate
fi
tg_start(){
isStart=`ps -ef|grep 'tgclient.py' |grep -v grep | awk '{print $2}'`
if [ "$isStart" == '' ];then
echo -e "starting tgclient... \c"
cd $mw_path
echo "python3 {$APP_PATH}/tgclient.py"
python3 {$APP_PATH}/tgclient.py >> {$APP_PATH}/task.log &
isStart=""
while [[ "$isStart" == "" ]];
do
echo -e ".\c"
sleep 0.5
isStart=`ps -ef|grep 'tgclient.py' |grep -v grep | awk '{print $2}'`
let n+=1
if [ $n -gt 20 ];then
break;
fi
done
if [ "$isStart" == '' ];then
echo -e "\033[31mfailed\033[0m"
echo -e "\033[31mError: tgclient service startup failed.\033[0m"
return;
fi
echo -e "\033[32mdone\033[0m"
else
echo "starting tgclient...(pid $(echo $isStart)) already running"
fi
}
tg_stop(){
echo -e "stopping tgclient ... \c";
arr=`ps aux|grep 'tgclient.py'|grep -v grep|awk '{print $2}'`
for p in ${arr[@]}
do
kill -9 $p > /dev/null 2>&1
done
echo -e "\033[32mdone\033[0m"
}
case "$1" in
start)
tg_start
;;
stop)
tg_stop
;;
restart|reload)
tg_stop
sleep 0.3
tg_start
;;
*)
echo "Please use start or stop as first argument"
;;
esac
+58
View File
@@ -0,0 +1,58 @@
#!/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
VERSION=$2
# pip3 install ccxt
if [ -f ${rootPath}/bin/activate ];then
source ${rootPath}/bin/activate
fi
pip3 install telethon
Install_App()
{
echo '正在安装脚本文件...' > $install_tmp
mkdir -p $serverPath/source
mkdir -p $serverPath/tgclient
echo "${VERSION}" > $serverPath/tgclient/version.pl
cp -rf ${rootPath}/plugins/tgclient/startup/* $serverPath/tgclient
cd ${rootPath} && python3 ${rootPath}/plugins/tgclient/index.py start
cd ${rootPath} && python3 ${rootPath}/plugins/tgclient/index.py initd_install
echo '安装完成' > $install_tmp
}
Uninstall_App()
{
if [ -f /usr/lib/systemd/system/tgclient.service ];then
systemctl stop tgclient
systemctl disable tgclient
rm -rf /usr/lib/systemd/system/tgclient.service
systemctl daemon-reload
fi
if [ -f $serverPath/tgclient/initd/tgclient ];then
$serverPath/tgclient/initd/tgclient stop
fi
rm -rf $serverPath/tgclient
echo "Uninstall_redis" > $install_tmp
}
action=$1
if [ "${1}" == 'install' ];then
Install_App
else
Uninstall_App
fi
+162
View File
@@ -0,0 +1,162 @@
function readme(){
var readme = '<ul class="help-info-text c7">';
readme += '<li>在填写好配置信息好后,还要执行下面命令。进行手机号和短信码验证。再重启,即可正常使用</li>';
readme += '<li>cd /www/server/mdserver-web && source bin/activate && python3 /www/server/tgclient/tgclient.py</li>';
readme += '</ul>';
$('.soft-man-con').html(readme);
}
function appPost(method, args,callback){
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
var req_data = {};
req_data['name'] = 'tgclient';
req_data['func'] = method;
if (typeof(args) == 'string'){
req_data['args'] = JSON.stringify(toArrayObject(args));
} else {
req_data['args'] = JSON.stringify(args);
}
$.post('/plugins/run', req_data, function(data) {
layer.close(loadT);
if(typeof(callback) == 'function'){
callback(data);
}
},'json');
}
function appPostCallbak(method, args,callback){
var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
var req_data = {};
req_data['name'] = 'tgclient';
req_data['func'] = method;
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 clientConf(){
appPost('get_client_conf','',function(data){
var rdata = $.parseJSON(data.data);
var api_id = 'api_id';
var api_hash = 'api_hash';
if(rdata['status']){
db_data = rdata['data'];
// api_id, api_hash
api_id = db_data['api_id'];
api_hash = db_data['api_hash'];
}
var mlist = '';
mlist += '<p><span>api_id</span><input style="width: 250px;" class="bt-input-text mr5" name="api_id" value="'+api_id+'" type="text"><font>必填写</font></p>';
mlist += '<p><span>api_hash</span><input style="width: 250px;" class="bt-input-text mr5" name="api_hash" value="'+api_hash+'" type="text"><font>必填写</font></p>';
var option = '<style>.conf_p p{margin-bottom: 2px}</style>\
<div class="conf_p" style="margin-bottom:0">\
' + mlist + '\
<div style="margin-top:10px; padding-right:15px" class="text-right">\
<button class="btn btn-success btn-sm" onclick="submitBotConf()">保存</button>\
</div>\
</div>';
$(".soft-man-con").html(option);
});
}
function submitBotConf(){
var pull_data = {};
pull_data['api_id'] = base64_encode($('input[name="api_id"]').val());
pull_data['api_hash'] = base64_encode($('input[name="api_hash"]').val());
appPost('set_client_conf',pull_data,function(data){
var rdata = $.parseJSON(data.data);
layer.msg(rdata['msg'],{icon:rdata['status']?1:2,time:2000,shade: [0.3, '#000']});
});
}
function botExtList(){
var body = '<div class="divtable mtb10">\
<table class="table table-hover" width="100%" cellspacing="0" cellpadding="0" border="0">\
<thead>\
<tr>\
<th width="20">脚本</th>\
<th width="120">类型</th>\
<th width="10">状态</th>\
</tr>\
</thead>\
<tbody id="ext_list"></tbody>\
</table>\
<div class="dataTables_paginate paging_bootstrap pagination">\
<ul id="ext_list_page" class="page"></ul>\
</div>\
</div>';
$('.soft-man-con').html(body);
botExtListP(1);
}
function setBotExtStatus(name,status){
appPost('set_ext_status',{'name':name,'status':status}, function(rdata){
var rdata = $.parseJSON(rdata.data);
layer.msg(rdata['msg'],);
showMsg(rdata['msg'], function(){
botExtListP(1);
},{icon:rdata['status']?1:2,shade: [0.3, '#000']},2000);
});
}
function botExtListP(p=1){
appPost('client_ext_list',{'p':p}, function(rdata){
// console.log(rdata);
var rdata = $.parseJSON(rdata.data);
// console.log(rdata);
var tBody = '';
if (!rdata.status && rdata.data.length == 0 ){
var tBody = '<tr><td colspan="4"><div style="text-align:center;">无数据</div></td></tr>';
} else{
var ldata = rdata.data.data;
for (var i = 0; i < ldata.length; i++) {
tBody += '<tr data-name="'+ldata[i]['name']+'">'
tBody += '<td>'+ldata[i]['name']+'</td>';
tBody += '<td>'+ldata[i]['tag']+'</td>';
if (ldata[i]['status'] == 'start'){
tBody += '<td><span style="color:#20a53a;cursor: pointer;" class="ext_status glyphicon glyphicon-play"></span></td>';
} else{
tBody += '<td><span style="color:red;cursor: pointer;" class="ext_status glyphicon glyphicon-pause"></span></td>';
}
tBody +='<tr>';
}
}
$('#ext_list').html(tBody);
$('#ext_list_page').html(rdata.data.list);
$('#ext_list .ext_status').click(function(){
var name = $(this).parent().parent().data('name');
var status = 'stop';
if ($(this).hasClass('glyphicon-pause')){
status = 'start';
}
setBotExtStatus(name,status);
});
});
}
@@ -0,0 +1,45 @@
# coding:utf-8
# func: 自动检测已经注销群成员
import sys
import io
import os
import time
import re
import json
import base64
import threading
import asyncio
sys.path.append(os.getcwd() + "/class/core")
import mw
import telebot
from telebot import types
from telebot.util import quick_markup
# 指定群ID
chat_id_list = [-1001578009023, -1001979545570]
async def run(client):
while True:
for chat_id in chat_id_list:
s = await client.send_message(chat_id, '开始自动检测已经注销群成员...')
count = 0
async for user in client.iter_participants(chat_id):
if user.deleted:
count += 1
msg = await client.kick_participant(chat_id, user)
await client.edit_message(chat_id, s.id, '已经检测到有(%d)个账户已失效' % (count))
await asyncio.sleep(3)
await client.edit_message(chat_id, s.id, '自动检测已经注销群成员完毕!!!')
await asyncio.sleep(3)
await client.delete_messages(chat_id, s)
await asyncio.sleep(300)
if __name__ == "__main__":
pass
@@ -0,0 +1,57 @@
# coding:utf-8
# func: 自动邀请群成员
import sys
import io
import os
import time
import re
import json
import base64
import threading
import asyncio
sys.path.append(os.getcwd() + "/class/core")
import mw
from telethon import utils
from telethon import functions, types
from telethon.tl.functions.messages import AddChatUserRequest
from telethon.tl.functions.channels import InviteToChannelRequest
# 指定群ID
chat_id = -1001979545570
filter_user_id = 5568699210
async def run(client):
while True:
info = await client.get_dialogs()
for chat in info:
print('name:{0} ids:{1} is_user:{2} is_channel:{3} is_group:{4}'.format(
chat.name, chat.id, chat.is_user, chat.is_channel, chat.is_group))
if chat.is_group and chat.id != chat_id:
list_user = []
async for user in client.iter_participants(chat.id):
# print(chat.id, user.id, user.first_name)
if filter_user_id != user.id and user.username != None and user.bot == False:
# print(user.first_name)
list_user.append(user.username)
# await client(AddChatUserRequest(
# chat_id=chat_id, # chat_id
# user_id=user.username, # 被邀请人id
# fwd_limit=1000 # Allow the user to see the 10 last messages
# ))
# print(list_user)
try:
await client(InviteToChannelRequest(
channel=chat_id, # chat_id
users=list_user, # 被邀请人id
))
except Exception as e:
print(str(e))
await asyncio.sleep(80000)
await asyncio.sleep(80000)
if __name__ == "__main__":
pass
+1
View File
@@ -0,0 +1 @@
push_*.py 识别为推送插件
+124
View File
@@ -0,0 +1,124 @@
# coding:utf-8
import sys
import io
import os
import time
import re
import json
import base64
import threading
import asyncio
import logging
# python /Users/midoks/Desktop/mwdev/server/tgclient/tgclient.py
# cd /www/server/mdserver-web && python3 /www/server/tgclient/tgclient.py
from telethon import TelegramClient
sys.path.append(os.getcwd() + "/class/core")
import mw
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def getPluginName():
return 'tgclient'
def getPluginDir():
return mw.getPluginDir() + '/' + getPluginName()
def getServerDir():
return mw.getServerDir() + '/' + getPluginName()
sys.path.append(getServerDir() + "/extend")
def getConfigData():
cfg_path = getServerDir() + "/data.cfg"
if not os.path.exists(cfg_path):
mw.writeFile(cfg_path, '{}')
t = mw.readFile(cfg_path)
return json.loads(t)
def writeConf(data):
cfg_path = getServerDir() + "/data.cfg"
mw.writeFile(cfg_path, json.dumps(data))
return True
def getExtCfg():
cfg_path = getServerDir() + "/extend.cfg"
if not os.path.exists(cfg_path):
mw.writeFile(cfg_path, '{}')
t = mw.readFile(cfg_path)
return json.loads(t)
def getStartExtCfgByTag(tag='push'):
# 获取开启的扩展
elist = getExtCfg()
rlist = []
for x in elist:
if x['tag'] == tag and x['status'] == 'start':
rlist.append(x)
return rlist
def writeLog(log_str):
if __name__ == "__main__":
print(log_str)
now = mw.getDateFromNow()
log_file = getServerDir() + '/task.log'
mw.writeFileLog(now + ':' + log_str, log_file, limit_size=5 * 1024)
return True
# start tgbot
cfg = getConfigData()
while True:
cfg = getConfigData()
if 'bot' in cfg and 'api_id' in cfg['bot']:
if cfg['bot']['api_id'] != '' and cfg['bot']['api_id'] != 'api_id':
break
if cfg['bot']['api_hash'] != '' and cfg['bot']['api_hash'] != 'api_hash':
break
writeLog('等待输入配置,api_id')
time.sleep(3)
client = TelegramClient('mdioks', cfg['bot']['api_id'], cfg['bot']['api_hash'])
async def plugins_run():
plist = getStartExtCfgByTag('client')
for p in plist:
try:
script = p['name'].split('.')[0]
await __import__(script).run(client)
except Exception as e:
writeLog('----- client error start -------')
writeLog(mw.getTracebackInfo())
writeLog('----- client error end -------')
async def main(loop):
await client.start()
# create new task
writeLog('creating plugins_run task.')
task = loop.create_task(plugins_run())
await task
writeLog('It works.')
await client.run_until_disconnected()
task.cancel()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
+2 -2
View File
@@ -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