mvm_runtime/vm/
firecracker.rs1use anyhow::Result;
2
3use crate::config::*;
4use crate::shell::{run_in_vm, run_in_vm_stdout, run_in_vm_visible};
5use crate::ui;
6use mvm_core::config::{ARCH, fc_version, fc_version_short};
7
8pub fn is_installed() -> Result<bool> {
10 let output = run_in_vm("command -v firecracker >/dev/null 2>&1")?;
11 Ok(output.status.success())
12}
13
14pub fn install() -> Result<()> {
16 if is_installed()? {
17 let version = run_in_vm_stdout("firecracker --version 2>&1 | head -1")?;
18 ui::info(&format!("Firecracker already installed: {}", version));
19 return Ok(());
20 }
21
22 let version = fc_version();
23 ui::info(&format!("Installing Firecracker {}...", version));
24 run_in_vm_visible(&format!(
25 r#"
26 cd /tmp
27 wget --progress=bar:force:noscroll https://github.com/firecracker-microvm/firecracker/releases/download/{fc_version}/firecracker-{fc_version}-{arch}.tgz
28 tar -xzf firecracker-{fc_version}-{arch}.tgz
29 sudo mv release-{fc_version}-{arch}/firecracker-{fc_version}-{arch} /usr/local/bin/firecracker
30 sudo chmod +x /usr/local/bin/firecracker
31 rm -rf firecracker-{fc_version}-{arch}.tgz release-{fc_version}-{arch}
32 firecracker --version
33 "#,
34 fc_version = version,
35 arch = ARCH,
36 ))?;
37
38 ui::success("Firecracker installed.");
39 Ok(())
40}
41
42pub fn download_assets() -> Result<()> {
44 let fc_short = fc_version_short();
45 ui::info("Downloading kernel and rootfs...");
46 run_in_vm_visible(&format!(
47 r#"
48 set -euo pipefail
49 mkdir -p {dir} && cd {dir}
50
51 if ls vmlinux-* >/dev/null 2>&1; then
52 echo '[mvm] Kernel already downloaded.'
53 else
54 echo '[mvm] Downloading kernel...'
55 latest_kernel_key=$(wget "http://spec.ccfc.min.s3.amazonaws.com/?prefix=firecracker-ci/{fc_short}/{arch}/vmlinux-5.10&list-type=2" -O - 2>/dev/null \
56 | grep -oP '(?<=<Key>)(firecracker-ci/{fc_short}/{arch}/vmlinux-5\.10\.[0-9]{{3}})(?=</Key>)')
57 if [ -z "$latest_kernel_key" ]; then
58 echo '[mvm] ERROR: Failed to find kernel.' >&2
59 exit 1
60 fi
61 wget --progress=bar:force:noscroll "https://s3.amazonaws.com/spec.ccfc.min/$latest_kernel_key"
62 echo '[mvm] Kernel downloaded.'
63 fi
64
65 if ls ubuntu-*.squashfs.upstream >/dev/null 2>&1; then
66 echo '[mvm] RootFS already downloaded.'
67 else
68 echo '[mvm] Downloading Ubuntu rootfs...'
69 latest_ubuntu_key=$(curl -s "http://spec.ccfc.min.s3.amazonaws.com/?prefix=firecracker-ci/{fc_short}/{arch}/ubuntu-&list-type=2" \
70 | grep -oP '(?<=<Key>)(firecracker-ci/{fc_short}/{arch}/ubuntu-[0-9]+\.[0-9]+\.squashfs)(?=</Key>)' \
71 | sort -V | tail -1)
72 if [ -z "$latest_ubuntu_key" ]; then
73 echo '[mvm] ERROR: Failed to find rootfs.' >&2
74 exit 1
75 fi
76 ubuntu_version=$(basename $latest_ubuntu_key .squashfs | grep -oE '[0-9]+\.[0-9]+')
77 wget --progress=bar:force:noscroll -O "ubuntu-${{ubuntu_version}}.squashfs.upstream" "https://s3.amazonaws.com/spec.ccfc.min/$latest_ubuntu_key"
78 echo "[mvm] RootFS downloaded (Ubuntu ${{ubuntu_version}})."
79 fi
80 "#,
81 dir = MICROVM_DIR,
82 arch = ARCH,
83 fc_short = fc_short,
84 ))?;
85
86 Ok(())
87}
88
89pub fn prepare_rootfs() -> Result<()> {
94 ui::info("Preparing root filesystem...");
95 run_in_vm_visible(&format!(
96 r#"
97 set -euo pipefail
98 cd {dir}
99
100 squashfs_file=$(ls ubuntu-*.squashfs.upstream 2>/dev/null | tail -1)
101 if [ -z "$squashfs_file" ]; then
102 echo '[mvm] ERROR: No squashfs file found.' >&2
103 exit 1
104 fi
105 ubuntu_version=$(echo $squashfs_file | grep -oE '[0-9]+\.[0-9]+')
106
107 if ls ubuntu-*.ext4 >/dev/null 2>&1; then
108 echo '[mvm] ext4 rootfs already exists, skipping.'
109 else
110 echo '[mvm] Extracting squashfs...'
111 sudo rm -rf squashfs-root
112 sudo unsquashfs $squashfs_file
113
114 echo '[mvm] Creating ext4 filesystem (1GB)...'
115 truncate -s 1G "ubuntu-${{ubuntu_version}}.ext4"
116 sudo mkfs.ext4 -d squashfs-root -F "ubuntu-${{ubuntu_version}}.ext4"
117
118 sudo rm -rf squashfs-root
119 echo '[mvm] Root filesystem prepared.'
120 fi
121
122 echo ''
123 echo 'Setup Summary:'
124 KERNEL=$(ls vmlinux-* 2>/dev/null | tail -1)
125 [ -f "$KERNEL" ] && echo " Kernel: $KERNEL" || echo " ERROR: Kernel not found"
126 ROOTFS=$(ls *.ext4 2>/dev/null | tail -1)
127 [ -f "$ROOTFS" ] && echo " Rootfs: $ROOTFS" || echo " ERROR: Rootfs not found"
128 "#,
129 dir = MICROVM_DIR,
130 ))?;
131
132 Ok(())
133}
134
135pub fn write_state() -> Result<()> {
137 run_in_vm(&format!(
138 r#"
139 cd {dir}
140 cat > .mvm-state <<STATEEOF
141{{
142 "kernel": "$(ls vmlinux-* 2>/dev/null | tail -1)",
143 "rootfs": "$(ls *.ext4 2>/dev/null | tail -1)",
144 "ssh_key": "$(ls *.id_rsa 2>/dev/null | tail -1)"
145}}
146STATEEOF
147 "#,
148 dir = MICROVM_DIR,
149 ))?;
150 Ok(())
151}
152
153pub fn validate_rootfs_squashfs() -> Result<bool> {
155 let output = run_in_vm(&format!(
156 "unsquashfs -l {dir}/ubuntu-*.squashfs.upstream >/dev/null 2>&1",
157 dir = MICROVM_DIR,
158 ))?;
159 Ok(output.status.success())
160}
161
162pub fn is_running() -> Result<bool> {
164 let output = run_in_vm("pgrep -x firecracker >/dev/null 2>&1")?;
165 Ok(output.status.success())
166}
167
168pub fn is_vm_running(pid_file: &str) -> Result<bool> {
171 let result = run_in_vm_stdout(&format!(
172 r#"[ -f {pid} ] && p=$(cat {pid}) && [ -f "/proc/$p/comm" ] && [ "$(cat /proc/$p/comm)" = "firecracker" ] && echo yes || echo no"#,
173 pid = pid_file,
174 ))?;
175 Ok(result.trim() == "yes")
176}