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 NixOS microVM

+156 -25
+1 -1
Cargo.lock
··· 169 169 170 170 [[package]] 171 171 name = "fireup" 172 - version = "0.1.1" 172 + version = "0.2.0" 173 173 dependencies = [ 174 174 "anyhow", 175 175 "clap",
+1 -1
README.md
··· 8 8 9 9 ## Features 10 10 11 - - **Quick Setup**: Prepares linux kernel, Ubuntu/Debian/Alpine rootfs, and SSH keys in one command. 11 + - **Quick Setup**: Prepares linux kernel, Ubuntu/Debian/Alpine/NixOS rootfs, and SSH keys in one command. 12 12 - **Seamless VM Management**: Start, stop, and monitor Firecracker microVMs with intuitive subcommands. 13 13 - **Network Configuration**: Automatically sets up TAP devices, IP forwarding, and NAT for connectivity. 14 14 - **SSH Access**: Easily connect to the microVM via SSH.
+7
crates/firecracker-prepare/src/downloader.rs
··· 170 170 run_command("tar", &["-xzf", &output, "-C", minirootfs], true)?; 171 171 Ok(()) 172 172 } 173 + 174 + pub fn download_nixos_rootfs(_arch: &str) -> Result<()> { 175 + let app_dir = crate::config::get_config_dir()?; 176 + let output = format!("{}/nixos-rootfs.squashfs", app_dir); 177 + download_file("https://public.rocksky.app/nixos-rootfs.squashfs", &output)?; 178 + Ok(()) 179 + }
+47 -9
crates/firecracker-prepare/src/lib.rs
··· 18 18 pub debian: Option<bool>, 19 19 pub alpine: Option<bool>, 20 20 pub ubuntu: Option<bool>, 21 + pub nixos: Option<bool>, 21 22 } 22 23 23 24 pub fn prepare(options: PrepareOptions) -> Result<()> { ··· 26 27 27 28 println!("[+] Detected architecture: {}", arch.bright_green()); 28 29 29 - let (kernel_file, ext4_file, ssh_key_file) = 30 - match (options.debian, options.alpine, options.ubuntu) { 31 - (Some(true), _, _) => prepare_debian(&arch)?, 32 - (_, Some(true), _) => prepare_alpine(&arch)?, 33 - (_, _, Some(true)) | (_, _, None) => prepare_ubuntu(&arch)?, 34 - _ => { 35 - return Err(anyhow::anyhow!("No valid rootfs option provided.")); 36 - } 37 - }; 30 + let (kernel_file, ext4_file, ssh_key_file) = match ( 31 + options.debian, 32 + options.alpine, 33 + options.ubuntu, 34 + options.nixos, 35 + ) { 36 + (Some(true), _, _, _) => prepare_debian(&arch)?, 37 + (_, Some(true), _, _) => prepare_alpine(&arch)?, 38 + (_, _, _, Some(true)) => prepare_nixos(&arch)?, 39 + (_, _, Some(true), _) => prepare_ubuntu(&arch)?, 40 + _ => { 41 + return Err(anyhow::anyhow!("No valid rootfs option provided.")); 42 + } 43 + }; 38 44 39 45 println!("[✓] Kernel: {}", kernel_file.bright_green()); 40 46 println!("[✓] Rootfs: {}", ext4_file.bright_green()); ··· 44 50 } 45 51 46 52 pub fn prepare_ubuntu(arch: &str) -> Result<(String, String, String)> { 53 + println!("[+] Preparing Ubuntu rootfs for {}...", arch.bright_green()); 47 54 let (kernel_file, ubuntu_file, ubuntu_version) = downloader::download_files(arch)?; 48 55 49 56 let app_dir = config::get_config_dir()?; ··· 71 78 } 72 79 73 80 pub fn prepare_debian(arch: &str) -> Result<(String, String, String)> { 81 + println!("[+] Preparing Debian rootfs for {}...", arch.bright_green()); 74 82 let kernel_file = downloader::download_kernel(arch)?; 75 83 let app_dir = config::get_config_dir()?; 76 84 let debootstrap_dir: &str = &format!("{}/debootstrap", app_dir); ··· 137 145 } 138 146 139 147 pub fn prepare_alpine(arch: &str) -> Result<(String, String, String)> { 148 + println!("[+] Preparing Alpine rootfs for {}...", arch.bright_green()); 140 149 let kernel_file = downloader::download_kernel(arch)?; 141 150 let app_dir = config::get_config_dir()?; 142 151 let minirootfs = format!("{}/minirootfs", app_dir); ··· 248 257 249 258 Ok((kernel_file, ext4_file, ssh_key_file)) 250 259 } 260 + 261 + pub fn prepare_nixos(arch: &str) -> Result<(String, String, String)> { 262 + println!("[+] Preparing NixOS rootfs for {}...", arch.bright_green()); 263 + 264 + let kernel_file = downloader::download_kernel(arch)?; 265 + let app_dir = config::get_config_dir()?; 266 + let nixos_rootfs = format!("{}/nixosrootfs", app_dir); 267 + let squashfs_file = format!("{}/nixos-rootfs.squashfs", app_dir); 268 + 269 + downloader::download_nixos_rootfs(arch)?; 270 + rootfs::extract_squashfs(&squashfs_file, &nixos_rootfs)?; 271 + 272 + let ssh_key_name = "id_rsa"; 273 + ssh::generate_and_copy_ssh_key_nixos(&ssh_key_name, &nixos_rootfs)?; 274 + 275 + let ext4_file = format!("{}/{}", app_dir, "nixos-rootfs.ext4"); 276 + if !std::path::Path::new(&ext4_file).exists() { 277 + rootfs::create_ext4_filesystem(&nixos_rootfs, &ext4_file, 5120)?; 278 + } 279 + 280 + let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 281 + 282 + println!( 283 + "[+] NixOS rootfs prepared at: {}", 284 + nixos_rootfs.bright_green() 285 + ); 286 + 287 + Ok((kernel_file, ext4_file.into(), ssh_key_file)) 288 + }
+8 -1
crates/firecracker-prepare/src/rootfs.rs
··· 3 3 use crate::command::run_command; 4 4 5 5 pub fn extract_squashfs(squashfs_file: &str, output_dir: &str) -> Result<()> { 6 - run_command("rm", &["-rf", output_dir], true)?; 6 + if std::path::Path::new(output_dir).exists() { 7 + println!( 8 + "[!] Warning: {} already exists, skipping extraction.", 9 + output_dir 10 + ); 11 + return Ok(()); 12 + } 13 + 7 14 println!("Extracting rootfs..."); 8 15 run_command("unsquashfs", &["-d", output_dir, squashfs_file], false)?; 9 16 Ok(())
+48
crates/firecracker-prepare/src/ssh.rs
··· 24 24 run_command("cp", &[&pub_key_path, &auth_keys_path], true)?; 25 25 Ok(()) 26 26 } 27 + 28 + pub fn generate_and_copy_ssh_key_nixos(key_name: &str, squashfs_root_dir: &str) -> Result<()> { 29 + let app_dir = crate::config::get_config_dir()?; 30 + const DEFAULT_SSH: &str = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAR4Gvuv3lTpXIYeZTRO22nVEj64uMmlDAdt5+GG80hm tsiry@tsiry-XPS-9320"; 31 + 32 + if std::path::Path::new(&format!("{}/{}", app_dir, key_name)).exists() { 33 + println!( 34 + "[!] Warning: {} already exists, skipping key generation.", 35 + key_name 36 + ); 37 + let pub_key_path = format!("{}/{}.pub", app_dir, key_name); 38 + let nixos_configuration = format!("{}/etc/nixos/configuration.nix", squashfs_root_dir); 39 + let public_key = std::fs::read_to_string(&pub_key_path) 40 + .map_err(|e| anyhow::anyhow!("Failed to read public key: {}", e))? 41 + .trim() 42 + .to_string(); 43 + run_command( 44 + "sed", 45 + &[ 46 + "-i", 47 + &format!("s|{}|{}|", DEFAULT_SSH, public_key), 48 + &nixos_configuration, 49 + ], 50 + true, 51 + )?; 52 + return Ok(()); 53 + } 54 + 55 + let key_name = format!("{}/{}", app_dir, key_name); 56 + run_command_with_stdout_inherit("ssh-keygen", &["-f", &key_name, "-N", ""], false)?; 57 + 58 + let pub_key_path = format!("{}.pub", key_name); 59 + let nixos_configuration = format!("{}/etc/nixos/configuration.nix", squashfs_root_dir); 60 + let public_key = std::fs::read_to_string(&pub_key_path) 61 + .map_err(|e| anyhow::anyhow!("Failed to read public key: {}", e))? 62 + .trim() 63 + .to_string(); 64 + run_command( 65 + "sed", 66 + &[ 67 + "-i", 68 + &format!("s|{}|{}|", DEFAULT_SSH, public_key), 69 + &nixos_configuration, 70 + ], 71 + true, 72 + )?; 73 + Ok(()) 74 + }
+1 -1
crates/firecracker-up/Cargo.toml
··· 1 1 [package] 2 2 name = "fireup" 3 - version = "0.1.1" 3 + version = "0.2.0" 4 4 authors.workspace = true 5 5 edition.workspace = true 6 6 license.workspace = true
+2
crates/firecracker-up/src/cmd/up.rs
··· 11 11 pub debian: Option<bool>, 12 12 pub alpine: Option<bool>, 13 13 pub ubuntu: Option<bool>, 14 + pub nixos: Option<bool>, 14 15 } 15 16 16 17 impl Into<PrepareOptions> for UpOptions { ··· 19 20 debian: self.debian, 20 21 alpine: self.alpine, 21 22 ubuntu: self.ubuntu, 23 + nixos: self.nixos, 22 24 } 23 25 } 24 26 }
+6 -1
crates/firecracker-up/src/main.rs
··· 36 36 Command::new("up") 37 37 .arg(arg!(--debian "Prepare Debian rootfs").default_value("false")) 38 38 .arg(arg!(--alpine "Prepare Alpine rootfs").default_value("false")) 39 + .arg(arg!(--nixos "Prepare NixOS rootfs").default_value("false")) 39 40 .arg(arg!(--ubuntu "Prepare Ubuntu rootfs").default_value("true")) 40 41 .about("Start Firecracker MicroVM"), 41 42 ) ··· 55 56 .subcommand(Command::new("reset").about("Reset the Firecracker MicroVM")) 56 57 .arg(arg!(--debian "Prepare Debian rootfs").default_value("false")) 57 58 .arg(arg!(--alpine "Prepare Alpine rootfs").default_value("false")) 59 + .arg(arg!(--nixos "Prepare NixOS rootfs").default_value("false")) 58 60 .arg(arg!(--ubuntu "Prepare Ubuntu rootfs").default_value("true")) 59 61 } 60 62 ··· 67 69 debian: args.get_one::<bool>("debian").copied(), 68 70 alpine: args.get_one::<bool>("alpine").copied(), 69 71 ubuntu: args.get_one::<bool>("ubuntu").copied(), 72 + nixos: args.get_one::<bool>("nixos").copied(), 70 73 }; 71 74 up(options)? 72 75 } ··· 82 85 // get args 83 86 let debian = matches.get_one::<bool>("debian").copied().unwrap_or(false); 84 87 let alpine = matches.get_one::<bool>("alpine").copied().unwrap_or(false); 85 - let ubuntu = matches.get_one::<bool>("ubuntu").copied().unwrap_or(true); 88 + let nixos = matches.get_one::<bool>("nixos").copied().unwrap_or(false); 89 + let ubuntu = matches.get_one::<bool>("ubuntu").copied().unwrap_or(false); 86 90 let options = UpOptions { 87 91 debian: Some(debian), 88 92 alpine: Some(alpine), 89 93 ubuntu: Some(ubuntu), 94 + nixos: Some(nixos), 90 95 }; 91 96 up(options)? 92 97 }
+21 -6
crates/firecracker-vm/src/firecracker.rs
··· 6 6 7 7 use crate::command::run_command; 8 8 9 + const NIXOS_BOOT_ARGS: &str = "init=/nix/store/pq529c6dd6x5vaxak4vpyxrv17ydvnwr-nixos-system-nixos-firecracker-25.05.802216.55d1f923c480/init root=/dev/vda ro console=ttyS0 reboot=k panic=1"; 10 + 9 11 pub fn configure(logfile: &str, kernel: &str, rootfs: &str, arch: &str) -> Result<()> { 10 12 configure_logger(logfile)?; 11 - setup_boot_source(kernel, arch)?; 13 + setup_boot_source(kernel, arch, rootfs.contains("nixos"))?; 12 14 setup_rootfs(rootfs)?; 13 15 setup_network_interface()?; 14 - setup_vcpu_and_memory(num_cpus::get(), 512)?; 16 + setup_vcpu_and_memory( 17 + num_cpus::get(), 18 + if rootfs.contains("nixos") { 2048 } else { 512 }, 19 + )?; 15 20 16 21 // Wait before starting instance 17 22 sleep(Duration::from_millis(15)); ··· 48 53 Ok(()) 49 54 } 50 55 51 - fn setup_boot_source(kernel: &str, arch: &str) -> Result<()> { 56 + fn setup_boot_source(kernel: &str, arch: &str, is_nixos: bool) -> Result<()> { 52 57 println!("[+] Setting boot source..."); 53 58 let mut boot_args = "console=ttyS0 reboot=k panic=1 pci=off".to_string(); 54 59 if arch == "aarch64" { 55 60 boot_args = format!("keep_bootcon {}", boot_args); 56 61 } 62 + 63 + if is_nixos { 64 + boot_args = NIXOS_BOOT_ARGS.into(); 65 + } 66 + 57 67 let payload = json!({ 58 68 "kernel_image_path": kernel, 59 69 "boot_args": boot_args 60 70 }); 71 + println!("{}", payload.to_string()); 61 72 run_command( 62 73 "curl", 63 74 &[ ··· 102 113 103 114 fn setup_network_interface() -> Result<()> { 104 115 println!("[+] Setting network interface..."); 116 + let iface = "eth0"; 105 117 let payload = json!({ 106 - "iface_id": "net1", 118 + "iface_id": iface, 107 119 "guest_mac": FC_MAC, 108 120 "host_dev_name": TAP_DEV 109 121 }); 122 + 123 + println!("{}", payload.to_string()); 110 124 run_command( 111 125 "curl", 112 126 &[ ··· 117 131 API_SOCKET, 118 132 "--data", 119 133 &payload.to_string(), 120 - "http://localhost/network-interfaces/net1", 134 + &format!("http://localhost/network-interfaces/{}", iface), 121 135 ], 122 136 true, 123 137 )?; ··· 150 164 println!("[+] Setting vCPU and memory..."); 151 165 let payload = json!({ 152 166 "vcpu_count": n, 153 - "mem_size_mib": memory 167 + "mem_size_mib": memory, 168 + "stmt": false, 154 169 }); 155 170 run_command( 156 171 "curl",
+14 -5
crates/firecracker-vm/src/lib.rs
··· 34 34 .display() 35 35 .to_string(); 36 36 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), 37 + let ext4_file = match ( 38 + options.debian, 39 + options.alpine, 40 + options.ubuntu, 41 + options.nixos, 42 + ) { 43 + (Some(true), _, _, _) => format!("{}/debian*.ext4", app_dir), 44 + (_, Some(true), _, _) => format!("{}/alpine*.ext4", app_dir), 45 + (_, _, _, Some(true)) => format!("{}/nixos*.ext4", app_dir), 46 + (_, _, Some(true), _) => format!("{}/ubuntu*.ext4", app_dir), 41 47 _ => { 42 48 return Err(anyhow::anyhow!("No valid rootfs option provided.")); 43 49 } ··· 76 82 let arch = String::from_utf8_lossy(&arch).trim().to_string(); 77 83 network::setup_network()?; 78 84 firecracker::configure(&logfile, &kernel, &rootfs, &arch)?; 79 - guest::configure_guest_network(&key_name)?; 85 + 86 + if !rootfs.contains("nixos") { 87 + guest::configure_guest_network(&key_name)?; 88 + } 80 89 81 90 println!("[✓] MicroVM booted and network is configured 🎉"); 82 91