Skip to main content

toride_ssh/
lib.rs

1#![warn(missing_docs)]
2#![allow(dead_code, reason = "scaffolding for modules under active development")]
3#![expect(
4    clippy::must_use_candidate,
5    reason = "service methods are call-and-forget; callers rarely use return"
6)]
7#![expect(
8    clippy::doc_markdown,
9    reason = "SSH-specific terms like ed25519 trigger false positives"
10)]
11
12//! `toride-ssh` — async SSH manager library.
13//!
14//! This is the **facade crate** that re-exports all sub-crates behind a
15//! unified API. The entry point is [`SshManager`], which resolves `~/.ssh`
16//! paths and provides accessor methods for each subsystem.
17//!
18//! # Subsystem accessors
19//!
20//! - [`SshManager::keys`] — key generation, inventory, repair
21//! - [`SshManager::config`] — config parsing, editing, resolution
22//! - [`SshManager::agent`] — SSH agent interaction *(feature: `agent`)*
23//! - [`SshManager::authorized_keys`] — authorized_keys management *(feature: `authorized-keys`)*
24//! - [`SshManager::doctor`] — diagnostic checks *(feature: `doctor`)*
25//! - [`SshManager::known_hosts`] — known_hosts management *(feature: `known-hosts`)*
26//! - [`SshManager::forward`] — port forwarding via ControlMaster *(feature: `forward`)*
27//! - [`SshManager::certificate`] — SSH certificate/CA operations *(feature: `certificate`)*
28//!
29//! # Testing with a custom CLI runner
30//!
31//! Use [`SshManager::with_cli_runner`] to inject a [`MockCliRunner`] so that
32//! no real SSH processes are spawned during tests.
33
34// Re-export core types (Error, Result, SshKey, CliRunner, etc.)
35pub use toride_ssh_core::*;
36
37// Re-export config subsystem
38pub use toride_ssh_config as config;
39
40// Re-export key subsystem
41pub use toride_ssh_key as key;
42
43// Feature-gated subsystem re-exports
44#[cfg(feature = "agent")]
45pub use toride_ssh_agent as agent;
46#[cfg(feature = "authorized-keys")]
47pub use toride_ssh_authorized_keys as authorized_keys;
48#[cfg(feature = "certificate")]
49pub use toride_ssh_certificate as certificate;
50#[cfg(feature = "doctor")]
51pub use toride_ssh_doctor as doctor;
52#[cfg(feature = "forward")]
53pub use toride_ssh_forward as forward;
54#[cfg(feature = "known-hosts")]
55pub use toride_ssh_known_hosts as known_hosts;
56
57use std::sync::Arc;
58
59/// Entry point for all SSH management operations.
60///
61/// `SshManager` is cheaply [`Clone`]-able and safe to share
62/// across async tasks. Each subsystem is accessed via a dedicated
63/// accessor method (e.g. [`keys()`](Self::keys), [`config()`](Self::config)).
64///
65/// # Examples
66///
67/// ```rust,no_run
68/// use toride_ssh::SshManager;
69///
70/// # async fn example() -> toride_ssh::Result<()> {
71/// let mgr = SshManager::new()?;
72/// let keys = mgr.keys().list().await?;
73/// println!("Found {} keys", keys.len());
74/// # Ok(())
75/// # }
76/// ```
77#[derive(Clone)]
78pub struct SshManager {
79    paths: SshPaths,
80    runner: Arc<dyn CliRunner>,
81}
82
83impl Default for SshManager {
84    /// Return a best-effort manager using [`DefaultCliRunner`].
85    ///
86    /// Falls back to `~/.ssh` if the home directory is unavailable.
87    fn default() -> Self {
88        Self {
89            paths: SshPaths::default(),
90            runner: Arc::new(DefaultCliRunner),
91        }
92    }
93}
94
95impl SshManager {
96    /// Create a new manager resolving `~/.ssh` from the user's home directory.
97    ///
98    /// Uses [`DefaultCliRunner`] for all CLI operations. For tests, prefer
99    /// [`with_cli_runner`](Self::with_cli_runner) with a [`MockCliRunner`].
100    ///
101    /// # Errors
102    ///
103    /// Returns [`Error::HomeNotFound`] if the user's home directory cannot
104    /// be resolved.
105    pub fn new() -> Result<Self> {
106        let paths = SshPaths::new()?;
107        Ok(Self {
108            paths,
109            runner: Arc::new(DefaultCliRunner),
110        })
111    }
112
113    /// Create a manager with a custom [`CliRunner`].
114    ///
115    /// This is the primary injection point for tests: pass a
116    /// [`MockCliRunner`] to control command execution without spawning
117    /// real SSH processes.
118    ///
119    /// # Errors
120    ///
121    /// Returns [`Error::HomeNotFound`] if the user's home directory cannot
122    /// be resolved.
123    ///
124    /// # Examples
125    ///
126    /// ```rust
127    /// use std::sync::Arc;
128    /// use toride_ssh::{SshManager, MockCliRunner};
129    ///
130    /// # fn example() -> toride_ssh::Result<()> {
131    /// let mock = Arc::new(MockCliRunner::new());
132    /// mock.set_tool_exists("ssh-keygen", true);
133    /// let mgr = SshManager::with_cli_runner(mock)?;
134    /// # Ok(())
135    /// # }
136    /// ```
137    pub fn with_cli_runner(runner: Arc<dyn CliRunner>) -> Result<Self> {
138        let paths = SshPaths::new()?;
139        Ok(Self { paths, runner })
140    }
141
142    /// Key management operations.
143    pub fn keys(&self) -> key::KeyService<'_> {
144        key::KeyService::new(&self.paths, &*self.runner)
145    }
146
147    /// SSH config operations.
148    pub fn config(&self) -> config::ConfigService<'_> {
149        config::ConfigService::new(&self.paths)
150    }
151
152    /// SSH agent operations.
153    #[cfg(feature = "agent")]
154    pub fn agent(&self) -> agent::AgentService<'_> {
155        agent::AgentService::new(&self.paths, &*self.runner)
156    }
157
158    /// `authorized_keys` management (listing, adding, removing keys).
159    #[cfg(feature = "authorized-keys")]
160    pub fn authorized_keys(&self) -> authorized_keys::AuthorizedKeysService<'_> {
161        authorized_keys::AuthorizedKeysService::new(&self.paths)
162    }
163
164    /// Diagnostic checks.
165    #[cfg(feature = "doctor")]
166    pub fn doctor(&self) -> doctor::DoctorService<'_> {
167        doctor::DoctorService::new(&self.paths, &*self.runner)
168    }
169
170    /// Known hosts management (listing, scanning, adding, removing).
171    #[cfg(feature = "known-hosts")]
172    pub fn known_hosts(&self) -> known_hosts::KnownHostsService<'_> {
173        known_hosts::KnownHostsService::new(&self.paths, &*self.runner)
174    }
175
176    /// SSH certificate and CA operations (inspection, validity, KRL).
177    #[cfg(feature = "certificate")]
178    pub fn certificate(&self) -> certificate::CertificateService {
179        certificate::CertificateService::new()
180    }
181
182    /// Port forwarding management via ControlMaster.
183    #[cfg(feature = "forward")]
184    pub fn forward(&self) -> forward::ForwardService<'_> {
185        forward::ForwardService::new(&self.paths)
186    }
187}