NEXON / backend /utils /ssh_client.py
Antigravity
initial clean push: N-A finalized build
5fe93dd
import socket
import paramiko
from config import settings
from utils.logger import logger
def execute_ssh_command(command: str, timeout: int = 15) -> dict:
"""
Execute a shell command on the configured SSH Lab Node.
Returns a dict with stdout, stderr, exit_code, and a formatted result string.
"""
if not settings.SSH_HOST or not settings.SSH_USER:
return {
"stdout": "",
"stderr": "SSH Lab Node not configured. Set SSH_HOST and SSH_USER in Settings.",
"exit_code": -1,
"result": "[SSH ERROR] Lab node credentials are not configured."
}
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
client.connect(
hostname=settings.SSH_HOST,
port=settings.SSH_PORT,
username=settings.SSH_USER,
password=settings.SSH_PASSWORD,
timeout=timeout,
look_for_keys=False,
allow_agent=False
)
_, stdout, stderr = client.exec_command(command, timeout=timeout)
exit_code = stdout.channel.recv_exit_status()
out = stdout.read().decode("utf-8", errors="replace").strip()
err = stderr.read().decode("utf-8", errors="replace").strip()
result_parts = []
if out:
result_parts.append(f"stdout:\n{out}")
if err:
result_parts.append(f"stderr:\n{err}")
result_parts.append(f"exit_code: {exit_code}")
return {
"stdout": out,
"stderr": err,
"exit_code": exit_code,
"result": "\n".join(result_parts) if result_parts else "(no output)"
}
except (paramiko.AuthenticationException, paramiko.SSHException) as e:
msg = f"[SSH AUTH ERROR] {e}"
logger.error(msg)
return {"stdout": "", "stderr": str(e), "exit_code": -1, "result": msg}
except socket.timeout:
msg = f"[SSH TIMEOUT] Connection to {settings.SSH_HOST}:{settings.SSH_PORT} timed out."
logger.error(msg)
return {"stdout": "", "stderr": "timeout", "exit_code": -1, "result": msg}
except Exception as e:
msg = f"[SSH ERROR] {e}"
logger.error(msg)
return {"stdout": "", "stderr": str(e), "exit_code": -1, "result": msg}
finally:
client.close()