Compare commits

...

1 Commits

Author SHA1 Message Date
teknium1
3dca48fece fix(terminal): add SSH preflight check 2026-03-15 21:07:18 -07:00
2 changed files with 49 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
import pytest
from tools.environments import ssh as ssh_env
def test_ensure_ssh_available_raises_clear_error_when_missing(monkeypatch):
monkeypatch.setattr(ssh_env.shutil, "which", lambda _name: None)
with pytest.raises(RuntimeError, match="SSH is not installed or not in PATH"):
ssh_env._ensure_ssh_available()
def test_ssh_environment_checks_availability_before_connect(monkeypatch):
monkeypatch.setattr(ssh_env.shutil, "which", lambda _name: None)
monkeypatch.setattr(
ssh_env.SSHEnvironment,
"_establish_connection",
lambda self: pytest.fail("_establish_connection should not run when ssh is missing"),
)
with pytest.raises(RuntimeError, match="openssh-client"):
ssh_env.SSHEnvironment(host="example.com", user="alice")
def test_ssh_environment_connects_when_ssh_exists(monkeypatch):
called = {"count": 0}
monkeypatch.setattr(ssh_env.shutil, "which", lambda _name: "/usr/bin/ssh")
def _fake_establish(self):
called["count"] += 1
monkeypatch.setattr(ssh_env.SSHEnvironment, "_establish_connection", _fake_establish)
env = ssh_env.SSHEnvironment(host="example.com", user="alice")
assert called["count"] == 1
assert env.host == "example.com"
assert env.user == "alice"

View File

@@ -1,6 +1,7 @@
"""SSH remote execution environment with ControlMaster connection persistence.""" """SSH remote execution environment with ControlMaster connection persistence."""
import logging import logging
import shutil
import subprocess import subprocess
import tempfile import tempfile
import threading import threading
@@ -13,6 +14,14 @@ from tools.interrupt import is_interrupted
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _ensure_ssh_available() -> None:
"""Fail fast with a clear error when the SSH client is unavailable."""
if not shutil.which("ssh"):
raise RuntimeError(
"SSH is not installed or not in PATH. Install OpenSSH client: apt install openssh-client"
)
class SSHEnvironment(BaseEnvironment): class SSHEnvironment(BaseEnvironment):
"""Run commands on a remote machine over SSH. """Run commands on a remote machine over SSH.
@@ -35,6 +44,7 @@ class SSHEnvironment(BaseEnvironment):
self.control_dir = Path(tempfile.gettempdir()) / "hermes-ssh" self.control_dir = Path(tempfile.gettempdir()) / "hermes-ssh"
self.control_dir.mkdir(parents=True, exist_ok=True) self.control_dir.mkdir(parents=True, exist_ok=True)
self.control_socket = self.control_dir / f"{user}@{host}:{port}.sock" self.control_socket = self.control_dir / f"{user}@{host}:{port}.sock"
_ensure_ssh_available()
self._establish_connection() self._establish_connection()
def _build_ssh_command(self, extra_args: list = None) -> list: def _build_ssh_command(self, extra_args: list = None) -> list: