1use anyhow::Result;
2
3use crate::ui;
4use mvm_core::platform::{self, Platform};
5use mvm_runtime::shell;
6
7pub fn check_package_manager() -> Result<()> {
12 if cfg!(target_os = "macos") {
13 check_homebrew()
14 } else {
15 check_linux_package_manager()
16 }
17}
18
19pub fn check_homebrew() -> Result<()> {
21 which::which("brew").map_err(|_| {
22 anyhow::anyhow!(
23 "Homebrew is not installed.\n\
24 Install it first:\n\n \
25 /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\n\n\
26 Then run 'mvm bootstrap' again."
27 )
28 })?;
29 ui::info("Homebrew found.");
30 Ok(())
31}
32
33fn check_linux_package_manager() -> Result<()> {
35 for cmd in &["apt-get", "dnf", "pacman"] {
36 if which::which(cmd).is_ok() {
37 ui::info(&format!("Package manager found: {}", cmd));
38 return Ok(());
39 }
40 }
41 anyhow::bail!(
42 "No supported package manager found (apt-get, dnf, or pacman).\n\
43 Install Lima manually: https://lima-vm.io/docs/installation/"
44 )
45}
46
47pub fn ensure_lima() -> Result<()> {
52 if platform::current() == Platform::LinuxNative {
53 ui::info("Native Linux with KVM detected — Lima not required.");
54 return Ok(());
55 }
56
57 if which::which("limactl").is_ok() {
58 let output = shell::run_host("limactl", &["--version"])?;
59 let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
60 ui::info(&format!("Lima already installed: {}", version));
61 return Ok(());
62 }
63
64 if cfg!(target_os = "macos") {
65 ui::info("Installing Lima via Homebrew...");
66 shell::run_host_visible("brew", &["install", "lima"])?;
67 } else {
68 install_lima_linux()?;
69 }
70
71 which::which("limactl").map_err(|_| {
72 anyhow::anyhow!("Lima installation completed but 'limactl' not found in PATH.")
73 })?;
74
75 ui::success("Lima installed successfully.");
76 Ok(())
77}
78
79fn install_lima_linux() -> Result<()> {
81 if which::which("apt-get").is_ok() {
82 ui::info("Installing Lima via apt...");
83 shell::run_host_visible(
84 "bash",
85 &["-c", "curl -fsSL https://lima-vm.io/install.sh | sudo sh"],
86 )?;
87 } else if which::which("dnf").is_ok() {
88 ui::info("Installing Lima via dnf...");
89 shell::run_host_visible(
90 "bash",
91 &["-c", "curl -fsSL https://lima-vm.io/install.sh | sudo sh"],
92 )?;
93 } else if which::which("pacman").is_ok() {
94 ui::info("Installing Lima via pacman...");
95 shell::run_host_visible("sudo", &["pacman", "-S", "--noconfirm", "lima"])?;
96 } else {
97 anyhow::bail!(
98 "No supported package manager found. Install Lima manually:\n\
99 https://lima-vm.io/docs/installation/"
100 );
101 }
102 Ok(())
103}
104
105pub fn is_lima_required() -> bool {
107 platform::current().needs_lima()
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn test_check_homebrew_error_message() {
116 if which::which("brew").is_err() {
117 let err = check_homebrew().unwrap_err();
118 let msg = err.to_string();
119 assert!(msg.contains("Homebrew is not installed"));
120 assert!(msg.contains("curl -fsSL"));
121 assert!(msg.contains("mvm bootstrap"));
122 } else {
123 assert!(check_homebrew().is_ok());
124 }
125 }
126
127 #[test]
128 fn test_ensure_lima_when_limactl_present() {
129 if which::which("limactl").is_ok() {
130 assert!(ensure_lima().is_ok());
131 }
132 }
133
134 #[test]
135 fn test_is_lima_required() {
136 let _ = is_lima_required();
137 }
138}