use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use async_trait::async_trait;
use russh::keys::*;
use russh::*;
use tokio::net::ToSocketAddrs;
use std::io::stdout;
use std::io::Write;
pub struct Client {}
#[async_trait]
impl client::Handler for Client {
type Error = russh::Error;
async fn check_server_key(
&mut self,
_server_public_key: &key::PublicKey,
) -> Result<bool, Self::Error> {
Ok(true)
}
}
pub struct Session {
session: client::Handle<Client>,
}
impl Session {
pub async fn connect<P: AsRef<Path>, A: ToSocketAddrs>(
key_path: P,
user: impl Into<String>,
addrs: A,
) -> Result<Self> {
let key_pair = load_secret_key(key_path, None)?;
let config = client::Config {
inactivity_timeout: Some(Duration::from_secs(5)),
..<_>::default()
};
let config = Arc::new(config);
let sh = Client {};
let mut session = client::connect(config, addrs, sh).await?;
let auth_res = session
.authenticate_publickey(user, Arc::new(key_pair))
.await?;
if !auth_res {
anyhow::bail!("Authentication failed");
}
Ok(Self { session })
}
pub async fn call(&mut self, command: &str) -> Result<u32> {
let mut channel = self.session.channel_open_session().await?;
channel.exec(true, command).await?;
let mut code = None;
let mut stdout = stdout();
loop {
let Some(msg) = channel.wait().await else {
break;
};
match msg {
ChannelMsg::Data { ref data } => {
stdout.write_all(data)?;
stdout.flush()?;
}
ChannelMsg::ExitStatus { exit_status } => {
code = Some(exit_status);
}
_ => {}
}
}
Ok(code.expect("program did not exit cleanly"))
}
pub async fn close(&mut self) -> Result<()> {
self.session
.disconnect(Disconnect::ByApplication, "", "English")
.await?;
Ok(())
}
}