use std::collections::HashMap; use std::sync::Arc; use async_trait::async_trait; use rand_core::OsRng; use ratatui::backend::CrosstermBackend; use ratatui::layout::Rect; use ratatui::style::{Color, Style}; use ratatui::widgets::{Block, Borders, Clear, Paragraph}; use ratatui::Terminal; use russh::keys::ssh_key::PublicKey; use russh::server::*; use russh::{Channel, ChannelId}; use tokio::sync::Mutex; type SshTerminal = Terminal>; struct App { pub counter: usize, } impl App { pub fn new() -> App { Self { counter: 0 } } } #[derive(Clone)] struct TerminalHandle { handle: Handle, // The sink collects the data which is finally flushed to the handle. sink: Vec, channel_id: ChannelId, } // The crossterm backend writes to the terminal handle. impl std::io::Write for TerminalHandle { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.sink.extend_from_slice(buf); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { let handle = self.handle.clone(); let channel_id = self.channel_id; let data = self.sink.clone().into(); futures::executor::block_on(async move { let result = handle.data(channel_id, data).await; if result.is_err() { eprintln!("Failed to send data: {:?}", result); } }); self.sink.clear(); Ok(()) } } #[derive(Clone)] struct AppServer { clients: Arc>>, id: usize, app: Arc>, } impl AppServer { pub fn new() -> Self { Self { clients: Arc::new(Mutex::new(HashMap::new())), id: 0, app: Arc::new(Mutex::new(App::new())), } } pub async fn run(&mut self) -> Result<(), anyhow::Error> { let app = self.app.clone(); let clients = self.clients.clone(); tokio::spawn(async move { loop { tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; app.lock().await.counter += 1; let counter = app.lock().await.counter; for (_, terminal) in clients.lock().await.iter_mut() { terminal .draw(|f| { let size = f.size(); f.render_widget(Clear, size); let style = match counter % 3 { 0 => Style::default().fg(Color::Red), 1 => Style::default().fg(Color::Green), _ => Style::default().fg(Color::Blue), }; let paragraph = Paragraph::new(format!("Counter: {counter}")) .alignment(ratatui::layout::Alignment::Center) .style(style); let block = Block::default() .title("Press 'c' to reset the counter!") .borders(Borders::ALL); f.render_widget(paragraph.block(block), size); }) .unwrap(); } } }); let config = Config { inactivity_timeout: Some(std::time::Duration::from_secs(3600)), auth_rejection_time: std::time::Duration::from_secs(3), auth_rejection_time_initial: Some(std::time::Duration::from_secs(0)), keys: vec![ russh_keys::PrivateKey::random(&mut OsRng, ssh_key::Algorithm::Ed25519).unwrap(), ], ..Default::default() }; self.run_on_address(Arc::new(config), ("0.0.0.0", 2222)) .await?; Ok(()) } } impl Server for AppServer { type Handler = Self; fn new_client(&mut self, _: Option) -> Self { let s = self.clone(); self.id += 1; s } } #[async_trait] impl Handler for AppServer { type Error = anyhow::Error; async fn channel_open_session( &mut self, channel: Channel, session: &mut Session, ) -> Result { { let mut clients = self.clients.lock().await; let terminal_handle = TerminalHandle { handle: session.handle(), sink: Vec::new(), channel_id: channel.id(), }; let backend = CrosstermBackend::new(terminal_handle.clone()); let terminal = Terminal::new(backend)?; clients.insert(self.id, terminal); } Ok(true) } async fn auth_publickey(&mut self, _: &str, _: &PublicKey) -> Result { Ok(Auth::Accept) } async fn data( &mut self, channel: ChannelId, data: &[u8], session: &mut Session, ) -> Result<(), Self::Error> { let app = self.app.clone(); match data { // Pressing 'q' closes the connection. b"q" => { self.clients.lock().await.remove(&self.id); session.close(channel)?; } // Pressing 'c' resets the counter for the app. // Every client sees the counter reset. b"c" => { app.lock().await.counter = 0; } _ => {} } Ok(()) } /// The client's pseudo-terminal window size has changed. async fn window_change_request( &mut self, _: ChannelId, col_width: u32, row_height: u32, _: u32, _: u32, _: &mut Session, ) -> Result<(), Self::Error> { let mut terminal = { let clients = self.clients.lock().await; clients.get(&self.id).unwrap().clone() }; let rect = Rect { x: 0, y: 0, width: col_width as u16, height: row_height as u16, }; terminal.resize(rect)?; Ok(()) } } #[tokio::main] async fn main() { let mut server = AppServer::new(); server.run().await.expect("Failed running server"); }