mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-19 22:25:45 +02:00
400 lines
13 KiB
Python
400 lines
13 KiB
Python
# coding: utf-8
|
|
import os
|
|
import re
|
|
import time
|
|
from typing import Any, Dict, List, Tuple
|
|
|
|
import public
|
|
|
|
|
|
SERVICE_NAME_RE = re.compile(r"^[A-Za-z0-9_.@:+-]+$")
|
|
|
|
DEFAULT_SERVICES = [
|
|
{"target_key": "nginx", "target_name": "nginx"},
|
|
{"target_key": "httpd", "target_name": "apache"},
|
|
{"target_key": "mysqld", "target_name": "mysql"},
|
|
{"target_key": "mariadbd", "target_name": "mariadb"},
|
|
{"target_key": "redis", "target_name": "redis"},
|
|
{"target_key": "pure-ftpd", "target_name": "pure-ftpd"},
|
|
{"target_key": "memcached", "target_name": "memcached"},
|
|
{"target_key": "mongodb", "target_name": "mongodb"},
|
|
{"target_key": "pgsql", "target_name": "pgsql"},
|
|
]
|
|
|
|
LOCAL_SERVICE_DEFINITIONS = [
|
|
{
|
|
"family": "nginx",
|
|
"target_key": "nginx",
|
|
"target_name": "Nginx",
|
|
"paths": ["/www/server/nginx/sbin/nginx", "/www/server/nginx/nginx/sbin/nginx", "/etc/init.d/nginx"],
|
|
},
|
|
{
|
|
"family": "mysql",
|
|
"target_key": "mysqld",
|
|
"target_name": "MySQL",
|
|
"paths": ["/www/server/mysql/bin/mysqld", "/etc/init.d/mysqld"],
|
|
},
|
|
{
|
|
"family": "apache",
|
|
"target_key": "httpd",
|
|
"target_name": "Apache",
|
|
"paths": ["/www/server/apache/bin/httpd", "/etc/init.d/httpd"],
|
|
},
|
|
{
|
|
"family": "redis",
|
|
"target_key": "redis",
|
|
"target_name": "Redis",
|
|
"paths": ["/www/server/redis/src/redis-server", "/etc/init.d/redis"],
|
|
},
|
|
{
|
|
"family": "pure-ftpd",
|
|
"target_key": "pure-ftpd",
|
|
"target_name": "Pure-Ftpd",
|
|
"paths": ["/www/server/pure-ftpd/sbin/pure-ftpd", "/etc/init.d/pure-ftpd"],
|
|
},
|
|
{
|
|
"family": "memcached",
|
|
"target_key": "memcached",
|
|
"target_name": "Memcached",
|
|
"paths": ["/usr/local/memcached/bin/memcached", "/etc/init.d/memcached"],
|
|
},
|
|
{
|
|
"family": "mongodb",
|
|
"target_key": "mongodb",
|
|
"target_name": "MongoDB",
|
|
"paths": ["/www/server/mongodb/bin/mongod", "/etc/init.d/mongodb"],
|
|
},
|
|
{
|
|
"family": "pgsql",
|
|
"target_key": "pgsql",
|
|
"target_name": "PostgreSQL",
|
|
"paths": ["/www/server/pgsql/bin/postgres", "/etc/init.d/pgsql"],
|
|
},
|
|
]
|
|
|
|
PROCESS_CHECK_SERVICES = {
|
|
"mysqld",
|
|
"mariadbd",
|
|
"redis",
|
|
"pure-ftpd",
|
|
"nginx",
|
|
"httpd",
|
|
"apache",
|
|
"memcached",
|
|
"mongodb",
|
|
"pgsql",
|
|
}
|
|
|
|
|
|
def valid_service_name(name: str) -> bool:
|
|
return bool(name and SERVICE_NAME_RE.match(name))
|
|
|
|
|
|
def normalize_service_targets(services: Any) -> List[Dict[str, Any]]:
|
|
if not isinstance(services, list):
|
|
return []
|
|
result = []
|
|
seen = set()
|
|
for idx, item in enumerate(services):
|
|
if isinstance(item, str):
|
|
key = item.strip()
|
|
name = key
|
|
enabled = 1
|
|
sort = idx
|
|
extra = {}
|
|
elif isinstance(item, dict):
|
|
key = str(item.get("target_key") or item.get("name") or "").strip()
|
|
name = str(item.get("target_name") or item.get("name") or key).strip()
|
|
enabled = int(item.get("enabled", 1))
|
|
sort = int(item.get("sort", idx))
|
|
extra = item.get("extra", {})
|
|
else:
|
|
continue
|
|
if not valid_service_name(key) or key in seen:
|
|
continue
|
|
seen.add(key)
|
|
service_family = str(item.get("service_family") or item.get("family") or "").strip() if isinstance(item, dict) else ""
|
|
if not service_family and key.startswith("php-fpm-"):
|
|
service_family = "php"
|
|
result.append({
|
|
"target_key": key,
|
|
"target_name": name or key,
|
|
"enabled": 1 if enabled else 0,
|
|
"sort": sort,
|
|
"extra": extra if isinstance(extra, dict) else {},
|
|
"service_family": service_family,
|
|
"version": str(item.get("version") or "").strip() if isinstance(item, dict) else "",
|
|
})
|
|
return result
|
|
|
|
|
|
def keep_single_version_services(services: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
result = []
|
|
seen_version_family = set()
|
|
for item in normalize_service_targets(services):
|
|
family = item.get("service_family") or ("php" if item["target_key"].startswith("php-fpm-") else "")
|
|
if family == "php":
|
|
if family in seen_version_family:
|
|
continue
|
|
seen_version_family.add(family)
|
|
result.append(item)
|
|
return result
|
|
|
|
|
|
def service_unknown_statuses(services: List[Dict[str, Any]], error_msg: str) -> List[Dict[str, Any]]:
|
|
now = int(time.time())
|
|
result = []
|
|
for item in normalize_service_targets(services):
|
|
result.append({
|
|
"name": item["target_key"],
|
|
"target_key": item["target_key"],
|
|
"target_name": item["target_name"],
|
|
"status": 2,
|
|
"running": False,
|
|
"state": "unknown",
|
|
"error_msg": error_msg,
|
|
"ts": now,
|
|
})
|
|
return result
|
|
|
|
|
|
def collect_local_service_status(services: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
now = int(time.time())
|
|
result = []
|
|
for item in normalize_service_targets(services):
|
|
if not int(item.get("enabled", 1)):
|
|
continue
|
|
running, err = _check_local_service(item["target_key"])
|
|
status = 1 if running else 0
|
|
if err and not running:
|
|
status = 2
|
|
result.append({
|
|
"name": item["target_key"],
|
|
"target_key": item["target_key"],
|
|
"target_name": item["target_name"],
|
|
"status": status,
|
|
"running": bool(running),
|
|
"state": "active" if running else ("unknown" if err else "inactive"),
|
|
"error_msg": err,
|
|
"ts": now,
|
|
})
|
|
return result
|
|
|
|
|
|
def discover_local_services() -> Dict[str, Any]:
|
|
services = []
|
|
for item in LOCAL_SERVICE_DEFINITIONS:
|
|
if not any(os.path.exists(path) for path in item["paths"]):
|
|
continue
|
|
status = collect_local_service_status([{
|
|
"target_key": item["target_key"],
|
|
"target_name": item["target_name"],
|
|
"service_family": item["family"],
|
|
}])
|
|
status_item = status[0] if status else {}
|
|
services.append(_service_option(
|
|
target_key=item["target_key"],
|
|
target_name=item["target_name"],
|
|
family=item["family"],
|
|
running=bool(status_item.get("running", False)),
|
|
status=int(status_item.get("status", 0)),
|
|
state=str(status_item.get("state") or "inactive"),
|
|
))
|
|
|
|
for version in _installed_php_versions():
|
|
key = "php-fpm-{}".format(version)
|
|
status = collect_local_service_status([{
|
|
"target_key": key,
|
|
"target_name": "PHP {}".format(_format_php_version(version)),
|
|
"service_family": "php",
|
|
"version": version,
|
|
}])
|
|
status_item = status[0] if status else {}
|
|
services.append(_service_option(
|
|
target_key=key,
|
|
target_name="PHP {}".format(_format_php_version(version)),
|
|
family="php",
|
|
version=version,
|
|
version_name=_format_php_version(version),
|
|
running=bool(status_item.get("running", False)),
|
|
status=int(status_item.get("status", 0)),
|
|
state=str(status_item.get("state") or "inactive"),
|
|
is_versioned=1,
|
|
))
|
|
|
|
return {
|
|
"services": services,
|
|
"groups": build_service_groups(services),
|
|
}
|
|
|
|
|
|
def get_service_file_probe_paths() -> List[str]:
|
|
paths = []
|
|
for item in LOCAL_SERVICE_DEFINITIONS:
|
|
paths.extend(item["paths"])
|
|
for version in _candidate_php_versions():
|
|
paths.extend(_php_version_paths(version))
|
|
return _dedupe(paths)
|
|
|
|
|
|
def build_service_options_from_file_exists(existing_paths: Any) -> Dict[str, Any]:
|
|
if isinstance(existing_paths, (list, tuple, set)):
|
|
exists_map = {str(path): True for path in existing_paths}
|
|
elif isinstance(existing_paths, dict):
|
|
exists_map = {str(path): bool(value) for path, value in existing_paths.items()}
|
|
else:
|
|
exists_map = {}
|
|
|
|
services = []
|
|
for item in LOCAL_SERVICE_DEFINITIONS:
|
|
if not any(exists_map.get(path) for path in item["paths"]):
|
|
continue
|
|
services.append(_service_option(
|
|
target_key=item["target_key"],
|
|
target_name=item["target_name"],
|
|
family=item["family"],
|
|
status=2,
|
|
state="unknown",
|
|
))
|
|
|
|
for version in _candidate_php_versions():
|
|
if not any(exists_map.get(path) for path in _php_version_paths(version)):
|
|
continue
|
|
services.append(_service_option(
|
|
target_key="php-fpm-{}".format(version),
|
|
target_name="PHP {}".format(_format_php_version(version)),
|
|
family="php",
|
|
version=version,
|
|
version_name=_format_php_version(version),
|
|
status=2,
|
|
state="unknown",
|
|
is_versioned=1,
|
|
))
|
|
|
|
return {
|
|
"services": services,
|
|
"groups": build_service_groups(services),
|
|
}
|
|
|
|
|
|
def build_service_groups(services: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
groups = []
|
|
group_map = {}
|
|
for item in services:
|
|
family = item.get("service_family") or item.get("family") or item.get("target_key")
|
|
if not family:
|
|
continue
|
|
if family not in group_map:
|
|
group_map[family] = {
|
|
"family": family,
|
|
"target_name": "PHP" if family == "php" else item.get("target_name", family),
|
|
"type": "version" if family == "php" else "single",
|
|
"select_mode": "single" if family == "php" else "toggle",
|
|
"multiple": 0 if family == "php" else 1,
|
|
"services": [],
|
|
}
|
|
groups.append(group_map[family])
|
|
group_map[family]["services"].append(item)
|
|
|
|
for group in groups:
|
|
if group["family"] == "php":
|
|
group["services"].sort(key=lambda x: x.get("version", ""))
|
|
else:
|
|
group["services"].sort(key=lambda x: x.get("sort", 0))
|
|
return groups
|
|
|
|
|
|
def _installed_php_versions() -> List[str]:
|
|
versions = []
|
|
for version in _candidate_php_versions():
|
|
for path in _php_version_paths(version):
|
|
if os.path.exists(path):
|
|
versions.append(str(version))
|
|
break
|
|
return versions
|
|
|
|
|
|
def _candidate_php_versions() -> List[str]:
|
|
try:
|
|
versions = public.get_php_versions()
|
|
except Exception:
|
|
versions = ['52', '53', '54', '55', '56', '70', '71', '72', '73', '74', '80', '81', '82', '83', '84', '85']
|
|
return [str(version) for version in versions if re.match(r"^\d{2}$", str(version))]
|
|
|
|
|
|
def _php_version_paths(version: str) -> List[str]:
|
|
return [
|
|
"/www/server/php/{}/sbin/php-fpm".format(version),
|
|
"/www/server/php/{}/bin/php".format(version),
|
|
"/etc/init.d/php-fpm-{}".format(version),
|
|
]
|
|
|
|
|
|
def _dedupe(items: List[str]) -> List[str]:
|
|
result = []
|
|
seen = set()
|
|
for item in items:
|
|
if item in seen:
|
|
continue
|
|
seen.add(item)
|
|
result.append(item)
|
|
return result
|
|
|
|
|
|
def _format_php_version(version: str) -> str:
|
|
version = str(version)
|
|
if len(version) == 2 and version.isdigit():
|
|
return "{}.{}".format(version[0], version[1])
|
|
return version
|
|
|
|
|
|
def _service_option(
|
|
target_key: str,
|
|
target_name: str,
|
|
family: str,
|
|
version: str = "",
|
|
version_name: str = "",
|
|
running: bool = False,
|
|
status: int = 0,
|
|
state: str = "inactive",
|
|
is_versioned: int = 0,
|
|
) -> Dict[str, Any]:
|
|
return {
|
|
"target_type": "service",
|
|
"target_key": target_key,
|
|
"target_name": target_name,
|
|
"service_family": family,
|
|
"version": version,
|
|
"version_name": version_name,
|
|
"enabled": 0,
|
|
"installed": 1,
|
|
"is_versioned": int(is_versioned),
|
|
"status": int(status),
|
|
"running": bool(running),
|
|
"state": state,
|
|
"sort": 0,
|
|
}
|
|
|
|
|
|
def _check_local_service(name: str) -> Tuple[bool, str]:
|
|
if not valid_service_name(name):
|
|
return False, "Invalid service name"
|
|
if name in PROCESS_CHECK_SERVICES or name.find("php-fpm") != -1:
|
|
try:
|
|
from system import system
|
|
return bool(system().check_service_status(name)), ""
|
|
except Exception as e:
|
|
return False, str(e)
|
|
|
|
cmd = (
|
|
"if command -v systemctl >/dev/null 2>&1; then "
|
|
"systemctl is-active --quiet {name}; "
|
|
"else service {name} status >/dev/null 2>&1; fi; echo $?"
|
|
).format(name=name)
|
|
try:
|
|
stdout, stderr = public.ExecShell(cmd)
|
|
lines = str(stdout or "").strip().splitlines()
|
|
return bool(lines and lines[-1].strip() == "0"), str(stderr or "")
|
|
except Exception as e:
|
|
return False, str(e)
|