use std::path::PathBuf;
use clap::ValueEnum;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum HostKeyCheckMode {
Yes,
#[default]
AcceptNew,
No,
}
impl HostKeyCheckMode {
pub fn as_openssh_value(self) -> &'static str {
match self {
Self::Yes => "yes",
Self::AcceptNew => "accept-new",
Self::No => "no",
}
}
}
pub const TIMEOUT_KILL_AFTER_SECS: u64 = 2;
pub const TIMEOUT_DETECTION_TIMEOUT_MS: u64 = 5000;
#[derive(Debug, Clone)]
pub struct SshConfig {
pub host: String,
pub port: u16,
pub username: String,
pub password: Option<String>,
pub private_key: Option<String>,
pub su_password: Option<String>,
pub sudo_password: Option<String>,
pub keepalive_interval: u64,
pub keepalive_max: u64,
pub max_output_tokens: Option<usize>,
pub reconnect_retries: u64,
pub reconnect_backoff_ms: u64,
pub health_probe_timeout_ms: u64,
pub host_key_checking: HostKeyCheckMode,
pub known_hosts: Option<PathBuf>,
}
impl SshConfig {
pub fn new(host: impl Into<String>, username: impl Into<String>) -> Self {
Self {
host: host.into(),
port: 22,
username: username.into(),
password: None,
private_key: None,
su_password: None,
sudo_password: None,
keepalive_interval: 30,
keepalive_max: 3,
max_output_tokens: Some(16_000),
reconnect_retries: 3,
reconnect_backoff_ms: 250,
health_probe_timeout_ms: 1500,
host_key_checking: HostKeyCheckMode::default(),
known_hosts: None,
}
}
pub fn with_port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn with_password(mut self, password: impl Into<String>) -> Self {
self.password = Some(password.into());
self
}
pub fn with_private_key(mut self, key: impl Into<String>) -> Self {
self.private_key = Some(key.into());
self
}
pub fn with_su_password(mut self, password: impl Into<String>) -> Self {
self.su_password = Some(password.into());
self
}
pub fn with_sudo_password(mut self, password: impl Into<String>) -> Self {
self.sudo_password = Some(password.into());
self
}
pub fn with_keepalive_interval(mut self, secs: u64) -> Self {
self.keepalive_interval = secs;
self
}
pub fn with_keepalive_max(mut self, max: u64) -> Self {
self.keepalive_max = max;
self
}
pub fn with_max_output_tokens(mut self, tokens: Option<usize>) -> Self {
self.max_output_tokens = tokens;
self
}
pub fn with_reconnect_retries(mut self, retries: u64) -> Self {
self.reconnect_retries = retries;
self
}
pub fn with_reconnect_backoff_ms(mut self, backoff_ms: u64) -> Self {
self.reconnect_backoff_ms = backoff_ms;
self
}
pub fn with_health_probe_timeout_ms(mut self, timeout_ms: u64) -> Self {
self.health_probe_timeout_ms = timeout_ms;
self
}
pub fn with_host_key_checking(mut self, mode: HostKeyCheckMode) -> Self {
self.host_key_checking = mode;
self
}
pub fn with_known_hosts(mut self, known_hosts: Option<PathBuf>) -> Self {
self.known_hosts = known_hosts;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ssh_config_builder() {
let config = SshConfig::new("192.168.1.1", "admin")
.with_port(2222)
.with_password("secret")
.with_max_output_tokens(Some(5_000))
.with_reconnect_retries(4)
.with_reconnect_backoff_ms(500)
.with_health_probe_timeout_ms(1_200);
assert_eq!(config.host, "192.168.1.1");
assert_eq!(config.port, 2222);
assert_eq!(config.username, "admin");
assert_eq!(config.password, Some("secret".to_string()));
assert!(config.private_key.is_none());
assert_eq!(config.reconnect_retries, 4);
assert_eq!(config.reconnect_backoff_ms, 500);
assert_eq!(config.health_probe_timeout_ms, 1_200);
assert_eq!(config.host_key_checking, HostKeyCheckMode::AcceptNew);
assert!(config.known_hosts.is_none());
}
}