use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::Command;
pub fn doctor() {
let mut failures: Vec<&str> = Vec::new();
println!("agentignore doctor");
println!("{}", "\u{2500}".repeat(48));
let in_container = is_in_container();
let _ = check_fusermount(false, &mut failures);
let _ = check_dev_fuse(false, in_container, &mut failures);
let _ = check_libfuse3(false, &mut failures);
let _ = check_fuse_module(false, in_container, &mut failures);
println!();
if failures.is_empty() {
println!("\u{2713} All checks passed.");
} else {
println!("\u{2717} {} check(s) failed:", failures.len());
for f in &failures {
println!(" \u{2022} {f}");
}
println!(" \u{2192} Fix the issues above, then run `agentignore doctor` again.");
std::process::exit(1);
}
}
pub fn check_prerequisites(silent: bool) {
let mut failures: Vec<&str> = Vec::new();
let in_container = is_in_container();
check_fusermount(silent, &mut failures);
check_dev_fuse(silent, in_container, &mut failures);
check_libfuse3(silent, &mut failures);
check_fuse_module(silent, in_container, &mut failures);
if !failures.is_empty() {
eprintln!(
"Error: {} prerequisite(s) not met. Run `agentignore doctor` for details.",
failures.len()
);
std::process::exit(1);
}
}
fn is_in_container() -> bool {
if Path::new("/.dockerenv").exists() {
return true;
}
if let Ok(cgroup) = std::fs::read_to_string("/proc/1/cgroup") {
let lower = cgroup.to_lowercase();
lower.contains("/docker/")
|| lower.contains("/containerd/")
|| lower.contains("/kubepods/")
|| lower.contains("/pod")
} else {
false
}
}
fn check_fusermount(silent: bool, failures: &mut Vec<&str>) -> bool {
let candidates = ["fusermount3", "fusermount"];
for bin in &candidates {
if let Ok(output) = Command::new(bin).arg("--version").output() {
if output.status.success() {
if !silent {
let version = String::from_utf8_lossy(&output.stdout);
let version = version.lines().next().unwrap_or("");
println!(" \u{2713} fusermount ({version})");
}
return true;
}
}
}
println!(" \u{2717} fusermount not found");
println!(" \u{2514} Install fuse3: sudo apt install fuse3 (Debian/Ubuntu)");
println!(" sudo dnf install fuse3 (Fedora)");
failures.push("fusermount binary missing — install fuse3");
false
}
fn check_dev_fuse(silent: bool, in_container: bool, failures: &mut Vec<&str>) -> bool {
match std::fs::metadata("/dev/fuse") {
Ok(meta) => {
let mode = meta.permissions().mode();
let readable = mode & 0o444 != 0;
let writable = mode & 0o222 != 0;
if readable && writable {
if !silent {
println!(" \u{2713} /dev/fuse (accessible)");
}
true
} else {
println!(" \u{2717} /dev/fuse (insufficient permissions)");
print_dev_fuse_help(in_container);
failures.push("/dev/fuse not readable/writable");
false
}
}
Err(e) => {
println!(" \u{2717} /dev/fuse: {e}");
print_dev_fuse_help(in_container);
failures.push("/dev/fuse not found");
false
}
}
}
fn print_dev_fuse_help(in_container: bool) {
if in_container {
println!(" \u{2514} Your container needs access to /dev/fuse:");
println!(" For devcontainer.json, add:");
println!(r#" "runArgs": ["--device", "/dev/fuse", "--cap-add", "SYS_ADMIN"]"#);
println!(" For docker run:");
println!(" docker run --device /dev/fuse --cap-add SYS_ADMIN ...");
} else {
println!(" \u{2514} Install fuse3 and ensure /dev/fuse exists:");
println!(" sudo apt install fuse3 (Debian/Ubuntu)");
println!(" sudo modprobe fuse");
}
}
fn check_libfuse3(silent: bool, failures: &mut Vec<&str>) -> bool {
if let Ok(output) = Command::new("ldconfig").args(["-p"]).output() {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.contains("libfuse3") {
if !silent {
println!(" \u{2713} libfuse3 (shared library found)");
}
return true;
}
}
}
println!(" \u{2717} libfuse3 shared library not found");
println!(" \u{2514} Install fuse3: sudo apt install fuse3 (Debian/Ubuntu)");
println!(" sudo dnf install fuse3 (Fedora)");
failures.push("libfuse3 library missing");
false
}
fn check_fuse_module(silent: bool, in_container: bool, failures: &mut Vec<&str>) -> bool {
match std::fs::read_to_string("/proc/filesystems") {
Ok(content) => {
if content.contains("fuseblk") || content.contains("fuse") {
if !silent {
let lines: Vec<&str> = content.lines().filter(|l| l.contains("fuse")).collect();
println!(" \u{2713} fuse kernel module ({})", lines.join(", "));
}
true
} else {
println!(" \u{2717} fuse kernel module not loaded");
print_fuse_module_help(in_container);
failures.push("fuse kernel module not loaded");
false
}
}
Err(e) => {
println!(" \u{2717} cannot read /proc/filesystems: {e}");
failures.push("cannot check kernel module — /proc/filesystems unavailable");
false
}
}
}
fn print_fuse_module_help(in_container: bool) {
if in_container {
println!(" \u{2514} The host needs the fuse module loaded:");
println!(" docker run --privileged ... (on older hosts)");
println!(" Or on the host: sudo modprobe fuse");
} else {
println!(" \u{2514} Load the kernel module:");
println!(" sudo modprobe fuse");
println!(" To load automatically at boot:");
println!(" echo fuse | sudo tee /etc/modules-load.d/fuse.conf");
}
}