Compare commits

...

5 commits

Author SHA1 Message Date
32bce3dc4c
feat(cli): improvements to outputs & bind options
It is now possible to specify the host and port the SSH server listens
to using CLI arguments. Also improved host key naming and some inline
strings within the CLI version menu.
2025-01-31 16:34:23 +00:00
7bbae7f21c
chore(build): no need to rebuild for Cargo.toml changes 2025-01-31 16:32:48 +00:00
bcc667f3ea
fix(tests): use pretty_assertions for keycode tests 2025-01-31 13:09:37 +00:00
0e4dd8a7a8
chore(build): print only stem instead of full path in warn 2025-01-31 13:09:00 +00:00
262c75d36d
feat(build): generate host keys automatically on build 2025-01-31 12:56:34 +00:00
7 changed files with 81 additions and 15 deletions

3
.gitignore vendored
View file

@ -1,3 +1,2 @@
/target
.data/*.log
*.pem*
.data/*.log

2
Cargo.lock generated
View file

@ -3434,6 +3434,7 @@ dependencies = [
"directories",
"futures",
"human-panic",
"indoc",
"json5",
"lazy_static",
"libc",
@ -3443,6 +3444,7 @@ dependencies = [
"serde",
"serde_json",
"signal-hook",
"ssh-key",
"strip-ansi-escapes",
"strum",
"tokio",

View file

@ -26,6 +26,7 @@ derive_deref = "1.1.1"
directories = "5.0.1"
futures = "0.3.31"
human-panic = "2.0.2"
indoc = "2.0.5"
json5 = "0.4.1"
lazy_static = "1.5.0"
libc = "0.2.161"
@ -45,4 +46,5 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] }
[build-dependencies]
anyhow = "1.0.90"
ssh-key = { version = "0.6.7", features = ["getrandom", "crypto"] }
vergen-gix = { version = "1.0.2", features = ["build", "cargo"] }

View file

@ -1,7 +1,43 @@
use std::{env, path::PathBuf};
use anyhow::Result;
use ssh_key::{rand_core, Algorithm, EcdsaCurve, LineEnding, PrivateKey};
use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, GixBuilder};
const SSH_KEY_ALGOS: &[(&'static str, Algorithm)] = &[
("rsa.pem", Algorithm::Rsa { hash: None }),
("ed25519.pem", Algorithm::Ed25519),
("ecdsa.pem", Algorithm::Ecdsa {
curve: EcdsaCurve::NistP256,
}),
];
fn main() -> Result<()> {
println!("cargo:rerun-if-changed=build.rs");
// Generate openSSH host keys
let mut rng = rand_core::OsRng::default();
let keys = SSH_KEY_ALGOS
.iter()
.map(|(file_name, algo)| (*file_name, PrivateKey::random(&mut rng, algo.to_owned()).map_err(anyhow::Error::from)))
.collect::<Vec<(&str, Result<PrivateKey>)>>();
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
for (file_name, key_res) in keys {
if let Ok(ref key) = key_res {
let path = out_dir.join(file_name);
if path.exists() {
println!("cargo:warning=Skipping existing host key: {:?}", path.file_stem());
continue;
}
key.write_openssh_file(&path, LineEnding::default())?;
} else {
println!("cargo:warning=Failed to generate host key: {:?}", key_res);
}
}
// Emit the build information
let build = BuildBuilder::all_build()?;
let gix = GixBuilder::all_git()?;
let cargo = CargoBuilder::all_cargo()?;

View file

@ -1,4 +1,5 @@
use clap::Parser;
use indoc::formatdoc;
use crate::config::{get_config_dir, get_data_dir};
@ -12,6 +13,13 @@ pub struct Cli {
/// Frame rate, i.e. number of frames per second
#[arg(short, long, value_name = "FLOAT", default_value_t = 60.0)]
pub frame_rate: f64,
/// The host address to start the SSH server on
#[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,
}
const VERSION_MESSAGE: &str = concat!(
@ -30,13 +38,12 @@ pub fn version() -> String {
let config_dir_path = get_config_dir().display().to_string();
let data_dir_path = get_data_dir().display().to_string();
format!(
"\
{VERSION_MESSAGE}
formatdoc! {"
{VERSION_MESSAGE}
Authors: {author}
Authors: {author}
Config directory: {config_dir_path}
Data directory: {data_dir_path}"
)
Config directory: {config_dir_path}
Data directory: {data_dir_path}
"}
}

View file

@ -110,6 +110,7 @@ impl KeyCodeExt for KeyCode {
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_keycode_from_control_chars() {

View file

@ -24,23 +24,42 @@ mod ssh;
mod tui;
const SSH_KEYS: &[&[u8]] = &[
include_bytes!("../rsa.pem"),
include_bytes!("../ed25519.pem"),
include_bytes!(concat!(env!("OUT_DIR"), "/rsa.pem")),
include_bytes!(concat!(env!("OUT_DIR"), "/ecdsa.pem")),
include_bytes!(concat!(env!("OUT_DIR"), "/ed25519.pem")),
];
lazy_static! {
pub(crate) static ref OPTIONS: Cli = Cli::parse();
pub(crate) static ref SOCKET_ADDR: SocketAddr = SocketAddr::from(([127, 0, 0, 1], 2222));
pub(crate) static ref SOCKET_ADDR: Option<SocketAddr> = 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::<Result<Vec<u8>>>()
.ok()?
)
.ok()?,
// The port to listen on
OPTIONS.port
)));
}
#[tokio::main]
async fn main() -> Result<()> {
crate::errors::init()?;
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();
tracing::info!("Attempting to listen on {}", *SOCKET_ADDR);
tracing::info!("Attempting to listen on {}", socket_addr);
SshServer::default()
.run_on_socket(Arc::new(config), &TcpListener::bind(*SOCKET_ADDR).await?)
.run_on_socket(Arc::new(config), &TcpListener::bind(socket_addr).await?)
.await
.map_err(|err| eyre!(err))
}
@ -53,7 +72,7 @@ fn ssh_config() -> Config {
.iter()
.filter_map(|pem| PrivateKey::from_openssh(pem).ok())
.collect();
tracing::trace!("SSH config: {:#?}", conf);
conf
}