Basic Use
Loading
(ns my-program
(:require [babashka.pods :as pods]))
(pods/load-pod 'epiccastle/bbssh "0.1.0")
Requiring
(require '[pod.epiccastle.bbssh.core :as bbssh])
Connecting
Use pod.epiccastle.bbssh.core/ssh
to open an ssh connection to a remote host. The returned session value will be a reference keyword of the following form:
(bbssh/ssh "localhost")
;; => :com.jcraft.jsch/Session-0r0e6yi4m9j8yabp
Here we open an ssh connection as a user on a remote host:
(let [session (bbssh/ssh "remotehost"
{:username "remoteusername"
:port 22})]
;; use session
)
If an ssh-agent and valid key is available it will be used for authentication. If the authentication method falls back to password and one is not available the user will be prompted at the terminal for the password.
You can specify a private key file to use for authentication with the option :identity
:
(let [session (bbssh/ssh "remotehost"
{:username "remoteusername"
:port 22
:identity "path/to/id_rsa"})]
;; use session
)
Alternatively you can specify the contents of a private key to use for authentication with :private-key
(let [session (bbssh/ssh "remotehost"
{:username "remoteusername"
:port 22
:private-key (slurp "path/to/id_rsa")})]
;; use session
)
Execution
To execute a remote process on the session use pod.epiccastle.bbssh.core/exec
. This returns an pod.epiccastle.bbssh.SshProcess
record:
(bbssh/exec session "echo 'I am running over ssh'")
;; => #pod.epiccastle.bbssh.SshProcess{:channel :com.jcraft.jsch/ChannelExec-i9qp6i1wk1uiqpio, :exit nil, :in nil, :out #object[babashka.impl.proxy.proxy$java.io.PipedInputStream$ff19274a 0x6f9c2c47 "babashka.impl.proxy.proxy$java.io.PipedInputStream$ff19274a@6f9c2c47"], :err #object[babashka.impl.proxy.proxy$java.io.PipedInputStream$ff19274a 0x207ed897 "babashka.impl.proxy.proxy$java.io.PipedInputStream$ff19274a@207ed897"], :prev nil, :cmd "echo 'I am running over ssh'"}
By default no :in
stream will be used. Streams will be returned for :out
and :err
. The funtion will return immediately and :exit
will be nil as an indicator the process is still running. Dereferencing the return value will block until the process is finished and fill in the exit value of the remote process:
(:exit @(bbssh/exec session "exit 3"))
;; => 3
Specify the format for the final value of :out
and :err
by passing in options:
(-> @(bbssh/exec session "echo stdout; echo stderr 1>&2; exit 3"
{:out :string
:err :string})
(select-keys [:exit :err :out]))
;; => {:exit 3, :err "stderr\n", :out "stdout\n"}
(-> @(bbssh/exec session "echo stdout"
{:out :bytes})
:out
seq)
;; => (115 116 100 111 117 116 10)
Streaming between local and remote
Bbssh interoperates with babashka.process pipelining. The first argument of pod.epiccastle.bbssh.core/exec
can be substituted for an SshProcess
or a babashka.process Process
. When doing so you need to pass in the session via the options hashmap. Here we stream from a local process to a remote process and then back to a local process again:
(-> (babashka.process/process "echo this is local")
(bbssh/exec "md5sum" {:session session})
(babashka.process/process
"bash -c \"echo 'our sum: $(cat)'\""
{:out :string})
deref
:out)
;; => "our sum: 4088b54321c3a731eda432ab09fa9f63 -\n"
Streaming between remotes
In a similar way we can stream from one remote execution to another.
(-> (bbssh/exec session-one "echo first host")
(bbssh/exec "echo $(cat) and now second host"
{:session session-two
:out :string})
deref
:out)
;; => "first host and now second host\n"
Note: This streamed data is transfered via your local machine. So it doesn’t make sense to stream from one process to another on the same remote host. In those cases use a pipe in the command to stream the data. This technique is useful for streaming from a remote process on one machine to a remote process on another machine.
Copying files from local to remote
Scp functionality is in the pod.epiccastle.bbssh.scp
namespace:
(require '[pod.epiccastle.bbssh.scp :as scp])
You can scp files from the local filesystem to a remote machine with pod.epiccastle.bbssh.scp/scp-to
.
(scp/scp-to [(io/as-file "file1.dat") (io/as-file "file2.dat")]
"remote-path"
{:session session})
;; => nil
Note: Remote paths are relative to the home directory of the remote user.
Copying directories from local to remote
You can recursively copy from a local directory to a remote machine by setting the option :recurse?
to true
:
(scp/scp-to [(io/as-file "a-directory")]
"remote-path"
{:session session
:recurse? true})
;; => nil
Copying a remote file to the local machine
You can scp files from the local filesystem to a remote machine with pod.epiccastle.bbssh.scp/scp-from
:
(scp/scp-from "remote-file.dat"
"local-destination"
{:session session})
;; => nil
Copying a remote directory to the local machine
You can recursively copy from a remote directory to the local machine by setting the option :recurse?
to true
:
(scp/scp-from "remote-directory"
"local-destination"
{:session session
:recurse? true})
;; => nil