Skip to main content

auths_cli/adapters/
system_diagnostic.rs

1//! POSIX-based diagnostic adapter — subprocess calls live here, nowhere else.
2
3use auths_sdk::ports::diagnostics::{
4    CheckCategory, CheckResult, CryptoDiagnosticProvider, DiagnosticError, GitDiagnosticProvider,
5};
6use std::process::Command;
7
8/// Production adapter that shells out to system binaries.
9pub struct PosixDiagnosticAdapter;
10
11impl GitDiagnosticProvider for PosixDiagnosticAdapter {
12    fn check_git_version(&self) -> Result<CheckResult, DiagnosticError> {
13        let output = crate::subprocess::git_command(&["--version"]).output();
14        let (passed, message) = match output {
15            Ok(out) if out.status.success() => {
16                let version = String::from_utf8_lossy(&out.stdout).trim().to_string();
17                (true, Some(version))
18            }
19            _ => (false, Some("git command not found on PATH".to_string())),
20        };
21        Ok(CheckResult {
22            name: "Git installed".to_string(),
23            passed,
24            message,
25            config_issues: vec![],
26            category: CheckCategory::Advisory,
27        })
28    }
29
30    /// Read the **effective** value of a git config key, not the global one.
31    ///
32    /// `auths init` scopes signing config to the repo by default — a scripted init
33    /// must not rewrite the user's `~/.gitconfig` — so reading `--global` would
34    /// report a correctly configured repo as broken. The effective value is also
35    /// what git itself will use, which is the thing a diagnostic should check.
36    fn get_git_config(&self, key: &str) -> Result<Option<String>, DiagnosticError> {
37        let output = crate::subprocess::git_command(&["config", "--get", key])
38            .output()
39            .map_err(|e| DiagnosticError::ExecutionFailed(e.to_string()))?;
40
41        if output.status.success() {
42            Ok(String::from_utf8(output.stdout)
43                .ok()
44                .map(|s| s.trim().to_string()))
45        } else {
46            Ok(None)
47        }
48    }
49}
50
51impl CryptoDiagnosticProvider for PosixDiagnosticAdapter {
52    fn check_ssh_keygen_available(&self) -> Result<CheckResult, DiagnosticError> {
53        // Use `which` crate logic: check if ssh-keygen exists on PATH
54        let output = Command::new("ssh-keygen")
55            .arg("-?")
56            .stderr(std::process::Stdio::piped())
57            .stdout(std::process::Stdio::piped())
58            .output();
59        let (passed, message) = match output {
60            Ok(out) if !out.stderr.is_empty() || !out.stdout.is_empty() => {
61                (true, Some("ssh-keygen found on PATH".to_string()))
62            }
63            Ok(_) => (true, Some("ssh-keygen found on PATH".to_string())),
64            Err(_) => {
65                let hint = ssh_install_hint();
66                (false, Some(format!("ssh-keygen not found on PATH. {hint}")))
67            }
68        };
69        Ok(CheckResult {
70            name: "ssh-keygen installed".to_string(),
71            passed,
72            message,
73            config_issues: vec![],
74            category: CheckCategory::Advisory,
75        })
76    }
77
78    fn check_ssh_version(&self) -> Result<String, DiagnosticError> {
79        // `ssh -V` writes to stderr, not stdout
80        let output = Command::new("ssh")
81            .arg("-V")
82            .stderr(std::process::Stdio::piped())
83            .stdout(std::process::Stdio::piped())
84            .output()
85            .map_err(|e| DiagnosticError::ExecutionFailed(format!("ssh -V failed: {e}")))?;
86
87        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
88        if !stderr.is_empty() {
89            return Ok(stderr);
90        }
91        let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
92        if !stdout.is_empty() {
93            return Ok(stdout);
94        }
95        Ok("unknown".to_string())
96    }
97}
98
99/// Platform-specific install hint for ssh-keygen / OpenSSH.
100fn ssh_install_hint() -> &'static str {
101    if cfg!(target_os = "macos") {
102        "ssh-keygen is normally pre-installed on macOS. Check your PATH."
103    } else if cfg!(target_os = "windows") {
104        "Install OpenSSH via Settings > Apps > Optional features, or `winget install Microsoft.OpenSSH.Client`."
105    } else {
106        "Install OpenSSH: `sudo apt install openssh-client` (Debian/Ubuntu) or `sudo dnf install openssh-clients` (Fedora/RHEL)."
107    }
108}