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

Add support for additional Linux distributions in Firecracker

- Introduced configuration for Fedora, Gentoo, Slackware, OpenSUSE, OpenSUSE Tumbleweed, AlmaLinux, RockyLinux, and ArchLinux.
- Updated downloader functions to fetch root filesystem images for the new distributions.
- Modified the RootfsPreparer trait and its implementations to handle the new distributions.
- Enhanced the CLI to include options for the new distributions.
- Adjusted the VM setup process to accommodate the new root filesystem formats (squashfs).
- Added a new resolved.conf configuration file for systemd-resolved settings.

+650 -74
+39
crates/firecracker-prepare/src/config/resolved.conf
··· 1 + # This file is part of systemd. 2 + # 3 + # systemd is free software; you can redistribute it and/or modify it under the 4 + # terms of the GNU Lesser General Public License as published by the Free 5 + # Software Foundation; either version 2.1 of the License, or (at your option) 6 + # any later version. 7 + # 8 + # Entries in this file show the compile time defaults. Local configuration 9 + # should be created by either modifying this file (or a copy of it placed in 10 + # /etc/ if the original file is shipped in /usr/), or by creating "drop-ins" in 11 + # the /etc/systemd/resolved.conf.d/ directory. The latter is generally 12 + # recommended. Defaults can be restored by simply deleting the main 13 + # configuration file and all drop-ins located in /etc/. 14 + # 15 + # Use 'systemd-analyze cat-config systemd/resolved.conf' to display the full config. 16 + # 17 + # See resolved.conf(5) for details. 18 + 19 + [Resolve] 20 + # Some examples of DNS servers which may be used for DNS= and FallbackDNS=: 21 + # Cloudflare: 1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 2606:4700:4700::1111#cloudflare-dns.com 2606:4700:4700::1001#cloudflare-dns.com 22 + # Google: 8.8.8.8#dns.google 8.8.4.4#dns.google 2001:4860:4860::8888#dns.google 2001:4860:4860::8844#dns.google 23 + # Quad9: 9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net 24 + #DNS= 25 + #FallbackDNS= 26 + DNS=1.1.1.1 8.8.8.8 8.8.4.4 172.16.0.1 27 + FallbackDNS=100.100.100.100 28 + #Domains= 29 + #DNSSEC=no 30 + #DNSOverTLS=no 31 + #MulticastDNS=no 32 + #LLMNR=no 33 + #Cache=no-negative 34 + #CacheFromLocalhost=no 35 + DNSStubListener=no 36 + #DNSStubListenerExtra= 37 + #ReadEtcHosts=yes 38 + #ResolveUnicastSingleLabel=no 39 + #StaleRetentionSec=0
+60 -1
crates/firecracker-prepare/src/downloader.rs
··· 174 174 pub fn download_nixos_rootfs(_arch: &str) -> Result<()> { 175 175 let app_dir = crate::config::get_config_dir()?; 176 176 let output = format!("{}/nixos-rootfs.squashfs", app_dir); 177 - download_file("https://public.rocksky.app/nixos-rootfs.squashfs", &output)?; 177 + download_file("https://public.rocksky.app/nixos-rootfs.img", &output)?; 178 + Ok(()) 179 + } 180 + 181 + pub fn download_fedora_rootfs(_arch: &str) -> Result<()> { 182 + let app_dir = crate::config::get_config_dir()?; 183 + let output = format!("{}/fedora-rootfs.img", app_dir); 184 + download_file("https://public.rocksky.app/fedora-rootfs.img", &output)?; 185 + Ok(()) 186 + } 187 + 188 + pub fn download_gentoo_rootfs(_arch: &str) -> Result<()> { 189 + let app_dir = crate::config::get_config_dir()?; 190 + let output = format!("{}/gentoo-rootfs.img", app_dir); 191 + download_file("https://public.rocksky.app/gentoo-rootfs.img", &output)?; 192 + Ok(()) 193 + } 194 + 195 + pub fn download_slackware_rootfs(_arch: &str) -> Result<()> { 196 + let app_dir = crate::config::get_config_dir()?; 197 + let output = format!("{}/slackware-rootfs.img", app_dir); 198 + download_file("https://public.rocksky.app/slackware-rootfs.img", &output)?; 199 + Ok(()) 200 + } 201 + 202 + pub fn download_opensuse_rootfs(_arch: &str) -> Result<()> { 203 + let app_dir = crate::config::get_config_dir()?; 204 + let output = format!("{}/opensuse-rootfs.img", app_dir); 205 + download_file("https://public.rocksky.app/opensuse-rootfs.img", &output)?; 206 + Ok(()) 207 + } 208 + 209 + pub fn download_opensuse_tumbleweed_rootfs(_arch: &str) -> Result<()> { 210 + let app_dir = crate::config::get_config_dir()?; 211 + let output = format!("{}/opensuse-tumbleweed-rootfs.img", app_dir); 212 + download_file( 213 + "https://public.rocksky.app/opensuse-tumbleweed-rootfs.img", 214 + &output, 215 + )?; 216 + Ok(()) 217 + } 218 + 219 + pub fn download_almalinux_rootfs(_arch: &str) -> Result<()> { 220 + let app_dir = crate::config::get_config_dir()?; 221 + let output = format!("{}/almalinux-rootfs.img", app_dir); 222 + download_file("https://public.rocksky.app/almalinux-rootfs.img", &output)?; 223 + Ok(()) 224 + } 225 + 226 + pub fn download_rockylinux_rootfs(_arch: &str) -> Result<()> { 227 + let app_dir = crate::config::get_config_dir()?; 228 + let output = format!("{}/rockylinux-rootfs.img", app_dir); 229 + download_file("https://public.rocksky.app/rockylinux-rootfs.img", &output)?; 230 + Ok(()) 231 + } 232 + 233 + pub fn download_archlinux_rootfs(_arch: &str) -> Result<()> { 234 + let app_dir = crate::config::get_config_dir()?; 235 + let output = format!("{}/archlinux-rootfs.img", app_dir); 236 + download_file("https://public.rocksky.app/archlinux-rootfs.img", &output)?; 178 237 Ok(()) 179 238 }
+372 -29
crates/firecracker-prepare/src/lib.rs
··· 20 20 Alpine, 21 21 Ubuntu, 22 22 NixOS, 23 + Fedora, 24 + Gentoo, 25 + Slackware, 26 + Opensuse, 27 + OpensuseTumbleweed, 28 + Almalinux, 29 + RockyLinux, 30 + Archlinux, 31 + } 32 + 33 + impl ToString for Distro { 34 + fn to_string(&self) -> String { 35 + match self { 36 + Distro::Debian => "debian".to_string(), 37 + Distro::Alpine => "alpine".to_string(), 38 + Distro::Ubuntu => "ubuntu".to_string(), 39 + Distro::NixOS => "nixos".to_string(), 40 + Distro::Fedora => "fedora".to_string(), 41 + Distro::Gentoo => "gentoo".to_string(), 42 + Distro::Slackware => "slackware".to_string(), 43 + Distro::Opensuse => "opensuse".to_string(), 44 + Distro::OpensuseTumbleweed => "opensuse-tumbleweed".to_string(), 45 + Distro::Almalinux => "almalinux".to_string(), 46 + Distro::RockyLinux => "rockylinux".to_string(), 47 + Distro::Archlinux => "archlinux".to_string(), 48 + } 49 + } 23 50 } 24 51 25 52 pub trait RootfsPreparer { ··· 31 58 pub struct AlpinePreparer; 32 59 pub struct UbuntuPreparer; 33 60 pub struct NixOSPreparer; 61 + pub struct FedoraPreparer; 62 + pub struct GentooPreparer; 63 + pub struct SlackwarePreparer; 64 + pub struct OpensusePreparer; 65 + pub struct OpensuseTumbleweedPreparer; 66 + pub struct AlmalinuxPreparer; 67 + pub struct RockyLinuxPreparer; 68 + pub struct ArchlinuxPreparer; 34 69 35 70 impl RootfsPreparer for DebianPreparer { 36 71 fn name(&self) -> &'static str { ··· 66 101 )?; 67 102 } 68 103 104 + run_command( 105 + "chroot", 106 + &[ 107 + &debootstrap_dir, 108 + "sh", 109 + "-c", 110 + "apt-get install -y systemd-resolved", 111 + ], 112 + true, 113 + )?; 114 + run_command( 115 + "chroot", 116 + &[ 117 + &debootstrap_dir, 118 + "systemctl", 119 + "enable", 120 + "systemd-networkd", 121 + "systemd-resolved", 122 + ], 123 + true, 124 + )?; 125 + 126 + const RESOLVED_CONF: &str = include_str!("./config/resolved.conf"); 127 + run_command( 128 + "chroot", 129 + &[ 130 + &debootstrap_dir, 131 + "sh", 132 + "-c", 133 + &format!("echo '{}' > /etc/systemd/resolved.conf", RESOLVED_CONF), 134 + ], 135 + true, 136 + )?; 137 + 69 138 let ssh_key_name = "id_rsa"; 70 139 run_command( 71 140 "mkdir", ··· 101 170 )?; 102 171 } 103 172 104 - let ext4_file = format!("{}/debian-{}.ext4", app_dir, arch); 105 - if !std::path::Path::new(&ext4_file).exists() { 106 - rootfs::create_ext4_filesystem(&debootstrap_dir, &ext4_file, 600)?; 107 - } 173 + let img_file = format!("{}/debian-rootfs.img", app_dir); 174 + rootfs::create_overlay_dirs(&debootstrap_dir)?; 175 + rootfs::add_overlay_init(&debootstrap_dir)?; 176 + rootfs::create_squashfs(&debootstrap_dir, &img_file)?; 108 177 109 178 let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 110 179 111 - Ok((kernel_file, ext4_file, ssh_key_file)) 180 + Ok((kernel_file, img_file, ssh_key_file)) 112 181 } 113 182 } 114 183 ··· 227 296 let ssh_key_name = "id_rsa"; 228 297 ssh::generate_and_copy_ssh_key(&ssh_key_name, &minirootfs)?; 229 298 230 - let ext4_file = format!("{}/alpine-{}.ext4", app_dir, arch); 231 - if !std::path::Path::new(&ext4_file).exists() { 232 - rootfs::create_ext4_filesystem(&minirootfs, &ext4_file, 500)?; 233 - } 299 + let img_file = format!("{}/alpine-rootfs.img", app_dir); 300 + rootfs::create_squashfs(&minirootfs, &img_file)?; 234 301 235 302 let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 236 303 237 - Ok((kernel_file, ext4_file, ssh_key_file)) 304 + Ok((kernel_file, img_file, ssh_key_file)) 238 305 } 239 306 } 240 307 ··· 249 316 self.name(), 250 317 arch.bright_green() 251 318 ); 252 - let (kernel_file, ubuntu_file, ubuntu_version) = downloader::download_files(arch)?; 319 + let (kernel_file, ubuntu_file, _ubuntu_version) = downloader::download_files(arch)?; 253 320 254 321 let squashfs_root_dir = format!("{}/squashfs_root", app_dir); 255 322 rootfs::extract_squashfs(&ubuntu_file, &squashfs_root_dir)?; 256 323 324 + run_command( 325 + "chroot", 326 + &[ 327 + &squashfs_root_dir, 328 + "systemctl", 329 + "enable", 330 + "systemd-networkd", 331 + ], 332 + true, 333 + )?; 334 + 335 + const RESOLVED_CONF: &str = include_str!("./config/resolved.conf"); 336 + run_command( 337 + "chroot", 338 + &[ 339 + &squashfs_root_dir, 340 + "sh", 341 + "-c", 342 + &format!("echo '{}' > /etc/systemd/resolved.conf", RESOLVED_CONF), 343 + ], 344 + true, 345 + )?; 346 + 257 347 let ssh_key_name = "id_rsa"; 258 348 ssh::generate_and_copy_ssh_key(&ssh_key_name, &squashfs_root_dir)?; 259 349 260 - let ext4_file = format!("{}/ubuntu-{}.ext4", app_dir, ubuntu_version); 261 - if !std::path::Path::new(&ext4_file).exists() { 262 - rootfs::create_ext4_filesystem(&squashfs_root_dir, &ext4_file, 400)?; 263 - } else { 264 - println!( 265 - "[!] {} already exists, skipping ext4 creation.", 266 - ext4_file.bright_yellow() 267 - ); 268 - } 350 + let img_file = format!("{}/ubuntu-rootfs.img", app_dir); 351 + rootfs::create_overlay_dirs(&squashfs_root_dir)?; 352 + rootfs::add_overlay_init(&squashfs_root_dir)?; 353 + rootfs::create_squashfs(&squashfs_root_dir, &img_file)?; 269 354 270 355 let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 271 356 272 - Ok((kernel_file, ext4_file, ssh_key_file)) 357 + Ok((kernel_file, img_file, ssh_key_file)) 273 358 } 274 359 } 275 360 ··· 285 370 arch.bright_green() 286 371 ); 287 372 let kernel_file = downloader::download_kernel(arch)?; 288 - let nixos_rootfs = format!("{}/nixosrootfs", app_dir); 373 + let nixos_rootfs = format!("{}/nixos-rootfs", app_dir); 289 374 let squashfs_file = format!("{}/nixos-rootfs.squashfs", app_dir); 290 375 291 376 downloader::download_nixos_rootfs(arch)?; ··· 294 379 let ssh_key_name = "id_rsa"; 295 380 ssh::generate_and_copy_ssh_key_nixos(&ssh_key_name, &nixos_rootfs)?; 296 381 297 - let ext4_file = format!("{}/nixos-rootfs.ext4", app_dir); 298 - if !std::path::Path::new(&ext4_file).exists() { 299 - rootfs::create_ext4_filesystem(&nixos_rootfs, &ext4_file, 5120)?; 300 - } 382 + let img_file = format!("{}/nixos-rootfs.img", app_dir); 383 + rootfs::create_squashfs(&nixos_rootfs, &img_file)?; 301 384 302 385 let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 303 386 ··· 307 390 nixos_rootfs.bright_green() 308 391 ); 309 392 310 - Ok((kernel_file, ext4_file, ssh_key_file)) 393 + Ok((kernel_file, img_file, ssh_key_file)) 394 + } 395 + } 396 + 397 + impl RootfsPreparer for FedoraPreparer { 398 + fn name(&self) -> &'static str { 399 + "Fedora" 400 + } 401 + 402 + fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 403 + println!( 404 + "[+] Preparing {} rootfs for {}...", 405 + self.name(), 406 + arch.bright_green() 407 + ); 408 + 409 + let kernel_file = downloader::download_kernel(arch)?; 410 + let fedora_rootfs = format!("{}/fedora-rootfs", app_dir); 411 + let squashfs_file = format!("{}/fedora-rootfs.squashfs", app_dir); 412 + 413 + downloader::download_fedora_rootfs(arch)?; 414 + rootfs::extract_squashfs(&squashfs_file, &fedora_rootfs)?; 415 + 416 + let ssh_key_name = "id_rsa"; 417 + ssh::generate_and_copy_ssh_key(&ssh_key_name, &fedora_rootfs)?; 418 + 419 + let img_file = format!("{}/fedora-rootfs.img", app_dir); 420 + rootfs::create_squashfs(&fedora_rootfs, &img_file)?; 421 + 422 + let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 423 + 424 + println!( 425 + "[+] {} rootfs prepared at: {}", 426 + self.name(), 427 + fedora_rootfs.bright_green() 428 + ); 429 + 430 + Ok((kernel_file, img_file, ssh_key_file)) 431 + } 432 + } 433 + 434 + impl RootfsPreparer for GentooPreparer { 435 + fn name(&self) -> &'static str { 436 + "Gentoo" 437 + } 438 + 439 + fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 440 + println!( 441 + "[+] Preparing {} rootfs for {}...", 442 + self.name(), 443 + arch.bright_green() 444 + ); 445 + 446 + let kernel_file = downloader::download_kernel(arch)?; 447 + let gentoo_rootfs = format!("{}/gentoo-rootfs", app_dir); 448 + let squashfs_file = format!("{}/gentoo-rootfs.squashfs", app_dir); 449 + 450 + downloader::download_gentoo_rootfs(arch)?; 451 + rootfs::extract_squashfs(&squashfs_file, &gentoo_rootfs)?; 452 + 453 + let ssh_key_name = "id_rsa"; 454 + ssh::generate_and_copy_ssh_key(&ssh_key_name, &gentoo_rootfs)?; 455 + 456 + let img_file = format!("{}/gentoo-rootfs.img", app_dir); 457 + rootfs::create_squashfs(&gentoo_rootfs, &img_file)?; 458 + 459 + let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 460 + 461 + Ok((kernel_file, img_file, ssh_key_file)) 462 + } 463 + } 464 + 465 + impl RootfsPreparer for SlackwarePreparer { 466 + fn name(&self) -> &'static str { 467 + "Slackware" 468 + } 469 + 470 + fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 471 + println!( 472 + "[+] Preparing {} rootfs for {}...", 473 + self.name(), 474 + arch.bright_green() 475 + ); 476 + 477 + let kernel_file = downloader::download_kernel(arch)?; 478 + let slackware_rootfs = format!("{}/slackware-rootfs", app_dir); 479 + let squashfs_file = format!("{}/slackware-rootfs.squashfs", app_dir); 480 + 481 + downloader::download_slackware_rootfs(arch)?; 482 + rootfs::extract_squashfs(&squashfs_file, &slackware_rootfs)?; 483 + 484 + let ssh_key_name = "id_rsa"; 485 + ssh::generate_and_copy_ssh_key(&ssh_key_name, &slackware_rootfs)?; 486 + 487 + let img_file = format!("{}/slackware-rootfs.img", app_dir); 488 + rootfs::create_squashfs(&slackware_rootfs, &img_file)?; 489 + 490 + let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 491 + 492 + Ok((kernel_file, img_file, ssh_key_file)) 493 + } 494 + } 495 + 496 + impl RootfsPreparer for OpensusePreparer { 497 + fn name(&self) -> &'static str { 498 + "OpenSUSE (Leap)" 499 + } 500 + 501 + fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 502 + println!( 503 + "[+] Preparing {} rootfs for {}...", 504 + self.name(), 505 + arch.bright_green() 506 + ); 507 + 508 + let kernel_file = downloader::download_kernel(arch)?; 509 + let opensuse_rootfs = format!("{}/opensuse-rootfs", app_dir); 510 + let squashfs_file = format!("{}/opensuse-rootfs.squashfs", app_dir); 511 + 512 + downloader::download_opensuse_rootfs(arch)?; 513 + rootfs::extract_squashfs(&squashfs_file, &opensuse_rootfs)?; 514 + 515 + let ssh_key_name = "id_rsa"; 516 + ssh::generate_and_copy_ssh_key(&ssh_key_name, &opensuse_rootfs)?; 517 + 518 + let img_file = format!("{}/opensuse-rootfs.img", app_dir); 519 + rootfs::create_squashfs(&opensuse_rootfs, &img_file)?; 520 + 521 + let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 522 + Ok((kernel_file, img_file, ssh_key_file)) 523 + } 524 + } 525 + 526 + impl RootfsPreparer for AlmalinuxPreparer { 527 + fn name(&self) -> &'static str { 528 + "AlmaLinux" 529 + } 530 + 531 + fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 532 + println!( 533 + "[+] Preparing {} rootfs for {}...", 534 + self.name(), 535 + arch.bright_green() 536 + ); 537 + 538 + let kernel_file = downloader::download_kernel(arch)?; 539 + let almalinux_rootfs = format!("{}/almalinux-rootfs", app_dir); 540 + let squashfs_file = format!("{}/almalinux-rootfs.squashfs", app_dir); 541 + 542 + downloader::download_almalinux_rootfs(arch)?; 543 + rootfs::extract_squashfs(&squashfs_file, &almalinux_rootfs)?; 544 + 545 + let ssh_key_name = "id_rsa"; 546 + ssh::generate_and_copy_ssh_key(&ssh_key_name, &almalinux_rootfs)?; 547 + 548 + let img_file = format!("{}/almalinux-rootfs.img", app_dir); 549 + rootfs::create_squashfs(&almalinux_rootfs, &img_file)?; 550 + 551 + let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 552 + 553 + Ok((kernel_file, img_file, ssh_key_file)) 554 + } 555 + } 556 + 557 + impl RootfsPreparer for RockyLinuxPreparer { 558 + fn name(&self) -> &'static str { 559 + "RockyLinux" 560 + } 561 + 562 + fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 563 + println!( 564 + "[+] Preparing {} rootfs for {}...", 565 + self.name(), 566 + arch.bright_green() 567 + ); 568 + 569 + let kernel_file = downloader::download_kernel(arch)?; 570 + let rockylinux_rootfs = format!("{}/rockylinux-rootfs", app_dir); 571 + let squashfs_file = format!("{}/rockylinux-rootfs.squashfs", app_dir); 572 + 573 + downloader::download_rockylinux_rootfs(arch)?; 574 + rootfs::extract_squashfs(&squashfs_file, &rockylinux_rootfs)?; 575 + 576 + let ssh_key_name = "id_rsa"; 577 + ssh::generate_and_copy_ssh_key(&ssh_key_name, &rockylinux_rootfs)?; 578 + 579 + let img_file = format!("{}/rockylinux-rootfs.img", app_dir); 580 + rootfs::create_squashfs(&rockylinux_rootfs, &img_file)?; 581 + 582 + let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 583 + 584 + Ok((kernel_file, img_file, ssh_key_file)) 585 + } 586 + } 587 + 588 + impl RootfsPreparer for ArchlinuxPreparer { 589 + fn name(&self) -> &'static str { 590 + "ArchLinux" 591 + } 592 + 593 + fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 594 + println!( 595 + "[+] Preparing {} rootfs for {}...", 596 + self.name(), 597 + arch.bright_green() 598 + ); 599 + 600 + let kernel_file = downloader::download_kernel(arch)?; 601 + let archlinux_rootfs = format!("{}/archlinux-rootfs", app_dir); 602 + let squashfs_file = format!("{}/archlinux-rootfs.squashfs", app_dir); 603 + 604 + downloader::download_archlinux_rootfs(arch)?; 605 + rootfs::extract_squashfs(&squashfs_file, &archlinux_rootfs)?; 606 + 607 + let ssh_key_name = "id_rsa"; 608 + ssh::generate_and_copy_ssh_key(&ssh_key_name, &archlinux_rootfs)?; 609 + 610 + let img_file = format!("{}/archlinux-rootfs.img", app_dir); 611 + rootfs::create_squashfs(&archlinux_rootfs, &img_file)?; 612 + 613 + let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 614 + 615 + Ok((kernel_file, img_file, ssh_key_file)) 616 + } 617 + } 618 + 619 + impl RootfsPreparer for OpensuseTumbleweedPreparer { 620 + fn name(&self) -> &'static str { 621 + "OpenSUSE (Tumbleweed)" 622 + } 623 + 624 + fn prepare(&self, arch: &str, app_dir: &str) -> Result<(String, String, String)> { 625 + println!( 626 + "[+] Preparing {} rootfs for {}...", 627 + self.name(), 628 + arch.bright_green() 629 + ); 630 + 631 + let kernel_file = downloader::download_kernel(arch)?; 632 + let opensuse_rootfs = format!("{}/opensuse-tumbleweed-rootfs", app_dir); 633 + let squashfs_file = format!("{}/opensuse-tumbleweed-rootfs.squashfs", app_dir); 634 + 635 + downloader::download_opensuse_tumbleweed_rootfs(arch)?; 636 + rootfs::extract_squashfs(&squashfs_file, &opensuse_rootfs)?; 637 + 638 + let ssh_key_name = "id_rsa"; 639 + ssh::generate_and_copy_ssh_key(&ssh_key_name, &opensuse_rootfs)?; 640 + 641 + let img_file = format!("{}/opensuse-tumbleweed-rootfs.img", app_dir); 642 + rootfs::create_squashfs(&opensuse_rootfs, &img_file)?; 643 + 644 + let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); 645 + Ok((kernel_file, img_file, ssh_key_file)) 311 646 } 312 647 } 313 648 ··· 322 657 Distro::Alpine => Box::new(AlpinePreparer), 323 658 Distro::Ubuntu => Box::new(UbuntuPreparer), 324 659 Distro::NixOS => Box::new(NixOSPreparer), 660 + Distro::Fedora => Box::new(FedoraPreparer), 661 + Distro::Gentoo => Box::new(GentooPreparer), 662 + Distro::Slackware => Box::new(SlackwarePreparer), 663 + Distro::Opensuse => Box::new(OpensusePreparer), 664 + Distro::OpensuseTumbleweed => Box::new(OpensuseTumbleweedPreparer), 665 + Distro::Almalinux => Box::new(AlmalinuxPreparer), 666 + Distro::RockyLinux => Box::new(RockyLinuxPreparer), 667 + Distro::Archlinux => Box::new(ArchlinuxPreparer), 325 668 }; 326 669 327 - let (kernel_file, ext4_file, ssh_key_file) = preparer.prepare(&arch, &app_dir)?; 670 + let (kernel_file, img_file, ssh_key_file) = preparer.prepare(&arch, &app_dir)?; 328 671 329 672 println!("[✓] Kernel: {}", kernel_file.bright_green()); 330 - println!("[✓] Rootfs: {}", ext4_file.bright_green()); 673 + println!("[✓] Rootfs: {}", img_file.bright_green()); 331 674 println!("[✓] SSH Key: {}", ssh_key_file.bright_green()); 332 675 333 676 Ok(())
+46
crates/firecracker-prepare/src/rootfs.rs
··· 26 26 run_command("mkfs.ext4", &["-d", squashfs_dir, "-F", output_file], true)?; 27 27 Ok(()) 28 28 } 29 + 30 + pub fn create_squashfs(squashfs_dir: &str, output_file: &str) -> Result<()> { 31 + if std::path::Path::new(output_file).exists() { 32 + println!( 33 + "[!] Warning: {} already exists, skipping. Delete it and try again if you want to recreate it.", 34 + output_file 35 + ); 36 + return Ok(()); 37 + } 38 + run_command("mksquashfs", &[squashfs_dir, output_file], true)?; 39 + Ok(()) 40 + } 41 + 42 + pub fn create_overlay_dirs(rootfs_dir: &str) -> Result<()> { 43 + run_command( 44 + "mkdir", 45 + &[ 46 + "-p", 47 + &format!("{}/overlay/work", rootfs_dir), 48 + &format!("{}/overlay/root", rootfs_dir), 49 + &format!("{}/rom", rootfs_dir), 50 + ], 51 + true, 52 + )?; 53 + Ok(()) 54 + } 55 + 56 + pub fn add_overlay_init(rootfs_dir: &str) -> Result<()> { 57 + const OVERLAY_INIT: &str = include_str!("./scripts/overlay-init.sh"); 58 + // add overlay-init script to rootfs/sbin/overlay-init 59 + println!("Adding overlay-init script..."); 60 + std::fs::write("/tmp/overlay-init", OVERLAY_INIT)?; 61 + run_command("mkdir", &["-p", &format!("{}/sbin", rootfs_dir)], true)?; 62 + run_command( 63 + "mv", 64 + &["/tmp/overlay-init", &format!("{}/sbin", rootfs_dir)], 65 + true, 66 + )?; 67 + println!("Making overlay-init executable..."); 68 + run_command( 69 + "chmod", 70 + &["+x", &format!("{}/sbin/overlay-init", rootfs_dir)], 71 + true, 72 + )?; 73 + Ok(()) 74 + }
+8
crates/firecracker-up/src/cmd/start.rs
··· 21 21 alpine: Some(vm.distro == "alpine"), 22 22 ubuntu: Some(vm.distro == "ubuntu"), 23 23 nixos: Some(vm.distro == "nixos"), 24 + fedora: Some(vm.distro == "fedora"), 25 + gentoo: Some(vm.distro == "gentoo"), 26 + slackware: Some(vm.distro == "slackware"), 27 + opensuse: Some(vm.distro == "opensuse"), 28 + opensuse_tumbleweed: Some(vm.distro == "opensuse-tumbleweed"), 29 + almalinux: Some(vm.distro == "almalinux"), 30 + rockylinux: Some(vm.distro == "rockylinux"), 31 + archlinux: Some(vm.distro == "archlinux"), 24 32 vcpu: vm.vcpu, 25 33 memory: vm.memory, 26 34 vmlinux: vm.vmlinux,
+67
crates/firecracker-up/src/main.rs
··· 60 60 .arg(arg!(--alpine "Prepare Alpine MicroVM").default_value("false")) 61 61 .arg(arg!(--nixos "Prepare NixOS MicroVM").default_value("false")) 62 62 .arg(arg!(--ubuntu "Prepare Ubuntu MicroVM").default_value("true")) 63 + .arg(arg!(--fedora "Prepare Fedora MicroVM").default_value("false")) 64 + .arg(arg!(--gentoo "Prepare Gentoo MicroVM").default_value("false")) 65 + .arg(arg!(--slackware "Prepare Slackware MicroVM").default_value("false")) 66 + .arg(arg!(--opensuse "Prepare OpenSUSE MicroVM").default_value("false")) 67 + .arg( 68 + arg!(--opensuse-tumbleweed "Prepare OpenSUSE Tumbleweed MicroVM") 69 + .default_value("false"), 70 + ) 71 + .arg(arg!(--almalinux "Prepare AlmaLinux MicroVM").default_value("false")) 72 + .arg(arg!(--rockylinux "Prepare RockyLinux MicroVM").default_value("false")) 73 + .arg(arg!(--archlinux "Prepare ArchLinux MicroVM").default_value("false")) 63 74 .arg(arg!(--vcpu <n> "Number of vCPUs")) 64 75 .arg(arg!(--memory <m> "Memory size in MiB")) 65 76 .arg(arg!(--vmlinux <path> "Path to the kernel image")) ··· 116 127 .arg(arg!(--alpine "Prepare Alpine MicroVM").default_value("false")) 117 128 .arg(arg!(--nixos "Prepare NixOS MicroVM").default_value("false")) 118 129 .arg(arg!(--ubuntu "Prepare Ubuntu MicroVM").default_value("true")) 130 + .arg(arg!(--fedora "Prepare Fedora MicroVM").default_value("false")) 131 + .arg(arg!(--gentoo "Prepare Gentoo MicroVM").default_value("false")) 132 + .arg(arg!(--slackware "Prepare Slackware MicroVM").default_value("false")) 133 + .arg(arg!(--opensuse "Prepare OpenSUSE MicroVM").default_value("false")) 134 + .arg( 135 + arg!(--opensuse-tumbleweed "Prepare OpenSUSE Tumbleweed MicroVM") 136 + .default_value("false"), 137 + ) 138 + .arg(arg!(--almalinux "Prepare AlmaLinux MicroVM").default_value("false")) 139 + .arg(arg!(--rockylinux "Prepare RockyLinux MicroVM").default_value("false")) 140 + .arg(arg!(--archlinux "Prepare ArchLinux MicroVM").default_value("false")) 119 141 .arg(arg!(--vcpu <n> "Number of vCPUs")) 120 142 .arg(arg!(--memory <m> "Memory size in MiB")) 121 143 .arg(arg!(--vmlinux <path> "Path to the kernel image")) ··· 197 219 alpine: args.get_one::<bool>("alpine").copied(), 198 220 ubuntu: args.get_one::<bool>("ubuntu").copied(), 199 221 nixos: args.get_one::<bool>("nixos").copied(), 222 + fedora: args.get_one::<bool>("fedora").copied(), 223 + gentoo: args.get_one::<bool>("gentoo").copied(), 224 + slackware: args.get_one::<bool>("slackware").copied(), 225 + opensuse: args.get_one::<bool>("opensuse").copied(), 226 + opensuse_tumbleweed: args.get_one::<bool>("opensuse-tumbleweed").copied(), 227 + almalinux: args.get_one::<bool>("almalinux").copied(), 228 + rockylinux: args.get_one::<bool>("rockylinux").copied(), 229 + archlinux: args.get_one::<bool>("archlinux").copied(), 200 230 vcpu, 201 231 memory, 202 232 vmlinux, ··· 236 266 let alpine = matches.get_one::<bool>("alpine").copied().unwrap_or(false); 237 267 let nixos = matches.get_one::<bool>("nixos").copied().unwrap_or(false); 238 268 let ubuntu = matches.get_one::<bool>("ubuntu").copied().unwrap_or(false); 269 + let fedora = matches.get_one::<bool>("fedora").copied().unwrap_or(false); 270 + let gentoo = matches.get_one::<bool>("gentoo").copied().unwrap_or(false); 271 + let slackware = matches 272 + .get_one::<bool>("slackware") 273 + .copied() 274 + .unwrap_or(false); 275 + let opensuse = matches 276 + .get_one::<bool>("opensuse") 277 + .copied() 278 + .unwrap_or(false); 279 + let opensuse_tumbleweed = matches 280 + .get_one::<bool>("opensuse-tumbleweed") 281 + .copied() 282 + .unwrap_or(false); 283 + let almalinux = matches 284 + .get_one::<bool>("almalinux") 285 + .copied() 286 + .unwrap_or(false); 287 + let rockylinux = matches 288 + .get_one::<bool>("rockylinux") 289 + .copied() 290 + .unwrap_or(false); 291 + let archlinux = matches 292 + .get_one::<bool>("archlinux") 293 + .copied() 294 + .unwrap_or(false); 295 + 239 296 let vcpu = matches 240 297 .get_one::<String>("vcpu") 241 298 .map(|s| s.parse::<u16>().unwrap()) 242 299 .unwrap_or(num_cpus::get() as u16); 300 + 243 301 let memory = matches 244 302 .get_one::<String>("memory") 245 303 .map(|s| s.parse::<u16>().unwrap()) 246 304 .unwrap_or(if nixos { 2048 } else { 512 }); 305 + 247 306 let vmlinux = matches.get_one::<String>("vmlinux").cloned(); 248 307 let rootfs = matches.get_one::<String>("rootfs").cloned(); 249 308 let bootargs = matches.get_one::<String>("boot-args").cloned(); ··· 263 322 alpine: Some(alpine), 264 323 ubuntu: Some(ubuntu), 265 324 nixos: Some(nixos), 325 + fedora: Some(fedora), 326 + gentoo: Some(gentoo), 327 + slackware: Some(slackware), 328 + opensuse: Some(opensuse), 329 + opensuse_tumbleweed: Some(opensuse_tumbleweed), 330 + almalinux: Some(almalinux), 331 + rockylinux: Some(rockylinux), 332 + archlinux: Some(archlinux), 266 333 vcpu, 267 334 memory, 268 335 vmlinux,
+6 -17
crates/firecracker-vm/src/firecracker.rs
··· 1 1 use crate::types::VmOptions; 2 2 use anyhow::Result; 3 - use firecracker_prepare::Distro; 4 3 use serde_json::json; 5 4 use std::thread::sleep; 6 5 use std::time::Duration; 7 6 8 7 use crate::command::run_command; 9 - 10 - const NIXOS_BOOT_ARGS: &str = "init=/nix/store/w1yqjd8sswh8zj9sz2v76dpw3llzkg5k-nixos-system-nixos-firecracker-25.05.802216.55d1f923c480/init root=/dev/vda ro console=ttyS0 reboot=k panic=1 ip=dhcp"; 11 8 12 9 pub fn configure( 13 10 logfile: &str, ··· 15 12 rootfs: &str, 16 13 arch: &str, 17 14 options: &VmOptions, 18 - distro: Distro, 19 15 ) -> Result<()> { 20 16 configure_logger(logfile, options)?; 21 - setup_boot_source(kernel, arch, distro == Distro::NixOS, &options)?; 17 + setup_boot_source(kernel, arch, &options)?; 22 18 setup_rootfs(rootfs, options)?; 23 19 setup_network_interface(options)?; 24 20 setup_vcpu_and_memory(options.vcpu, options.memory, &options.api_socket)?; ··· 58 54 Ok(()) 59 55 } 60 56 61 - fn setup_boot_source( 62 - kernel: &str, 63 - arch: &str, 64 - is_nixos: bool, 65 - options: &VmOptions, 66 - ) -> Result<String> { 57 + fn setup_boot_source(kernel: &str, arch: &str, options: &VmOptions) -> Result<String> { 67 58 println!("[+] Setting boot source..."); 68 - let mut boot_args = "console=ttyS0 reboot=k panic=1 pci=off ip=dhcp".to_string(); 59 + let mut boot_args = 60 + "console=ttyS0 reboot=k panic=1 pci=off ip=dhcp init=/sbin/overlay-init overlay_root=ram" 61 + .to_string(); 69 62 if arch == "aarch64" { 70 63 boot_args = format!("keep_bootcon {}", boot_args); 71 - } 72 - 73 - if is_nixos { 74 - boot_args = NIXOS_BOOT_ARGS.into(); 75 64 } 76 65 77 66 if let Some(args) = &options.bootargs { ··· 109 98 "drive_id": "rootfs", 110 99 "path_on_host": rootfs, 111 100 "is_root_device": true, 112 - "is_read_only": false 101 + "is_read_only": true 113 102 }); 114 103 run_command( 115 104 "curl",
+19 -26
crates/firecracker-vm/src/lib.rs
··· 58 58 .display() 59 59 .to_string(); 60 60 61 - let ext4_file = match distro { 62 - Distro::Debian => format!("{}/debian*.ext4", app_dir), 63 - Distro::Alpine => format!("{}/alpine*.ext4", app_dir), 64 - Distro::NixOS => format!("{}/nixos*.ext4", app_dir), 65 - Distro::Ubuntu => format!("{}/ubuntu*.ext4", app_dir), 61 + // readonly rootfs (squashfs) 62 + let img_file = match distro { 63 + Distro::Debian => format!("{}/debian-rootfs.img", app_dir), 64 + Distro::Alpine => format!("{}/alpine-rootfs.img", app_dir), 65 + Distro::NixOS => format!("{}/nixos-rootfs.img", app_dir), 66 + Distro::Ubuntu => format!("{}/ubuntu-rootfs.img", app_dir), 67 + Distro::Fedora => format!("{}/fedora-rootfs.img", app_dir), 68 + Distro::Gentoo => format!("{}/gentoo-rootfs.img", app_dir), 69 + Distro::Slackware => format!("{}/slackware-rootfs.img", app_dir), 70 + Distro::Opensuse => format!("{}/opensuse-rootfs.img", app_dir), 71 + Distro::OpensuseTumbleweed => format!("{}/opensuse-tumbleweed-rootfs.img", app_dir), 72 + Distro::Almalinux => format!("{}/almalinux-rootfs.img", app_dir), 73 + Distro::RockyLinux => format!("{}/rockylinux-rootfs.img", app_dir), 74 + Distro::Archlinux => format!("{}/archlinux-rootfs.img", app_dir), 66 75 }; 67 76 68 - let rootfs = glob::glob(&ext4_file) 69 - .with_context(|| "Failed to glob rootfs files")? 70 - .last() 71 - .ok_or_else(|| anyhow!("No rootfs file found"))? 72 - .with_context(|| "Failed to get rootfs path")?; 73 - let rootfs = fs::canonicalize(&rootfs) 74 - .with_context(|| { 75 - format!( 76 - "Failed to resolve absolute path for rootfs: {}", 77 - rootfs.display() 78 - ) 79 - })? 77 + let rootfs = fs::canonicalize(&img_file) 78 + .with_context(|| format!("Failed to resolve absolute path for rootfs: {}", img_file))? 80 79 .display() 81 80 .to_string(); 82 81 ··· 101 100 coredns::setup_coredns(options)?; 102 101 nextdhcp::setup_nextdhcp(options)?; 103 102 104 - firecracker::configure(&logfile, &kernel, &rootfs, &arch, &options, distro)?; 103 + firecracker::configure(&logfile, &kernel, &rootfs, &arch, &options)?; 105 104 106 105 if distro != Distro::NixOS { 107 106 let guest_ip = format!("{}.firecracker", name); 108 107 guest::configure_guest_network(&key_name, &guest_ip)?; 109 108 } 110 109 let pool = firecracker_state::create_connection_pool().await?; 111 - let distro = match distro { 112 - Distro::Debian => "debian".into(), 113 - Distro::Alpine => "alpine".into(), 114 - Distro::NixOS => "nixos".into(), 115 - Distro::Ubuntu => "ubuntu".into(), 116 - }; 117 110 118 111 let ip_file = format!("/tmp/firecracker-{}.ip", name); 119 112 ··· 155 148 mac_address: options.mac_address.clone(), 156 149 name: name.clone(), 157 150 pid: Some(pid), 158 - distro, 151 + distro: distro.to_string(), 159 152 ip_address: Some(ip_addr.clone()), 160 153 status: "RUNNING".into(), 161 154 project_dir, ··· 179 172 mac_address: options.mac_address.clone(), 180 173 name: name.clone(), 181 174 pid: Some(pid), 182 - distro, 175 + distro: distro.to_string(), 183 176 ip_address: Some(ip_addr.clone()), 184 177 status: "RUNNING".into(), 185 178 project_dir,
+1 -1
crates/firecracker-vm/src/nextdhcp.rs
··· 17 17 18 18 let nextdhcp_config: &str = r#" 19 19 172.16.0.1/24 { 20 - lease 5m 20 + lease 30m 21 21 22 22 range 172.16.0.2 172.16.0.150 23 23
+32
crates/firecracker-vm/src/types.rs
··· 9 9 pub alpine: Option<bool>, 10 10 pub ubuntu: Option<bool>, 11 11 pub nixos: Option<bool>, 12 + pub fedora: Option<bool>, 13 + pub gentoo: Option<bool>, 14 + pub slackware: Option<bool>, 15 + pub opensuse: Option<bool>, 16 + pub opensuse_tumbleweed: Option<bool>, 17 + pub almalinux: Option<bool>, 18 + pub rockylinux: Option<bool>, 19 + pub archlinux: Option<bool>, 12 20 pub vcpu: u16, 13 21 pub memory: u16, 14 22 pub vmlinux: Option<String>, ··· 28 36 alpine: Some(config.distro == Distro::Alpine), 29 37 ubuntu: Some(config.distro == Distro::Ubuntu), 30 38 nixos: Some(config.distro == Distro::NixOS), 39 + fedora: Some(config.distro == Distro::Fedora), 40 + gentoo: Some(config.distro == Distro::Gentoo), 41 + slackware: Some(config.distro == Distro::Slackware), 42 + opensuse: Some(config.distro == Distro::Opensuse), 43 + opensuse_tumbleweed: Some(config.distro == Distro::OpensuseTumbleweed), 44 + almalinux: Some(config.distro == Distro::Almalinux), 45 + rockylinux: Some(config.distro == Distro::RockyLinux), 46 + archlinux: Some(config.distro == Distro::Archlinux), 31 47 vcpu: vm.vcpu.unwrap_or(num_cpus::get() as u16), 32 48 memory: vm.memory.unwrap_or(512), 33 49 vmlinux: vm.vmlinux, ··· 51 67 Distro::NixOS 52 68 } else if self.ubuntu.unwrap_or(true) { 53 69 Distro::Ubuntu 70 + } else if self.fedora.unwrap_or(false) { 71 + Distro::Fedora 72 + } else if self.gentoo.unwrap_or(false) { 73 + Distro::Gentoo 74 + } else if self.slackware.unwrap_or(false) { 75 + Distro::Slackware 76 + } else if self.opensuse.unwrap_or(false) { 77 + Distro::Opensuse 78 + } else if self.opensuse_tumbleweed.unwrap_or(false) { 79 + Distro::OpensuseTumbleweed 80 + } else if self.almalinux.unwrap_or(false) { 81 + Distro::Almalinux 82 + } else if self.rockylinux.unwrap_or(false) { 83 + Distro::RockyLinux 84 + } else if self.archlinux.unwrap_or(false) { 85 + Distro::Archlinux 54 86 } else { 55 87 panic!("No valid distribution option provided."); 56 88 }