Hussh (pronounced "hush") is a client-side ssh library that offers low level performance through a high level interface.
Hussh uses pyo3 to create Python bindings around the ssh2 library for Rust.
pip install hussh
Hussh currently just offers a Connection class as your primary interface.
from hussh import Connection
conn = Connection(host="my.test.server", username="user", password="pass")
result = conn.execute("ls")
print(result.stdout)That's it! One import and class instantion is all you need to:
- Execute commands
- Perform SCP actions
- Perform SFTP actions
- Get an interactive shell
- 🔥 Blazingly fast!
- 🪶 Incredibly lightweight!
- đź§ Super easy to use!
Hussh demonstrates the performance you'd expect from a low level ssh library. Hussh is also much lighter weight in both total memory and memory allocations.
Hussh's benchmark script are also open sourced in the benchmarks directory in this repository.
Clone the repo, follow the setup instructions, then let us know how it did!
You've already seen password-based authentication, but here it is again.
conn = Connection(host="my.test.server", username="user", password="pass")
# or leave out username and connect as root
conn = Connection(host="my.test.server", password="pass")If you prefer key-based authentication, Hussh can do that as well.
conn = Connection(host="my.test.server", private_key="~/.ssh/id_rsa")
# If your key is password protected, just use the password argument
conn = Connection(host="my.test.server", private_key="~/.ssh/id_rsa", password="pass")Hussh can also do agent-based authentication, if you've already established it.
conn = Connection("my.test.server")Hussh will clean up after itself automatically when the Connection object is garbage collected.
However, if you want to more explicitly clean up after yourself, you can close the connection.
conn.close()or you can use the Connection class' context manager, which will close when you exit the context.
with Connection(host="my.test.server", password="pass") as conn:
result = conn.execute("ls")
assert result.status == 0The most basic foundation of ssh libraries is the ability to execute commands against the remote host.
For Hussh, just use the Connection object's execute method.
result = conn.execute("whoami")
print(result.stdout, result.stderr, result.status)Each execute returns an SSHResult object with command's stdout, stderr, and status.
If you need to transfer files to/from the remote host, SFTP may be your best bet.
# write a local file to the remote destination
conn.sftp_write(local_path="/path/to/my/file", remote_path="/dest/path/file")
# Write UTF-8 data to a remote file
conn.sftp_write_data(data="Hello there!", remote_path="/dest/path/file")# You can copy a remote file to a local destination
conn.sftp_read(remote_path="/dest/path/file", local_path="/path/to/my/file")
# Or copy the remote file contents to a string
contents = conn.sftp_read(remote_path="/dest/path/file")Hussh offers a shortcut that allows you to copy a file between two established connections.
source_conn = Connection("my.first.server")
dest_conn = Connection("my.second.server", password="secret")
# Copy from source to destination
source_conn.remote_copy(source_path="/root/myfile.txt", dest_conn=dest_conn)By default, if you don't pass in an alternate dest_path, Hussh will copy it to the same path as it came from on source.
For remote servers that support SCP, Hussh can do that to.
# write a local file to the remote destination
conn.scp_write(local_path="/path/to/my/file", remote_path="/dest/path/file")
# Write UTF-8 data to a remote file
conn.scp_write_data(data="Hello there!", remote_path="/dest/path/file")# You can copy a remote file to a local destination
conn.scp_read(remote_path="/dest/path/file", local_path="/path/to/my/file")
# Or copy the remote file contents to a string
contents = conn.scp_read(remote_path="/dest/path/file")Hussh offers a built-in method for tailing files on a Connection with the tail method.
with conn.tail("/path/to/file.txt") as tf:
# perform some actions or wait
print(tf.read()) # at any time, you can read any unread contents
# when you're done tailing, exit the context manager
print(tf.contents)If you need to keep a shell open to perform more complex interactions, you can get an InteractiveShell instance from the Connection class instance.
To use the interactive shell, it is recommended to use the shell() context manager from the Connection class.
You can send commands to the shell using the send method, then get the results from result when you exit the context manager.
with conn.shell() as shell:
shell.send("ls")
shell.send("pwd")
shell.send("whoami")
print(shell.result.stdout)Note: The read method sends an EOF to the shell, so you won't be able to send more commands after calling read. If you want to send more commands, you would need to create a new InteractiveShell instance.
Hussh also offers an AsyncConnection class for asynchronous operations.
import asyncio
from hussh.aio import AsyncConnection
async def main():
async with AsyncConnection(host="my.test.server", username="user", password="pass") as conn:
result = await conn.execute("ls")
print(result.stdout)
asyncio.run(main())You can specify a timeout (in seconds) for the connection and for individual command executions.
# Set a default timeout for the connection
async with AsyncConnection(host="my.test.server", timeout=10) as conn:
# This command will use the connection's default timeout (10s)
await conn.execute("sleep 5")
# You can override the timeout for specific commands
try:
await conn.execute("sleep 20", timeout=5)
except TimeoutError:
print("Command timed out!")Async SFTP operations are available through the sftp() method.
async with AsyncConnection(host="my.test.server", username="user", password="pass") as conn:
sftp = await conn.sftp()
# Upload
await sftp.put(local_path="local.txt", remote_path="remote.txt")
# Download
await sftp.get(remote_path="remote.txt", local_path="downloaded.txt")
# List
files = await sftp.list(".")async with AsyncConnection(host="my.test.server", username="user", password="pass") as conn:
async with await conn.shell() as shell:
await shell.send("ls")
result = await shell.read()
print(result.stdout)
# one of the advantages of an async shell is that you can read and write multiple times
await shell.send("whoami")
result = await shell.read()
print(f"I'm logged in as {result.stdout}")async with AsyncConnection(host="my.test.server", username="user", password="pass") as conn:
async with conn.tail("/path/to/file.txt") as tf:
print(await tf.read())
# You can also access the full contents read during the session
print(tf.contents)This is an early project that should not be used in sensitive production code! There isn't full exception handling, so expect some Rust panics to fall through. With that said, try it out and let me know your thoughts!
- Concurrent actions class
- Low level bindings
- Misc codebase improvements
- TBD...

