"""Install psutil on managed servers via SSH (system Python for watch probe).""" from __future__ import annotations import re from server.application.server_batch_common import sudo_wrap from server.domain.models import Server from server.infrastructure.ssh.asyncssh_pool import exec_ssh_command # pip --user first (no root); package managers and system pip via run_root (sudo when non-root). PSUTIL_INSTALL_SHELL = r""" run_root() { if [ "$(id -u)" -eq 0 ]; then "$@" else sudo "$@" fi } verify_psutil() { for py in python3 python3.12 python3.11 python3.10; do if command -v "$py" >/dev/null 2>&1 && "$py" -c "import psutil" 2>/dev/null; then echo psutil_ok exit 0 fi done return 1 } verify_psutil && exit 0 for py in python3 python3.12 python3.11 python3.10; do command -v "$py" >/dev/null 2>&1 || continue if "$py" -m pip install --user -q psutil 2>/dev/null; then verify_psutil && exit 0 fi done if command -v apt-get >/dev/null 2>&1; then if run_root apt-get update -qq \ && run_root env DEBIAN_FRONTEND=noninteractive apt-get install -y -qq python3-psutil; then verify_psutil && exit 0 fi fi if command -v dnf >/dev/null 2>&1; then if run_root dnf install -y -q python3-psutil; then verify_psutil && exit 0 fi fi if command -v yum >/dev/null 2>&1; then if run_root yum install -y -q python3-psutil; then verify_psutil && exit 0 fi fi for py in python3 python3.12 python3.11 python3.10; do command -v "$py" >/dev/null 2>&1 || continue if run_root "$py" -m pip install -q psutil 2>/dev/null; then verify_psutil && exit 0 fi done echo "ERROR: psutil install failed — pip/apt unavailable or permission denied" >&2 exit 1 """ _APT_PERM_RE = re.compile( r"lock file|permission denied|unable to lock|not allowed to execute", re.I, ) def _psutil_install_error_zh(stderr: str, stdout: str) -> str: text = f"{stderr}\n{stdout}" if _APT_PERM_RE.search(text): return ( "安装 psutil 需要 root 或免密 sudo(apt/pip)。" "请改用 root SSH,或在子机配置免密 sudo 后重试;也可手动执行:" "sudo apt-get install -y python3-psutil" ) if "sudo requires a password" in text.lower(): return "sudo 需要密码,请配置免密 sudo 或使用 root SSH 登录" line = "" for chunk in (stderr, stdout): for raw in reversed(chunk.splitlines()): raw = raw.strip() if raw and not raw.startswith("W:"): line = raw[:300] break if line: break return line or "psutil 安装失败" def build_psutil_install_cmd(*, ssh_user: str) -> str: return sudo_wrap(PSUTIL_INSTALL_SHELL.strip(), ssh_user) async def install_psutil_via_ssh(server: Server, *, timeout: int = 120) -> dict[str, str | int | bool]: """Run remote install; returns success, message, stdout/stderr.""" ssh_user = (server.username or "root").strip() cmd = build_psutil_install_cmd(ssh_user=ssh_user) result = await exec_ssh_command(server, cmd, timeout=timeout) stdout = (result.get("stdout") or "").strip() stderr = (result.get("stderr") or "").strip() raw_exit = result.get("exit_code") exit_code = int(raw_exit) if raw_exit is not None else 1 if exit_code == 0 and "psutil_ok" in stdout: return { "success": True, "exit_code": 0, "stdout": stdout[:2000], "stderr": stderr[:500], "message": "psutil 已就绪", } return { "success": False, "exit_code": exit_code, "stdout": stdout[:2000], "stderr": stderr[:500], "message": _psutil_install_error_zh(stderr, stdout), }