feat: added basic ui

This commit is contained in:
Himadri Bhattacharjee
2025-03-19 17:53:10 +05:30
parent 55138724a8
commit 9a25292c9a
3 changed files with 702 additions and 55 deletions

446
Cargo.lock generated
View File

@@ -61,6 +61,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-tzdata"
version = "0.1.1"
@@ -126,6 +132,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]]
name = "argon2"
version = "0.5.3"
@@ -243,6 +255,21 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "castaway"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
dependencies = [
"rustversion",
]
[[package]]
name = "cbc"
version = "0.1.2"
@@ -314,6 +341,20 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "compact_str"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"rustversion",
"ryu",
"static_assertions",
]
[[package]]
name = "const-oid"
version = "0.9.6"
@@ -344,6 +385,31 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossterm"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
"bitflags",
"crossterm_winapi",
"mio",
"parking_lot",
"rustix",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "crypto-bigint"
version = "0.5.5"
@@ -403,6 +469,41 @@ dependencies = [
"syn",
]
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "data-encoding"
version = "2.8.0"
@@ -482,6 +583,12 @@ dependencies = [
"zeroize",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "elliptic-curve"
version = "0.13.8"
@@ -538,6 +645,22 @@ dependencies = [
"log",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "ff"
version = "0.13.1"
@@ -564,6 +687,18 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "futures"
version = "0.3.31"
@@ -704,6 +839,23 @@ dependencies = [
"subtle",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hex"
version = "0.4.3"
@@ -766,6 +918,18 @@ dependencies = [
"cc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indoc"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
[[package]]
name = "inout"
version = "0.1.4"
@@ -776,6 +940,19 @@ dependencies = [
"generic-array",
]
[[package]]
name = "instability"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d"
dependencies = [
"darling",
"indoc",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "internal-russh-forked-ssh-key"
version = "0.6.10+upstream-0.6.7"
@@ -810,6 +987,21 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.4"
@@ -865,12 +1057,48 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags",
"libc",
"redox_syscall",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "lru"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
dependencies = [
"hashbrown",
]
[[package]]
name = "md5"
version = "0.7.0"
@@ -899,6 +1127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.52.0",
]
@@ -973,6 +1202,12 @@ dependencies = [
"libm",
]
[[package]]
name = "numtoa"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f"
[[package]]
name = "object"
version = "0.36.7"
@@ -1048,6 +1283,29 @@ dependencies = [
"windows",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "password-hash"
version = "0.5.0"
@@ -1059,6 +1317,12 @@ dependencies = [
"subtle",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pbkdf2"
version = "0.12.2"
@@ -1197,11 +1461,14 @@ dependencies = [
name = "publik"
version = "0.1.0"
dependencies = [
"anyhow",
"env_logger",
"log",
"ratatui",
"russh",
"thiserror 2.0.12",
"tokio",
"tui-textarea",
]
[[package]]
@@ -1243,6 +1510,43 @@ dependencies = [
"getrandom",
]
[[package]]
name = "ratatui"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
dependencies = [
"bitflags",
"cassowary",
"compact_str",
"crossterm",
"indoc",
"instability",
"itertools",
"lru",
"paste",
"strum",
"termion",
"unicode-segmentation",
"unicode-truncate",
"unicode-width 0.2.0",
]
[[package]]
name = "redox_syscall"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
[[package]]
name = "regex"
version = "1.11.1"
@@ -1407,12 +1711,31 @@ dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa20"
version = "0.10.2"
@@ -1422,6 +1745,12 @@ dependencies = [
"cipher",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "scrypt"
version = "0.11.0"
@@ -1501,6 +1830,36 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "signature"
version = "2.2.0"
@@ -1581,6 +1940,40 @@ dependencies = [
"sha2",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "subtle"
version = "2.6.1"
@@ -1598,6 +1991,18 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "termion"
version = "4.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f359c854fbecc1ea65bc3683f1dcb2dce78b174a1ca7fda37acd1fff81df6ff"
dependencies = [
"libc",
"libredox",
"numtoa",
"redox_termios",
]
[[package]]
name = "thiserror"
version = "1.0.69"
@@ -1665,6 +2070,18 @@ dependencies = [
"syn",
]
[[package]]
name = "tui-textarea"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae"
dependencies = [
"crossterm",
"ratatui",
"termion",
"unicode-width 0.2.0",
]
[[package]]
name = "typenum"
version = "1.18.0"
@@ -1677,6 +2094,35 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-truncate"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [
"itertools",
"unicode-segmentation",
"unicode-width 0.1.14",
]
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "universal-hash"
version = "0.5.1"

View File

@@ -4,8 +4,11 @@ version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.97"
env_logger = "0.11.7"
log = "0.4.26"
ratatui = "0.29.0"
russh = "0.51.1"
thiserror = "2.0.12"
tokio = "1.44.1"
tui-textarea = { version = "0.7.0", features = ["termion"] }

View File

@@ -1,54 +1,27 @@
use std::collections::{HashMap, HashSet};
use std::fmt::format;
use std::path::Path;
use std::sync::Arc;
use authfile::Entity;
// use crossterm::event::{Event, read};
use ratatui::backend::CrosstermBackend;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Style};
use ratatui::termion::event::Event;
use ratatui::text::Text;
use ratatui::widgets::{Block, Borders, Clear, List, Paragraph};
use ratatui::{Terminal, TerminalOptions, Viewport};
use russh::keys::ssh_key;
use russh::keys::{PublicKey, ssh_key::public::KeyData, ssh_key::rand_core::OsRng};
use russh::server::{self, Auth, Handle, Msg, Server as _, Session};
use russh::{Channel, ChannelId, CryptoVec};
use russh::server::{self, Auth, Config, Handle, Handler, Msg, Server, Session};
use russh::{Channel, ChannelId, CryptoVec, Pty};
use tokio::sync::Mutex;
use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
use tui_textarea::TextArea;
mod authfile;
#[tokio::main]
async fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();
let mut methods = russh::MethodSet::empty();
methods.push(russh::MethodKind::PublicKey);
let keychain = authfile::read(Path::new("./authfile")).await.unwrap();
let key_data_pool = new_atomic(build_key_data_pool(&keychain));
let key_data_to_id = new_atomic(HashMap::new());
let id_to_user = new_atomic(HashMap::new());
let clients = new_atomic(HashMap::new());
let key_data_to_user = new_atomic(keychain.iter().map(|e| (e.key_data(), e.clone())).collect());
let keychain = new_atomic(keychain);
let config = russh::server::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)),
methods,
keys: vec![
russh::keys::PrivateKey::random(&mut OsRng, russh::keys::Algorithm::Ed25519).unwrap(),
],
..Default::default()
};
let config = Arc::new(config);
let mut sh = Server {
keychain,
id_to_user,
key_data_to_id,
key_data_pool,
key_data_to_user,
clients,
id: 0,
};
sh.run_on_address(config, ("0.0.0.0", 2222)).await.unwrap();
}
type SshTerminal = Terminal<CrosstermBackend<TerminalHandle>>;
fn build_key_data_pool(entities: &[Arc<Entity>]) -> HashSet<KeyData> {
entities.iter().map(|e| e.key_data()).collect()
@@ -62,6 +35,12 @@ fn new_atomic<T>(object: T) -> Atomic<T> {
type Atomic<T> = Arc<Mutex<T>>;
#[derive(Default)]
struct App {
pub history: Vec<String>,
pub counter: usize,
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("failed to disconnect client with id {0}")]
@@ -75,11 +54,58 @@ pub enum Error {
pub struct Client {
channel: ChannelId,
handle: Handle,
terminal: SshTerminal,
textarea: TextArea<'static>,
// entity: Arc<Entity>,
}
struct TerminalHandle {
sender: UnboundedSender<Vec<u8>>,
// The sink collects the data which is finally sent to sender.
sink: Vec<u8>,
}
impl TerminalHandle {
async fn start(handle: Handle, channel_id: ChannelId) -> Self {
let (sender, mut receiver) = unbounded_channel::<Vec<u8>>();
tokio::spawn(async move {
while let Some(data) = receiver.recv().await {
let result = handle.data(channel_id, data.into()).await;
if result.is_err() {
eprintln!("Failed to send data: {:?}", result);
}
}
});
Self {
sender,
sink: Vec::new(),
}
}
}
// The crossterm backend writes to the terminal handle.
impl std::io::Write for TerminalHandle {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.sink.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
let result = self.sender.send(self.sink.clone());
if result.is_err() {
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
result.unwrap_err(),
));
}
self.sink.clear();
Ok(())
}
}
#[derive(Clone)]
struct Server {
struct AppServer {
keychain: Atomic<Vec<Arc<Entity>>>,
key_data_pool: Atomic<HashSet<KeyData>>,
key_data_to_user: Atomic<HashMap<KeyData, Arc<Entity>>>,
@@ -88,9 +114,32 @@ struct Server {
clients: Atomic<HashMap<usize, Client>>,
id: usize,
app: Atomic<App>,
}
impl Server {
impl AppServer {
pub async fn run(&mut self) -> Result<(), anyhow::Error> {
let app = self.app.clone();
let clients = self.clients.clone();
let mut methods = russh::MethodSet::empty();
methods.push(russh::MethodKind::PublicKey);
let config = russh::server::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)),
methods,
keys: vec![
russh::keys::PrivateKey::random(&mut OsRng, russh::keys::Algorithm::Ed25519)
.unwrap(),
],
..Default::default()
};
self.run_on_address(Arc::new(config), ("0.0.0.0", 2222))
.await?;
Ok(())
}
async fn reload(&mut self) -> Result<(), Error> {
let new_keychain = authfile::read(Path::new("./authfile")).await?;
let new_key_data_pool = build_key_data_pool(&new_keychain);
@@ -156,7 +205,7 @@ impl Server {
}
}
impl server::Server for Server {
impl Server for AppServer {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
let s = self.clone();
@@ -168,7 +217,7 @@ impl server::Server for Server {
}
}
impl server::Handler for Server {
impl Handler for AppServer {
type Error = Error;
async fn channel_open_session(
@@ -180,14 +229,25 @@ impl server::Handler for Server {
// let entity = self.entity().await;
let channel = channel.id();
let handle = session.handle();
let terminal_handle = TerminalHandle::start(handle.clone(), channel.clone()).await;
let backend = CrosstermBackend::new(terminal_handle);
// the correct viewport area will be set when the client request a pty
let options = TerminalOptions {
viewport: Viewport::Fixed(Rect::default()),
};
let terminal = Terminal::with_options(backend, options).unwrap();
let mut clients = self.clients.lock().await;
clients.insert(
self.id,
Client {
textarea: TextArea::default(),
channel,
handle,
// entity,
terminal,
},
);
}
@@ -225,23 +285,134 @@ impl server::Handler for Server {
return Err(russh::Error::Disconnect.into());
}
// Alt+Return
if data == [27, 13] {
let text = self
.clients
.lock()
.await
.get_mut(&self.id)
.unwrap()
.textarea
.lines()
.to_vec()
.join("\n");
let name = self.entity().await.name().to_string();
let message = format!("[{name}]: {text}");
self.app.lock().await.history.push(message);
}
if !data.is_empty() {
let mut iterator = data.iter().skip(1).map(|d| Ok(*d));
let keycode = ratatui::termion::event::parse_event(data[0], &mut iterator).unwrap();
self.clients
.lock()
.await
.get_mut(&self.id)
.unwrap()
.textarea
.input(keycode);
}
// Press `r` to reload the authorization file
if data == [114] {
self.reload().await?;
}
let data = CryptoVec::from(format!(
"[{}]: {}\r\n",
self.entity().await.name(),
String::from_utf8_lossy(data)
));
self.post(data.clone()).await;
session.data(channel, data)?;
let clients = self.clients.clone();
let history: Vec<String> = self
.app
.lock()
.await
.history
.iter()
.rev()
.take(20)
.cloned()
.collect();
tokio::spawn(async move {
for (
_,
Client {
terminal, textarea, ..
},
) in clients.lock().await.iter_mut()
{
terminal
.draw(|f| {
// clear the screen
let area = f.area();
f.render_widget(Clear, area);
// split vertically as 80-20
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![
Constraint::Percentage(80),
Constraint::Percentage(20),
])
.split(f.area());
let style = Style::default().fg(Color::Green);
let paragraphs: Vec<_> = history
.iter()
.map(|message| Text::styled(message.to_string(), style))
.collect();
let paragraphs =
List::new(paragraphs).block(Block::bordered().title("chat"));
// let block = Block::default()
// .title("chat")
// .borders(Borders::ALL);
// f.render_widget(paragraph.block(block), area);
f.render_widget(paragraphs, layout[0]);
f.render_widget(&*textarea, layout[1]);
})
.unwrap();
}
});
// let data = CryptoVec::from(format!(
// "[{}]: {}\r\n",
// self.entity().await.name(),
// String::from_utf8_lossy(data)
// ));
// self.post(data.clone()).await;
// session.data(channel, data)?;
Ok(())
}
async fn pty_request(
&mut self,
channel: ChannelId,
_: &str,
col_width: u32,
row_height: u32,
_: u32,
_: u32,
_: &[(Pty, u32)],
session: &mut Session,
) -> Result<(), Self::Error> {
let rect = Rect {
x: 0,
y: 0,
width: col_width as u16,
height: row_height as u16,
};
let mut clients = self.clients.lock().await;
let client = clients.get_mut(&self.id).unwrap();
client.terminal.resize(rect).unwrap();
session.channel_success(channel)?;
Ok(())
}
}
impl Drop for Server {
impl Drop for AppServer {
fn drop(&mut self) {
let id = self.id;
let clients = self.clients.clone();
@@ -251,3 +422,30 @@ impl Drop for Server {
});
}
}
#[tokio::main]
async fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();
let keychain = authfile::read(Path::new("./authfile")).await.unwrap();
let key_data_pool = new_atomic(build_key_data_pool(&keychain));
let key_data_to_id = new_atomic(HashMap::new());
let id_to_user = new_atomic(HashMap::new());
let clients = new_atomic(HashMap::new());
let key_data_to_user = new_atomic(keychain.iter().map(|e| (e.key_data(), e.clone())).collect());
let keychain = new_atomic(keychain);
let mut sh = AppServer {
app: new_atomic(App::default()),
keychain,
id_to_user,
key_data_to_id,
key_data_pool,
key_data_to_user,
clients,
id: 0,
};
sh.run().await.unwrap();
}