Skip to content

Instantly share code, notes, and snippets.

@mamaj
Last active May 5, 2025 12:16
Show Gist options
  • Save mamaj/a7b378a5c969e3e32a9e4f9bceb0c5eb to your computer and use it in GitHub Desktop.
Save mamaj/a7b378a5c969e3e32a9e4f9bceb0c5eb to your computer and use it in GitHub Desktop.
Python: simple class to perform commands and copy files (scp) on ssh using subprocess and native ssh client (OpenSSH).
import subprocess
import os
from pathlib import Path
from typing import Union
class SshClient():
""" Perform commands and copy files on ssh using subprocess
and native ssh client (OpenSSH).
"""
def __init__(self,
user: str,
remote: str,
key_path: Union[str, Path]) -> None:
"""
Args:
user (str): username for the remote
remote (str): remote host IP/DNS
key_path (str or pathlib.Path): path to .pem file
"""
self.user = user
self.remote = remote
self.key_path = str(key_path)
def cmd(self,
cmds: list[str],
check=True,
strict_host_key_checking=False,
**run_kwargs) -> subprocess.CompletedProcess:
"""runs commands consecutively, ensuring success of each
after calling the next command.
Args:
cmds (list[str]): list of commands to run.
strict_host_key_checking (bool, optional): Defaults to True.
"""
strict_host_key_checking = 'yes' if strict_host_key_checking else 'no'
cmd = ' && '.join(cmds)
return subprocess.run(
[
'ssh',
'-i', self.key_path,
'-o', f'StrictHostKeyChecking={strict_host_key_checking}',
'-o', 'UserKnownHostsFile=/dev/null',
'-o', 'LogLevel=ERROR',
f'{self.user}@{self.remote}',
cmd
],
check=check,
**run_kwargs
)
def scp(self,
sources: list[Union[str, bytes, os.PathLike]],
destination: Union[str, bytes, os.PathLike],
check=True,
strict_host_key_checking=False,
recursive=False,
**run_kwargs) -> subprocess.CompletedProcess:
"""Copies `srouce` file to remote `destination` using the
native `scp` command.
Args:
source (Union[str, bytes, os.PathLike]): List of source files path.
destination (Union[str, bytes, os.PathLike]): Destination path on remote.
"""
strict_host_key_checking = 'yes' if strict_host_key_checking else 'no'
return subprocess.run(
list(filter(bool, [
'scp',
'-i', self.key_path,
'-o', f'StrictHostKeyChecking={strict_host_key_checking}',
'-o', 'UserKnownHostsFile=/dev/null',
'-o', 'LogLevel=ERROR',
'-r' if recursive else '',
*map(str, sources),
# sources,
f'{self.user}@{self.remote}:{str(destination)}',
])),
check=check,
**run_kwargs
)
def validate(self):
return self.cmd([f'echo " "'], check=False).returncode == 0
def ssh_connect_cmd(self) -> str:
return f'ssh -i {self.key_path} {self.user}@{self.remote}'
@linuxguy123
Copy link

Thanks

@larsblumberg
Copy link

larsblumberg commented Dec 11, 2024

Thanks for this gist, I got the link to here from Stackoverflow.

I pretty much prefer your tiny SSH client code rather than importing an entire module just for running something remotely via ssh.

Well done!

For my project I removed the key_path as I deploy the ssh key in the standard user directory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment