diff --git a/Cargo.lock b/Cargo.lock index fc63c1d..f8a42a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,189 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dfe5c9e0004c623edc65391dfd51daa201e7e30ebd9c9bedf873048ec32bc2" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64 0.22.1", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2 0.3.27", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.96", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.5.10", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -80,6 +263,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -219,7 +417,7 @@ dependencies = [ "atrium-common", "atrium-xrpc", "chrono", - "http", + "http 1.3.1", "ipld-core", "langtag", "regex", @@ -277,7 +475,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0216ad50ce34e9ff982e171c3659e65dedaa2ed5ac2994524debdc9a9647ffa8" dependencies = [ - "http", + "http 1.3.1", "serde", "serde_html_form", "serde_json", @@ -410,6 +608,27 @@ dependencies = [ "cipher", ] +[[package]] +name = "brotli" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bstr" version = "1.11.3" @@ -439,6 +658,15 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "bytestring" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" +dependencies = [ + "bytes", +] + [[package]] name = "camino" version = "1.1.9" @@ -501,6 +729,8 @@ version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -738,6 +968,17 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1046,6 +1287,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "unicode-xid", +] + [[package]] name = "des" version = "0.8.1" @@ -2095,6 +2357,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.7.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.12" @@ -2106,7 +2387,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.3.1", "indexmap 2.7.1", "slab", "tokio", @@ -2207,6 +2488,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.3.1" @@ -2225,7 +2517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.3.1", ] [[package]] @@ -2236,7 +2528,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", + "http 1.3.1", "http-body", "pin-project-lite", ] @@ -2247,6 +2539,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "human-panic" version = "2.0.2" @@ -2272,8 +2570,8 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2", - "http", + "h2 0.4.12", + "http 1.3.1", "http-body", "httparse", "itoa", @@ -2289,7 +2587,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", + "http 1.3.1", "hyper", "hyper-util", "rustls", @@ -2327,7 +2625,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", + "http 1.3.1", "http-body", "hyper", "ipnet", @@ -2510,6 +2808,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + [[package]] name = "indenter" version = "0.3.3" @@ -2663,6 +2967,16 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -2693,6 +3007,12 @@ dependencies = [ "serde", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.5.0" @@ -2737,6 +3057,23 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.12" @@ -2828,6 +3165,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3039,9 +3386,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "opaque-debug" @@ -3656,6 +4003,12 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -3680,8 +4033,8 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", + "h2 0.4.12", + "http 1.3.1", "http-body", "http-body-util", "hyper", @@ -3918,6 +4271,42 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "rust-embed" +version = "8.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" +dependencies = [ + "actix-web", + "mime_guess", + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.96", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rust-ini" version = "0.20.0" @@ -4421,6 +4810,7 @@ dependencies = [ name = "ssh-portfolio" version = "0.1.0" dependencies = [ + "actix-web", "anyhow", "async-trait", "atrium-api", @@ -4444,10 +4834,12 @@ dependencies = [ "json5", "lazy_static", "libc", + "mime_guess", "pretty_assertions", "ratatui", "reqwest", "russh", + "rust-embed", "serde", "serde_json", "signal-hook", @@ -4861,7 +5253,7 @@ dependencies = [ "bitflags", "bytes", "futures-util", - "http", + "http 1.3.1", "http-body", "iri-string", "pin-project-lite", @@ -4888,6 +5280,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5039,6 +5432,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -5815,3 +6214,31 @@ dependencies = [ "quote", "syn 2.0.96", ] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index f7a52c8..1926f9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ blog = [ ] [dependencies] +actix-web = "4.11.0" anyhow = "1.0.90" async-trait = "0.1.85" atrium-api = { version = "0.25.4", optional = true } @@ -48,10 +49,12 @@ ipld-core = { version = "0.4.2", optional = true } json5 = "0.4.1" lazy_static = "1.5.0" libc = "0.2.161" +mime_guess = "2.0.5" pretty_assertions = "1.4.1" ratatui = { version = "0.29.0", features = ["serde", "macros"] } reqwest = { version = "0.12", features = ["rustls-tls"], optional = true } russh = "0.49.2" +rust-embed = { version = "8.7.2", features = ["actix"] } serde = { version = "1.0.211", features = ["derive"] } serde_json = "1.0.132" signal-hook = "0.3.17" diff --git a/src/cli.rs b/src/cli.rs index b1cee2d..2e96de4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -18,8 +18,11 @@ pub struct Cli { #[arg(short = 'H', long, value_name = "ADDRESS", default_value_t = String::from("127.0.0.1"))] pub host: String, /// The port to start the SSH server on - #[arg(short, long, value_name = "PORT", default_value_t = 2222)] - pub port: u16, + #[arg(short = 'P', long, value_name = "PORT", default_value_t = 2222)] + pub ssh_port: u16, + /// The port to start the web server on + #[arg(short = 'p', long, value_name = "PORT", default_value_t = 80)] + pub web_port: u16 } const VERSION_MESSAGE: &str = concat!( diff --git a/src/landing.rs b/src/landing.rs new file mode 100644 index 0000000..117876f --- /dev/null +++ b/src/landing.rs @@ -0,0 +1,60 @@ +use std::{io, net::SocketAddr}; + +use actix_web::{middleware::Logger, web, App, HttpResponse, HttpServer, Responder}; +use rust_embed::Embed; +use tracing::instrument; + +macro_rules! embedded_route { + ($path:expr) => { + match WebLandingServer::get($path) { + Some(content) => HttpResponse::Ok() + .content_type( + mime_guess::from_path($path) + .first_or_octet_stream() + .as_ref(), + ) + .body(content.data.into_owned()), + None => HttpResponse::NotFound().body("404 Not Found"), + } + }; + + ($name:ident,$pat:expr,$path:literal) => { + #[actix_web::get($pat)] + async fn $name() -> impl Responder { + embedded_route!($path) + } + }; + + ($name:ident,$pat:expr) => { + #[actix_web::get($pat)] + async fn $name(path: web::Path) -> impl Responder { + embedded_route!(path.as_str()) + } + }; +} + +#[derive(Embed)] +#[folder = "www/build"] +pub struct WebLandingServer; + +impl WebLandingServer { + #[instrument(level = "trace")] + pub async fn start(addr: SocketAddr) -> io::Result<()> { + tracing::info!("Web server listening on {}", addr); + HttpServer::new(|| { + // TODO: register a default service for a nicer 404 page + App::new() + .service(index) + .service(favicon) + .service(dist) + .wrap(Logger::default()) + }) + .bind(addr)? + .run() + .await + } +} + +embedded_route!(index, "/", "index.html"); +embedded_route!(favicon, "/favicon.png", "favicon.png"); +embedded_route!(dist, "/{path:.*}"); diff --git a/src/main.rs b/src/main.rs index 92c3cf2..7e0afdb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,19 @@ -use std::{net::SocketAddr, sync::Arc}; +use std::net::SocketAddr; use clap::Parser as _; use cli::Cli; use color_eyre::{eyre::eyre, Result}; use lazy_static::lazy_static; -use russh::{ - keys::PrivateKey, - server::{Config, Server}, - MethodSet, -}; +use russh::{keys::PrivateKey, server::Config, MethodSet}; use ssh::SshServer; -use tokio::net::TcpListener; #[cfg(feature = "blog")] pub(crate) use atproto::com; #[cfg(feature = "blog")] pub(crate) use atrium_api::*; +use crate::landing::WebLandingServer; + mod action; mod app; #[cfg(feature = "blog")] @@ -26,6 +23,7 @@ mod components; mod config; mod errors; mod keycode; +mod landing; mod logging; mod ssh; mod tui; @@ -35,24 +33,12 @@ const SSH_KEYS: &[&[u8]] = &[ include_bytes!(concat!(env!("OUT_DIR"), "/ecdsa.pem")), include_bytes!(concat!(env!("OUT_DIR"), "/ed25519.pem")), ]; + +#[rustfmt::skip] lazy_static! { pub(crate) static ref OPTIONS: Cli = Cli::parse(); - pub(crate) static ref SOCKET_ADDR: Option = Some(SocketAddr::from(( - // Convert the hostname IP to a fixed size array of [u8; 4] - TryInto::<[u8; 4]>::try_into( - OPTIONS - .host - .splitn(4, ".") - .map(|octet_str| u8::from_str_radix(octet_str, 10) - .map_err(|_| eyre!("Octet component out of range (expected u8)"))) - .collect::>>() - .ok()? - ) - .ok()?, - - // The port to listen on - OPTIONS.port - ))); + pub(crate) static ref SSH_SOCKET_ADDR: Option = SocketAddr::try_from((host_ip().ok()?, OPTIONS.ssh_port)).ok(); + pub(crate) static ref WEB_SERVER_ADDR: Option = SocketAddr::try_from((host_ip().ok()?, OPTIONS.web_port)).ok(); } #[tokio::main] @@ -61,14 +47,28 @@ async fn main() -> Result<()> { crate::logging::init()?; let _ = *OPTIONS; // force clap to run by evaluating it - let socket_addr = SOCKET_ADDR.ok_or(eyre!("Invalid host IP provided"))?; - let config = ssh_config(); + let ssh_socket_addr = SSH_SOCKET_ADDR.ok_or(eyre!("Invalid host IP provided"))?; + let web_server_addr = WEB_SERVER_ADDR.ok_or(eyre!("Invalid host IP provided"))?; - tracing::info!("Attempting to listen on {}", socket_addr); - SshServer::default() - .run_on_socket(Arc::new(config), &TcpListener::bind(socket_addr).await?) - .await - .map_err(|err| eyre!(err)) + tokio::select! { + ssh_res = SshServer::start(ssh_socket_addr, ssh_config()) => ssh_res, + web_res = WebLandingServer::start(web_server_addr) => web_res.map_err(|err| eyre!(err)), + } +} + +/// Converts the supplied hostname IP via CLI to a fixed size array of `[u8; 4]`, defaults to `127.0.0.1` +pub fn host_ip() -> Result<[u8; 4]> { + TryInto::<[u8; 4]>::try_into( + OPTIONS + .host + .splitn(4, ".") + .map(|octet_str| { + u8::from_str_radix(octet_str, 10) + .map_err(|_| eyre!("Octet component out of range (expected u8)")) + }) + .collect::>>()?, + ) + .map_err(|_| eyre!("Invalid host IP provided")) } fn ssh_config() -> Config { diff --git a/src/ssh.rs b/src/ssh.rs index 2441efa..5c15ac6 100644 --- a/src/ssh.rs +++ b/src/ssh.rs @@ -3,10 +3,11 @@ use std::{io::Write, net::SocketAddr, sync::Arc}; use async_trait::async_trait; use color_eyre::eyre::{self, eyre}; use russh::{ - server::{Auth, Handle, Handler, Msg, Server, Session}, + server::{Auth, Config, Handle, Handler, Msg, Server, Session}, Channel, ChannelId, CryptoVec, Pty, }; use tokio::{ + net::TcpListener, runtime::Handle as TokioHandle, sync::{mpsc, oneshot, Mutex, RwLock}, }; @@ -252,6 +253,18 @@ impl Handler for SshSession { #[derive(Default)] pub struct SshServer; +impl SshServer { + #[instrument(level = "trace")] + pub async fn start(addr: SocketAddr, config: Config) -> eyre::Result<()> { + let listener = TcpListener::bind(addr).await?; + + Self::default() + .run_on_socket(Arc::new(config), &listener) + .await + .map_err(|err| eyre!(err)) + } +} + #[async_trait] impl Server for SshServer { type Handler = SshSession;