mermaid_cli/proxy/
podman.rs

1use anyhow::Result;
2use std::path::PathBuf;
3
4/// Check if a command is available on the system
5fn is_command_available(cmd: &str) -> bool {
6    which::which(cmd).is_ok()
7}
8
9/// Check if container runtime is available and return its name
10/// Prefers Podman over Docker
11pub fn is_container_runtime_available() -> Option<&'static str> {
12    if is_command_available("podman-compose") {
13        Some("podman-compose")
14    } else if is_command_available("docker-compose") {
15        Some("docker-compose")
16    } else if is_command_available("podman") {
17        // Fallback to standalone podman with compose plugin
18        Some("podman")
19    } else if is_command_available("docker") {
20        // Fallback to standalone docker with compose plugin
21        Some("docker")
22    } else {
23        None
24    }
25}
26
27/// Get the directory where docker-compose.yml is located
28pub fn get_compose_dir() -> Result<PathBuf> {
29    // Priority 1: MERMAID_PROXY_DIR environment variable (for custom installations)
30    if let Ok(proxy_dir) = std::env::var("MERMAID_PROXY_DIR") {
31        let path = PathBuf::from(proxy_dir);
32        if path.join("docker-compose.yml").exists() {
33            return Ok(path);
34        }
35    }
36
37    // Priority 2: ~/.config/mermaid/proxy/ (standard location)
38    if let Ok(config_dir) = crate::app::get_config_dir() {
39        let proxy_dir = config_dir.join("proxy");
40
41        // Create directory if it doesn't exist
42        if !proxy_dir.exists() {
43            std::fs::create_dir_all(&proxy_dir)?;
44        }
45
46        let compose_file = proxy_dir.join("docker-compose.yml");
47        let config_file = proxy_dir.join("litellm_config.yaml");
48
49        // If compose file doesn't exist, create it from embedded template
50        if !compose_file.exists() {
51            std::fs::write(&compose_file, include_str!("../../docker-compose.yml"))?;
52            eprintln!(
53                "[INFO] Created docker-compose.yml at: {}",
54                compose_file.display()
55            );
56        }
57
58        // If litellm config doesn't exist, create it from embedded template
59        if !config_file.exists() {
60            std::fs::write(&config_file, include_str!("../../litellm_config.yaml"))?;
61            eprintln!(
62                "[INFO] Created litellm_config.yaml at: {}",
63                config_file.display()
64            );
65        }
66
67        // Also ensure .env.example exists for reference
68        let env_example = proxy_dir.join(".env.example");
69        if !env_example.exists() {
70            std::fs::write(&env_example, include_str!("../../.env.example"))?;
71            eprintln!("[INFO] Created .env.example at: {}", env_example.display());
72        }
73
74        // Check for .env file, warn if missing
75        let env_file = proxy_dir.join(".env");
76        if !env_file.exists() {
77            eprintln!("[WARNING] No .env file found at: {}", env_file.display());
78            eprintln!("[WARNING] Copy .env.example and add your API keys:");
79            eprintln!("  cp {} {}", env_example.display(), env_file.display());
80        }
81
82        return Ok(proxy_dir);
83    }
84
85    // Priority 3: Development directory (current dir) - only for development
86    let current_dir = std::env::current_dir()?;
87    let compose_file = current_dir.join("docker-compose.yml");
88    if compose_file.exists() {
89        eprintln!("[DEV] Using docker-compose.yml from current directory");
90        return Ok(current_dir);
91    }
92
93    anyhow::bail!(
94        "Could not find or create docker-compose.yml.\n\
95         Set MERMAID_PROXY_DIR to specify a custom location, or ensure ~/.config/mermaid is writable."
96    );
97}
98
99/// Check if other mermaid processes are running
100pub fn count_mermaid_processes() -> usize {
101    use std::process::Command;
102
103    let output = Command::new("pgrep").arg("-c").arg("mermaid").output();
104
105    match output {
106        Ok(output) if output.status.success() => {
107            String::from_utf8_lossy(&output.stdout)
108                .trim()
109                .parse::<usize>()
110                .unwrap_or(1) // Default to 1 (ourselves) if parse fails
111        },
112        _ => 1, // Assume just us if pgrep fails
113    }
114}