use anyhow::{Context, Result};
use russh::keys::agent::client::AgentClient;
use russh::keys::ssh_key::PublicKey;
use std::sync::Arc;
use tokio::sync::Mutex;
pub struct SshAgentManager {
client: Option<Arc<Mutex<AgentClient<tokio::net::UnixStream>>>>,
}
impl SshAgentManager {
pub fn new() -> Self {
Self { client: None }
}
pub async fn connect(&mut self) -> Result<()> {
log::debug!("🔑 Tentative de connexion à ssh-agent...");
let agent_client = AgentClient::connect_env()
.await
.context("Impossible de se connecter à ssh-agent (SSH_AUTH_SOCK)")?;
self.client = Some(Arc::new(Mutex::new(agent_client)));
log::info!("✅ Connexion à ssh-agent réussie");
Ok(())
}
pub async fn list_identities(&self) -> Result<Vec<PublicKey>> {
if let Some(ref client) = self.client {
let mut client_guard = client.lock().await;
let identities = client_guard
.request_identities()
.await
.context("Impossible de récupérer les identités de ssh-agent")?;
log::debug!("🔑 {} clé(s) trouvée(s) dans ssh-agent", identities.len());
Ok(identities)
} else {
anyhow::bail!("ssh-agent n'est pas connecté");
}
}
pub fn get_client(&self) -> Option<Arc<Mutex<AgentClient<tokio::net::UnixStream>>>> {
self.client.clone()
}
pub async fn try_connect() -> Option<Self> {
let mut manager = Self::new();
match manager.connect().await {
Ok(()) => {
log::info!("🔐 ssh-agent disponible et connecté");
Some(manager)
}
Err(e) => {
log::debug!("ℹ️ ssh-agent non disponible: {}", e);
None
}
}
}
}
impl Default for SshAgentManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_agent_connection_no_panic() {
let result = SshAgentManager::try_connect().await;
if result.is_some() {
println!("ssh-agent est disponible dans l'environnement de test");
} else {
println!("ssh-agent n'est pas disponible dans l'environnement de test");
}
}
}