first
This commit is contained in:
@@ -3,12 +3,18 @@ from __future__ import annotations
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.redis_client import get_redis
|
||||
from app.services.runtime_settings_service import get_runtime_settings
|
||||
|
||||
|
||||
WORKER_CONTROL_CHANNEL = "domain_tool:worker_control"
|
||||
WORKER_PENDING_COMMAND_KEY = "domain_tool:worker_pending_command"
|
||||
|
||||
|
||||
def _domain_root() -> Path:
|
||||
return Path(settings.domain_root)
|
||||
|
||||
@@ -30,6 +36,73 @@ def _run_shell(command: list[str], timeout: int = 20) -> subprocess.CompletedPro
|
||||
return subprocess.run(command, capture_output=True, text=True, timeout=timeout)
|
||||
|
||||
|
||||
def _run_systemctl(command: list[str], timeout: int = 20, require_sudo: bool = True) -> subprocess.CompletedProcess[str]:
|
||||
systemctl_command = ["systemctl", *command]
|
||||
if os.geteuid() == 0 or not require_sudo:
|
||||
return _run_shell(systemctl_command, timeout=timeout)
|
||||
return _run_shell(["sudo", "-n", *systemctl_command], timeout=timeout)
|
||||
|
||||
|
||||
def _parse_systemd_timestamp(raw_timestamp: str) -> str:
|
||||
raw_timestamp = (raw_timestamp or "").strip()
|
||||
if not raw_timestamp:
|
||||
return ""
|
||||
try:
|
||||
parsed = datetime.strptime(raw_timestamp, "%a %Y-%m-%d %H:%M:%S %Z")
|
||||
return parsed.strftime("%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
parsed = datetime.strptime(raw_timestamp.rsplit(" ", 1)[0], "%a %Y-%m-%d %H:%M:%S")
|
||||
return parsed.strftime("%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
|
||||
def probe_systemd_service(service_name: str, *, mode: str = "linux-systemd") -> dict:
|
||||
result = _run_systemctl(
|
||||
[
|
||||
"show",
|
||||
service_name,
|
||||
"--no-page",
|
||||
"--property=ActiveState,SubState,MainPID,ExecMainStartTimestamp,ActiveEnterTimestamp",
|
||||
],
|
||||
require_sudo=False,
|
||||
)
|
||||
output = (result.stdout or result.stderr or "").strip()
|
||||
if result.returncode != 0:
|
||||
return {
|
||||
"mode": mode,
|
||||
"service_name": service_name,
|
||||
"running": False,
|
||||
"process_count": 0,
|
||||
"latest_start_time": "",
|
||||
"message": output or f"systemd service {service_name} not available",
|
||||
}
|
||||
|
||||
data: dict[str, str] = {}
|
||||
for line in output.splitlines():
|
||||
if "=" in line:
|
||||
key, value = line.split("=", 1)
|
||||
data[key] = value
|
||||
main_pid = int(data.get("MainPID", "0") or 0)
|
||||
active_state = data.get("ActiveState", "")
|
||||
sub_state = data.get("SubState", "")
|
||||
latest_start_time = ""
|
||||
for raw_timestamp in (data.get("ExecMainStartTimestamp", ""), data.get("ActiveEnterTimestamp", "")):
|
||||
latest_start_time = _parse_systemd_timestamp(raw_timestamp)
|
||||
if latest_start_time:
|
||||
break
|
||||
return {
|
||||
"mode": mode,
|
||||
"service_name": service_name,
|
||||
"running": active_state == "active",
|
||||
"process_count": 1 if main_pid > 0 else 0,
|
||||
"latest_start_time": latest_start_time,
|
||||
"message": f"{active_state}/{sub_state}" if active_state else "",
|
||||
}
|
||||
|
||||
|
||||
def _windows_runtime() -> dict:
|
||||
command = """
|
||||
$targets = Get-CimInstance Win32_Process -Filter "name='python.exe'" |
|
||||
@@ -83,32 +156,23 @@ def _windows_runtime() -> dict:
|
||||
def _linux_runtime() -> dict:
|
||||
runtime = _runtime_config()
|
||||
service_name = runtime["worker_service_name"]
|
||||
result = _run_shell(["systemctl", "show", service_name, "--no-page", "--property=ActiveState,SubState,MainPID"])
|
||||
output = (result.stdout or result.stderr or "").strip()
|
||||
if result.returncode != 0:
|
||||
return probe_systemd_service(service_name, mode="linux-systemd")
|
||||
|
||||
|
||||
def detect_sync_agent_runtime() -> dict:
|
||||
runtime = _runtime_config()
|
||||
worker_mode = runtime.get("worker_mode", settings.worker_mode)
|
||||
service_name = runtime.get("sync_agent_service_name", settings.sync_agent_service_name)
|
||||
if worker_mode != "linux-systemd":
|
||||
return {
|
||||
"mode": "linux-systemd",
|
||||
"mode": worker_mode,
|
||||
"service_name": service_name,
|
||||
"running": False,
|
||||
"process_count": 0,
|
||||
"latest_start_time": "",
|
||||
"message": output or f"systemd service {service_name} not available",
|
||||
"message": "sync-agent 仅在 Linux systemd 多机部署中使用",
|
||||
}
|
||||
|
||||
data: dict[str, str] = {}
|
||||
for line in output.splitlines():
|
||||
if "=" in line:
|
||||
key, value = line.split("=", 1)
|
||||
data[key] = value
|
||||
main_pid = int(data.get("MainPID", "0") or 0)
|
||||
active_state = data.get("ActiveState", "")
|
||||
sub_state = data.get("SubState", "")
|
||||
return {
|
||||
"mode": "linux-systemd",
|
||||
"running": active_state == "active",
|
||||
"process_count": 1 if main_pid > 0 else 0,
|
||||
"latest_start_time": "",
|
||||
"message": f"{active_state}/{sub_state}" if active_state else "",
|
||||
}
|
||||
return probe_systemd_service(service_name, mode="linux-systemd")
|
||||
|
||||
|
||||
def detect_worker_runtime() -> dict:
|
||||
@@ -132,7 +196,7 @@ def start_worker() -> tuple[bool, str]:
|
||||
worker_mode = runtime["worker_mode"]
|
||||
service_name = runtime["worker_service_name"]
|
||||
if worker_mode == "linux-systemd":
|
||||
result = _run_shell(["systemctl", "start", service_name], timeout=30)
|
||||
result = _run_systemctl(["start", service_name], timeout=30)
|
||||
if result.returncode != 0:
|
||||
return False, (result.stderr or result.stdout or "启动 Linux Worker 失败").strip()
|
||||
return True, f"Linux Worker 启动命令已发送: {service_name}"
|
||||
@@ -160,7 +224,7 @@ def stop_worker() -> tuple[bool, str]:
|
||||
worker_mode = runtime["worker_mode"]
|
||||
service_name = runtime["worker_service_name"]
|
||||
if worker_mode == "linux-systemd":
|
||||
result = _run_shell(["systemctl", "stop", service_name], timeout=30)
|
||||
result = _run_systemctl(["stop", service_name], timeout=30)
|
||||
if result.returncode != 0:
|
||||
return False, (result.stderr or result.stdout or "停止 Linux Worker 失败").strip()
|
||||
return True, f"Linux Worker 停止命令已发送: {service_name}"
|
||||
@@ -186,3 +250,17 @@ def stop_worker() -> tuple[bool, str]:
|
||||
if "NO_PROCESS" in output:
|
||||
return True, "当前没有运行中的检测端进程"
|
||||
return True, output or "检测端已停止"
|
||||
|
||||
|
||||
def send_worker_command(action: str, payload: dict | None = None) -> tuple[bool, str]:
|
||||
try:
|
||||
redis_client = get_redis()
|
||||
command_payload = {"action": action}
|
||||
if payload:
|
||||
command_payload.update(payload)
|
||||
serialized = json.dumps(command_payload, ensure_ascii=False)
|
||||
redis_client.set(WORKER_PENDING_COMMAND_KEY, serialized, ex=120)
|
||||
redis_client.publish(WORKER_CONTROL_CHANNEL, serialized)
|
||||
return True, f"已发送 Worker 控制指令: {action}"
|
||||
except Exception as exc:
|
||||
return False, f"发送 Worker 控制指令失败: {exc}"
|
||||
|
||||
Reference in New Issue
Block a user