Prepare, configure, and manage Firecracker microVMs in seconds!
virtualization linux microvm firecracker
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

add support for debian microVM

+248 -60
+1
Cargo.lock
··· 159 159 dependencies = [ 160 160 "anyhow", 161 161 "dirs", 162 + "firecracker-prepare", 162 163 "glob", 163 164 "libc", 164 165 "num_cpus",
+49 -33
crates/firecracker-prepare/src/downloader.rs
··· 8 8 pub fn download_files(arch: &str) -> Result<(String, String, String)> { 9 9 let app_dir = 10 10 crate::config::get_config_dir().with_context(|| "Failed to get configuration directory")?; 11 + let kernel_file = download_kernel(arch)?; 12 + 13 + let ci_version = get_ci_version().with_context(|| "Failed to get CI version")?; 14 + 15 + let ubuntu_prefix = format!("firecracker-ci/{}/{}/ubuntu-", ci_version, arch); 16 + let latest_ubuntu_key = 17 + get_latest_key("http://spec.ccfc.min.s3.amazonaws.com/", &ubuntu_prefix)?; 18 + let ubuntu_version = Path::new(&latest_ubuntu_key) 19 + .file_name() 20 + .ok_or_else(|| anyhow!("Failed to get ubuntu filename"))? 21 + .to_string_lossy() 22 + .to_string(); 23 + let re_version = Regex::new(r"^\d+\.\d+$").with_context(|| "Failed to create version regex")?; 24 + let ubuntu_version = re_version 25 + .find(&ubuntu_version) 26 + .map(|m| m.as_str().to_string()) 27 + .unwrap_or_else(|| { 28 + Path::new(&latest_ubuntu_key) 29 + .file_name() 30 + .unwrap() 31 + .to_string_lossy() 32 + .strip_prefix("ubuntu-") 33 + .unwrap() 34 + .strip_suffix(".squashfs") 35 + .unwrap() 36 + .to_string() 37 + }); 38 + let ubuntu_file = format!("{}/ubuntu-{}.squashfs.upstream", app_dir, ubuntu_version); 39 + download_file( 40 + &format!( 41 + "https://s3.amazonaws.com/spec.ccfc.min/{}", 42 + latest_ubuntu_key 43 + ), 44 + &ubuntu_file, 45 + )?; 46 + 47 + Ok((kernel_file, ubuntu_file, ubuntu_version)) 48 + } 49 + 50 + fn get_ci_version() -> Result<String> { 11 51 let output = run_command( 12 52 "curl", 13 53 &[ ··· 30 70 .rsplitn(2, '.') 31 71 .last() 32 72 .ok_or_else(|| anyhow!("Failed to parse CI version"))?; 73 + Ok(ci_version.to_string()) 74 + } 75 + 76 + pub fn download_kernel(arch: &str) -> Result<String> { 77 + let app_dir = 78 + crate::config::get_config_dir().with_context(|| "Failed to get configuration directory")?; 79 + let ci_version = 80 + get_ci_version().with_context(|| "Failed to get CI version for kernel download")?; 33 81 34 82 let kernel_prefix = format!("firecracker-ci/{}/{}/vmlinux-", ci_version, arch); 35 83 let latest_kernel_key = ··· 48 96 &kernel_file, 49 97 )?; 50 98 51 - let ubuntu_prefix = format!("firecracker-ci/{}/{}/ubuntu-", ci_version, arch); 52 - let latest_ubuntu_key = 53 - get_latest_key("http://spec.ccfc.min.s3.amazonaws.com/", &ubuntu_prefix)?; 54 - let ubuntu_version = Path::new(&latest_ubuntu_key) 55 - .file_name() 56 - .ok_or_else(|| anyhow!("Failed to get ubuntu filename"))? 57 - .to_string_lossy() 58 - .to_string(); 59 - let re_version = Regex::new(r"^\d+\.\d+$").with_context(|| "Failed to create version regex")?; 60 - let ubuntu_version = re_version 61 - .find(&ubuntu_version) 62 - .map(|m| m.as_str().to_string()) 63 - .unwrap_or_else(|| { 64 - Path::new(&latest_ubuntu_key) 65 - .file_name() 66 - .unwrap() 67 - .to_string_lossy() 68 - .strip_prefix("ubuntu-") 69 - .unwrap() 70 - .strip_suffix(".squashfs") 71 - .unwrap() 72 - .to_string() 73 - }); 74 - let ubuntu_file = format!("{}/ubuntu-{}.squashfs.upstream", app_dir, ubuntu_version); 75 - download_file( 76 - &format!( 77 - "https://s3.amazonaws.com/spec.ccfc.min/{}", 78 - latest_ubuntu_key 79 - ), 80 - &ubuntu_file, 81 - )?; 82 - 83 - Ok((kernel_file, ubuntu_file, ubuntu_version)) 99 + Ok(kernel_file) 84 100 } 85 101 86 102 fn get_latest_key(url: &str, prefix: &str) -> Result<String> {
+104 -8
crates/firecracker-prepare/src/lib.rs
··· 1 + use std::fs; 2 + 1 3 use anyhow::Result; 2 4 use owo_colors::OwoColorize; 5 + 6 + use crate::command::{run_command, run_command_with_stdout_inherit}; 3 7 4 8 pub mod command; 5 9 pub mod config; ··· 7 11 pub mod rootfs; 8 12 pub mod ssh; 9 13 10 - pub fn prepare() -> Result<()> { 14 + #[derive(Default, Clone)] 15 + pub struct PrepareOptions { 16 + pub debian: Option<bool>, 17 + pub alpine: Option<bool>, 18 + pub ubuntu: Option<bool>, 19 + } 20 + 21 + pub fn prepare(options: PrepareOptions) -> Result<()> { 11 22 let arch = command::run_command("uname", &["-m"], false)?.stdout; 12 23 let arch = String::from_utf8_lossy(&arch).trim().to_string(); 13 24 14 25 println!("[+] Detected architecture: {}", arch.bright_green()); 15 26 16 - let (kernel_file, ubuntu_file, ubuntu_version) = downloader::download_files(&arch)?; 27 + let (kernel_file, ext4_file, ssh_key_file) = 28 + match (options.debian, options.alpine, options.ubuntu) { 29 + (Some(true), _, _) => prepare_debian(&arch)?, 30 + (_, Some(true), _) => prepare_alpine(&arch)?, 31 + (_, _, Some(true)) | (_, _, None) => prepare_ubuntu(&arch)?, 32 + _ => { 33 + return Err(anyhow::anyhow!("No valid rootfs option provided.")); 34 + } 35 + }; 36 + 37 + println!("[✓] Kernel: {}", kernel_file.bright_green()); 38 + println!("[✓] Rootfs: {}", ext4_file.bright_green()); 39 + println!("[✓] SSH Key: {}", ssh_key_file.bright_green()); 40 + 41 + Ok(()) 42 + } 43 + 44 + pub fn prepare_ubuntu(arch: &str) -> Result<(String, String, String)> { 45 + let (kernel_file, ubuntu_file, ubuntu_version) = downloader::download_files(arch)?; 17 46 18 47 let app_dir = config::get_config_dir()?; 19 48 let squashfs_root_dir = format!("{}/squashfs_root", app_dir); 20 49 21 50 rootfs::extract_squashfs(&ubuntu_file, &squashfs_root_dir)?; 22 51 23 - let ssh_key_name = format!("ubuntu-{}.id_rsa", ubuntu_version); 52 + let ssh_key_name = "id_rsa"; 24 53 ssh::generate_and_copy_ssh_key(&ssh_key_name, &squashfs_root_dir)?; 25 54 26 55 let ext4_file = format!("{}/ubuntu-{}.ext4", app_dir, ubuntu_version); 27 56 28 57 if !std::path::Path::new(&ext4_file).exists() { 29 - rootfs::create_ext4_filesystem(&squashfs_root_dir, &ext4_file)?; 58 + rootfs::create_ext4_filesystem(&squashfs_root_dir, &ext4_file, 400)?; 30 59 } else { 31 60 println!( 32 61 "[!] {} already exists, skipping ext4 creation.", ··· 36 65 37 66 let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 38 67 39 - println!("[✓] Kernel: {}", kernel_file.bright_green()); 40 - println!("[✓] Rootfs: {}", ext4_file.bright_green()); 41 - println!("[✓] SSH Key: {}", ssh_key_file.bright_green()); 68 + Ok((kernel_file, ext4_file, ssh_key_file)) 69 + } 42 70 43 - Ok(()) 71 + pub fn prepare_debian(arch: &str) -> Result<(String, String, String)> { 72 + let kernel_file = downloader::download_kernel(arch)?; 73 + let app_dir = config::get_config_dir()?; 74 + let debootstrap_dir: &str = &format!("{}/debootstrap", app_dir); 75 + 76 + let arch = match arch { 77 + "x86_64" => "amd64", 78 + "aarch64" => "arm64", 79 + _ => arch, 80 + }; 81 + 82 + if !std::path::Path::new(debootstrap_dir).exists() { 83 + fs::create_dir_all(debootstrap_dir)?; 84 + run_command_with_stdout_inherit( 85 + "debootstrap", 86 + &[ 87 + &format!("--arch={}", arch), 88 + "stable", 89 + debootstrap_dir, 90 + "http://deb.debian.org/debian/", 91 + ], 92 + true, 93 + )?; 94 + } 95 + 96 + let ssh_key_name = "id_rsa"; 97 + run_command( 98 + "mkdir", 99 + &["-p", &format!("{}/root/.ssh", debootstrap_dir)], 100 + true, 101 + )?; 102 + ssh::generate_and_copy_ssh_key(&ssh_key_name, &debootstrap_dir)?; 103 + 104 + if !run_command("chroot", &[debootstrap_dir, "which", "sshd"], true) 105 + .map(|output| output.status.success()) 106 + .unwrap_or(false) 107 + { 108 + run_command_with_stdout_inherit("chroot", &[debootstrap_dir, "apt-get", "update"], true)?; 109 + run_command_with_stdout_inherit( 110 + "chroot", 111 + &[ 112 + debootstrap_dir, 113 + "apt-get", 114 + "install", 115 + "-y", 116 + "openssh-server", 117 + ], 118 + true, 119 + )?; 120 + run_command( 121 + "chroot", 122 + &[debootstrap_dir, "systemctl", "enable", "ssh"], 123 + true, 124 + )?; 125 + } 126 + 127 + let ext4_file = format!("{}/debian-{}.ext4", app_dir, arch); 128 + if !std::path::Path::new(&ext4_file).exists() { 129 + rootfs::create_ext4_filesystem(debootstrap_dir, &ext4_file, 600)?; 130 + } 131 + 132 + let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 133 + 134 + Ok((kernel_file, ext4_file, ssh_key_file)) 135 + } 136 + 137 + pub fn prepare_alpine(arch: &str) -> Result<(String, String, String)> { 138 + let kernel_file = downloader::download_kernel(arch)?; 139 + unimplemented!("Alpine preparation is not implemented yet."); 44 140 }
+6 -2
crates/firecracker-prepare/src/rootfs.rs
··· 9 9 Ok(()) 10 10 } 11 11 12 - pub fn create_ext4_filesystem(squashfs_dir: &str, output_file: &str) -> Result<()> { 12 + pub fn create_ext4_filesystem(squashfs_dir: &str, output_file: &str, size: usize) -> Result<()> { 13 13 run_command("chown", &["-R", "root:root", squashfs_dir], true)?; 14 - run_command("truncate", &["-s", "400M", output_file], false)?; 14 + run_command( 15 + "truncate", 16 + &["-s", &format!("{}M", size), output_file], 17 + false, 18 + )?; 15 19 run_command("mkfs.ext4", &["-d", squashfs_dir, "-F", output_file], true)?; 16 20 Ok(()) 17 21 }
+10 -4
crates/firecracker-up/src/cmd/ssh.rs
··· 1 + use crate::{command::run_command, config::get_config_dir}; 1 2 use anyhow::Error; 3 + use firecracker_vm::constants::GUEST_IP; 2 4 use glob::glob; 3 - 4 - use crate::{command::run_command, config::get_config_dir}; 5 5 6 6 pub fn ssh() -> Result<(), Error> { 7 7 let app_dir = get_config_dir()?; 8 - let private_key = glob(format!("{}/*.id_rsa", app_dir).as_str()) 8 + let private_key = glob(format!("{}/id_rsa", app_dir).as_str()) 9 9 .map_err(|e| Error::msg(format!("Failed to find SSH key: {}", e)))? 10 10 .last() 11 11 .ok_or_else(|| Error::msg("No SSH key file found"))?; 12 12 run_command( 13 13 "ssh", 14 - &["-i", &private_key?.display().to_string(), "root@172.16.0.2"], 14 + &[ 15 + "-i", 16 + &private_key?.display().to_string(), 17 + "-o", 18 + "StrictHostKeyChecking=no", 19 + &format!("root@{}", GUEST_IP), 20 + ], 15 21 true, 16 22 )?; 17 23 Ok(())
+21 -3
crates/firecracker-up/src/cmd/up.rs
··· 1 1 use std::thread; 2 2 3 3 use anyhow::Error; 4 + use firecracker_prepare::PrepareOptions; 4 5 use owo_colors::OwoColorize; 5 6 6 7 use crate::command::run_command; 7 8 8 - pub fn up() -> Result<(), Error> { 9 + #[derive(Default, Clone)] 10 + pub struct UpOptions { 11 + pub debian: Option<bool>, 12 + pub alpine: Option<bool>, 13 + pub ubuntu: Option<bool>, 14 + } 15 + 16 + impl Into<PrepareOptions> for UpOptions { 17 + fn into(self) -> PrepareOptions { 18 + PrepareOptions { 19 + debian: self.debian, 20 + alpine: self.alpine, 21 + ubuntu: self.ubuntu, 22 + } 23 + } 24 + } 25 + 26 + pub fn up(options: UpOptions) -> Result<(), Error> { 9 27 check_kvm_support()?; 10 28 11 29 firecracker_process::start()?; ··· 18 36 } 19 37 } 20 38 21 - firecracker_prepare::prepare()?; 22 - firecracker_vm::setup()?; 39 + firecracker_prepare::prepare(options.clone().into())?; 40 + firecracker_vm::setup(options.into())?; 23 41 Ok(()) 24 42 } 25 43
+38 -4
crates/firecracker-up/src/main.rs
··· 2 2 use clap::{arg, Command}; 3 3 use owo_colors::OwoColorize; 4 4 5 - use crate::cmd::{down::down, logs::logs, reset::reset, ssh::ssh, status::status, up::up}; 5 + use crate::cmd::{ 6 + down::down, 7 + logs::logs, 8 + reset::reset, 9 + ssh::ssh, 10 + status::status, 11 + up::{up, UpOptions}, 12 + }; 6 13 7 14 pub mod cmd; 8 15 pub mod command; ··· 25 32 Command::new("fireup") 26 33 .version(env!("CARGO_PKG_VERSION")) 27 34 .about(&banner) 28 - .subcommand(Command::new("up").about("Start Firecracker MicroVM")) 35 + .subcommand( 36 + Command::new("up") 37 + .arg(arg!(--debian "Prepare Debian rootfs").default_value("false")) 38 + .arg(arg!(--alpine "Prepare Alpine rootfs").default_value("false")) 39 + .arg(arg!(--ubuntu "Prepare Ubuntu rootfs").default_value("true")) 40 + .about("Start Firecracker MicroVM"), 41 + ) 29 42 .subcommand(Command::new("down").about("Stop Firecracker MicroVM")) 30 43 .subcommand(Command::new("status").about("Check the status of Firecracker MicroVM")) 31 44 .subcommand( ··· 40 53 ) 41 54 .subcommand(Command::new("ssh").about("SSH into the Firecracker MicroVM")) 42 55 .subcommand(Command::new("reset").about("Reset the Firecracker MicroVM")) 56 + .arg(arg!(--debian "Prepare Debian rootfs").default_value("false")) 57 + .arg(arg!(--alpine "Prepare Alpine rootfs").default_value("false")) 58 + .arg(arg!(--ubuntu "Prepare Ubuntu rootfs").default_value("true")) 43 59 } 44 60 45 61 fn main() -> Result<()> { 46 62 let matches = cli().get_matches(); 47 63 48 64 match matches.subcommand() { 49 - Some(("up", _)) => up()?, 65 + Some(("up", args)) => { 66 + let options = UpOptions { 67 + debian: args.get_one::<bool>("debian").copied(), 68 + alpine: args.get_one::<bool>("alpine").copied(), 69 + ubuntu: args.get_one::<bool>("ubuntu").copied(), 70 + }; 71 + up(options)? 72 + } 50 73 Some(("down", _)) => down()?, 51 74 Some(("status", _)) => status()?, 52 75 Some(("logs", args)) => { ··· 55 78 } 56 79 Some(("ssh", _)) => ssh()?, 57 80 Some(("reset", _)) => reset()?, 58 - _ => up()?, 81 + _ => { 82 + // get args 83 + let debian = matches.get_one::<bool>("debian").copied().unwrap_or(false); 84 + let alpine = matches.get_one::<bool>("alpine").copied().unwrap_or(false); 85 + let ubuntu = matches.get_one::<bool>("ubuntu").copied().unwrap_or(true); 86 + let options = UpOptions { 87 + debian: Some(debian), 88 + alpine: Some(alpine), 89 + ubuntu: Some(ubuntu), 90 + }; 91 + up(options)? 92 + } 59 93 } 60 94 61 95 Ok(())
+1
crates/firecracker-vm/Cargo.toml
··· 7 7 repository.workspace = true 8 8 9 9 [dependencies] 10 + firecracker-prepare = { path = "../firecracker-prepare" } 10 11 anyhow = "1.0.98" 11 12 dirs = "6.0.0" 12 13 glob = "0.3.2"
+4 -2
crates/firecracker-vm/src/guest.rs
··· 4 4 5 5 pub fn configure_guest_network(key_name: &str) -> Result<()> { 6 6 println!("[+] Configuring network in guest..."); 7 - run_command( 7 + if let Err(err) = run_command( 8 8 "ssh", 9 9 &[ 10 10 "-i", ··· 15 15 "ip route add default via 172.16.0.1 dev eth0", 16 16 ], 17 17 false, 18 - )?; 18 + ) { 19 + println!("[-] Failed to set default route: {}", err); 20 + } 19 21 run_command( 20 22 "ssh", 21 23 &[
+14 -4
crates/firecracker-vm/src/lib.rs
··· 1 1 use anyhow::{anyhow, Context, Result}; 2 + use firecracker_prepare::PrepareOptions; 2 3 use owo_colors::OwoColorize; 3 4 use std::fs; 4 5 ··· 6 7 7 8 mod command; 8 9 mod config; 9 - mod constants; 10 + pub mod constants; 10 11 mod firecracker; 11 12 mod guest; 12 13 mod network; 13 14 14 - pub fn setup() -> Result<()> { 15 + pub fn setup(options: PrepareOptions) -> Result<()> { 15 16 let app_dir = get_config_dir().with_context(|| "Failed to get configuration directory")?; 16 17 17 18 let logfile = format!("{}/firecracker.log", app_dir); ··· 33 34 .display() 34 35 .to_string(); 35 36 36 - let rootfs = glob::glob(format!("{}/*.ext4", app_dir).as_str()) 37 + let ext4_file = match (options.debian, options.alpine, options.ubuntu) { 38 + (Some(true), _, _) => format!("{}/debian*.ext4", app_dir), 39 + (_, Some(true), _) => format!("{}/alpine*.ext4", app_dir), 40 + (_, _, Some(true)) | (_, _, None) => format!("{}/ubuntu*.ext4", app_dir), 41 + _ => { 42 + return Err(anyhow::anyhow!("No valid rootfs option provided.")); 43 + } 44 + }; 45 + 46 + let rootfs = glob::glob(&ext4_file) 37 47 .with_context(|| "Failed to glob rootfs files")? 38 48 .last() 39 49 .ok_or_else(|| anyhow!("No rootfs file found"))? ··· 48 58 .display() 49 59 .to_string(); 50 60 51 - let key_name = glob::glob(format!("{}/*.id_rsa", app_dir).as_str()) 61 + let key_name = glob::glob(format!("{}/id_rsa", app_dir).as_str()) 52 62 .with_context(|| "Failed to glob ssh key files")? 53 63 .last() 54 64 .ok_or_else(|| anyhow!("No SSH key file found"))?