1mod assert;
2pub mod image;
3pub mod machine;
4pub mod ports;
5pub mod progress;
6
7use std::process::Stdio;
8
9use anyhow::Result;
10
11pub struct HostMemory {
13 pub total_mb: u64,
14 pub available_mb: u64,
15 pub swap_total_mb: u64,
16 pub swap_used_mb: u64,
17}
18
19pub fn read_host_memory() -> Option<HostMemory> {
21 let meminfo = std::fs::read_to_string("/proc/meminfo").ok()?;
22 let parse_kb = |key: &str| -> Option<u64> {
23 meminfo
24 .lines()
25 .find(|l| l.starts_with(key))
26 .and_then(|l| l.split_whitespace().nth(1))
27 .and_then(|v| v.parse::<u64>().ok())
28 };
29 let total_kb = parse_kb("MemTotal:")?;
30 let avail_kb = parse_kb("MemAvailable:")?;
31 let swap_total_kb = parse_kb("SwapTotal:").unwrap_or(0);
32 let swap_free_kb = parse_kb("SwapFree:").unwrap_or(0);
33 Some(HostMemory {
34 total_mb: total_kb / 1024,
35 available_mb: avail_kb / 1024,
36 swap_total_mb: swap_total_kb / 1024,
37 swap_used_mb: swap_total_kb.saturating_sub(swap_free_kb) / 1024,
38 })
39}
40
41pub fn check_prerequisites(use_kvm: bool) -> Result<()> {
43 let required = [
44 "qemu-system-aarch64",
45 "qemu-img",
46 "ssh",
47 "scp",
48 "ssh-keygen",
49 "curl",
50 ];
51 let mut missing: Vec<&str> = required
52 .iter()
53 .filter(|c| !has_command(c))
54 .copied()
55 .collect();
56
57 if !has_any_iso_tool() {
58 missing.push("genisoimage");
59 }
60
61 if !missing.is_empty() {
62 anyhow::bail!(
63 "missing required tools: {}\n\
64 Install with:\n \
65 sudo apt install qemu-system-arm qemu-utils qemu-efi-aarch64 \\\n \
66 genisoimage openssh-client curl # Debian/Ubuntu\n \
67 sudo dnf install qemu-system-aarch64 qemu-img edk2-aarch64 \\\n \
68 genisoimage openssh-clients curl # Fedora\n \
69 sudo pacman -S qemu-system-aarch64 qemu-img edk2-aarch64 \\\n \
70 cdrtools openssh curl # Arch",
71 missing.join(", ")
72 );
73 }
74
75 if use_kvm {
76 check_kvm()?;
77 }
78
79 Ok(())
80}
81
82fn has_command(cmd: &str) -> bool {
83 std::process::Command::new("which")
84 .arg(cmd)
85 .stdout(Stdio::null())
86 .stderr(Stdio::null())
87 .status()
88 .map(|s| s.success())
89 .unwrap_or(false)
90}
91
92fn has_any_iso_tool() -> bool {
93 ["genisoimage", "mkisofs"]
94 .iter()
95 .any(|cmd| has_command(cmd))
96}
97
98fn check_kvm() -> Result<()> {
100 let kvm = std::path::Path::new("/dev/kvm");
101 if !kvm.exists() {
102 anyhow::bail!(
103 "/dev/kvm not found — KVM is not available on this machine.\n\
104 Run with --no-kvm to use software emulation (slower), or \
105 run on a machine with KVM support."
106 );
107 }
108 let accessible = std::fs::File::open(kvm).is_ok();
109 if !accessible {
110 anyhow::bail!(
111 "/dev/kvm exists but is not accessible — permission denied.\n\
112 Add your user to the kvm group and re-login:\n \
113 sudo usermod -aG kvm $USER\n \
114 # then log out and back in, or run: newgrp kvm"
115 );
116 }
117 Ok(())
118}
119
120pub fn accel_args() -> &'static [&'static str] {
122 &["-enable-kvm"]
123}