Skip to main content

mvm_runtime/vm/
firecracker.rs

1use 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
8/// Check if Firecracker is installed inside the Lima VM.
9pub 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
14/// Install Firecracker inside the Lima VM.
15pub 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
42/// Download kernel and rootfs into ~/microvm/ inside the Lima VM.
43pub 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
89/// Prepare the ext4 root filesystem from the downloaded squashfs.
90///
91/// No SSH is configured in the rootfs. MicroVMs run headless and
92/// communicate via vsock only.
93pub 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
135/// Write the state file with discovered asset filenames.
136pub 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
153/// Check whether the downloaded squashfs file is intact.
154pub 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
162/// Check if the Firecracker process is running inside the Lima VM.
163pub 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
168/// Check if a specific VM's Firecracker process is alive (by PID file path).
169/// Uses /proc/<pid>/comm instead of kill -0 because firecracker runs as root.
170pub 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}