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= 1): return None total = self._task_config.get_by_id(task_id).get("number_rule", {}).get("total", 1) s_list = ['>Number of expiring sites: {} '.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: {} ".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: Login failed more than {} times in {} minutes ".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: Total:{}, Add:{}, Delete:{}.".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:{} ".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 "" 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: "Time remaining less than {} days {}".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: "Alert when the panel is logged in" ), "7": ( lambda x: "When an SSH login is detected, an alarm is generated" ), "4": ( lambda x: "Triggered by {} consecutive failed logins within {} minutes, and tested again every {} seconds".format( x["task_data"].get("count"), x["task_data"].get("cycle"), x["task_data"].get("interval"), ) ), "5": ( lambda x: "A notification is sent once when the service is stopped, and it is detected again after {} seconds".format( x["task_data"].get("interval")) ), "9": ( lambda x: "A notification is sent when the item is stopped, and the test is repeated after {} seconds, {} times per day".format( x["task_data"].get("interval"), x.get("number_rule", {}).get("day_num", 0)) ), "6": ( lambda x: "Alerts are sent when dangerous operations such as user changes, panel logs are deleted, and developers are enabled" ), "10": ( lambda x: "A notification is sent once when a new version is detected" ) } 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