Files
getDomain/domain-api/app/services/worker_control_service.py
Your Name 32efff1670 dev
2026-04-16 13:05:07 +08:00

189 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import json
import os
import subprocess
from pathlib import Path
from app.core.config import settings
from app.services.runtime_settings_service import get_runtime_settings
def _domain_root() -> Path:
return Path(settings.domain_root)
def _runtime_config() -> dict:
return get_runtime_settings()
def _run_powershell(command: str, timeout: int = 20) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", command],
capture_output=True,
text=True,
timeout=timeout,
)
def _run_shell(command: list[str], timeout: int = 20) -> subprocess.CompletedProcess[str]:
return subprocess.run(command, capture_output=True, text=True, timeout=timeout)
def _windows_runtime() -> dict:
command = """
$targets = Get-CimInstance Win32_Process -Filter "name='python.exe'" |
Where-Object { $_.CommandLine -like '*detect_worker.py*' } |
Select-Object ProcessId, CommandLine
if (-not $targets) {
Write-Output '{"running":false,"process_count":0,"latest_start_time":"","mode":"windows-local"}'
exit 0
}
$latest = $null
foreach ($item in $targets) {
try {
$proc = Get-Process -Id $item.ProcessId -ErrorAction Stop
if (-not $latest -or $proc.StartTime -gt $latest.StartTime) {
$latest = $proc
}
} catch {}
}
$payload = @{
running = $true
process_count = @($targets).Count
latest_start_time = if ($latest) { $latest.StartTime.ToString('yyyy-MM-dd HH:mm:ss') } else { '' }
mode = 'windows-local'
} | ConvertTo-Json -Compress
Write-Output $payload
"""
result = _run_powershell(command)
output = (result.stdout or "").strip()
if result.returncode != 0 or not output:
return {
"mode": "windows-local",
"running": False,
"process_count": 0,
"latest_start_time": "",
"message": (result.stderr or result.stdout or "worker runtime probe failed").strip(),
}
try:
payload = json.loads(output)
except json.JSONDecodeError:
return {
"mode": "windows-local",
"running": False,
"process_count": 0,
"latest_start_time": "",
"message": output,
}
payload.setdefault("message", "")
return payload
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 {
"mode": "linux-systemd",
"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", "")
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 "",
}
def detect_worker_runtime() -> dict:
runtime = _runtime_config()
worker_mode = runtime["worker_mode"]
if worker_mode == "linux-systemd":
return _linux_runtime()
if os.name == "nt":
return _windows_runtime()
return {
"mode": worker_mode,
"running": False,
"process_count": 0,
"latest_start_time": "",
"message": f"unsupported worker_mode: {worker_mode}",
}
def start_worker() -> tuple[bool, str]:
runtime = _runtime_config()
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)
if result.returncode != 0:
return False, (result.stderr or result.stdout or "启动 Linux Worker 失败").strip()
return True, f"Linux Worker 启动命令已发送: {service_name}"
if os.name != "nt":
return False, "当前仅实现 Windows 本地 Worker 启动Linux 请将 worker_mode 设为 linux-systemd。"
script_path = _domain_root() / "start_worker.ps1"
if not script_path.exists():
return False, f"未找到启动脚本: {script_path}"
command = (
"Start-Process powershell "
f"-ArgumentList '-ExecutionPolicy Bypass -File \"{script_path}\"' "
f"-WorkingDirectory '{_domain_root()}'"
)
result = _run_powershell(command)
if result.returncode != 0:
return False, (result.stderr or result.stdout or "启动检测端失败").strip()
return True, "检测端启动命令已发送"
def stop_worker() -> tuple[bool, str]:
runtime = _runtime_config()
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)
if result.returncode != 0:
return False, (result.stderr or result.stdout or "停止 Linux Worker 失败").strip()
return True, f"Linux Worker 停止命令已发送: {service_name}"
if os.name != "nt":
return False, "当前仅实现 Windows 本地 Worker 停止Linux 请将 worker_mode 设为 linux-systemd。"
command = """
$targets = Get-CimInstance Win32_Process -Filter "name='python.exe'" |
Where-Object { $_.CommandLine -like '*detect_worker.py*' } |
Select-Object -ExpandProperty ProcessId
if (-not $targets) {
Write-Output 'NO_PROCESS'
exit 0
}
$targets | ForEach-Object { Stop-Process -Id $_ -Force }
Write-Output ('STOPPED:' + (($targets | Measure-Object).Count))
"""
result = _run_powershell(command)
output = (result.stdout or result.stderr or "").strip()
if result.returncode != 0:
return False, output or "停止检测端失败"
if "NO_PROCESS" in output:
return True, "当前没有运行中的检测端进程"
return True, output or "检测端已停止"