mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
1. Added Domains menu to manage Domains and SSL Certificates 2. Added Add Site automatic record creation when adding websites (Only supported by IPv4) 3. Added Vietnamese language 4. Added Indonesian language 5. Added HTTPS Protection function for Website (disables automatic HTTP to HTTPS redirection when enabled) 6. Added Mail Marketing -- Automation trigger tasks 7. Added Mail Marketing -- Groups Import, Export, Merge 8. Added Mail Marketing -- Subscribers to support paste import 9. Added Mail Marketing -- Template Import Export Duplicate 10. Added Mail server -- Other Settings add timed Auto Responder 11. Added Mail Marketing -- Suspend List to detect abnormal mailboxes 12. Added Mail Marketing -- Marketing Task to export error mail logs 13. Added Mail Domain -- SSL certificate expiration alert 14. Optimized Webmail management to display based on domain additions 15. Optimize Website Interface Button Integration 16. Optimize the response speed of WP Toolkit interface
1786 lines
63 KiB
Python
1786 lines
63 KiB
Python
#!/bin/python
|
|
#coding: utf-8
|
|
# +-------------------------------------------------------------------
|
|
# | aaPanel
|
|
# +-------------------------------------------------------------------
|
|
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
|
|
# +-------------------------------------------------------------------
|
|
# | Author: hwliang <hwl@aapanel.com>
|
|
# +-------------------------------------------------------------------
|
|
|
|
# ------------------------------
|
|
# 计划任务
|
|
# ------------------------------
|
|
|
|
# import tracemalloc
|
|
# import objgraph
|
|
import sys
|
|
import os
|
|
import logging
|
|
from datetime import datetime, timedelta,timezone
|
|
from json import dumps, loads
|
|
from psutil import Process, pids, cpu_count, cpu_percent, net_io_counters, disk_io_counters, virtual_memory, pids, pid_exists, NoSuchProcess, AccessDenied, ZombieProcess
|
|
os.environ['BT_TASK'] = '1'
|
|
base_path = "/www/server/panel"
|
|
sys.path.insert(0, "/www/server/panel/class/")
|
|
if os.path.exists("/www/server/panel/plugin/mail_sys"):
|
|
sys.path.insert(1, "/www/server/panel/plugin/mail_sys")
|
|
import time
|
|
import public
|
|
import db
|
|
import json
|
|
import threading
|
|
import panelTask
|
|
import process_task
|
|
|
|
from public.hook_import import hook_import
|
|
hook_import()
|
|
|
|
from power_mta.maillog_stat import maillog_event, aggregate_maillogs_task
|
|
from power_mta.automations import schedule_automations_forever
|
|
|
|
|
|
task_obj = panelTask.bt_task()
|
|
task_obj.not_web = True
|
|
global pre, timeoutCount, logPath, isTask, oldEdate, isCheck
|
|
pre = 0
|
|
timeoutCount = 0
|
|
isCheck = 0
|
|
oldEdate = None
|
|
logPath = '/tmp/panelExec.log'
|
|
isTask = '/tmp/panelTask.pl'
|
|
python_bin = None
|
|
thread_dict = {}
|
|
|
|
|
|
|
|
# def log_malloc():
|
|
# snapshot = tracemalloc.take_snapshot()
|
|
# top_stats = snapshot.statistics('lineno')
|
|
#
|
|
# s = 'TOP 50 difference (traced_memory: {} tracemalloc_memory: {})\n'.format(tracemalloc.get_traced_memory(), tracemalloc.get_tracemalloc_memory())
|
|
# for stat in top_stats[:50]:
|
|
# s += '{}\n'.format(stat)
|
|
#
|
|
# with open('{}/logs/malloc.log'.format(public.get_panel_path()), 'a') as fp:
|
|
# fp.write('[{}]\n'.format(time.strftime('%Y-%m-%d %X')))
|
|
# fp.write('{}\n'.format(s))
|
|
#
|
|
#
|
|
# def objgraph_log():
|
|
# with open('{}/logs/objgraph.log'.format(public.get_panel_path()), 'a') as fp:
|
|
# fp.write('[{}]\n'.format(time.strftime('%Y-%m-%d %X')))
|
|
# fp.write('-------------------- SHOW GROWTH --------------------\n')
|
|
# objgraph.show_growth(limit=50, file=fp)
|
|
# fp.write('\n\n')
|
|
#
|
|
# fp.write('-------------------- SHOW MOST COMMON TYPES --------------------\n')
|
|
# objgraph.show_most_common_types(limit=50, file=fp)
|
|
# fp.write('\n\n')
|
|
#
|
|
#
|
|
# def print_malloc_thread():
|
|
# time.sleep(60)
|
|
# log_malloc()
|
|
# objgraph_log()
|
|
# print_malloc_thread()
|
|
|
|
|
|
def get_python_bin():
|
|
global python_bin
|
|
if python_bin: return python_bin
|
|
bin_file = '/www/server/panel/pyenv/bin/python'
|
|
bin_file2 = '/usr/bin/python'
|
|
if os.path.exists(bin_file):
|
|
python_bin = bin_file
|
|
return bin_file
|
|
python_bin = bin_file2
|
|
return bin_file2
|
|
def WriteFile(filename,s_body,mode='w+'):
|
|
"""
|
|
写入文件内容
|
|
@filename 文件名
|
|
@s_body 欲写入的内容
|
|
return bool 若文件不存在则尝试自动创建
|
|
"""
|
|
try:
|
|
fp = open(filename, mode)
|
|
fp.write(s_body)
|
|
fp.close()
|
|
return True
|
|
except:
|
|
try:
|
|
fp = open(filename, mode,encoding="utf-8")
|
|
fp.write(s_body)
|
|
fp.close()
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
def ReadFile(filename, mode='r'):
|
|
"""
|
|
读取文件内容
|
|
@filename 文件名
|
|
return string(bin) 若文件不存在,则返回None
|
|
"""
|
|
if not os.path.exists(filename):
|
|
return False
|
|
f_body = None
|
|
with open(filename, mode) as fp:
|
|
f_body = fp.read()
|
|
return f_body
|
|
|
|
# 下载文件
|
|
def DownloadFile(url, filename):
|
|
try:
|
|
import urllib
|
|
import socket
|
|
socket.setdefaulttimeout(10)
|
|
urllib.urlretrieve(url, filename=filename, reporthook=DownloadHook)
|
|
os.system('chown www.www ' + filename)
|
|
WriteLogs('done')
|
|
except:
|
|
WriteLogs('done')
|
|
|
|
|
|
# 下载文件进度回调
|
|
def DownloadHook(count, blockSize, totalSize):
|
|
global pre
|
|
used = count * blockSize
|
|
pre1 = int((100.0 * used / totalSize))
|
|
if pre == pre1:
|
|
return
|
|
speed = {'total': totalSize, 'used': used, 'pre': pre}
|
|
WriteLogs(dumps(speed))
|
|
pre = pre1
|
|
|
|
# 写输出日志
|
|
def WriteLogs(logMsg):
|
|
try:
|
|
global logPath
|
|
with open(logPath, 'w+') as fp:
|
|
fp.write(logMsg)
|
|
fp.close()
|
|
except:
|
|
pass
|
|
|
|
|
|
def ExecShell(cmdstring, cwd=None, timeout=None, shell=True, symbol = '&>'):
|
|
try:
|
|
global logPath
|
|
import shlex
|
|
import subprocess
|
|
import time
|
|
sub = subprocess.Popen(cmdstring+ symbol +logPath, cwd=cwd,
|
|
stdin=subprocess.PIPE, shell=shell, bufsize=4096)
|
|
|
|
while sub.poll() is None:
|
|
time.sleep(0.1)
|
|
|
|
return sub.returncode
|
|
except:
|
|
return None
|
|
|
|
|
|
# 任务队列
|
|
def startTask():
|
|
global isTask,logPath,thread_dict
|
|
tip_file = '/dev/shm/.panelTask.pl'
|
|
n = 0
|
|
tick = 60
|
|
while 1:
|
|
try:
|
|
if os.path.exists(isTask):
|
|
with db.Sql() as sql:
|
|
sql.table('tasks').where(
|
|
"status=?", ('-1',)).setField('status', '0')
|
|
taskArr = sql.table('tasks').where("status=?", ('0',)).field('id,type,execstr').order("id asc").select()
|
|
for value in taskArr:
|
|
start = int(time.time())
|
|
if not sql.table('tasks').where("id=?", (value['id'],)).count():
|
|
public.writeFile(tip_file, str(int(time.time())))
|
|
continue
|
|
sql.table('tasks').where("id=?", (value['id'],)).save('status,start', ('-1', start))
|
|
if value['type'] == 'download':
|
|
argv = value['execstr'].split('|bt|')
|
|
DownloadFile(argv[0], argv[1])
|
|
elif value['type'] == 'execshell':
|
|
ExecShell(value['execstr'])
|
|
end = int(time.time())
|
|
sql.table('tasks').where("id=?", (value['id'],)).save('status,end', ('1', end))
|
|
if(sql.table('tasks').where("status=?", ('0')).count() < 1):
|
|
if os.path.exists(isTask):
|
|
os.remove(isTask)
|
|
sql.close()
|
|
taskArr = None
|
|
public.writeFile(tip_file, str(int(time.time())))
|
|
|
|
# 线程检查
|
|
n+=1
|
|
if n > tick:
|
|
run_thread()
|
|
n = 0
|
|
except:
|
|
pass
|
|
time.sleep(2)
|
|
|
|
|
|
|
|
# 网站到期处理
|
|
def siteEdate():
|
|
global oldEdate
|
|
try:
|
|
if not oldEdate:
|
|
oldEdate = ReadFile('/www/server/panel/data/edate.pl')
|
|
if not oldEdate:
|
|
oldEdate = '0000-00-00'
|
|
mEdate = time.strftime('%Y-%m-%d', time.localtime())
|
|
if oldEdate == mEdate:
|
|
return False
|
|
oldEdate = mEdate
|
|
os.system("nohup " + get_python_bin() + " /www/server/panel/script/site_task.py > /dev/null 2>&1 &")
|
|
|
|
except Exception as ex:
|
|
logging.info(ex)
|
|
pass
|
|
|
|
|
|
def GetLoadAverage():
|
|
c = os.getloadavg()
|
|
data = {}
|
|
data['one'] = float(c[0])
|
|
data['five'] = float(c[1])
|
|
data['fifteen'] = float(c[2])
|
|
data['max'] = cpu_count() * 2
|
|
data['limit'] = data['max']
|
|
data['safe'] = data['max'] * 0.75
|
|
return data
|
|
|
|
|
|
# 系统监控任务
|
|
def systemTask():
|
|
try:
|
|
filename = '{}/data/control.conf'.format(base_path)
|
|
with db.Sql() as sql:
|
|
sql = sql.dbfile('system')
|
|
|
|
csql = '''CREATE TABLE IF NOT EXISTS `load_average` (
|
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
`pro` REAL,
|
|
`one` REAL,
|
|
`five` REAL,
|
|
`fifteen` REAL,
|
|
`addtime` INTEGER
|
|
)'''
|
|
|
|
network_sql = '''CREATE TABLE IF NOT EXISTS `network` (
|
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
`up` INTEGER,
|
|
`down` INTEGER,
|
|
`total_up` INTEGER,
|
|
`total_down` INTEGER,
|
|
`down_packets` INTEGER,
|
|
`up_packets` INTEGER,
|
|
`addtime` INTEGER
|
|
)'''
|
|
|
|
cpuio_sql = '''CREATE TABLE IF NOT EXISTS `cpuio` (
|
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
`pro` INTEGER,
|
|
`mem` INTEGER,
|
|
`addtime` INTEGER
|
|
)'''
|
|
|
|
diskio_sql = '''CREATE TABLE IF NOT EXISTS `diskio` (
|
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
`read_count` INTEGER,
|
|
`write_count` INTEGER,
|
|
`read_bytes` INTEGER,
|
|
`write_bytes` INTEGER,
|
|
`read_time` INTEGER,
|
|
`write_time` INTEGER,
|
|
`addtime` INTEGER
|
|
)'''
|
|
|
|
|
|
sql.execute(csql, ())
|
|
sql.execute(network_sql, ())
|
|
sql.execute(cpuio_sql, ())
|
|
sql.execute(diskio_sql, ())
|
|
|
|
sql.close()
|
|
|
|
count = 0
|
|
reloadNum=0
|
|
diskio_1 = diskio_2 = networkInfo = cpuInfo = diskInfo = None
|
|
network_up = {}
|
|
network_down = {}
|
|
cycle = 60
|
|
# try:
|
|
# from panelDaily import panelDaily
|
|
# panelDaily().check_databases()
|
|
# except Exception as e:
|
|
# logging.info(e)
|
|
|
|
proc_task_obj = process_task.process_task()
|
|
|
|
while True:
|
|
if not os.path.exists(filename):
|
|
time.sleep(10)
|
|
continue
|
|
|
|
day = 30
|
|
try:
|
|
day = int(ReadFile(filename))
|
|
if day < 1:
|
|
time.sleep(10)
|
|
continue
|
|
except:
|
|
day = 30
|
|
|
|
|
|
addtime = int(time.time())
|
|
deltime = addtime - (day * 86400)
|
|
# 取当前CPU Io
|
|
tmp = {}
|
|
tmp['used'] = proc_task_obj.get_monitor_list(addtime)
|
|
tmp['mem'] = GetMemUsed()
|
|
cpuInfo = tmp
|
|
|
|
# 取当前网络Io
|
|
networkIo_list = net_io_counters(pernic=True)
|
|
tmp = {}
|
|
tmp['upTotal'] = 0
|
|
tmp['downTotal'] = 0
|
|
tmp['up'] = 0
|
|
tmp['down'] = 0
|
|
tmp['downPackets'] = {}
|
|
tmp['upPackets'] = {}
|
|
|
|
for k in networkIo_list.keys():
|
|
networkIo = networkIo_list[k][:4]
|
|
if not k in network_up.keys():
|
|
network_up[k] = networkIo[0]
|
|
network_down[k] = networkIo[1]
|
|
|
|
tmp['upTotal'] += networkIo[0]
|
|
tmp['downTotal'] += networkIo[1]
|
|
tmp['downPackets'][k] = round(
|
|
float((networkIo[1] - network_down[k]) / 1024)/cycle, 2)
|
|
tmp['upPackets'][k] = round(
|
|
float((networkIo[0] - network_up[k]) / 1024)/cycle, 2)
|
|
tmp['up'] += tmp['upPackets'][k]
|
|
tmp['down'] += tmp['downPackets'][k]
|
|
|
|
network_up[k] = networkIo[0]
|
|
network_down[k] = networkIo[1]
|
|
|
|
# if not networkInfo:
|
|
# networkInfo = tmp
|
|
# if (tmp['up'] + tmp['down']) > (networkInfo['up'] + networkInfo['down']):
|
|
networkInfo = tmp
|
|
|
|
# 取磁盘Io
|
|
disk_ios = True
|
|
try:
|
|
if os.path.exists('/proc/diskstats'):
|
|
diskio_2 = disk_io_counters()
|
|
|
|
if not diskio_1:
|
|
diskio_1 = diskio_2
|
|
tmp = {}
|
|
tmp['read_count'] = int((diskio_2.read_count - diskio_1.read_count) / cycle)
|
|
tmp['write_count'] = int((diskio_2.write_count - diskio_1.write_count) / cycle)
|
|
tmp['read_bytes'] = int((diskio_2.read_bytes - diskio_1.read_bytes) / cycle)
|
|
tmp['write_bytes'] = int((diskio_2.write_bytes - diskio_1.write_bytes) / cycle)
|
|
tmp['read_time'] = int((diskio_2.read_time - diskio_1.read_time) / cycle)
|
|
tmp['write_time'] = int((diskio_2.write_time - diskio_1.write_time) / cycle)
|
|
|
|
if not diskInfo:
|
|
diskInfo = tmp
|
|
|
|
# if (tmp['read_bytes'] + tmp['write_bytes']) > (diskInfo['read_bytes'] + diskInfo['write_bytes']):
|
|
diskInfo['read_count'] = tmp['read_count']
|
|
diskInfo['write_count'] = tmp['write_count']
|
|
diskInfo['read_bytes'] = tmp['read_bytes']
|
|
diskInfo['write_bytes'] = tmp['write_bytes']
|
|
diskInfo['read_time'] = tmp['read_time']
|
|
diskInfo['write_time'] = tmp['write_time']
|
|
|
|
# logging.info(['read: ',tmp['read_bytes'] / 1024 / 1024,'write: ',tmp['write_bytes'] / 1024 / 1024])
|
|
diskio_1 = diskio_2
|
|
except:
|
|
logging.info(public.get_error_info())
|
|
disk_ios = False
|
|
|
|
try:
|
|
sql = db.Sql().dbfile('system')
|
|
data = (cpuInfo['used'], cpuInfo['mem'], addtime)
|
|
#
|
|
sql.table('cpuio').add('pro,mem,addtime', data)
|
|
sql.table('cpuio').where("addtime<?", (deltime,)).delete()
|
|
data = (networkInfo['up'], networkInfo['down'], networkInfo['upTotal'], networkInfo['downTotal'], dumps(networkInfo['downPackets']), dumps(networkInfo['upPackets']), addtime)
|
|
sql.table('network').add('up,down,total_up,total_down,down_packets,up_packets,addtime', data)
|
|
sql.table('network').where("addtime<?", (deltime,)).delete()
|
|
# logging.info(diskInfo)
|
|
if os.path.exists('/proc/diskstats') and disk_ios:
|
|
data = (diskInfo['read_count'], diskInfo['write_count'], diskInfo['read_bytes'],diskInfo['write_bytes'], diskInfo['read_time'], diskInfo['write_time'], addtime)
|
|
sql.table('diskio').add('read_count,write_count,read_bytes,write_bytes,read_time,write_time,addtime', data)
|
|
sql.table('diskio').where("addtime<?", (deltime,)).delete()
|
|
|
|
# LoadAverage
|
|
load_average = GetLoadAverage()
|
|
lpro = round(
|
|
(load_average['one'] / load_average['max']) * 100, 2)
|
|
if lpro > 100:
|
|
lpro = 100
|
|
sql.table('load_average').add('pro,one,five,fifteen,addtime', (lpro, load_average['one'], load_average['five'], load_average['fifteen'], addtime))
|
|
sql.table('load_average').where("addtime<?", (deltime,)).delete()
|
|
sql.close()
|
|
|
|
lpro = None
|
|
load_average = None
|
|
cpuInfo = None
|
|
networkInfo = None
|
|
diskInfo = None
|
|
data = None
|
|
count = 0
|
|
reloadNum += 1
|
|
if reloadNum > 1440:
|
|
reloadNum = 0
|
|
|
|
|
|
|
|
# 日报数据收集
|
|
# if os.path.exists("/www/server/panel/data/start_daily.pl"):
|
|
# try:
|
|
# from panelDaily import panelDaily
|
|
# pd = panelDaily()
|
|
# t_now = time.localtime()
|
|
# yesterday = time.localtime(time.mktime((
|
|
# t_now.tm_year, t_now.tm_mon, t_now.tm_mday-1,
|
|
# 0,0,0,0,0,0
|
|
# )))
|
|
# yes_time_key = pd.get_time_key(yesterday)
|
|
# con = ReadFile("/www/server/panel/data/store_app_usage.pl")
|
|
# # logging.info(str(con))
|
|
# store = False
|
|
# if con:
|
|
# if con != str(yes_time_key):
|
|
# store = True
|
|
# else:
|
|
# store = True
|
|
#
|
|
# if store:
|
|
# date_str = str(yes_time_key)
|
|
# daily_data = pd.get_daily_data_local(date_str)
|
|
# if "status" in daily_data.keys():
|
|
# if daily_data["status"]:
|
|
# score = daily_data["score"]
|
|
# if public.M("system").dbfile("system").table("daily").where("time_key=?", (yes_time_key,)).count() == 0:
|
|
# public.M("system").dbfile("system").table("daily").add("time_key,evaluate,addtime", (yes_time_key, score, time.time()))
|
|
# pd.store_app_usage(yes_time_key)
|
|
# WriteFile("/www/server/panel/data/store_app_usage.pl", str(yes_time_key), "w")
|
|
# # logging.info("更新应用存储信息:"+str(yes_time_key))
|
|
# pd.check_server()
|
|
# except Exception as e:
|
|
# logging.info("存储应用空间信息错误:"+str(e))
|
|
except Exception as ex:
|
|
logging.info(str(ex))
|
|
del(tmp)
|
|
time.sleep(cycle)
|
|
count += 1
|
|
except Exception as ex:
|
|
logging.info(ex)
|
|
time.sleep(cycle)
|
|
systemTask()
|
|
|
|
|
|
# 取内存使用率
|
|
def GetMemUsed():
|
|
try:
|
|
mem = virtual_memory()
|
|
memInfo = {'memTotal': mem.total/1024/1024, 'memFree': mem.free/1024/1024,
|
|
'memBuffers': mem.buffers/1024/1024, 'memCached': mem.cached/1024/1024}
|
|
tmp = memInfo['memTotal'] - memInfo['memFree'] - \
|
|
memInfo['memBuffers'] - memInfo['memCached']
|
|
tmp1 = memInfo['memTotal'] / 100
|
|
return (tmp / tmp1)
|
|
except:
|
|
return 1
|
|
|
|
# 检查502错误
|
|
|
|
|
|
def check502():
|
|
try:
|
|
phpversions = public.get_php_versions()
|
|
for version in phpversions:
|
|
if version in ['52','5.2']: continue
|
|
php_path = '/www/server/php/' + version + '/sbin/php-fpm'
|
|
if not os.path.exists(php_path):
|
|
continue
|
|
if checkPHPVersion(version):
|
|
continue
|
|
if startPHPVersion(version):
|
|
public.WriteLog('PHP daemon',
|
|
'PHP-' + version + 'processing exception was detected and has been automatically fixed!',
|
|
not_web=True)
|
|
except Exception as ex:
|
|
logging.info(ex)
|
|
|
|
# 处理指定PHP版本
|
|
|
|
|
|
def startPHPVersion(version):
|
|
try:
|
|
fpm = '/etc/init.d/php-fpm-' + version
|
|
php_path = '/www/server/php/' + version + '/sbin/php-fpm'
|
|
if not os.path.exists(php_path):
|
|
if os.path.exists(fpm): os.remove(fpm)
|
|
return False
|
|
|
|
# 尝试重载服务
|
|
os.system(fpm + ' start')
|
|
os.system(fpm + ' reload')
|
|
if checkPHPVersion(version): return True
|
|
|
|
# 尝试重启服务
|
|
cgi = '/tmp/php-cgi-' + version + '.sock'
|
|
pid = '/www/server/php/' + version + '/var/run/php-fpm.pid'
|
|
os.system('pkill -9 php-fpm-' + version)
|
|
time.sleep(0.5)
|
|
if os.path.exists(cgi):
|
|
os.remove(cgi)
|
|
if os.path.exists(pid):
|
|
os.remove(pid)
|
|
os.system(fpm + ' start')
|
|
if checkPHPVersion(version):
|
|
return True
|
|
# 检查是否正确启动
|
|
if os.path.exists(cgi):
|
|
return True
|
|
return False
|
|
except Exception as ex:
|
|
logging.info(ex)
|
|
return True
|
|
|
|
|
|
# 检查指定PHP版本
|
|
def checkPHPVersion(version):
|
|
try:
|
|
cgi_file = '/tmp/php-cgi-{}.sock'.format(version)
|
|
if os.path.exists(cgi_file):
|
|
init_file = '/etc/init.d/php-fpm-{}'.format(version)
|
|
if os.path.exists(init_file):
|
|
init_body = public.ReadFile(init_file)
|
|
if not init_body: return True
|
|
uri = "/phpfpm_"+version+"_status?json"
|
|
result = public.request_php(version, uri, '')
|
|
loads(result)
|
|
return True
|
|
except:
|
|
logging.info("PHP-{} unreachable detected".format(version))
|
|
return False
|
|
|
|
|
|
# 502错误检查线程
|
|
def check502Task():
|
|
try:
|
|
while True:
|
|
public.auto_backup_panel()
|
|
check502()
|
|
sess_expire()
|
|
mysql_quota_check()
|
|
siteEdate()
|
|
time.sleep(600)
|
|
except Exception as ex:
|
|
logging.info(ex)
|
|
time.sleep(600)
|
|
check502Task()
|
|
|
|
# MySQL配额检查
|
|
def mysql_quota_check():
|
|
os.system("nohup " + get_python_bin() +" /www/server/panel/script/mysql_quota.py > /dev/null 2>&1 &")
|
|
|
|
# session过期处理
|
|
def sess_expire():
|
|
try:
|
|
sess_path = '{}/data/session'.format(base_path)
|
|
if not os.path.exists(sess_path): return
|
|
s_time = time.time()
|
|
f_list = os.listdir(sess_path)
|
|
f_num = len(f_list)
|
|
for fname in f_list:
|
|
filename = '/'.join((sess_path, fname))
|
|
fstat = os.stat(filename)
|
|
f_time = s_time - fstat.st_mtime
|
|
if f_time > 3600:
|
|
os.remove(filename)
|
|
continue
|
|
if fstat.st_size < 256 and len(fname) == 32:
|
|
if f_time > 60 or f_num > 30:
|
|
os.remove(filename)
|
|
continue
|
|
del (f_list)
|
|
|
|
except Exception as ex:
|
|
logging.info(str(ex))
|
|
|
|
|
|
# 检查面板证书是否有更新
|
|
def check_panel_ssl():
|
|
try:
|
|
while True:
|
|
lets_info = ReadFile("{}/ssl/lets.info".format(base_path))
|
|
if not lets_info:
|
|
time.sleep(3600)
|
|
continue
|
|
os.system(get_python_bin() + " {}/script/panel_ssl_task.py > /dev/null".format(base_path))
|
|
time.sleep(3600)
|
|
except Exception as e:
|
|
public.writeFile("/tmp/panelSSL.pl", str(e), "a+")
|
|
|
|
# 面板进程守护
|
|
def daemon_panel11():
|
|
cycle = 10
|
|
panel_pid_file = "{}/logs/panel.pid".format(public.get_panel_path())
|
|
while 1:
|
|
time.sleep(cycle)
|
|
|
|
# 检查pid文件是否存在
|
|
if not os.path.exists(panel_pid_file):
|
|
continue
|
|
|
|
# 读取pid文件
|
|
panel_pid = public.readFile(panel_pid_file)
|
|
if not panel_pid:
|
|
logging.info("not pid -- {}".format(panel_pid_file))
|
|
service_panel('start')
|
|
continue
|
|
|
|
# 检查进程是否存在
|
|
comm_file = "/proc/{}/comm".format(panel_pid)
|
|
if not os.path.exists(comm_file):
|
|
logging.info("not comm_file-- {}".format(comm_file))
|
|
service_panel('start')
|
|
continue
|
|
|
|
# 是否为面板进程
|
|
comm = public.readFile(comm_file)
|
|
if comm.find('BT-Panel') == -1:
|
|
logging.info("not BT-Panel-- {}".format(comm))
|
|
service_panel('start')
|
|
continue
|
|
|
|
# # 是否为面板进程
|
|
# with open(comm_file, 'r') as f:
|
|
# comm = f.read()
|
|
# if comm.find('BT-Panel') == -1:
|
|
# logging.info("3 not BT-Panel-- {}".format(comm))
|
|
# service_panel('start')
|
|
# continue
|
|
|
|
|
|
# 查找面板进程并返回PID
|
|
def find_panel_pid():
|
|
for pid in pids():
|
|
try:
|
|
p = Process(pid)
|
|
if 'BT-Panel' in p.name(): # 假设进程名包含 'BT-Panel'
|
|
return pid
|
|
except (NoSuchProcess, AccessDenied, ZombieProcess):
|
|
continue
|
|
return None
|
|
|
|
# 更新PID文件
|
|
def update_pid_file(pid):
|
|
pid_file = "{}/logs/panel.pid".format(public.get_panel_path())
|
|
try:
|
|
with open(pid_file, 'w') as f:
|
|
f.write(str(pid))
|
|
logging.info(f'Updated panel PID file with PID {pid}')
|
|
except Exception as e:
|
|
logging.error(f'Error writing to PID file: {e}')
|
|
|
|
|
|
def daemon_panel():
|
|
cycle = 10
|
|
panel_pid_file = "{}/logs/panel.pid".format(public.get_panel_path())
|
|
|
|
while True:
|
|
time.sleep(cycle)
|
|
|
|
# 检查PID文件是否存在
|
|
if not os.path.exists(panel_pid_file):
|
|
logging.info(f'{panel_pid_file} not found, starting panel service...')
|
|
continue
|
|
|
|
panel_pid=""
|
|
try:
|
|
# 读取PID文件
|
|
with open(panel_pid_file, 'r') as file:
|
|
panel_pid = file.read()
|
|
except Exception as e:
|
|
service_panel('start')
|
|
continue
|
|
if not panel_pid:
|
|
logging.info(f'PID is empty in {panel_pid_file}, starting panel service...')
|
|
service_panel('start')
|
|
continue
|
|
panel_pid = panel_pid.strip()
|
|
# 检查PID对应的进程是否存在
|
|
if not pid_exists(int(panel_pid)):
|
|
logging.info(f'PID {panel_pid} not found, attempting to find running panel process...')
|
|
panel_pid = find_panel_pid()
|
|
|
|
if panel_pid:
|
|
# 更新PID文件
|
|
update_pid_file(panel_pid)
|
|
else:
|
|
logging.info('No panel process found, starting service...')
|
|
service_panel('start')
|
|
|
|
else:
|
|
# 检查进程是否是面板进程
|
|
comm_file = f"/proc/{panel_pid}/comm"
|
|
if os.path.exists(comm_file):
|
|
with open(comm_file, 'r') as file:
|
|
comm = file.read()
|
|
if not comm or comm.find("BT-Panel") == -1:
|
|
logging.info(f'Process {panel_pid} is not a BT-Panel process,comm-{comm} commtype-{type(comm)}')
|
|
service_panel('start')
|
|
continue
|
|
|
|
else:
|
|
logging.info(f'comm file not found for PID {panel_pid}, restarting service...')
|
|
service_panel('start')
|
|
|
|
|
|
|
|
def update_panel():
|
|
os.system("curl -k https://node.aapanel.com/install/update_7.x_en.sh|bash &")
|
|
|
|
|
|
def service_panel(action='reload'):
|
|
if not os.path.exists('{}/init.sh'.format(base_path)):
|
|
update_panel()
|
|
else:
|
|
os.system("nohup bash /www/server/panel/init.sh {} > /dev/null 2>&1 &".format(action))
|
|
logging.info("Panel Service: {}".format(action))
|
|
|
|
|
|
# 重启面板服务
|
|
def restart_panel_service():
|
|
rtips = '{}/data/restart.pl'.format(base_path)
|
|
reload_tips = '{}/data/reload.pl'.format(base_path)
|
|
while True:
|
|
if os.path.exists(rtips):
|
|
os.remove(rtips)
|
|
service_panel('restart')
|
|
if os.path.exists(reload_tips):
|
|
os.remove(reload_tips)
|
|
service_panel('reload')
|
|
time.sleep(1)
|
|
|
|
# 取面板pid
|
|
def get_panel_pid():
|
|
try:
|
|
pid = ReadFile('/www/server/panel/logs/panel.pid')
|
|
if pid:
|
|
return int(pid)
|
|
for pid in pids():
|
|
try:
|
|
p = Process(pid)
|
|
n = p.cmdline()[-1]
|
|
if n.find('runserver') != -1 or n.find('BT-Panel') != -1:
|
|
return pid
|
|
except:
|
|
pass
|
|
except:
|
|
pass
|
|
return None
|
|
|
|
|
|
def HttpGet(url, timeout=6, headers={}):
|
|
if sys.version_info[0] == 2:
|
|
try:
|
|
import urllib2
|
|
req = urllib2.Request(url, headers=headers)
|
|
response = urllib2.urlopen(req, timeout=timeout,)
|
|
return response.read()
|
|
except Exception as ex:
|
|
logging.info(str(ex))
|
|
return str(ex)
|
|
else:
|
|
try:
|
|
import urllib.request
|
|
req = urllib.request.Request(url, headers=headers)
|
|
response = urllib.request.urlopen(req, timeout=timeout)
|
|
result = response.read()
|
|
if type(result) == bytes:
|
|
result = result.decode('utf-8')
|
|
return result
|
|
except Exception as ex:
|
|
logging.info("URL: {} => {}".format(url, ex))
|
|
return str(ex)
|
|
|
|
|
|
# 定时任务去检测邮件信息
|
|
def send_mail_time():
|
|
while True:
|
|
try:
|
|
os.system("nohup " + get_python_bin() +" /www/server/panel/script/mail_task.py > /dev/null 2>&1 &")
|
|
time.sleep(180)
|
|
except:
|
|
time.sleep(360)
|
|
send_mail_time()
|
|
|
|
#5个小时更新一次更新软件列表
|
|
def update_software_list():
|
|
while True:
|
|
try:
|
|
import panelPlugin
|
|
panelPlugin.panelPlugin().get_cloud_list_status(None)
|
|
time.sleep(18000)
|
|
except:
|
|
time.sleep(1800)
|
|
update_software_list()
|
|
|
|
# 面板消息提醒
|
|
def check_panel_msg():
|
|
python_bin = get_python_bin()
|
|
while True:
|
|
os.system('nohup {} /www/server/panel/script/check_msg.py > /dev/null 2>&1 &'.format(python_bin))
|
|
time.sleep(3600)
|
|
|
|
# 面板推送消息
|
|
def push_msg():
|
|
python_bin = get_python_bin()
|
|
while True:
|
|
time.sleep(60)
|
|
os.system('nohup {} /www/server/panel/script/push_msg.py > /dev/null 2>&1 &'.format(python_bin))
|
|
|
|
def JavaProDaemons():
|
|
'''
|
|
@name Java 项目守护进程
|
|
@author lkq@aapanel.com
|
|
@time 2022-07-19
|
|
@param None
|
|
'''
|
|
if public.M('sites').where('project_type=?',('Java')).count()>=1:
|
|
project_info=public.M('sites').where('project_type=?',('Java')).select()
|
|
for i in project_info:
|
|
try:
|
|
import json
|
|
i['project_config'] = json.loads(i['project_config'])
|
|
#判断项目是否设置了守护进程
|
|
if i['project_config']['java_type']!='springboot':continue
|
|
if 'auth' in i['project_config'] and i['project_config']['auth']==1 or i['project_config']['auth']=='1':
|
|
print("Java",i['name'])
|
|
from projectModel import javaModel
|
|
java = javaModel.main()
|
|
if java.get_project_run_state(project_name=i['name']):
|
|
continue
|
|
else:
|
|
#如果项目是在后台停止的,那么就不再启动
|
|
if os.path.exists("/var/tmp/springboot/vhost/pids/{}.pid".format(i['name'])):
|
|
get=public.dict_obj()
|
|
get.project_name=i['name']
|
|
java.start_project(get)
|
|
public.WriteLog('守护进程','Java项目[{}]已经被守护进程启动'.format(i['name']))
|
|
except:
|
|
continue
|
|
|
|
def ProLog():
|
|
path_list=["/www/server/go_project/vhost/logs","/var/tmp/springboot/vhost/logs/"]
|
|
try:
|
|
for i2 in path_list:
|
|
if os.path.exists(i2):
|
|
for dir in os.listdir(i2):
|
|
dir = os.path.join(i2, dir)
|
|
# 判断当前目录是否为文件夹
|
|
if os.path.isfile(dir):
|
|
if dir.endswith(".log"):
|
|
#文件大于500M的时候则清空文件
|
|
if os.stat(dir).st_size >200000000:
|
|
public.ExecShell("echo ''>{}".format(dir))
|
|
except:
|
|
pass
|
|
|
|
def ProDadmons():
|
|
'''
|
|
@name 项目守护进程
|
|
@author
|
|
'''
|
|
n = 30
|
|
while 1:
|
|
n += 1
|
|
if n >= 30:
|
|
n = 1
|
|
ProLog()
|
|
time.sleep(120)
|
|
try:
|
|
JavaProDaemons()
|
|
except:
|
|
pass
|
|
|
|
def process_task_thread():
|
|
'''
|
|
@name 进程监控
|
|
@auther hwliang
|
|
'''
|
|
|
|
|
|
# 进程流量监控,如果文件:/www/server/panel/data/is_net_task.pl 或 /www/server/panel/data/control.conf不存在,则不监控进程流量
|
|
net_task_obj = process_task.process_network_total()
|
|
net_task_obj.start()
|
|
|
|
# 检测面板授权
|
|
def check_panel_auth():
|
|
while True:
|
|
pro_file='/www/server/panel/data/panel_pro.pl'
|
|
update_file='/www/server/panel/data/now_update_pro.pl'
|
|
if os.path.exists(pro_file):
|
|
python_bin = get_python_bin()
|
|
from BTPanel import cache
|
|
if cache:
|
|
key='pro_check_sdfjslk'
|
|
res = cache.get(key)
|
|
if os.path.exists(update_file) or res is None:
|
|
os.system('nohup {} /www/server/panel/script/check_auth.py > /dev/null 2>&1 &'.format(python_bin))
|
|
if cache:
|
|
cache.set(key, 'sddsf', 3600)
|
|
if os.path.exists(update_file):os.remove(update_file)
|
|
time.sleep(2)
|
|
|
|
def count_ssh_logs():
|
|
'''
|
|
@name 统计SSH登录日志
|
|
@return None
|
|
'''
|
|
if os.path.exists("/etc/debian_version"):
|
|
version = public.readFile('/etc/debian_version')
|
|
if not version:
|
|
return
|
|
version = version.strip()
|
|
if 'bookworm' in version or 'jammy' in version or 'impish' in version:
|
|
version = 12
|
|
else:
|
|
try:
|
|
version = float(version)
|
|
except:
|
|
version = 11
|
|
|
|
if version >= 12:
|
|
while True:
|
|
filepath = "/www/server/panel/data/ssh_login_counts.json"
|
|
|
|
# 获取今天的日期
|
|
today = datetime.now().strftime('%Y-%m-%d')
|
|
result = {
|
|
'date': today, # 添加日期字段
|
|
'error': 0,
|
|
'success': 0,
|
|
'today_error': 0,
|
|
'today_success': 0
|
|
}
|
|
# public.print_log("进入计算count_ssh_logs--{}")
|
|
try:
|
|
filedata = public.readFile(filepath) if os.path.exists(filepath) else public.writeFile(filepath,
|
|
"[]")
|
|
try:
|
|
data_list = json.loads(filedata)
|
|
except:
|
|
data_list = []
|
|
|
|
# 检查是否已有今天的记录,避免重复统计
|
|
found_today = False
|
|
for day in data_list:
|
|
if day['date'] == today:
|
|
found_today = True
|
|
# public.print_log("进入计算count_ssh_logs--{退出1}")
|
|
break
|
|
|
|
if found_today:
|
|
# public.print_log("进入计算count_ssh_logs--{退出2}")
|
|
break # 如果找到今天的记录,跳出while循环
|
|
|
|
|
|
today_err_num1 = int(public.ExecShell(
|
|
"journalctl -u ssh --no-pager -S today |grep -a 'Failed password for' |grep -v 'invalid' |wc -l")[
|
|
0])
|
|
|
|
today_err_num2 = int(public.ExecShell(
|
|
"journalctl -u ssh --no-pager -S today |grep -a 'Connection closed by authenticating user' |grep -a 'preauth' |wc -l")[
|
|
0])
|
|
|
|
today_success = int(
|
|
public.ExecShell("journalctl -u ssh --no-pager -S today |grep -a 'Accepted' |wc -l")[0])
|
|
|
|
# 查看文件大小 判断是否超过5G
|
|
is_bigfile = False
|
|
|
|
res, err = public.ExecShell("journalctl --disk-usage")
|
|
# import re
|
|
import json
|
|
total_bytes = parse_journal_disk_usage(res)
|
|
# public.print_log("文件大小--{}".format(total_bytes))
|
|
limit_bytes = 5 * 1024 * 1024 * 1024
|
|
if total_bytes > limit_bytes:
|
|
is_bigfile = True
|
|
|
|
if is_bigfile:
|
|
# public.print_log("取30天--{}".format(total_bytes))
|
|
err_num1 = int(public.ExecShell("journalctl -u ssh --since '30 days ago' --no-pager | grep -a 'Failed password for' | grep -v 'invalid' | wc -l")[0])
|
|
err_num2 = int(public.ExecShell("journalctl -u ssh --since '30 days ago' --no-pager --grep='Connection closed by authenticating user|preauth' | wc -l")[0])
|
|
success = int(public.ExecShell("journalctl -u ssh --since '30 days ago' --no-pager | grep -a 'Accepted' | wc -l")[0])
|
|
else:
|
|
# public.print_log("取所有--{}")
|
|
# 统计失败登陆次数
|
|
err_num1 = int(public.ExecShell("journalctl -u ssh --no-pager |grep -a 'Failed password for' |grep -v 'invalid' |wc -l")[0])
|
|
err_num2 = int(public.ExecShell("journalctl -u ssh --no-pager --grep='Connection closed by authenticating user|preauth' |wc -l")[0])
|
|
success = int(public.ExecShell("journalctl -u ssh --no-pager|grep -a 'Accepted' |wc -l")[0])
|
|
# public.print_log("计算完毕22--{}")
|
|
result['error'] = err_num1 + err_num2
|
|
# 统计成功登录次数
|
|
result['success'] = success
|
|
result['today_error'] = today_err_num1 + today_err_num2
|
|
result['today_success'] = today_success
|
|
|
|
data_list.insert(0, result)
|
|
data_list = data_list[:7]
|
|
public.writeFile(filepath, json.dumps(data_list))
|
|
except:
|
|
public.print_log(public.get_error_info())
|
|
public.writeFile(filepath, json.dumps([{
|
|
'date': today, # 添加日期字段
|
|
'error': 0,
|
|
'success': 0,
|
|
'today_error': 0,
|
|
'today_success': 0
|
|
}]))
|
|
time.sleep(86400)
|
|
else:
|
|
time.sleep(86400)
|
|
|
|
|
|
def parse_journal_disk_usage(output):
|
|
import re
|
|
# 使用正则表达式来提取数字和单位
|
|
match = re.search(r'take up (\d+(\.\d+)?)\s*([KMGTP]?)', output)
|
|
total_bytes = 0
|
|
if match:
|
|
value = float(match.group(1)) # 数字
|
|
unit = match.group(3) # 单位
|
|
# public.print_log("匹配字符--value>{} unit>{}".format(value, unit))
|
|
# 将所有单位转换为字节
|
|
if unit == '':
|
|
unit_value = 1
|
|
elif unit == 'K':
|
|
unit_value = 1024
|
|
elif unit == 'M':
|
|
unit_value = 1024 * 1024
|
|
elif unit == 'G':
|
|
unit_value = 1024 * 1024 * 1024
|
|
elif unit == 'T':
|
|
unit_value = 1024 * 1024 * 1024 * 1024
|
|
elif unit == 'P':
|
|
unit_value = 1024 * 1024 * 1024 * 1024 * 1024
|
|
else:
|
|
unit_value = 0
|
|
|
|
# 计算总字节数
|
|
total_bytes = value * unit_value
|
|
return total_bytes
|
|
|
|
def update_vulnerabilities():
|
|
if "/www/server/panel/class_v2/wp_toolkit/" not in sys.path:
|
|
sys.path.insert(1, "/www/server/panel/class_v2/wp_toolkit/")
|
|
import totle_db
|
|
import requests, json
|
|
requests.packages.urllib3.disable_warnings()
|
|
|
|
def auto_scan():
|
|
'''
|
|
@name 自动扫描
|
|
@msg 一天一次
|
|
:return:
|
|
'''
|
|
path_time = "/www/server/panel/data/auto_scan.pl"
|
|
if not os.path.exists(path_time):
|
|
public.writeFile(path_time, json.dumps({"time": int(time.time())}))
|
|
share_ip_info = {"time": 0}
|
|
else:
|
|
share_ip_info = json.loads(public.readFile(path_time))
|
|
if (int(time.time()) - share_ip_info["time"]) < 86400:
|
|
return public.returnMsg(False, "未达到时间")
|
|
share_ip_info["time"] = int(time.time())
|
|
public.writeFile(path_time, json.dumps(share_ip_info))
|
|
import wordpress_scan
|
|
wordpress_scan.wordpress_scan().auto_scan()
|
|
|
|
def M(table, db="wordpress_vulnerabilities"):
|
|
'''
|
|
@name 获取数据库对象
|
|
@param table 表名
|
|
@param db 数据库名
|
|
'''
|
|
with totle_db.Sql(db) as sql:
|
|
return sql.table(table)
|
|
|
|
def check_vlun():
|
|
path_time = "/www/server/panel/data/wordpress_check_vlun.pl"
|
|
if not os.path.exists(path_time):
|
|
public.writeFile(path_time, json.dumps({"time": int(time.time())}))
|
|
share_ip_info = {"time": 0}
|
|
else:
|
|
share_ip_info = json.loads(public.readFile(path_time))
|
|
if (int(time.time()) - share_ip_info["time"]) < 86400:
|
|
return public.returnMsg(False, "未达到时间")
|
|
share_ip_info["time"] = int(time.time())
|
|
public.writeFile(path_time, json.dumps(share_ip_info))
|
|
load_time = M("wordpress_vulnerabilities", "wordpress_vulnerabilities").order("data_time desc").limit(
|
|
"1").field(
|
|
"data_time").find()
|
|
if type(load_time) != dict: return
|
|
|
|
def get_yun_infos(page):
|
|
url = "https://wafapi2.aapanel.com/api/bt_waf/get_wordpress_scan?size=100&p=" + str(page)
|
|
yun_infos = requests.get(url, verify=False, timeout=60).json()
|
|
for i in yun_infos['res']:
|
|
if i['data_time'] > load_time['data_time']:
|
|
del i['id']
|
|
M("wordpress_vulnerabilities", "wordpress_vulnerabilities").insert(i)
|
|
else:
|
|
return True
|
|
|
|
for i in range(1, 20):
|
|
time.sleep(26)
|
|
if get_yun_infos(i):
|
|
return
|
|
|
|
def check_plugin_close():
|
|
'''
|
|
@name 检查插件是否关闭
|
|
@return True 关闭
|
|
@return False 开启
|
|
'''
|
|
path_time = "/www/server/panel/data/wordpress_check_plugin_close.pl"
|
|
if not os.path.exists(path_time):
|
|
public.writeFile(path_time, json.dumps({"time": int(time.time())}))
|
|
share_ip_info = {"time": 0}
|
|
else:
|
|
share_ip_info = json.loads(public.readFile(path_time))
|
|
if (int(time.time()) - share_ip_info["time"]) < 86400:
|
|
return public.returnMsg(False, "未达到时间")
|
|
share_ip_info["time"] = int(time.time())
|
|
public.writeFile(path_time, json.dumps(share_ip_info))
|
|
check_sql=M("plugin_error", "plugin_error").order("id desc").limit("1").field("id").find()
|
|
if type(check_sql) != dict: return
|
|
time.sleep(30)
|
|
url = "https://wafapi2.aapanel.com/api/bt_waf/plugin_error_list"
|
|
try:
|
|
res = requests.get(url, verify=False, timeout=60).json()
|
|
except:
|
|
return False
|
|
res_list = res['res']
|
|
for i in res_list:
|
|
if M("plugin_error", "plugin_error").where("slug=? and status=0", i['slug']).count() > 0:
|
|
M("plugin_error", "plugin_error").where("slug=?", i['slug']).update({"status": 1})
|
|
|
|
def get_plugin_update_time():
|
|
'''
|
|
@name 获取插件更新时间
|
|
@ps 一周更新一次
|
|
'''
|
|
path_time = "/www/server/panel/data/wordpress_get_plugin_update_time.pl"
|
|
if not os.path.exists(path_time):
|
|
public.writeFile(path_time, json.dumps({"time": int(time.time())}))
|
|
#如果第一次运行则三天后再运行
|
|
share_ip_info = {"time": int(time.time())-86400*2}
|
|
else:
|
|
share_ip_info = json.loads(public.readFile(path_time))
|
|
if (int(time.time()) - share_ip_info["time"]) < 259200:
|
|
return public.returnMsg(False, "未达到时间")
|
|
share_ip_info["time"] = int(time.time())
|
|
public.writeFile(path_time, json.dumps(share_ip_info))
|
|
|
|
check_sql=M("wordpress_not_update", "wordpress_not_update").order("id desc").limit("1").field("id").find()
|
|
if type(check_sql) != dict: return
|
|
import random
|
|
def get_plugin_time(id):
|
|
time.sleep(30)
|
|
url = "https://wafapi2.aapanel.com/api/bt_waf/get_wordpress_not_update?p=" + str(id)
|
|
try:
|
|
res = requests.get(url, verify=False, timeout=60).json()
|
|
if len(res['res']) == 0:
|
|
return True
|
|
for i in res['res']:
|
|
if M("wordpress_not_update", "wordpress_not_update").where("slug=?", i['slug']).count() > 0:
|
|
if M("wordpress_not_update", "wordpress_not_update").where("slug=? and last_time=?", (
|
|
i['slug'], i['last_time'])).count() == 0:
|
|
M("wordpress_not_update", "wordpress_not_update").where("slug=?", i['slug']).update(
|
|
{"last_time": i['last_time']})
|
|
# 随机时间
|
|
except:
|
|
pass
|
|
|
|
for i in range(1, 50):
|
|
time.sleep(random.randint(10, 30))
|
|
if get_plugin_time(i):
|
|
return
|
|
|
|
while True:
|
|
try:
|
|
auto_scan()
|
|
check_vlun()
|
|
check_plugin_close()
|
|
get_plugin_update_time()
|
|
time.sleep(7200)
|
|
except:
|
|
time.sleep(3600)
|
|
|
|
# 每天提交一次昨天的邮局发送总数
|
|
def submit_email_statistics():
|
|
# 添加提交标记 每次提交昨天的 标记存在跳过 不存在添加 删除前天标记
|
|
yesterday = datetime.now() - timedelta(days=1)
|
|
yesterday = yesterday.strftime('%Y-%m-%d')
|
|
cloud_yesterday_submit = f'/www/server/panel/data/{yesterday}_submit_email_statistics.pl'
|
|
if os.path.exists(cloud_yesterday_submit):
|
|
return
|
|
# 判断是否有邮局
|
|
if not os.path.exists('/www/server/panel/plugin/mail_sys/mail_send_bulk.py') or not os.path.exists('/www/vmail'):
|
|
return
|
|
|
|
# 处理昨天数据
|
|
all = _get_yesterday_count()
|
|
if not all:
|
|
return
|
|
|
|
# 记录昨日发件总数
|
|
public.set_module_logs('sys_mail', 'sent', all)
|
|
|
|
# 添加标记
|
|
public.writeFile(cloud_yesterday_submit, '1')
|
|
# 删除前天标记
|
|
before_yesterday = datetime.now() - timedelta(days=2)
|
|
before_yesterday = before_yesterday.strftime('%Y-%m-%d')
|
|
cloud_before_yesterday_submit = f'/www/server/panel/data/{before_yesterday}_submit_email_statistics.pl'
|
|
if os.path.exists(cloud_before_yesterday_submit):
|
|
os.remove(cloud_before_yesterday_submit)
|
|
|
|
|
|
|
|
def submit_module_call_statistics():
|
|
# 添加提交标记 每次提交昨天的 标记存在跳过 不存在添加 删除前天标记 提交过的数据在统计文件里删除
|
|
yesterday = datetime.now() - timedelta(days=1)
|
|
yesterday = yesterday.strftime('%Y-%m-%d')
|
|
cloud_yesterday_submit = '{}/data/{}_submit_module_call_statistics.pl'.format(public.get_panel_path(), yesterday)
|
|
if os.path.exists(cloud_yesterday_submit):
|
|
return
|
|
|
|
# 取文件中yesterday和yesterday以前的数据
|
|
datainfo = {}
|
|
path = '{}/data/mod_log.json'.format(public.get_panel_path())
|
|
if os.path.exists(path):
|
|
try:
|
|
datainfo = json.loads(public.readFile(path))
|
|
except:
|
|
pass
|
|
else:
|
|
return
|
|
|
|
if type(datainfo) != dict:
|
|
datainfo = {}
|
|
# 需要提交的
|
|
data_submit = {}
|
|
# 当天数据
|
|
data_reserve = {}
|
|
|
|
try:
|
|
for date, modules in datainfo.items():
|
|
# 如果日期小于昨天,则提交数据
|
|
if date.strip() <= yesterday.strip():
|
|
data_submit[date] = modules
|
|
else:
|
|
data_reserve[date] = modules
|
|
except:
|
|
public.print_log(public.get_error_info())
|
|
|
|
if not data_submit:
|
|
return
|
|
_submit_to_cloud(data_submit)
|
|
|
|
# 将data_reserve 重新写入
|
|
public.writeFile(path, json.dumps(data_reserve))
|
|
|
|
# 添加标记
|
|
public.writeFile(cloud_yesterday_submit, '1')
|
|
# 删除前天标记
|
|
before_yesterday = datetime.now() - timedelta(days=2)
|
|
before_yesterday = before_yesterday.strftime('%Y-%m-%d')
|
|
cloud_before_yesterday_submit = '{}/data/{}_submit_module_call_statistics.pl'.format(public.get_panel_path(), before_yesterday)
|
|
if os.path.exists(cloud_before_yesterday_submit):
|
|
os.remove(cloud_before_yesterday_submit)
|
|
# print("提交完")
|
|
return
|
|
|
|
def mailsys_domain_restrictions():
|
|
if not os.path.exists('/www/server/panel/plugin/mail_sys/mail_send_bulk.py'):
|
|
# public.print_log('000 /mail_send_bulk')
|
|
# print('000 /mail_send_bulk')
|
|
return
|
|
|
|
if not os.path.exists('/www/vmail'):
|
|
# public.print_log('000 /www/vmail')
|
|
# print('000 /www/vmail')
|
|
return
|
|
|
|
yesterday = datetime.now() - timedelta(days=1)
|
|
yesterday = yesterday.strftime('%Y-%m-%d')
|
|
cloud_yesterday_submit = '{}/data/{}_update_mailsys_domain_restrictions.pl'.format(public.get_panel_path(), yesterday)
|
|
if os.path.exists(cloud_yesterday_submit):
|
|
return
|
|
|
|
# 检查版本 检查是否能查询额度 剩余额度
|
|
import public.PluginLoader as plugin_loader
|
|
bulk = plugin_loader.get_module('{}/plugin/mail_sys/mail_send_bulk.py'.format(public.get_panel_path()))
|
|
SendMailBulk = bulk.SendMailBulk
|
|
try:
|
|
SendMailBulk()._get_user_quota()
|
|
# public.print_log('111 调用插件方法检查额度')
|
|
# print('111 调用插件方法检查额度')
|
|
except:
|
|
public.print_log(public.get_error_info())
|
|
return
|
|
|
|
|
|
# 添加标记
|
|
public.writeFile(cloud_yesterday_submit, '1')
|
|
# 删除前天标记
|
|
before_yesterday = datetime.now() - timedelta(days=2)
|
|
before_yesterday = before_yesterday.strftime('%Y-%m-%d')
|
|
cloud_before_yesterday_submit = '{}/data/{}_update_mailsys_domain_restrictions.pl'.format(public.get_panel_path(), before_yesterday)
|
|
if os.path.exists(cloud_before_yesterday_submit):
|
|
os.remove(cloud_before_yesterday_submit)
|
|
return
|
|
|
|
|
|
def _submit_to_cloud(data_submit):
|
|
"""提交用户统计数据 接口调用 安装量等 """
|
|
import panelAuth
|
|
import requests
|
|
from BTPanel import session
|
|
cloudUrl = '{}/api/panel/submit_feature_invoked_bulk'.format(public.OfficialApiBase())
|
|
pdata = panelAuth.panelAuth().create_serverid(None)
|
|
url_headers = {}
|
|
if 'token' in pdata:
|
|
url_headers = {"authorization": "bt {}".format(pdata['token'])}
|
|
|
|
pdata['environment_info'] = json.dumps(public.fetch_env_info())
|
|
|
|
|
|
pdata['data'] = data_submit
|
|
pdata['utc_offset'] = _get_utc_offset_modele()
|
|
listTmp = requests.post(cloudUrl, json=pdata, headers=url_headers)
|
|
ret = listTmp.json()
|
|
# public.print_log(f"提交上传['data']999 ---{pdata['data']}")
|
|
# public.print_log(f"提交返回999 ---{ret}")
|
|
# if not ret['success']:
|
|
# print(f"|-{ret['res']}")
|
|
# public.print_log(f"|-submit_module_call_statistics: {ret['res']}")
|
|
# public.print_log(f"|-{ret}")
|
|
return
|
|
|
|
|
|
# 获取系统时间与utc时间的差值
|
|
def _get_utc_offset_modele():
|
|
# 系统时间戳
|
|
current_local_time = datetime.now()
|
|
current_local_timestamp = int(current_local_time.timestamp())
|
|
|
|
# 获取当前 UTC 时间的时间戳
|
|
current_utc_time = datetime.utcnow()
|
|
current_utc_timestamp = int(current_utc_time.timestamp())
|
|
|
|
# 计算时区差值(秒)
|
|
timezone_offset = current_local_timestamp - current_utc_timestamp
|
|
offset = timezone_offset/3600
|
|
|
|
return offset
|
|
|
|
|
|
# 获取系统时间与utc时间的差值
|
|
def _get_utc_offset():
|
|
# 系统时间戳
|
|
current_local_time = datetime.now()
|
|
current_local_timestamp = int(current_local_time.timestamp())
|
|
|
|
# UTC时间戳
|
|
current_utc_time = datetime.utcnow()
|
|
current_utc_timestamp = int(current_utc_time.timestamp())
|
|
|
|
# 计算时区差值(秒)
|
|
timezone_offset = current_local_timestamp - current_utc_timestamp
|
|
|
|
if timezone_offset > 0: # 东时区 需要减
|
|
return timezone_offset, True
|
|
else: # 西时区
|
|
return abs(timezone_offset), False
|
|
|
|
|
|
# 获取昨日邮局收发件统计数据
|
|
def _get_yesterday_count():
|
|
maillog_path = '/var/log/maillog'
|
|
if "ubuntu" in public.get_linux_distribution().lower():
|
|
maillog_path = '/var/log/mail.log'
|
|
|
|
if not os.path.exists(maillog_path):
|
|
return 0
|
|
output, err = public.ExecShell(
|
|
f'pflogsumm -d yesterday --verbose-msg-detail --zero-fill --iso-date-time --rej-add-from {maillog_path}')
|
|
if err:
|
|
# public.print_log(f"err 4444 {err}")
|
|
return 0
|
|
|
|
data = _pflogsumm_data_treating(output)
|
|
all = 0
|
|
# public.print_log(f"yesterday sent: {data['stats_dict']}")
|
|
if data.get('stats_dict', None):
|
|
all = data['stats_dict'].get('delivered', 0)
|
|
return all
|
|
# 分析命令执行后的数据
|
|
def _pflogsumm_data_treating(output):
|
|
import re
|
|
stats_dict = {}
|
|
|
|
# 使用正则表达式来匹配和提取关键信息
|
|
patterns = [
|
|
r'(\d+)\s+received',
|
|
r'(\d+)\s+delivered',
|
|
r'(\d+)\s+forwarded',
|
|
r'(\d+)\s+deferred\s+\((\d+)\s+deferrals\)',
|
|
r'(\d+)\s+bounced',
|
|
r'(\d+)\s+rejected\s+\((\d+)%\)',
|
|
r'(\d+)\s+reject\s+warnings',
|
|
r'(\d+)\s+held',
|
|
r'(\d+)\s+discarded\s+\((\d+)%\)',
|
|
r'(\d+)\s+bytes\s+received',
|
|
r'(\d+)k\s+bytes\s+delivered',
|
|
r'(\d+)\s+senders',
|
|
r'(\d+)\s+sending\s+hosts/domains',
|
|
r'(\d+)\s+recipients',
|
|
r'(\d+)\s+recipient\s+hosts/domains'
|
|
]
|
|
|
|
for pattern in patterns:
|
|
match = re.search(pattern, output)
|
|
if match:
|
|
# 将找到的数字转换为整数并存入字典
|
|
stats_dict[pattern] = int(match.group(1))
|
|
|
|
friendly_names = {
|
|
r'(\d+)\s+received': 'received',
|
|
r'(\d+)\s+delivered': 'delivered',
|
|
r'(\d+)\s+forwarded': 'forwarded',
|
|
r'(\d+)\s+deferred\s+\((\d+)\s+deferrals\)': 'deferred',
|
|
r'(\d+)\s+bounced': 'bounced',
|
|
r'(\d+)\s+rejected\s+\((\d+)%\)': 'rejected',
|
|
r'(\d+)\s+reject\s+warnings': 'reject_warnings',
|
|
r'(\d+)\s+held': 'held',
|
|
r'(\d+)\s+discarded\s+\((\d+)%\)': 'discarded',
|
|
r'(\d+)\s+bytes\s+received': 'bytes_received',
|
|
r'(\d+)k\s+bytes\s+delivered': 'bytes_delivered_kilo',
|
|
r'(\d+)\s+senders': 'senders',
|
|
r'(\d+)\s+sending\s+hosts/domains': 'sending_hosts_domains',
|
|
r'(\d+)\s+recipients': 'recipients',
|
|
r'(\d+)\s+recipient\s+hosts/domains': 'recipient_hosts_domains'
|
|
}
|
|
|
|
stats_dict = {friendly_names[key]: value for key, value in stats_dict.items() if key in friendly_names}
|
|
keys_to_remove = [
|
|
"reject_warnings",
|
|
"held",
|
|
"discarded",
|
|
"bytes_received",
|
|
"senders",
|
|
"sending_hosts_domains",
|
|
"recipients",
|
|
"recipient_hosts_domains"
|
|
]
|
|
|
|
for key in keys_to_remove:
|
|
stats_dict.pop(key, None)
|
|
|
|
data = {
|
|
"stats_dict": stats_dict,
|
|
}
|
|
return data
|
|
|
|
|
|
|
|
def mailsys_domain_blecklisted_alarm():
|
|
if not os.path.exists('/www/server/panel/plugin/mail_sys/mail_send_bulk.py') or not os.path.exists('/www/vmail'):
|
|
return
|
|
|
|
yesterday = datetime.now() - timedelta(days=1)
|
|
yesterday = yesterday.strftime('%Y-%m-%d')
|
|
cloud_yesterday_submit = '{}/data/{}_mailsys_domain_blecklisted_alarm.pl'.format(public.get_panel_path(), yesterday)
|
|
if os.path.exists(cloud_yesterday_submit):
|
|
return
|
|
|
|
# 检查版本 检查是否能查询额度 剩余额度
|
|
import public.PluginLoader as plugin_loader
|
|
bulk = plugin_loader.get_module('{}/plugin/mail_sys/mail_send_bulk.py'.format(public.get_panel_path()))
|
|
SendMailBulk = bulk.SendMailBulk
|
|
try:
|
|
SendMailBulk().check_domain_blacklist_corn()
|
|
|
|
except:
|
|
public.print_log(public.get_error_info())
|
|
return
|
|
|
|
|
|
# 添加标记
|
|
public.writeFile(cloud_yesterday_submit, '1')
|
|
# 删除前天标记
|
|
before_yesterday = datetime.now() - timedelta(days=2)
|
|
before_yesterday = before_yesterday.strftime('%Y-%m-%d')
|
|
cloud_before_yesterday_submit = '{}/data/{}_mailsys_domain_blecklisted_alarm.pl'.format(public.get_panel_path(), before_yesterday)
|
|
if os.path.exists(cloud_before_yesterday_submit):
|
|
os.remove(cloud_before_yesterday_submit)
|
|
return
|
|
|
|
# 邮件证书过期告警
|
|
def mailsys_cert_expiry_alarm():
|
|
if not os.path.exists('/www/server/panel/plugin/mail_sys/mail_send_bulk.py') or not os.path.exists('/www/vmail'):
|
|
return
|
|
|
|
yesterday = datetime.now() - timedelta(days=1)
|
|
yesterday = yesterday.strftime('%Y-%m-%d')
|
|
cloud_yesterday_submit = '{}/data/{}_mailsys_cert_expiry_alarm.pl'.format(public.get_panel_path(), yesterday)
|
|
if os.path.exists(cloud_yesterday_submit):
|
|
return
|
|
|
|
script = '/www/server/panel/plugin/mail_sys/script/certificate_expired.py'
|
|
if not os.path.exists(script):
|
|
return
|
|
|
|
cmd = f"btpython {script}"
|
|
aa = public.ExecShell(cmd)
|
|
|
|
|
|
# 添加标记
|
|
public.writeFile(cloud_yesterday_submit, '1')
|
|
# 删除前天标记
|
|
before_yesterday = datetime.now() - timedelta(days=2)
|
|
before_yesterday = before_yesterday.strftime('%Y-%m-%d')
|
|
cloud_before_yesterday_submit = '{}/data/{}_mailsys_cert_expiry_alarm.pl'.format(public.get_panel_path(), before_yesterday)
|
|
if os.path.exists(cloud_before_yesterday_submit):
|
|
os.remove(cloud_before_yesterday_submit)
|
|
return
|
|
|
|
# 邮局自动回复
|
|
def auto_reply_tasks():
|
|
while True:
|
|
try:
|
|
|
|
if not os.path.exists('/www/server/panel/plugin/mail_sys/mail_sys_main.py') or not os.path.exists(
|
|
'/www/vmail'):
|
|
time.sleep(3600)
|
|
continue
|
|
|
|
# 检查是否有回复功能
|
|
import public.PluginLoader as plugin_loader
|
|
main = plugin_loader.get_module('{}/plugin/mail_sys/mail_sys_main.py'.format(public.get_panel_path()))
|
|
mail_sys_main = main.mail_sys_main
|
|
try:
|
|
mail_sys_main().auto_reply_tasks()
|
|
except Exception as e:
|
|
# public.print_log(f"The post office auto-reply task was executed incorrectly: {str(e)}")
|
|
public.print_log(public.get_error_info())
|
|
|
|
except:
|
|
pass
|
|
|
|
# 每小时执行一次
|
|
time.sleep(3600)
|
|
|
|
# 邮局自动扫描异常邮箱
|
|
def auto_scan_abnormal_mail():
|
|
while True:
|
|
try:
|
|
|
|
if not os.path.exists('/www/server/panel/plugin/mail_sys/mail_send_bulk.py') or not os.path.exists(
|
|
'/www/vmail'):
|
|
time.sleep(7200)
|
|
continue
|
|
|
|
# 检查授权
|
|
endtime = public.get_pd()[1]
|
|
curtime = int(time.time())
|
|
if endtime != 0 and endtime < curtime:
|
|
time.sleep(7200)
|
|
continue
|
|
|
|
# 检查是否关闭了自动扫描
|
|
path = '/www/server/panel/plugin/mail_sys/data/abnormal_mail_check_switch'
|
|
if os.path.exists(path):
|
|
time.sleep(7200)
|
|
continue
|
|
|
|
# 导入并执行扫描
|
|
import public.PluginLoader as plugin_loader
|
|
bulk = plugin_loader.get_module('{}/plugin/mail_sys/mail_send_bulk.py'.format(public.get_panel_path()))
|
|
SendMailBulk = bulk.SendMailBulk
|
|
try:
|
|
SendMailBulk().check_abnormal_emails()
|
|
# public.print_log(f"MailServer automatically scans for abnormal mailboxes ----")
|
|
except Exception as e:
|
|
# public.print_log(f"异常邮箱扫描出错: {str(e)}")
|
|
public.print_log(public.get_error_info())
|
|
|
|
except:
|
|
pass
|
|
|
|
time.sleep(7200)
|
|
|
|
def domain_ssl_service():
|
|
try:
|
|
from ssl_domainModelV2.service import make_suer_org_ssl
|
|
make_suer_org_ssl()
|
|
except Exception as e:
|
|
public.print_log("domain_ssl_service error , %s" % e)
|
|
|
|
|
|
|
|
def run_thread():
|
|
global thread_dict,task_obj
|
|
tkeys = thread_dict.keys()
|
|
|
|
thread_list = {
|
|
"start_task": task_obj.start_task,
|
|
"systemTask": systemTask,
|
|
"check502Task": check502Task,
|
|
"daemon_panel": daemon_panel,
|
|
"restart_panel_service": restart_panel_service,
|
|
"check_panel_ssl": check_panel_ssl,
|
|
"update_software_list": update_software_list,
|
|
"send_mail_time": send_mail_time,
|
|
"check_panel_msg": check_panel_msg,
|
|
"check_breaking_through_cron": check_breaking_through_cron,
|
|
"push_msg": push_msg,
|
|
"domain_ssl_service": domain_ssl_service,
|
|
"ProDadmons":ProDadmons,
|
|
"check_panel_auth": check_panel_auth,
|
|
# "process_task_thread":process_task_thread, # 监控工具不支持
|
|
"count_ssh_logs": count_ssh_logs,
|
|
"submit_email_statistics": submit_email_statistics, # 每天一次 昨日邮件发送统计
|
|
"update_vulnerabilities": update_vulnerabilities,
|
|
"submit_module_call_statistics": submit_module_call_statistics, # 每天一次 提交今天之前的统计数据
|
|
# "mailsys_domain_restrictions": mailsys_domain_restrictions, # 更新邮局发件状态 暂停
|
|
"mailsys_domain_blecklisted_alarm": mailsys_domain_blecklisted_alarm, # 每天一次 邮局黑名单检测
|
|
"mailsys_cert_expiry_alarm": mailsys_cert_expiry_alarm, # 每天一次 邮局证书告警
|
|
"auto_reply_tasks": auto_reply_tasks, # 每小时执行一次 自动回复邮件
|
|
"auto_scan_abnormal_mail": auto_scan_abnormal_mail, # 每两小时执行一次 自动扫描异常邮箱
|
|
"maillog_event": maillog_event,
|
|
"aggregate_maillogs": aggregate_maillogs_task,
|
|
# "print_malloc": print_malloc_thread,
|
|
"mail_automations": schedule_automations_forever,
|
|
}
|
|
|
|
for skey in thread_list.keys():
|
|
if not skey in tkeys or not thread_dict[skey].is_alive():
|
|
# logging.info('restart thread - {}'.format(skey))
|
|
thread_dict[skey] = threading.Thread(target=thread_list[skey])
|
|
# thread_dict[skey].setDaemon(True)
|
|
thread_dict[skey].start()
|
|
|
|
def func():
|
|
os.system(get_python_bin() + " {}/script/scan_log.py > /dev/null".format(base_path))
|
|
#如果需要循环调用,就要添加以下方法
|
|
timer = threading.Timer(86400, func)
|
|
timer.start()
|
|
|
|
def scan_log_site():
|
|
now_time = datetime.datetime.now()
|
|
next_time = now_time + datetime.timedelta(days=+1)
|
|
next_year = next_time.date().year
|
|
next_month = next_time.date().month
|
|
next_day = next_time.date().day
|
|
next_time = datetime.datetime.strptime(str(next_year) + "-" + str(next_month) + "-" + str(next_day) + " 03:00:00",
|
|
"%Y-%m-%d %H:%M:%S")
|
|
timer_start_time = (next_time - now_time).total_seconds()
|
|
timer = threading.Timer(timer_start_time, func)
|
|
timer.start()
|
|
|
|
# 面板消息提醒
|
|
# def check_panel_msg():
|
|
# python_bin = get_python_bin()
|
|
# while True:
|
|
# os.system('{} {}/script/check_msg.py &'.format(python_bin,base_path))
|
|
# time.sleep(600)
|
|
|
|
# 检测防爆破计划任务
|
|
def check_breaking_through_cron():
|
|
try:
|
|
key='breaking_through_cron'
|
|
import class_v2.breaking_through as breaking_through
|
|
_breaking_through_obj = breaking_through.main()
|
|
_breaking_through_obj.del_cron()
|
|
while True:
|
|
time.sleep(60)
|
|
_breaking_through_obj.cron_method()
|
|
except Exception as e:
|
|
time.sleep(60)
|
|
# public.writeFile('/www/server/panel/logs/breaking_through.log', "[{}]{}\n".format(public.format_date(), public.get_error_info()), 'a')
|
|
public.writeFile('/www/server/panel/logs/breaking_through.log',"{}".format(e))
|
|
check_breaking_through_cron()
|
|
|
|
|
|
|
|
|
|
def main():
|
|
main_pid = 'logs/task.pid'
|
|
if os.path.exists(main_pid):
|
|
os.system("kill -9 $(cat {}) &> /dev/null".format(main_pid))
|
|
pid = os.fork()
|
|
if pid:
|
|
sys.exit(0)
|
|
|
|
os.setsid()
|
|
|
|
_pid = os.fork()
|
|
if _pid:
|
|
public.writeFile(main_pid, str(_pid))
|
|
sys.exit(0)
|
|
|
|
sys.stdout.flush()
|
|
sys.stderr.flush()
|
|
task_log_file = 'logs/task.log'
|
|
|
|
# trace malloc
|
|
# tracemalloc.start()
|
|
|
|
is_debug = False
|
|
|
|
if os.path.exists('{}/data/debug.pl'.format(base_path)):
|
|
is_debug = True
|
|
|
|
try:
|
|
logging.basicConfig(level=logging.NOTSET if is_debug else logging.INFO, format='%(asctime)s [%(levelname)s]: %(message)s',
|
|
datefmt='%Y-%m-%d %H:%M:%S', filename=task_log_file, filemode='a+')
|
|
except Exception as ex:
|
|
print(ex)
|
|
logging.info('Service Up')
|
|
time.sleep(5)
|
|
run_thread()
|
|
# time.sleep(15)
|
|
startTask()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|