2026-06-12 04:04:48 +08:00
|
|
|
|
"""Install psutil on managed servers via SSH (system Python for watch probe)."""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-06-12 04:09:10 +08:00
|
|
|
|
import re
|
|
|
|
|
|
|
2026-06-12 04:04:48 +08:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-06-12 04:09:10 +08:00
|
|
|
|
# pip --user first (no root); package managers and system pip via run_root (sudo when non-root).
|
2026-06-12 04:04:48 +08:00
|
|
|
|
PSUTIL_INSTALL_SHELL = r"""
|
2026-06-12 04:09:10 +08:00
|
|
|
|
run_root() {
|
|
|
|
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
|
|
|
|
"$@"
|
|
|
|
|
|
else
|
|
|
|
|
|
sudo "$@"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-12 04:04:48 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-06-12 04:09:10 +08:00
|
|
|
|
|
|
|
|
|
|
verify_psutil && exit 0
|
2026-06-12 04:04:48 +08:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-06-12 04:09:10 +08:00
|
|
|
|
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
|
2026-06-12 04:04:48 +08:00
|
|
|
|
fi
|
|
|
|
|
|
if command -v dnf >/dev/null 2>&1; then
|
2026-06-12 04:09:10 +08:00
|
|
|
|
if run_root dnf install -y -q python3-psutil; then
|
|
|
|
|
|
verify_psutil && exit 0
|
|
|
|
|
|
fi
|
2026-06-12 04:04:48 +08:00
|
|
|
|
fi
|
|
|
|
|
|
if command -v yum >/dev/null 2>&1; then
|
2026-06-12 04:09:10 +08:00
|
|
|
|
if run_root yum install -y -q python3-psutil; then
|
|
|
|
|
|
verify_psutil && exit 0
|
|
|
|
|
|
fi
|
2026-06-12 04:04:48 +08:00
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
for py in python3 python3.12 python3.11 python3.10; do
|
|
|
|
|
|
command -v "$py" >/dev/null 2>&1 || continue
|
2026-06-12 04:09:10 +08:00
|
|
|
|
if run_root "$py" -m pip install -q psutil 2>/dev/null; then
|
2026-06-12 04:04:48 +08:00
|
|
|
|
verify_psutil && exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
echo "ERROR: psutil install failed — pip/apt unavailable or permission denied" >&2
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-06-12 04:09:10 +08:00
|
|
|
|
_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 安装失败"
|
|
|
|
|
|
|
2026-06-12 04:04:48 +08:00
|
|
|
|
|
|
|
|
|
|
def build_psutil_install_cmd(*, ssh_user: str) -> str:
|
|
|
|
|
|
return sudo_wrap(PSUTIL_INSTALL_SHELL.strip(), ssh_user)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-12 04:09:10 +08:00
|
|
|
|
async def install_psutil_via_ssh(server: Server, *, timeout: int = 120) -> dict[str, str | int | bool]:
|
|
|
|
|
|
"""Run remote install; returns success, message, stdout/stderr."""
|
2026-06-12 04:04:48 +08:00
|
|
|
|
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],
|
2026-06-12 04:09:10 +08:00
|
|
|
|
"message": _psutil_install_error_zh(stderr, stdout),
|
2026-06-12 04:04:48 +08:00
|
|
|
|
}
|