Files
yakpanel-core/mod/base/push_mod/site_push.py
2026-04-07 02:04:22 +05:30

1152 lines
42 KiB
Python

import hashlib
import json
import os
import re
import sys
import time
from datetime import datetime
from importlib import import_module
from typing import Tuple, Union, Optional, List
import psutil
import public
public.sys_path_append("class_v2/")
try:
from public.hook_import import hook_import
hook_import()
except:
pass
from script.restart_services import manual_flag
from .base_task import BaseTask
from .mods import PUSH_DATA_PATH, TaskConfig, SenderConfig
from .send_tool import WxAccountMsg, WxAccountLoginMsg
from .util import read_file, DB, write_file, check_site_status, GET_CLASS, get_config_value, \
public_get_cache_func, \
public_set_cache_func, get_network_ip, public_get_user_info, public_http_post, panel_version
import public.PluginLoader as plugin_loader
class _WebInfo:
def __init__(self):
self.last_time = 0
self._items = None
self._items_by_type = None
def __call__(self):
if self._items is not None and self.last_time > time.time() - 300:
return self._items, self._items_by_type
items = []
items_by_type = [[], [], [], [], []]
res_list = DB('sites').field('id,name,project_type,project_config').select()
for i in res_list:
# if not check_site_status(i):
# continue
items.append({
"title": i["name"] + "[" + i["project_type"] + "]",
"value": i["name"]
})
if i["project_type"] == "PHP" or i["project_type"] == "proxy":
continue
idx: int = ProjectStatusTask._to_project_id(i["project_type"])
if idx is None:
continue
items_by_type[idx].append({
"title": i["name"],
"value": i["id"]
})
self._items = items
self._items_by_type = items_by_type
return items, items_by_type
web_info = _WebInfo()
class SSLCertificateTask(BaseTask):
def __init__(self):
super().__init__()
self.source_name = "SSL"
self.title = "SSL Certificate expiration"
self.template_name = "Certificate (SSL) Expiration"
self._task_config = TaskConfig()
self.task_id = None
self.domains = []
def get_keyword(self, task_data: dict) -> str:
return task_data["project"]
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
try:
days = int(task_data.get("cycle", 15))
except Exception:
days = 15
# 过滤要告警的对象
msg_list = []
domain_list = [] # 域名模块
mail_list = [] # 邮件模块
# ============= domain ssl manager ================
from ssl_domainModelV2.model import DnsDomainSSL
after_ts = (time.time() + 86400 * days) * 1000
ssl_obj = DnsDomainSSL.objects.filter(not_after_ts__lt=after_ts)
for ssl in ssl_obj:
if ssl:
domain_list.append(ssl.subject)
# ============= mail ssl ========================
# 有插件 已开启 即将过期
mail_database_path = '/www/vmail/postfixadmin.db'
vmail_ssl_map = '/etc/postfix/vmail_ssl.map'
if os.path.exists(mail_database_path) and os.path.exists(vmail_ssl_map):
ssl_conf = public.readFile(vmail_ssl_map)
if ssl_conf:
try:
with public.S("domain", mail_database_path) as obj:
if public.check_field_exists(obj, "domain", "ssl_alarm"):
domains = obj.where('ssl_alarm', 1).select()
mail_domain_list = [i['domain'] for i in domains]
if mail_domain_list:
for domain in mail_domain_list:
cert_path = '/www/server/panel/plugin/mail_sys/cert/{}/fullchain.pem'.format(domain)
if not os.path.exists(cert_path):
continue
if domain not in ssl_conf:
continue
# ssl
main = plugin_loader.get_module(
'{}/plugin/mail_sys/mail_sys_main.py'.format(public.get_panel_path()))
mail_sys_main = main.mail_sys_main
ssl_info = mail_sys_main().get_ssl_info(domain)
endtime = ssl_info.get('endtime', None)
if endtime and endtime <= 15:
mail_list.append(domain)
except:
pass
# =================================================
if domain_list:
msg_list.append(f"> Domain SSL {domain_list} certificate expired\n")
if mail_list:
msg_list.append(f"> Mail Server SSL {mail_list} certificate expired\n")
self.domains = domain_list + mail_list
return {"msg_list": msg_list} if msg_list else None
def filter_template(self, template) -> dict:
# 前端模板展示选项
return template
def check_task_data(self, task_data: dict) -> Union[dict, str]:
# 校验保存更改配置的合法性
task_data["interval"] = 60 * 60 * 24 # 默认检测间隔时间 1 天
if not (isinstance(task_data['cycle'], int) and task_data['cycle'] >= 1):
return "The remaining time parameter is incorrect, at least 1 day"
return task_data
def get_title(self, task_data: dict) -> str:
return "SSL Certificate expiration"
def check_num_rule(self, num_rule: dict) -> Union[dict, str]:
num_rule["get_by_func"] = "can_send_by_num_rule"
return num_rule
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
# 构造sms特殊消息体
return 'ssl_end|YakPanel SSL Expiration Reminder', {
"name": push_public_data["ip"],
"domain": self.domains[0],
# 'time': self.ssl_list[0]["notAfter"],
'total': len(self.domains)
}
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
# 构造wx特殊消息体
msg = WxAccountMsg.new_msg()
msg.thing_type = "SSL expiration reminder"
msg.msg = "There are {} domains whose certificates will expire, affecting access".format(len(self.domains))
msg.next_msg = "Please login to the YakPanel and renew in the certificates"
return msg
# 不需要额外hook
class SiteEndTimeTask(BaseTask):
def __init__(self):
super().__init__()
self.source_name = "site_end_time"
self.template_name = "Site expiration reminders"
self.title = "Site expiration reminders"
self._tip_file = "{}/site_end_time.tip".format(PUSH_DATA_PATH)
self._tip_data: Optional[dict] = None
self._task_config = TaskConfig()
self.push_keys = []
self.task_id = None
@property
def tips(self) -> dict:
if self._tip_data is not None:
return self._tip_data
try:
self._tip_data = json.loads(read_file(self._tip_file))
except:
self._tip_data = {}
return self._tip_data
def save_tip(self):
write_file(self._tip_file, json.dumps(self.tips))
def check_task_data(self, task_data: dict) -> Union[dict, str]:
task_data["interval"] = 60 * 60 * 24 # 默认检测间隔时间 1 天
if not (isinstance(task_data['cycle'], int) and task_data['cycle'] >= 1):
return "The remaining time parameter is incorrect, at least 1 day"
return task_data
def get_keyword(self, task_data: dict) -> str:
return "site_end_time"
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
m_end_date = time.strftime('%Y-%m-%d', time.localtime(time.time() + 86400 * int(task_data['cycle'])))
web_list = DB('sites').where(
'edate>? AND edate<? AND (status=? OR status=?)',
('0000-00-00', m_end_date, 1, u'正在运行')
).field('id,name, edate').select()
if not (isinstance(web_list, list) and len(web_list) >= 1):
return None
total = self._task_config.get_by_id(task_id).get("number_rule", {}).get("total", 1)
s_list = ['>Number of expiring sites: <font color=#ff0000>{} </font>'.format(len(web_list))]
for x in web_list:
if self.tips.get(x['name'], 0) >= total:
continue
self.push_keys.append(x['name'])
s_list.append(">Website: {} Expiration: {}".format(x['name'], x['edate']))
if not self.push_keys:
return None
self.task_id = task_id
self.title = self.get_title(task_data)
return {
"msg_list": s_list
}
def check_num_rule(self, num_rule: dict) -> Union[dict, str]:
num_rule["get_by_func"] = "can_send_by_num_rule"
return num_rule
# 实际的次数检查已在 get_push_data 其他位置完成
def can_send_by_num_rule(self, task_id: str, task_data: dict, number_rule: dict, push_data: dict) -> Optional[str]:
return None
def filter_template(self, template) -> dict:
return template
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
return '', {}
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
msg = WxAccountMsg.new_msg()
msg.thing_type = "Website expiration reminders"
msg.msg = "There are {} sites that are about to expire and may affect site visits".format(len(self.push_keys))
msg.next_msg = "Please log in to the YakPanel and check the details on the website"
return msg
def task_run_end_hook(self, res) -> None:
if not res["do_send"]:
return
if self.push_keys:
for w in self.push_keys:
if w in self.tips:
self.tips[w] += 1
else:
self.tips[w] = 1
self.save_tip()
def task_config_update_hook(self, task: dict) -> None:
if os.path.exists(self._tip_file):
os.remove(self._tip_file)
def task_config_remove_hook(self, task: dict) -> None:
if os.path.exists(self._tip_file):
os.remove(self._tip_file)
class PanelPwdEndTimeTask(BaseTask):
def __init__(self):
super().__init__()
self.source_name = "panel_pwd_end_time"
self.template_name = "YakPanel password expiration date"
self.title = "YakPanel password expiration date"
self.limit_days = 0
def check_task_data(self, task_data: dict) -> Union[dict, str]:
task_data["interval"] = 60 * 60 * 24 # 默认检测间隔时间 1 天
if not (isinstance(task_data['cycle'], int) and task_data['cycle'] >= 1):
return "The remaining time parameter is incorrect, at least 1 day"
return task_data
def get_keyword(self, task_data: dict) -> str:
return "pwd_end_time"
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
import config
c_obj = config.config()
res = c_obj.get_password_config(None)
if res['expire'] > 0 and res['expire_day'] < task_data['cycle']:
self.limit_days = res['expire_day']
s_list = [">Alarm Type: The login password is about to expire",
">Days Remaining: <font color=#ff0000>{} </font>".format(res['expire_day'])]
return {
'msg_list': s_list
}
self.title = self.get_title(task_data)
return None
def filter_template(self, template) -> dict:
return template
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
return '', dict()
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
msg = WxAccountMsg.new_msg()
msg.thing_type = "YakPanel password expiration reminder"
msg.msg = "The login password will expire after {} days".format(self.limit_days)
msg.next_msg = "Log in to the panel and change your password in Settings"
return msg
class PanelLoginTask(BaseTask):
push_tip_file = "/www/server/panel/data/panel_login_send.pl"
def __init__(self):
# import public
# public.print_log("panel_login")
super().__init__()
self.source_name = "panel_login"
self.template_name = "YakPanel login alarm"
self.title = "YakPanel login alarm"
def check_task_data(self, task_data: dict) -> Union[dict, str]:
return {}
def get_keyword(self, task_data: dict) -> str:
return "panel_login"
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
return None
def filter_template(self, template) -> dict:
return template
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
return "login_panel|YakPanel login reminders", {
'name': '[' + push_data.get("ip") + ']',
'time': time.strftime('%Y-%m-%d %X', time.localtime()),
'type': '[' + push_data.get("is_type") + ']',
'user': push_data.get("username")
}
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
msg = WxAccountLoginMsg.new_msg()
msg.thing_type = "YakPanel login reminders"
msg.login_name = push_data.get("username")
msg.login_ip = push_data.get("login_ip")
msg.login_type = push_data.get("is_type")
msg.address = push_data.get("login_ip_area")
return msg
def task_config_update_hook(self, task: dict) -> None:
# import public
# public.print_log(4444444444444)
sender = task["sender"]
if len(sender) > 0:
send_id = sender[0]
else:
return
sender_data = SenderConfig().get_by_id(send_id)
if sender_data:
write_file(self.push_tip_file, sender_data["sender_type"])
def task_config_create_hook(self, task: dict) -> None:
# import public
# public.print_log(444444433333)
sender = task["sender"]
if len(sender) > 0:
send_id = sender[0]
else:
return
sender_data = SenderConfig().get_by_id(send_id)
if sender_data:
write_file(self.push_tip_file, sender_data["sender_type"])
def task_config_remove_hook(self, task: dict) -> None:
# import public
# public.print_log(33333333333333333333333)
if os.path.exists(self.push_tip_file):
os.remove(self.push_tip_file)
class SSHLoginErrorTask(BaseTask):
_months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07', 'Aug': '08',
'Sep': '09', 'Sept': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'}
def __init__(self):
super().__init__()
self.source_name = "ssh_login_error"
self.template_name = "SSH login failure alarm"
self.title = "SSH login failure alarm"
def check_task_data(self, task_data: dict) -> Union[dict, str]:
if not (isinstance(task_data['cycle'], int) and task_data['cycle'] >= 1):
return "The duration parameter is incorrect, at least 1 minute"
if not (isinstance(task_data['count'], int) and task_data['count'] >= 1):
return "The quantity parameter is incorrect, at least 1 time"
if not (isinstance(task_data['interval'], int) and task_data['interval'] >= 60):
return "The interval time parameter is incorrect, at least 60 seconds"
return task_data
def get_keyword(self, task_data: dict) -> str:
return "ssh_login_error"
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
import PluginLoader
args = GET_CLASS()
args.model_index = 'safe'
args.count = task_data['count']
args.p = 1
res = PluginLoader.module_run("syslog", "get_ssh_error", args)
if 'status' in res:
return None
last_info = res[task_data['count'] - 1]
if self.to_date(times=last_info['time']) >= time.time() - task_data['cycle'] * 60:
s_list = [">Notification type: SSH login failure alarm",
">Content of alarm: <font color=#ff0000> Login failed more than {} times in {} minutes</font> ".format(
task_data['cycle'], task_data['count'])]
return {
'msg_list': s_list,
'count': task_data['count']
}
return None
@staticmethod
def to_date(times, fmt_str="%Y-%m-%d %H:%M:%S"):
if times:
if isinstance(times, int):
return times
if isinstance(times, float):
return int(times)
if re.match(r"^\d+$", times):
return int(times)
else:
return 0
ts = time.strptime(times, fmt_str)
return time.mktime(ts)
def filter_template(self, template) -> dict:
return template
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
return '', dict()
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
msg = WxAccountMsg.new_msg()
msg.thing_type = "SSH login failure alarm"
msg.msg = "More than {} login failures".format(push_data['count'])
msg.next_msg = "Log in to the panel to view the SSH login logs"
return msg
class ServicesTask(BaseTask):
def __init__(self):
super().__init__()
self.source_name = "services"
self.template_name = "Service Stop Alert"
self.pids = None
self.service_name = ""
self.restart = None
@staticmethod
def services_list() -> list:
"""
获取已安装的服务
"""
from script.restart_services import SERVICES_MAP, ServicesHelper
res = []
for s in SERVICES_MAP.keys():
if s == "panel":
continue
obj = ServicesHelper(s)
if obj.is_install:
res.append({
"title": "{} service discontinued".format(s),
"value": s
})
return res
def check_task_data(self, task_data: dict) -> Union[dict, str]:
if task_data["project"] not in {
i["value"] for i in self.services_list()
}:
return "The selected service does not exist"
if task_data["count"] not in (1, 2):
return "Auto-restart selection error"
if not (isinstance(task_data['interval'], int) and task_data['interval'] >= 60):
return "The interval time parameter is incorrect, at least 60 seconds"
return task_data
def get_keyword(self, task_data: dict) -> str:
return task_data["project"]
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
self.title = self.get_title(task_data)
self.service_name = task_data.get("project")
if self.service_name not in [v["value"] for v in self.services_list()]:
return None
if self.get_server_status():
return None
s_list = [
"> Service Type: " + self.service_name,
"> Service State: 【" + self.service_name + "】Service Has Been Discontinued"
]
if manual_flag().get(self.service_name) == 1:
# is manually closed
return None
else:
if task_data.get("count") == 1:
count = 0
while count <= 1: # retry
self._services_start(self.service_name)
count += 1
if not self.get_server_status():
self.restart = False
s_list[1] = "> Service State: 【" + self.service_name + "】Service Restart Failed"
else:
self.restart = True
s_list[1] = "> Service State: 【" + self.service_name + "】Service Restart Successfully"
if self.restart is True:
break
return {"msg_list": s_list}
def get_title(self, task_data: dict) -> str:
return "Service Stop Alert --" + task_data["project"]
def _services_start(self, service_name: str):
from script.restart_services import ServicesHelper
obj = ServicesHelper(service_name)
obj.script("start")
time.sleep(3)
if not obj.is_running:
obj.script("restart")
self.pids = psutil.pids() # renew pids
return
def get_server_status(self) -> bool:
time.sleep(3)
from script.restart_services import ServicesHelper
return ServicesHelper(self.service_name).is_running
def filter_template(self, template: dict) -> Optional[dict]:
server_list = self.services_list()
if not server_list:
return None
default = None
for i in server_list:
if i.get("value") == "nginx":
default = "nginx"
break
elif i.get("value") == "apache":
default = "nginx"
break
default = server_list[0].get("value") if not default and len(server_list) != 0 else ""
template["field"][0]["items"] = server_list
template["field"][0]["default"] = default
return template
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
return "servcies|{}".format(self.title), {
'name': '{}'.format(get_config_value('title')),
'product': self.service_name,
'product1': self.service_name
}
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
msg = WxAccountMsg.new_msg()
if len(self.service_name) > 14:
service_name = self.service_name[:11] + "..."
else:
service_name = self.service_name
msg.thing_type = "{} service discontinued remind".format(service_name)
if self.restart is None:
msg.msg = "{}service has been discontinued".format(service_name)
elif self.restart is True:
msg.msg = "{}ervice restarted successfully".format(service_name)
else:
msg.msg = "{}service restart failed".format(service_name)
return msg
class PanelSafePushTask(BaseTask):
def __init__(self):
super().__init__()
self.source_name = "panel_safe_push"
self.template_name = "YakPanel safety alarm"
self.title = "YakPanel safety alarm"
self.msg_list = []
def check_task_data(self, task_data: dict) -> Union[dict, str]:
task_data["interval"] = 60
return task_data
def get_keyword(self, task_data: dict) -> str:
return "panel_safe_push"
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
s_list = []
# 面板登录用户安全
t_add, t_del, total = self.get_records_calc('login_user_safe', DB('users'))
if t_add > 0 or t_del > 0:
s_list.append(
">Change of logged-in user: <font color=#ff0000> Total:{}, Add:{}, Delete:{}</font>.".format(total,
t_add,
t_del))
# 面板日志发生删除
t_add, t_del, total = self.get_records_calc('panel_logs_safe', DB('logs'), 1)
if t_del > 0:
s_list.append(
">If the panel log is deleted, the number of deleted logs is as follows:<font color=#ff0000>{} </font>".format(
t_del))
debug_str = 'Off'
debug_status = 'False'
# 面板开启开发者模式告警
if os.path.exists('/www/server/panel/data/debug.pl'):
debug_status = 'True'
debug_str = 'On'
skey = 'panel_debug_safe'
tmp = public_get_cache_func(skey)['data']
if not tmp:
public_set_cache_func(skey, debug_status)
else:
if str(debug_status) != tmp:
s_list.append(">Panel developer mode has changed, current state:{}".format(debug_str))
public_set_cache_func(skey, debug_status)
# 面板用户名和密码发生变更
find = DB('users').where('id=?', (1,)).find()
if find:
skey = 'panel_user_change_safe'
user_str = self.hash_md5(find['username']) + '|' + self.hash_md5(find['password'])
tmp = public_get_cache_func(skey)['data']
if not tmp:
public_set_cache_func(skey, user_str)
else:
if user_str != tmp:
s_list.append(">The login account or password of the panel has been changed")
public_set_cache_func(skey, user_str)
if len(s_list) == 0:
return None
self.msg_list = s_list
return {"msg_list": s_list}
@staticmethod
def hash_md5(data: str) -> str:
h = hashlib.md5()
h.update(data.encode('utf-8'))
return h.hexdigest()
@staticmethod
def get_records_calc(skey, table, stype=0):
"""
@name 获取指定表数据是否发生改变
@param skey string 缓存key
@param table db 表对象
@param stype : 0 计算总条数 1 只计算删除
@return array
total int 总数
"""
total_add = 0
total_del = 0
# 获取当前总数和最大索引值
u_count = table.count()
u_max = table.order('id desc').getField('id')
n_data = {'count': u_count, 'max': u_max}
tmp = public_get_cache_func(skey)['data']
if not tmp:
public_set_cache_func(skey, n_data)
else:
n_data = tmp
# 检测上一次记录条数是否被删除
pre_count = table.where('id<=?', (n_data['max'])).count()
if stype == 1:
if pre_count < n_data['count']: # 有数据被删除,记录被删条数
total_del += n_data['count'] - pre_count
n_count = u_max - pre_count # 上次记录后新增的条数
n_idx = u_max - n_data['max'] # 上次记录后新增的索引差
if n_count < n_idx:
total_del += n_idx - n_count
else:
if pre_count < n_data['count']: # 有数据被删除,记录被删条数
total_del += n_data['count'] - pre_count
elif pre_count > n_data['count']:
total_add += pre_count - n_data['count']
t1_del = 0
n_count = u_count - pre_count # 上次记录后新增的条数
if u_max > n_data['max']:
n_idx = u_max - n_data['max'] # 上次记录后新增的索引差
if n_count < n_idx: t1_del = n_idx - n_count
# 新纪录除开删除,全部计算为新增
t1_add = n_count - t1_del
if t1_add > 0:
total_add += t1_add
total_del += t1_del
public_set_cache_func(skey, {'count': u_count, 'max': u_max})
return total_add, total_del, u_count
def filter_template(self, template: dict) -> Optional[dict]:
return template
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
return '', {}
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
msg = WxAccountMsg.new_msg()
msg.thing_type = "YakPanel security alarms"
the_msg = []
for d in self.msg_list:
if d.find("Change of logged-in user"):
the_msg.append("User Changes")
if d.find("the panel log is deleted"):
the_msg.append("the panel log is deleted")
if d.find("Panel developer mode has changed"):
the_msg.append("Panel developer mode has changed")
if d.find("The login account or password"):
the_msg.append("Account and Password change")
msg.msg = "".join(the_msg)
if len(the_msg) > 20:
msg.msg = msg.msg[:17] + "..."
msg.next_msg = "Please log in to the panel to view the corresponding information"
return msg
class SSHLoginTask(BaseTask):
push_tip_file = "/www/server/panel/data/ssh_send_type.pl"
def __init__(self):
super().__init__()
self.source_name = "ssh_login"
self.template_name = "SSH login alert"
self.title = "SSH login alert"
def check_task_data(self, task_data: dict) -> Union[dict, str]:
return {}
def get_keyword(self, task_data: dict) -> str:
return "ssh_login"
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
return None
def filter_template(self, template) -> dict:
return template
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
return "", {}
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
login_ip = push_data.get("login_ip")
msg = WxAccountMsg.new_msg()
msg.thing_type = "SSH login security alert"
if len(login_ip) == 0: # 检查后门用户时使同
msg.msg = "The server has a backdoor user"
msg.next_msg = "Check the '/ect/passwd' file"
return msg
elif len(login_ip) > 15:
login_ip = login_ip[:12] + "..."
msg.msg = "login ip:{}".format(login_ip)
msg.next_msg = "Please log in to the panel and check whether the login is secure"
return msg
def task_config_update_hook(self, task: dict) -> None:
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
from ssh_security import ssh_security
ssh_security().start_jian(None)
sender = task["sender"]
if len(sender) > 0:
send_id = sender[0]
else:
return
sender_data = SenderConfig().get_by_id(send_id)
if sender_data:
write_file(self.push_tip_file, sender_data["sender_type"])
def task_config_create_hook(self, task: dict) -> None:
return self.task_config_update_hook(task)
def task_config_remove_hook(self, task: dict) -> None:
if os.path.exists(self.push_tip_file):
os.remove(self.push_tip_file)
class PanelUpdateTask(BaseTask):
def __init__(self):
super().__init__()
self.source_name = "panel_update"
self.template_name = "YakPanel update reminders"
self.title = "YakPanel update reminders"
self.new_ver = ''
def _get_no_user_tip(self) -> str:
"""没有用户信息的需要,写一个临时文件做标记,并尽可能保持不变"""
tip_file = "/www/server/panel/data/no_user_tip.pl"
if not os.path.exists(tip_file):
data: str = get_network_ip()
data = "Tag files when there is no user information\n" + hashlib.sha256(data.encode("utf-8")).hexdigest()
write_file(tip_file, data)
else:
data = read_file(tip_file)
if isinstance(data, bool):
os.remove(tip_file)
return self._get_no_user_tip()
return data
def user_can_request_hour(self):
"""根据哈希值,输出一个用户可查询"""
user_info = public_get_user_info()
if not bool(user_info):
user_info_str = self._get_no_user_tip()
else:
user_info_str = json.dumps(user_info)
hash_value = hashlib.md5(user_info_str.encode("utf-8")).digest()
sum_value = 0
for i in range(4):
sum_value = sum_value + int.from_bytes(hash_value[i * 32: (i + 1) * 32], "big")
res = sum_value % 24
return res
def check_task_data(self, task_data: dict) -> Union[dict, str]:
task_data["interval"] = 60 * 60 # 默认检测间隔时间 1 小时
return task_data
def check_num_rule(self, num_rule: dict) -> Union[dict, str]:
num_rule['day_num'] = 1 # 默认一天发一次
return num_rule
def get_keyword(self, task_data: dict) -> str:
return "panel_update"
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
# 不在固定时间段内,跳过
if self.user_can_request_hour() != datetime.now().hour:
return
# 面板更新日志 todo 暂时隐藏 后期可改成 ajax?action=UpdatePanel 获取更新日志
if public.is_self_hosted():
return None
s_url = 'https://wafapi2.yakpanel.com/api/panel/updateLinux'
try:
res = json.loads(public_http_post(s_url, {}))
if not res:
return None
except:
return None
n_ver = res['version']
if res['is_beta']:
n_ver = res['beta']['version']
self.new_ver = n_ver
cache_key = "panel_update_cache"
old_ver = public_get_cache_func(cache_key)['data']
if old_ver and old_ver != n_ver:
s_list = [">Notification type: 面板版本更新",
">当前版本:{} ".format(panel_version()),
">最新版本:{}".format(n_ver)]
return {
"msg_list": s_list
}
else:
public_set_cache_func(cache_key, n_ver)
return None
def filter_template(self, template: dict) -> Optional[dict]:
return template
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
return "", {}
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
msg = WxAccountMsg.new_msg()
msg.thing_type = "YakPanel update reminders"
msg.msg = "最新版:{}已发布".format(self.new_ver)
msg.next_msg = "您可以登录面板,执行更新"
return msg
def task_run_end_hook(self, res: dict) -> None:
if res["do_send"]:
public_set_cache_func("panel_update_cache", self.new_ver)
class ProjectStatusTask(BaseTask):
def __init__(self):
super().__init__()
self.source_name = "project_status"
self.template_name = "Project stop alarm"
# self.title = "Project stop alarm"
self.project_name = ''
self.restart = None
@staticmethod
def _to_project_type(type_id: int):
if type_id == 1:
return "Java"
if type_id == 2:
return "Node"
if type_id == 3:
return "Go"
if type_id == 4:
return "Python"
if type_id == 5:
return "Other"
@staticmethod
def _to_project_id(type_name):
if type_name == "Java":
return 0
if type_name == "Node":
return 1
if type_name == "Go":
return 2
if type_name == "Python":
return 3
if type_name == "Other":
return 4
@staticmethod
def _to_project_model(type_id: int):
if type_id == 1:
return "javaModel"
if type_id == 2:
return "nodejsModel"
if type_id == 3:
return "goModel"
if type_id == 4:
return "pythonModel"
if type_id == 5:
return "otherModel"
def get_title(self, task_data: dict) -> str:
return "Project stop alarm -- {}".format(self._get_project_name(task_data["project"]))
def check_task_data(self, task_data: dict) -> Union[dict, str]:
if not (isinstance(task_data["cycle"], int) and 1 <= task_data["cycle"] <= 5):
return 'Unsupported project types.'
sql = DB("sites")
web_info = sql.where(
"project_type = ? and id = ?",
(self._to_project_type(task_data["cycle"]), task_data["project"])
).field("id,name").find()
if not web_info:
return 'If you do not have this item, you cannot set an alarm'
if task_data["count"] not in (1, 2):
return "Auto-restart selection error"
if not (isinstance(task_data['interval'], int) and task_data['interval'] >= 60):
return "The interval time parameter is incorrect, at least 60 seconds"
return task_data
def get_web_list(self) -> List:
items_by_type = [[], [], [], [], []]
res_list = DB('sites').field('id,name,project_type').select()
for i in res_list:
if i["project_type"] == "PHP" or i["project_type"] == "proxy":
continue
idx: int = self._to_project_id(i["project_type"])
if idx is None:
continue
items_by_type[idx].append({
"title": i["name"],
"value": i["id"]
})
return items_by_type
def get_keyword(self, task_data: dict) -> str:
return "{}_{}".format(task_data["cycle"], self._get_project_name(task_data["project"]))
@staticmethod
def _get_project_name(project_id: int) -> str:
data = DB('sites').where('id = ?', (project_id,)).field('id,name').find()
if isinstance(data, dict):
return data["name"]
return "<unknown>"
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
model_obj = import_module(".{}".format(self._to_project_model(task_data["cycle"])), package="projectModelV2")
model_main_obj = model_obj.main()
running, project_name = getattr(model_main_obj, "get_project_status")(task_data["project"])
if running is not False:
return None
s_list = [
">Project type: " + self._to_project_type(task_data["cycle"]),
">Project name: " + project_name,
">Project state: The project status is stopped"]
self.project_name = project_name
if int(task_data["count"]) == 1:
get_obj = GET_CLASS()
get_obj.project_name = project_name
result = getattr(model_main_obj, "StartProject")(get_obj)
if result["status"] == 0:
self.restart = True
s_list[
2] = ">Project state: Check that the project status is stopped, and it has been restarted successfully"
else:
self.restart = False
s_list[2] = ">Project state: Check that the project status is stopped, try to restart but fail"
self.title = self.get_title(task_data)
return {
"msg_list": s_list,
}
def filter_template(self, template: dict) -> Optional[dict]:
_, web_by_type = web_info()
template["field"][1]["all_items"] = web_by_type
template["field"][1]["items"] = web_by_type[0]
if not web_by_type:
return None
return template
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
return '', {}
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
msg = WxAccountMsg.new_msg()
if len(self.project_name) >= 14:
project_name = self.project_name[:11] + "..."
else:
project_name = self.project_name
msg.thing_type = "Project stop alarm"
if self.restart is None:
msg.msg = "Project {} has been stopped".format(project_name)
elif self.restart is True:
msg.msg = "Project {} was successfully restarted".format(project_name)
else:
msg.msg = "Project {} failed to restart".format(project_name)
return msg
class ViewMsgFormat(object):
_FORMAT = {
"1": (
lambda x: "<span>Time remaining less than {} days {}</span>".format(
x["task_data"].get("cycle"),
("(If it is not processed, it will be resent 1 time the next day for %d days)" % x.get("number_rule",
{}).get("total",
0)) if x.get(
"number_rule", {}).get("total", 0) else ""
)
),
"2": (),
"3": (),
"8": (
lambda x: "<span>Alert when the panel is logged in</span>"
),
"7": (
lambda x: "<span>When an SSH login is detected, an alarm is generated</span>"
),
"4": (
lambda
x: "<span>Triggered by {} consecutive failed logins within {} minutes, and tested again every {} seconds</span>".format(
x["task_data"].get("count"),
x["task_data"].get("cycle"),
x["task_data"].get("interval"),
)
),
"5": (
lambda
x: "<span>A notification is sent once when the service is stopped, and it is detected again after {} seconds</span>".format(
x["task_data"].get("interval"))
),
"9": (
lambda
x: "<span>A notification is sent when the item is stopped, and the test is repeated after {} seconds, {} times per day</span>".format(
x["task_data"].get("interval"),
x.get("number_rule", {}).get("day_num", 0))
),
"6": (
lambda
x: "<span>Alerts are sent when dangerous operations such as user changes, panel logs are deleted, and developers are enabled</span>"
),
"10": (
lambda x: "<span>A notification is sent once when a new version is detected</span>"
)
}
def get_msg(self, task: dict) -> Optional[str]:
if task["template_id"] in ["1", "2", "3"]:
return self._FORMAT["1"](task)
if task["template_id"] in self._FORMAT:
return self._FORMAT[task["template_id"]](task)
return None