Initial YakPanel commit
This commit is contained in:
816
script/restart_services.py
Normal file
816
script/restart_services.py
Normal file
@@ -0,0 +1,816 @@
|
||||
# coding: utf-8
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from functools import wraps, partial
|
||||
from typing import Optional, Dict, Callable
|
||||
|
||||
import fcntl
|
||||
|
||||
os.chdir("/www/server/panel")
|
||||
sys.path.insert(0, "class/")
|
||||
sys.path.insert(0, "class_v2/")
|
||||
|
||||
SETUP_PATH = "/www/server"
|
||||
DATA_PATH = os.path.join(SETUP_PATH, "panel/data")
|
||||
PLUGINS_PATH = os.path.join(SETUP_PATH, "panel/plugin")
|
||||
|
||||
DAEMON_SERVICE = os.path.join(DATA_PATH, "daemon_service.pl")
|
||||
DAEMON_SERVICE_LOCK = os.path.join(DATA_PATH, "daemon_service_lock.pl")
|
||||
DAEMON_RESTART_RECORD = os.path.join(DATA_PATH, "daemon_restart_record.pl")
|
||||
MANUAL_FLAG = os.path.join(SETUP_PATH, "panel/data/mod_push_data", "manual_flag.pl")
|
||||
|
||||
|
||||
def read_file(filename: str):
|
||||
fp = None
|
||||
try:
|
||||
fp = open(filename, "rb")
|
||||
f_body_bytes: bytes = fp.read()
|
||||
f_body = f_body_bytes.decode("utf-8", errors='ignore')
|
||||
fp.close()
|
||||
return f_body
|
||||
except Exception:
|
||||
return False
|
||||
finally:
|
||||
if fp and not fp.closed:
|
||||
fp.close()
|
||||
|
||||
|
||||
def run_command(cmd, timeout=5) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
check=False
|
||||
)
|
||||
output = result.stdout.strip()
|
||||
return output if output else ""
|
||||
except subprocess.TimeoutExpired as t:
|
||||
write_logs(f"Command execution timed out: {cmd}, error: {t}")
|
||||
return ""
|
||||
except Exception as e:
|
||||
write_logs(f"Command execution failed: {cmd}, error: {e}")
|
||||
return ""
|
||||
|
||||
|
||||
def service_shop_name(services_name: str) -> str:
|
||||
# 对应商店名字
|
||||
shop_name = {
|
||||
"postfix": "mail_sys",
|
||||
"pgsql": "pgsql_manager",
|
||||
"pure-ftpd": "pureftpd",
|
||||
|
||||
"php-fpm-52": "php-5.2",
|
||||
"php-fpm-53": "php-5.3",
|
||||
"php-fpm-54": "php-5.4",
|
||||
"php-fpm-55": "php-5.5",
|
||||
"php-fpm-56": "php-5.6",
|
||||
"php-fpm-70": "php-7.0",
|
||||
"php-fpm-71": "php-7.1",
|
||||
"php-fpm-72": "php-7.2",
|
||||
"php-fpm-73": "php-7.3",
|
||||
"php-fpm-74": "php-7.4",
|
||||
"php-fpm-80": "php-8.0",
|
||||
"php-fpm-81": "php-8.1",
|
||||
"php-fpm-82": "php-8.2",
|
||||
"php-fpm-83": "php-8.3",
|
||||
"php-fpm-84": "php-8.4",
|
||||
"php-fpm-85": "php-8.5",
|
||||
}
|
||||
return shop_name.get(services_name, services_name)
|
||||
|
||||
|
||||
def pretty_title(services_name: str) -> str:
|
||||
title = {
|
||||
"apache": "Apache",
|
||||
"nginx": "Nginx",
|
||||
"openlitespeed": "OpenLiteSpeed",
|
||||
"redis": "Redis",
|
||||
"mysql": "MySQL/MariaDB",
|
||||
"mongodb": "MongoDB",
|
||||
"pgsql": "PostgreSQL",
|
||||
"pure-ftpd": "Pure-FTPd",
|
||||
"memcached": "Memcached",
|
||||
"ssh": "SSH",
|
||||
"postfix": "Postfix",
|
||||
"pdns": "PowerDNS",
|
||||
|
||||
# PHP-FPM
|
||||
"php-fpm-52": "PHP 5.2 FPM",
|
||||
"php-fpm-53": "PHP 5.3 FPM",
|
||||
"php-fpm-54": "PHP 5.4 FPM",
|
||||
"php-fpm-55": "PHP 5.5 FPM",
|
||||
"php-fpm-56": "PHP 5.6 FPM",
|
||||
"php-fpm-70": "PHP 7.0 FPM",
|
||||
"php-fpm-71": "PHP 7.1 FPM",
|
||||
"php-fpm-72": "PHP 7.2 FPM",
|
||||
"php-fpm-73": "PHP 7.3 FPM",
|
||||
"php-fpm-74": "PHP 7.4 FPM",
|
||||
"php-fpm-80": "PHP 8.0 FPM",
|
||||
"php-fpm-81": "PHP 8.1 FPM",
|
||||
"php-fpm-82": "PHP 8.2 FPM",
|
||||
"php-fpm-83": "PHP 8.3 FPM",
|
||||
"php-fpm-84": "PHP 8.4 FPM",
|
||||
"php-fpm-85": "PHP 8.5 FPM",
|
||||
|
||||
# plugins
|
||||
"btwaf": "YakPanel WAF",
|
||||
"fail2ban": "Fail2Ban",
|
||||
}
|
||||
return title.get(services_name, services_name)
|
||||
|
||||
|
||||
# =======================================================
|
||||
def ssh_ver() -> str:
|
||||
version_info = run_command(["ssh", "-V"])
|
||||
if version_info and "OpenSSH" in version_info:
|
||||
return version_info.split(",")[0]
|
||||
return ""
|
||||
|
||||
|
||||
def postfix_ver() -> str:
|
||||
# mail_version = x.x.x
|
||||
output = run_command(["/usr/sbin/postconf", "mail_version"])
|
||||
if output and "=" in output:
|
||||
return output.split("=")[-1].strip()
|
||||
return ""
|
||||
|
||||
|
||||
def pgsql_pid() -> str:
|
||||
pid_str = run_command(["pgrep", "-o", "postgres"])
|
||||
if pid_str and pid_str.isdigit():
|
||||
return pid_str
|
||||
return ""
|
||||
|
||||
|
||||
def pgsql_ver() -> str:
|
||||
# psql (PostgreSQL) 18.0
|
||||
output = run_command([f"{SETUP_PATH}/pgsql/bin/psql", "--version"])
|
||||
if output:
|
||||
from re import search
|
||||
match = search(r"(\d+\.\d+(\.\d+)?)", output)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
org_file = f"{SETUP_PATH}/pgsql/data/PG_VERSION",
|
||||
if os.path.exists(str(org_file)):
|
||||
try:
|
||||
with open(str(org_file), "r") as f:
|
||||
version = f.read().strip()
|
||||
return version
|
||||
except:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
|
||||
def pdns_pid() -> str:
|
||||
pid_str = run_command(["pgrep", "-x", "pdns_server"])
|
||||
if pid_str and pid_str.isdigit():
|
||||
return pid_str
|
||||
return ""
|
||||
|
||||
|
||||
def pdns_ver() -> str:
|
||||
# PowerDNS Authoritative Server x.x.x
|
||||
output = run_command(["pdns_server", "--version"])
|
||||
if not output:
|
||||
return ""
|
||||
from re import search
|
||||
match = search(r"(\d+\.\d+\.\d+)", output)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
if "PowerDNS" in output:
|
||||
return output.split()[-1]
|
||||
return ""
|
||||
|
||||
|
||||
def waf_pid() -> str:
|
||||
pid_str = run_command(["pgrep", "-x", "BT-WAF"])
|
||||
if pid_str and pid_str.isdigit():
|
||||
return pid_str
|
||||
return ""
|
||||
|
||||
|
||||
def pluign_ver(plugin_name: str) -> str:
|
||||
info = f"{PLUGINS_PATH}/{plugin_name}/info.json"
|
||||
if not os.path.exists(info):
|
||||
return ""
|
||||
try:
|
||||
with open(info, "r") as f:
|
||||
json_data = json.load(f)
|
||||
ret = json_data.get("versions", "")
|
||||
return ret
|
||||
except:
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
SERVICES_MAP = {
|
||||
# ========================= core base =============================
|
||||
"panel": (
|
||||
"Yak-Panel", f"{SETUP_PATH}/panel/logs/panel.pid", f"{SETUP_PATH}/panel/init.sh",
|
||||
f"{SETUP_PATH}/nginx/version.pl",
|
||||
),
|
||||
"apache": (
|
||||
"httpd", f"{SETUP_PATH}/apache/logs/httpd.pid", "/etc/init.d/httpd",
|
||||
f"{SETUP_PATH}/apache/version.pl",
|
||||
),
|
||||
"nginx": (
|
||||
"nginx", f"{SETUP_PATH}/nginx/logs/nginx.pid", "/etc/init.d/nginx",
|
||||
f"{SETUP_PATH}/nginx/version.pl",
|
||||
),
|
||||
"openlitespeed": (
|
||||
"litespeed", "/tmp/lshttpd/lshttpd.pid", "/usr/local/lsws/bin/lswsctrl",
|
||||
"/usr/local/lsws/VERSION",
|
||||
),
|
||||
"redis": (
|
||||
"redis-server", f"{SETUP_PATH}/redis/redis.pid", "/etc/init.d/redis",
|
||||
f"{SETUP_PATH}/redis/version.pl",
|
||||
),
|
||||
"mysql": (
|
||||
"mysqld", "/tmp/mysql.sock", "/etc/init.d/mysqld",
|
||||
f"{SETUP_PATH}/mysql/version.pl",
|
||||
),
|
||||
"mongodb": (
|
||||
"mongod", f"{SETUP_PATH}/mongodb/log/configsvr.pid", "/etc/init.d/mongodb",
|
||||
f"{SETUP_PATH}/mongodb/version.pl",
|
||||
),
|
||||
"pgsql": (
|
||||
"postgres", pgsql_pid, "/etc/init.d/pgsql",
|
||||
pgsql_ver,
|
||||
),
|
||||
"pure-ftpd": (
|
||||
"pure-ftpd", "/var/run/pure-ftpd.pid", "/etc/init.d/pure-ftpd",
|
||||
f"{SETUP_PATH}/pure-ftpd/version.pl",
|
||||
),
|
||||
"memcached": (
|
||||
"memcached", "/var/run/memcached.pid", "/etc/init.d/memcached",
|
||||
"/usr/local/memcached/version_check.pl",
|
||||
),
|
||||
"ssh": (
|
||||
"sshd", "/var/run/sshd.pid", "/etc/init.d/ssh",
|
||||
ssh_ver,
|
||||
),
|
||||
"postfix": (
|
||||
"master", "/var/spool/postfix/pid/master.pid", "/etc/init.d/postfix",
|
||||
postfix_ver,
|
||||
),
|
||||
"pdns": (
|
||||
# "pdns_server", pdns_pid, "/usr/sbin/pdns_server",
|
||||
"pdns_server", pdns_pid, "/www/server/panel/class_v2/ssl_dnsV2/aadns.pl",
|
||||
pdns_ver,
|
||||
),
|
||||
|
||||
# ======================== PHP-FPM ============================
|
||||
"php-fpm-52": ("php-fpm", f"{SETUP_PATH}/php/52/var/run/php-fpm.pid", "/etc/init.d/php-fpm-52",
|
||||
f"{SETUP_PATH}/php/52/version.pl"),
|
||||
"php-fpm-53": ("php-fpm", f"{SETUP_PATH}/php/53/var/run/php-fpm.pid", "/etc/init.d/php-fpm-53",
|
||||
f"{SETUP_PATH}/php/53/version.pl"),
|
||||
"php-fpm-54": ("php-fpm", f"{SETUP_PATH}/php/54/var/run/php-fpm.pid", "/etc/init.d/php-fpm-54",
|
||||
f"{SETUP_PATH}/php/54/version.pl"),
|
||||
"php-fpm-55": ("php-fpm", f"{SETUP_PATH}/php/55/var/run/php-fpm.pid", "/etc/init.d/php-fpm-55",
|
||||
f"{SETUP_PATH}/php/55/version.pl"),
|
||||
"php-fpm-56": ("php-fpm", f"{SETUP_PATH}/php/56/var/run/php-fpm.pid", "/etc/init.d/php-fpm-56",
|
||||
f"{SETUP_PATH}/php/56/version.pl"),
|
||||
"php-fpm-70": ("php-fpm", f"{SETUP_PATH}/php/70/var/run/php-fpm.pid", "/etc/init.d/php-fpm-70",
|
||||
f"{SETUP_PATH}/php/70/version.pl"),
|
||||
"php-fpm-71": ("php-fpm", f"{SETUP_PATH}/php/71/var/run/php-fpm.pid", "/etc/init.d/php-fpm-71",
|
||||
f"{SETUP_PATH}/php/71/version.pl"),
|
||||
"php-fpm-72": ("php-fpm", f"{SETUP_PATH}/php/72/var/run/php-fpm.pid", "/etc/init.d/php-fpm-72",
|
||||
f"{SETUP_PATH}/php/72/version.pl"),
|
||||
"php-fpm-73": ("php-fpm", f"{SETUP_PATH}/php/73/var/run/php-fpm.pid", "/etc/init.d/php-fpm-73",
|
||||
f"{SETUP_PATH}/php/73/version.pl"),
|
||||
"php-fpm-74": ("php-fpm", f"{SETUP_PATH}/php/74/var/run/php-fpm.pid", "/etc/init.d/php-fpm-74",
|
||||
f"{SETUP_PATH}/php/74/version.pl"),
|
||||
"php-fpm-80": ("php-fpm", f"{SETUP_PATH}/php/80/var/run/php-fpm.pid", "/etc/init.d/php-fpm-80",
|
||||
f"{SETUP_PATH}/php/80/version.pl"),
|
||||
"php-fpm-81": ("php-fpm", f"{SETUP_PATH}/php/81/var/run/php-fpm.pid", "/etc/init.d/php-fpm-81",
|
||||
f"{SETUP_PATH}/php/81/version.pl"),
|
||||
"php-fpm-82": ("php-fpm", f"{SETUP_PATH}/php/82/var/run/php-fpm.pid", "/etc/init.d/php-fpm-82",
|
||||
f"{SETUP_PATH}/php/82/version.pl"),
|
||||
"php-fpm-83": ("php-fpm", f"{SETUP_PATH}/php/83/var/run/php-fpm.pid", "/etc/init.d/php-fpm-83",
|
||||
f"{SETUP_PATH}/php/83/version.pl"),
|
||||
"php-fpm-84": ("php-fpm", f"{SETUP_PATH}/php/84/var/run/php-fpm.pid", "/etc/init.d/php-fpm-84",
|
||||
f"{SETUP_PATH}/php/84/version_check.pl"),
|
||||
"php-fpm-85": ("php-fpm", f"{SETUP_PATH}/php/85/var/run/php-fpm.pid", "/etc/init.d/php-fpm-85",
|
||||
f"{SETUP_PATH}/php/85/version_check.pl"),
|
||||
|
||||
# ======================== plugins ============================
|
||||
# nginx : btwaf
|
||||
# apache: btwaf_httpd
|
||||
"btwaf": (
|
||||
# "BT-WAF", f"{PLUGINS_PATH}/btwaf/BT-WAF.pid", f"/etc/init.d/btwaf",
|
||||
"BT-WAF", waf_pid, f"/etc/init.d/btwaf",
|
||||
partial(pluign_ver, "btwaf"),
|
||||
),
|
||||
"fail2ban": (
|
||||
"fail2ban-server", f"{PLUGINS_PATH}/fail2ban/fail2ban.pid", "/etc/init.d/fail2ban",
|
||||
partial(pluign_ver, "fail2ban"),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
# 日志
|
||||
def write_logs(msg: str, logger_name: str = "Service Daemon"):
|
||||
try:
|
||||
from public import M
|
||||
t = time.strftime('%Y-%m-%d %X', time.localtime())
|
||||
M("logs").add("uid,username,type,log,addtime", (1, "system", logger_name, msg, t))
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# 手动干预
|
||||
def manual_flag(server_name: str = None, open_: str = None) -> Optional[dict]:
|
||||
if not server_name: # only read
|
||||
return DaemonManager.manual_safe_read()
|
||||
# 人为干预
|
||||
if open_ in ["start", "restart"]: # 激活服务检查
|
||||
return DaemonManager.active_daemon(server_name)
|
||||
elif open_ == "stop": # 跳过服务检查
|
||||
return DaemonManager.skip_daemon(server_name)
|
||||
return DaemonManager.manual_safe_read()
|
||||
|
||||
|
||||
# 管理服务助手
|
||||
class ServicesHelper:
|
||||
def __init__(self, nick_name: str = None):
|
||||
self.nick_name = nick_name
|
||||
self._serviced = None
|
||||
self._pid_source = None
|
||||
self._bash = None
|
||||
self._ver_source = None
|
||||
self._pid_cache = None
|
||||
self._ver_cache = None
|
||||
self._install_cache = None
|
||||
self._info_inited = False
|
||||
|
||||
def __check_pid_process(self, pid: int) -> bool:
|
||||
try:
|
||||
# 是否活跃
|
||||
with open(f"/proc/{pid}/stat", "r") as f:
|
||||
if f.read().split()[2] == "Z":
|
||||
return False # 僵尸进程
|
||||
# 进程是否名字匹配
|
||||
with open(f"/proc/{pid}/comm", "r") as f:
|
||||
proc_name = f.read().strip()
|
||||
|
||||
# 特殊处理 mysql
|
||||
if self.nick_name != "mysql":
|
||||
return proc_name == self._serviced or proc_name == self.nick_name
|
||||
else:
|
||||
return proc_name in ["mysqld", "mariadbd"]
|
||||
except (FileNotFoundError, IndexError):
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _init_info(self) -> None:
|
||||
if self._info_inited:
|
||||
return
|
||||
if not self.nick_name or not isinstance(self.nick_name, str):
|
||||
self._info_inited = True
|
||||
return
|
||||
|
||||
map_info = SERVICES_MAP.get(self.nick_name)
|
||||
if map_info:
|
||||
self._serviced, self._pid_source, self._bash, self._ver_source = map_info
|
||||
|
||||
self._info_inited = True
|
||||
|
||||
@property
|
||||
def pid(self):
|
||||
"""返回pid文件路径或pid值"""
|
||||
if self._pid_cache is None:
|
||||
self._init_info()
|
||||
if isinstance(self._pid_source, Callable):
|
||||
try:
|
||||
pid_val = self._pid_source()
|
||||
self._pid_cache = int(pid_val) if pid_val and pid_val.isdigit() else pid_val
|
||||
except Exception:
|
||||
self._pid_cache = self._pid_source
|
||||
else:
|
||||
self._pid_cache = self._pid_source
|
||||
return self._pid_cache
|
||||
|
||||
@property
|
||||
def version(self) -> str:
|
||||
"""返回服务版本号"""
|
||||
if self._ver_cache is None:
|
||||
self._init_info()
|
||||
if isinstance(self._ver_source, Callable):
|
||||
try:
|
||||
self._ver_cache = self._ver_source()
|
||||
except:
|
||||
self._ver_cache = ""
|
||||
elif isinstance(self._ver_source, str) and os.path.exists(self._ver_source):
|
||||
try:
|
||||
with open(self._ver_source, "r") as f:
|
||||
self._ver_cache = f.read().strip()
|
||||
except:
|
||||
self._ver_cache = ""
|
||||
else:
|
||||
self._ver_cache = ""
|
||||
|
||||
return str(self._ver_cache).strip()
|
||||
|
||||
@property
|
||||
def is_install(self) -> bool:
|
||||
"""判断是否安装"""
|
||||
if self._install_cache is not None:
|
||||
return self._install_cache
|
||||
|
||||
self._init_info()
|
||||
self._install_cache = False
|
||||
# waf特殊处理
|
||||
if self.nick_name == "btwaf":
|
||||
if os.path.exists(f"{PLUGINS_PATH}/btwaf"):
|
||||
self._install_cache = True
|
||||
return self._install_cache
|
||||
# postfix特殊处理
|
||||
if self.nick_name == "postfix":
|
||||
if os.path.exists(f"{PLUGINS_PATH}/mail_sys"):
|
||||
self._install_cache = True
|
||||
return self._install_cache
|
||||
|
||||
if any([
|
||||
self._serviced and os.path.exists(f"/etc/init.d/{self._serviced}"),
|
||||
self.nick_name and os.path.exists(f"/etc/init.d/{self.nick_name}"),
|
||||
self._bash and os.path.exists(self._bash),
|
||||
]):
|
||||
self._install_cache = True
|
||||
|
||||
return self._install_cache
|
||||
|
||||
@property
|
||||
def shop_name(self) -> str:
|
||||
return service_shop_name(self.nick_name)
|
||||
|
||||
@property
|
||||
def pretty_title(self) -> str:
|
||||
return pretty_title(self.nick_name)
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
"""
|
||||
判断进程是否存活
|
||||
pid: 进程pid文件 str 或 int
|
||||
serviced: 进程服务名称
|
||||
nick_name: 进程别名
|
||||
"""
|
||||
if not self.is_install:
|
||||
return False
|
||||
|
||||
if not self.pid:
|
||||
return False
|
||||
|
||||
if isinstance(self.pid, int):
|
||||
return self.__check_pid_process(self.pid)
|
||||
|
||||
if isinstance(self.pid, str):
|
||||
if not os.path.exists(self.pid):
|
||||
return False
|
||||
if self.pid.endswith(".pid"):
|
||||
try:
|
||||
with open(self.pid, "r") as f:
|
||||
temp_pid = int(f.read().strip())
|
||||
return self.__check_pid_process(temp_pid)
|
||||
except (ValueError, FileNotFoundError):
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
elif self.pid.endswith(".sock"):
|
||||
try:
|
||||
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
|
||||
s.settimeout(0.1)
|
||||
s.connect(self.pid)
|
||||
return True
|
||||
except (socket.timeout, ConnectionRefusedError, FileNotFoundError):
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
# not str and not int
|
||||
return False
|
||||
|
||||
def script(self, act: str, logger_name: str = "Service Daemon") -> None:
|
||||
if not self.is_install or act not in ["start", "stop", "restart", "status"]:
|
||||
return
|
||||
try:
|
||||
# "try to {act} [{self.nick_name}]..."
|
||||
self._init_info()
|
||||
bash_path = self._bash if self._bash else f"/etc/init.d/{self._serviced}"
|
||||
|
||||
# if isinstance(self.pid, str) and (
|
||||
# self.pid.endswith(".sock") or self.pid.endswith(".pid")
|
||||
# ):
|
||||
# try:
|
||||
# os.remove(self.pid)
|
||||
# except:
|
||||
# pass
|
||||
|
||||
if self.nick_name == "panel":
|
||||
if not os.path.exists(f"{SETUP_PATH}/panel/init.sh"):
|
||||
from public import get_url
|
||||
os.system(f"curl -k {get_url()}/install/update_7.x_en.sh|bash &")
|
||||
cmd = ["bash", bash_path, act]
|
||||
|
||||
elif self.nick_name == "pdns":
|
||||
cmd = ["systemctl", act, "pdns"]
|
||||
else:
|
||||
cmd = [bash_path, act]
|
||||
|
||||
subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=True
|
||||
)
|
||||
write_logs(f"Service [ {self.nick_name} ] {act}", logger_name)
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
write_logs(f"Failed to {act} {self.nick_name}, error: {e}", logger_name)
|
||||
|
||||
|
||||
# 守护服务管理
|
||||
class DaemonManager:
|
||||
_ensured = False
|
||||
|
||||
@classmethod
|
||||
def __ensure(cls):
|
||||
if cls._ensured:
|
||||
return
|
||||
if not os.path.exists(DAEMON_SERVICE_LOCK):
|
||||
with open(DAEMON_SERVICE_LOCK, "w") as _:
|
||||
pass
|
||||
if not os.path.exists(DAEMON_RESTART_RECORD):
|
||||
with open(DAEMON_RESTART_RECORD, "w") as fm:
|
||||
fm.write(json.dumps({}))
|
||||
os.makedirs(os.path.dirname(MANUAL_FLAG), exist_ok=True)
|
||||
if not os.path.exists(MANUAL_FLAG):
|
||||
with open(MANUAL_FLAG, "w") as fm:
|
||||
fm.write(json.dumps({}))
|
||||
if not os.path.exists(DAEMON_SERVICE):
|
||||
with open(DAEMON_SERVICE, "w") as fm:
|
||||
fm.write(json.dumps([]))
|
||||
cls._ensured = True
|
||||
|
||||
@staticmethod
|
||||
def read_lock(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
DaemonManager.__ensure()
|
||||
with open(DAEMON_SERVICE_LOCK, "r") as lock_file:
|
||||
fcntl.flock(lock_file, fcntl.LOCK_SH)
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
fcntl.flock(lock_file, fcntl.LOCK_UN)
|
||||
|
||||
return wrapper
|
||||
|
||||
@staticmethod
|
||||
def write_lock(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
DaemonManager.__ensure()
|
||||
with open(DAEMON_SERVICE_LOCK, "r+") as lock_file:
|
||||
fcntl.flock(lock_file, fcntl.LOCK_EX)
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
fcntl.flock(lock_file, fcntl.LOCK_UN)
|
||||
|
||||
return wrapper
|
||||
|
||||
@staticmethod
|
||||
@write_lock
|
||||
def operate_daemon(service_name: str, flag: int = 0) -> list:
|
||||
"""
|
||||
flag: 0 add, 1 del
|
||||
"""
|
||||
with open(DAEMON_SERVICE, "r+") as f:
|
||||
try:
|
||||
service = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
service = []
|
||||
if flag == 0:
|
||||
service.append(service_name)
|
||||
elif flag == 1:
|
||||
service = [x for x in service if x != service_name]
|
||||
service = list(set(service))
|
||||
f.seek(0)
|
||||
# noinspection PyTypeChecker
|
||||
json.dump(service, f)
|
||||
f.truncate()
|
||||
return service
|
||||
|
||||
@staticmethod
|
||||
@write_lock
|
||||
def operate_manual_flag(service_name: str, flag: int = 0) -> dict:
|
||||
"""
|
||||
flag: 0 normal, 1 manual closed
|
||||
"""
|
||||
with open(MANUAL_FLAG, "r+") as f:
|
||||
try:
|
||||
service = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
service = {}
|
||||
service[service_name] = flag
|
||||
f.seek(0)
|
||||
# noinspection PyTypeChecker
|
||||
json.dump(service, f)
|
||||
f.truncate()
|
||||
return service
|
||||
|
||||
@staticmethod
|
||||
def remove_daemon(service_name: str) -> list:
|
||||
"""移除守护进程服务"""
|
||||
return DaemonManager.operate_daemon(service_name, 1)
|
||||
|
||||
@staticmethod
|
||||
def add_daemon(service_name: str) -> list:
|
||||
"""添加守护进程服务"""
|
||||
return DaemonManager.operate_daemon(service_name, 0)
|
||||
|
||||
@staticmethod
|
||||
def skip_daemon(service_name: str) -> dict:
|
||||
"""跳过服务检查"""
|
||||
return DaemonManager.operate_manual_flag(service_name, 1)
|
||||
|
||||
@staticmethod
|
||||
def active_daemon(service_name: str) -> dict:
|
||||
"""激活服务检查"""
|
||||
return DaemonManager.operate_manual_flag(service_name, 0)
|
||||
|
||||
@staticmethod
|
||||
@read_lock
|
||||
def safe_read():
|
||||
"""服务守护进程服务列表"""
|
||||
try:
|
||||
res = read_file(DAEMON_SERVICE)
|
||||
return json.loads(res) if res else []
|
||||
except:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
@read_lock
|
||||
def manual_safe_read():
|
||||
"""手动干预服务字典, 0: 需要干预, 1: 被手动关闭的"""
|
||||
try:
|
||||
manual = read_file(MANUAL_FLAG)
|
||||
return json.loads(manual) if manual else {}
|
||||
except:
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def update_restart_record(service_name: str, max_count: int) -> bool:
|
||||
try:
|
||||
with open(DAEMON_RESTART_RECORD, "r+") as f:
|
||||
fcntl.flock(f, fcntl.LOCK_EX)
|
||||
try:
|
||||
record = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
record = {}
|
||||
|
||||
count = record.get(service_name, 0)
|
||||
if count >= max_count:
|
||||
return True
|
||||
|
||||
record[service_name] = count + 1
|
||||
f.seek(0)
|
||||
json.dump(record, f)
|
||||
f.truncate()
|
||||
return False
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def safe_read_restart_record() -> Dict[str, int]:
|
||||
try:
|
||||
with open(DAEMON_RESTART_RECORD, "r") as f:
|
||||
fcntl.flock(f, fcntl.LOCK_SH)
|
||||
record = json.load(f)
|
||||
except Exception:
|
||||
record = {}
|
||||
return record
|
||||
|
||||
|
||||
# 服务守护
|
||||
class RestartServices:
|
||||
COUNT = 30
|
||||
|
||||
@staticmethod
|
||||
def __keep_flag_right(manual_info: dict) -> None:
|
||||
try:
|
||||
with open(MANUAL_FLAG, "r+") as f:
|
||||
try:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
||||
f.seek(0)
|
||||
# noinspection PyTypeChecker
|
||||
json.dump(manual_info, f)
|
||||
f.truncate()
|
||||
except:
|
||||
print("Error writing manual flag file")
|
||||
finally:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
except Exception as e:
|
||||
print("Error keep_flag_right:", e)
|
||||
|
||||
def _overhead(self, nick_name) -> bool:
|
||||
return DaemonManager.update_restart_record(
|
||||
nick_name, self.COUNT
|
||||
)
|
||||
|
||||
@DaemonManager.read_lock
|
||||
def main(self):
|
||||
manaul = read_file(MANUAL_FLAG)
|
||||
services = read_file(DAEMON_SERVICE)
|
||||
try:
|
||||
manual_info = json.loads(manaul) if manaul else {}
|
||||
check_list = json.loads(services) if services else []
|
||||
check_list = ["panel"] + check_list # panel 强制守护
|
||||
except Exception:
|
||||
manual_info = {}
|
||||
check_list = ["panel"]
|
||||
|
||||
record = DaemonManager.safe_read_restart_record()
|
||||
for service in [
|
||||
x for x in list(set(check_list)) if record.get(x, 0) < self.COUNT
|
||||
]:
|
||||
obj = ServicesHelper(service)
|
||||
if not obj.is_install:
|
||||
continue
|
||||
|
||||
if not obj.is_running:
|
||||
if int(manual_info.get(obj.nick_name, 0)) == 1:
|
||||
# service closed maually, skip
|
||||
continue
|
||||
# if obj.nick_name != "panel":
|
||||
# write_logs(f"Service [ {obj.nick_name} ] is Not Running, Try to start it...")
|
||||
|
||||
if not self._overhead(obj.nick_name):
|
||||
obj.script("restart")
|
||||
|
||||
if obj.is_running and manual_info.get(obj.nick_name) == 1:
|
||||
# service is running, fix the wrong flag
|
||||
manual_info[obj.nick_name] = 0
|
||||
# under lock file read lock
|
||||
self.__keep_flag_right(manual_info)
|
||||
|
||||
|
||||
def first_time_installed(data: dict) -> None:
|
||||
"""
|
||||
首次安装服务启动守护进程服务
|
||||
"""
|
||||
# todo 虽然支持, 但是守护目前不干预, 前端没开放
|
||||
exculde = [
|
||||
"pgsql", "fail2ban", "btwaf", "ssh", "pdns", "php-fpm", "memcached"
|
||||
]
|
||||
if not data:
|
||||
return
|
||||
try:
|
||||
for service in SERVICES_MAP.keys(): # support service
|
||||
if service in exculde:
|
||||
continue
|
||||
if "php-fpm" in service:
|
||||
continue
|
||||
pl_name = f"{DATA_PATH}/first_installed_flag_{service}.pl"
|
||||
if data.get(service): # panel installed
|
||||
setup = data[service].get("setup", False)
|
||||
if setup is False and os.path.exists(pl_name):
|
||||
os.remove(pl_name)
|
||||
elif setup is True and not os.path.exists(pl_name):
|
||||
DaemonManager.add_daemon(service)
|
||||
with open(pl_name, "w") as f:
|
||||
f.write("1")
|
||||
else:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) == 2:
|
||||
service_name = sys.argv[1]
|
||||
if service_name in SERVICES_MAP:
|
||||
helper = ServicesHelper(service_name)
|
||||
print(f"Attempting to restart service: {service_name}")
|
||||
helper.script("restart")
|
||||
print(f"'{service_name}' restart.")
|
||||
write_logs(f"Service [ {service_name} ] restart ...")
|
||||
else:
|
||||
print(f"Error: Service '{service_name}' not found.")
|
||||
print("Available services are:")
|
||||
for key in SERVICES_MAP.keys():
|
||||
print(f" - {key}")
|
||||
elif len(sys.argv) == 1:
|
||||
pass
|
||||
else:
|
||||
print("Usage: python restart_services.py [service_name]")
|
||||
Reference in New Issue
Block a user