diff --git a/haproxy_mcp/ssh_ops.py b/haproxy_mcp/ssh_ops.py index 5b41e53..6876060 100644 --- a/haproxy_mcp/ssh_ops.py +++ b/haproxy_mcp/ssh_ops.py @@ -22,6 +22,7 @@ def _ssh_base_cmd() -> list[str]: "ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", + "-o", "LogLevel=ERROR", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "-p", str(SSH_PORT), @@ -35,6 +36,9 @@ def _ssh_base_cmd() -> list[str]: def remote_exec(command: str, timeout: int = SUBPROCESS_TIMEOUT) -> subprocess.CompletedProcess: """Execute a command on the remote host via SSH. + The command is passed as a single SSH argument so the remote shell + interprets it as-is (compatible with fish, bash, zsh). + Args: command: Shell command to execute remotely timeout: Command timeout in seconds @@ -46,7 +50,7 @@ def remote_exec(command: str, timeout: int = SUBPROCESS_TIMEOUT) -> subprocess.C subprocess.TimeoutExpired: If command times out OSError: If SSH command fails to execute """ - ssh_cmd = _ssh_base_cmd() + ["bash", "-c", command] + ssh_cmd = _ssh_base_cmd() + [command] logger.debug("SSH exec: %s", command) return subprocess.run( ssh_cmd, @@ -81,7 +85,8 @@ def remote_read_file(path: str) -> str: def remote_write_file(path: str, content: str) -> None: """Write content to a file on the remote host atomically. - Uses temp file + mv for atomic write, matching local behavior. + Uses bash explicitly for bash-specific syntax ($(mktemp), $tmpf). + The entire script is passed as a single argument to 'bash -c'. Args: path: Absolute file path on remote host @@ -91,9 +96,9 @@ def remote_write_file(path: str, content: str) -> None: IOError: If write fails """ ssh_cmd = _ssh_base_cmd() - # Atomic write: write to temp file, then rename (force bash for compatibility) - remote_script = f"tmpf=$(mktemp {path}.tmp.XXXXXX) && cat > \"$tmpf\" && mv \"$tmpf\" {path}" - ssh_cmd.extend(["bash", "-c", remote_script]) + # Atomic write via bash (single-quoted to survive remote shell interpretation) + remote_script = f"tmpf=$(mktemp {path}.tmp.XXXXXX) && cat >\"$tmpf\" && mv \"$tmpf\" {path}" + ssh_cmd.append(f"bash -c '{remote_script}'") logger.debug("SSH write: %s (%d bytes)", path, len(content)) result = subprocess.run(