Skip to main content

this_me/
host.rs

1//! host.rs
2//!
3//! Helper utilities to inspect the current host machine. Used by the
4//! `this-me host` CLI command. The implementation keeps dependencies
5//! minimal (standard library only) so it works everywhere the CLI
6//! compiles.
7use std::env;
8use std::net::UdpSocket;
9use std::process::Command;
10use owo_colors::OwoColorize;
11use atty;
12use get_if_addrs::get_if_addrs;
13/// Print a small summary about the machine running the CLI.
14///
15/// We keep the API result-based so callers can surface a friendly
16/// error message without panicking.
17pub fn print_host_summary() -> Result<(), String> {
18    // Enable color on stdout if terminal supports it
19    if atty::is(atty::Stream::Stdout) {
20        // No manual override needed; owo_colors auto-detects support.
21    }
22    println!("\n{}", "======HOST INFORMATION======".bright_yellow().bold());
23    let hostname = current_hostname().unwrap_or_else(|| "(unknown)".to_string());
24    let primary_ip = discover_primary_ip().unwrap_or_else(|| "(unavailable)".to_string());
25    println!("{} {}", "🖥️  Hostname :".bright_blue().bold(), hostname.white());
26    println!("{} {}", "🌐 Primary IP :".cyan().bold(), primary_ip.white());
27    println!("{} {} {}", "🧰 Platform :".magenta().bold(), env::consts::OS.white(), env::consts::ARCH.white());
28    println!("{} {}", "🗓️  Rust env :".bright_cyan().bold(), env::var("RUSTUP_TOOLCHAIN").unwrap_or_else(|_| "default".into()).bright_white());
29    println!("{}", "---------------------------------\n".bright_black());
30    Ok(())
31}
32
33pub fn print_network_summary() -> Result<(), String> {
34    println!("\n{}", "====== NETWORK INTERFACES ======".bright_yellow().bold());
35    let ifaces = get_if_addrs().map_err(|e| format!("Failed to get network interfaces: {}", e))?;
36    for iface in ifaces {
37        let name_colored = iface.name.bright_blue();
38        let ip = iface.addr.ip();
39        let ip_string = ip.to_string();
40        let ip_colored = ip_string.white();
41        println!("🔸 {} {}", name_colored, ip_colored);
42    }
43    println!("{}", "---------------------------------".bright_black());
44    Ok(())
45}
46
47fn current_hostname() -> Option<String> {
48    // Common environment variables across platforms
49    if let Ok(value) = env::var("HOSTNAME") {
50        if !value.is_empty() {
51            return Some(value);
52        }
53    }
54    if cfg!(target_os = "windows") {
55        if let Ok(value) = env::var("COMPUTERNAME") {
56            if !value.is_empty() {
57                return Some(value);
58            }
59        }
60    }
61
62    // Fallback: ask the OS via the `hostname` command.
63    Command::new(if cfg!(target_os = "windows") { "hostname" } else { "hostname" })
64        .output()
65        .ok()
66        .and_then(|out| String::from_utf8(out.stdout).ok())
67        .map(|s| s.trim().to_string())
68        .filter(|s| !s.is_empty())
69}
70
71fn discover_primary_ip() -> Option<String> {
72    // Bind to a UDP socket and connect to an external address (no packets sent).
73    // This is a common trick to determine the interface used for outbound traffic.
74    UdpSocket::bind(("0.0.0.0", 0))
75        .ok()
76        .and_then(|socket| {
77            socket
78                .connect(("8.8.8.8", 80))
79                .ok()
80                .and_then(|_| socket.local_addr().ok())
81        })
82        .map(|addr| addr.ip().to_string())
83}