diff --git a/crates/fire-server/src/services/microvm.rs b/crates/fire-server/src/services/microvm.rs index 7980f8b..552a6fb 100644 --- a/crates/fire-server/src/services/microvm.rs +++ b/crates/fire-server/src/services/microvm.rs @@ -76,6 +76,9 @@ pub async fn start_microvm(pool: Arc>, id: &str) -> Result) -> Result<()> { +pub fn prepare( + distro: Distro, + kernel_file: Option, + ssh_keys: Option>, +) -> Result<()> { let arch = run_command("uname", &["-m"], false)?.stdout; let arch = String::from_utf8_lossy(&arch).trim().to_string(); println!("[+] Detected architecture: {}", arch.bright_green()); @@ -85,13 +89,17 @@ pub fn prepare(distro: Distro, kernel_file: Option) -> Result<()> { Distro::Archlinux => Box::new(ArchlinuxPreparer), }; - let (kernel_file, img_file, ssh_key_file) = preparer.prepare(&arch, &app_dir, kernel_file)?; + let (kernel_file, img_file, ssh_key_file) = + preparer.prepare(&arch, &app_dir, kernel_file, ssh_keys)?; extract_vmlinuz(&kernel_file)?; println!("[✓] Kernel: {}", kernel_file.bright_green()); println!("[✓] Rootfs: {}", img_file.bright_green()); - println!("[✓] SSH Key: {}", ssh_key_file.bright_green()); + match ssh_key_file { + None => println!("[✓] SSH Keys: User provided"), + Some(ssh_key_file) => println!("[✓] SSH Key: {}", ssh_key_file.bright_green()), + } Ok(()) } @@ -102,7 +110,8 @@ pub trait RootfsPreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)>; + ssh_keys: Option>, + ) -> Result<(String, String, Option)>; fn name(&self) -> &'static str; } @@ -129,7 +138,8 @@ impl RootfsPreparer for DebianPreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -202,7 +212,10 @@ impl RootfsPreparer for DebianPreparer { &["-p", &format!("{}/root/.ssh", debootstrap_dir)], true, )?; - ssh::generate_and_copy_ssh_key(&ssh_key_name, &debootstrap_dir)?; + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &debootstrap_dir)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &debootstrap_dir)?, + } if !run_command("chroot", &[&debootstrap_dir, "which", "sshd"], true) .map(|output| output.status.success()) @@ -236,7 +249,10 @@ impl RootfsPreparer for DebianPreparer { rootfs::add_overlay_init(&debootstrap_dir)?; rootfs::create_squashfs(&debootstrap_dir, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; Ok((kernel_file, img_file, ssh_key_file)) } @@ -252,7 +268,8 @@ impl RootfsPreparer for AlpinePreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -364,12 +381,18 @@ impl RootfsPreparer for AlpinePreparer { )?; let ssh_key_name = "id_rsa"; - ssh::generate_and_copy_ssh_key(&ssh_key_name, &minirootfs)?; + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &minirootfs)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &minirootfs)?, + } let img_file = format!("{}/alpine-rootfs.img", app_dir); rootfs::create_squashfs(&minirootfs, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; Ok((kernel_file, img_file, ssh_key_file)) } @@ -385,7 +408,8 @@ impl RootfsPreparer for UbuntuPreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -425,14 +449,20 @@ impl RootfsPreparer for UbuntuPreparer { )?; let ssh_key_name = "id_rsa"; - ssh::generate_and_copy_ssh_key(&ssh_key_name, &squashfs_root_dir)?; + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &squashfs_root_dir)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &squashfs_root_dir)?, + } let img_file = format!("{}/ubuntu-rootfs.img", app_dir); rootfs::create_overlay_dirs(&squashfs_root_dir)?; rootfs::add_overlay_init(&squashfs_root_dir)?; rootfs::create_squashfs(&squashfs_root_dir, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; Ok((kernel_file, img_file, ssh_key_file)) } @@ -448,7 +478,8 @@ impl RootfsPreparer for NixOSPreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -465,12 +496,18 @@ impl RootfsPreparer for NixOSPreparer { rootfs::extract_squashfs(&squashfs_file, &nixos_rootfs)?; let ssh_key_name = "id_rsa"; - ssh::generate_and_copy_ssh_key_nixos(&ssh_key_name, &nixos_rootfs)?; + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &nixos_rootfs)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &nixos_rootfs)?, + } let img_file = format!("{}/nixos-rootfs.img", app_dir); rootfs::create_squashfs(&nixos_rootfs, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; println!( "[+] {} rootfs prepared at: {}", @@ -492,7 +529,8 @@ impl RootfsPreparer for FedoraPreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -516,12 +554,18 @@ impl RootfsPreparer for FedoraPreparer { )?; let ssh_key_name = "id_rsa"; - ssh::generate_and_copy_ssh_key(&ssh_key_name, &fedora_rootfs)?; + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &fedora_rootfs)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &fedora_rootfs)?, + } let img_file = format!("{}/fedora-rootfs.img", app_dir); rootfs::create_squashfs(&fedora_rootfs, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; println!( "[+] {} rootfs prepared at: {}", @@ -543,7 +587,8 @@ impl RootfsPreparer for GentooPreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -569,12 +614,18 @@ impl RootfsPreparer for GentooPreparer { )?; let ssh_key_name = "id_rsa"; - ssh::generate_and_copy_ssh_key(&ssh_key_name, &gentoo_rootfs)?; + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &gentoo_rootfs)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &gentoo_rootfs)?, + } let img_file = format!("{}/gentoo-rootfs.img", app_dir); rootfs::create_squashfs(&gentoo_rootfs, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; Ok((kernel_file, img_file, ssh_key_file)) } @@ -590,7 +641,8 @@ impl RootfsPreparer for SlackwarePreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -621,12 +673,18 @@ impl RootfsPreparer for SlackwarePreparer { )?; let ssh_key_name = "id_rsa"; - ssh::generate_and_copy_ssh_key(&ssh_key_name, &slackware_rootfs)?; + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &slackware_rootfs)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &slackware_rootfs)?, + } let img_file = format!("{}/slackware-rootfs.img", app_dir); rootfs::create_squashfs(&slackware_rootfs, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; Ok((kernel_file, img_file, ssh_key_file)) } @@ -642,7 +700,8 @@ impl RootfsPreparer for OpensusePreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -667,12 +726,19 @@ impl RootfsPreparer for OpensusePreparer { )?; let ssh_key_name = "id_rsa"; - ssh::generate_and_copy_ssh_key(&ssh_key_name, &opensuse_rootfs)?; + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &opensuse_rootfs)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &opensuse_rootfs)?, + } let img_file = format!("{}/opensuse-rootfs.img", app_dir); rootfs::create_squashfs(&opensuse_rootfs, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; + Ok((kernel_file, img_file, ssh_key_file)) } } @@ -687,7 +753,8 @@ impl RootfsPreparer for AlmalinuxPreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -706,12 +773,18 @@ impl RootfsPreparer for AlmalinuxPreparer { rootfs::extract_squashfs(&squashfs_file, &almalinux_rootfs)?; let ssh_key_name = "id_rsa"; - ssh::generate_and_copy_ssh_key(&ssh_key_name, &almalinux_rootfs)?; + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &almalinux_rootfs)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &almalinux_rootfs)?, + } let img_file = format!("{}/almalinux-rootfs.img", app_dir); rootfs::create_squashfs(&almalinux_rootfs, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; Ok((kernel_file, img_file, ssh_key_file)) } @@ -727,7 +800,8 @@ impl RootfsPreparer for RockyLinuxPreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -746,12 +820,18 @@ impl RootfsPreparer for RockyLinuxPreparer { rootfs::extract_squashfs(&squashfs_file, &rockylinux_rootfs)?; let ssh_key_name = "id_rsa"; - ssh::generate_and_copy_ssh_key(&ssh_key_name, &rockylinux_rootfs)?; + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &rockylinux_rootfs)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &rockylinux_rootfs)?, + } let img_file = format!("{}/rockylinux-rootfs.img", app_dir); rootfs::create_squashfs(&rockylinux_rootfs, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; Ok((kernel_file, img_file, ssh_key_file)) } @@ -767,7 +847,8 @@ impl RootfsPreparer for ArchlinuxPreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -796,12 +877,19 @@ impl RootfsPreparer for ArchlinuxPreparer { )?; let ssh_key_name = "id_rsa"; - ssh::generate_and_copy_ssh_key(&ssh_key_name, &archlinux_rootfs)?; + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &archlinux_rootfs)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &archlinux_rootfs)?, + } let img_file = format!("{}/archlinux-rootfs.img", app_dir); + rootfs::create_squashfs(&archlinux_rootfs, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; Ok((kernel_file, img_file, ssh_key_file)) } @@ -817,7 +905,8 @@ impl RootfsPreparer for OpensuseTumbleweedPreparer { arch: &str, app_dir: &str, kernel_file: Option, - ) -> Result<(String, String, String)> { + ssh_keys: Option>, + ) -> Result<(String, String, Option)> { println!( "[+] Preparing {} rootfs for {}...", self.name(), @@ -842,12 +931,20 @@ impl RootfsPreparer for OpensuseTumbleweedPreparer { )?; let ssh_key_name = "id_rsa"; - ssh::generate_and_copy_ssh_key(&ssh_key_name, &opensuse_rootfs)?; + + match ssh_keys { + Some(ref keys) => ssh::copy_ssh_keys(keys, &opensuse_rootfs)?, + None => ssh::generate_and_copy_ssh_key(&ssh_key_name, &opensuse_rootfs)?, + } let img_file = format!("{}/opensuse-tumbleweed-rootfs.img", app_dir); rootfs::create_squashfs(&opensuse_rootfs, &img_file)?; - let ssh_key_file = format!("{}/{}", app_dir, ssh_key_name); + let ssh_key_file = match ssh_keys { + Some(_) => None, + None => Some(format!("{}/{}", app_dir, ssh_key_name)), + }; + Ok((kernel_file, img_file, ssh_key_file)) } } diff --git a/crates/firecracker-prepare/src/ssh.rs b/crates/firecracker-prepare/src/ssh.rs index 20d0826..20192ec 100644 --- a/crates/firecracker-prepare/src/ssh.rs +++ b/crates/firecracker-prepare/src/ssh.rs @@ -30,7 +30,7 @@ pub fn generate_and_copy_ssh_key(key_name: &str, squashfs_root_dir: &str) -> Res Ok(()) } -pub fn generate_and_copy_ssh_key_nixos(key_name: &str, squashfs_root_dir: &str) -> Result<()> { +pub fn generate_and_copy_ssh_key_nixos(key_name: &str, squashfs_root_dir: &str) -> Result { let app_dir = crate::config::get_config_dir()?; const DEFAULT_SSH: &str = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAR4Gvuv3lTpXIYeZTRO22nVEj64uMmlDAdt5+GG80hm tsiry@tsiry-XPS-9320"; @@ -54,7 +54,22 @@ pub fn generate_and_copy_ssh_key_nixos(key_name: &str, squashfs_root_dir: &str) ], true, )?; - return Ok(()); + + run_command( + "mkdir", + &["-p", &format!("{}/root/.ssh", squashfs_root_dir)], + true, + )?; + run_command( + "cp", + &[ + &pub_key_path, + &format!("{}/root/.ssh/authorized_keys", squashfs_root_dir), + ], + true, + )?; + + return Ok(public_key); } let key_name = format!("{}/{}", app_dir, key_name); @@ -75,5 +90,54 @@ pub fn generate_and_copy_ssh_key_nixos(key_name: &str, squashfs_root_dir: &str) ], true, )?; + + run_command( + "mkdir", + &["-p", &format!("{}/root/.ssh", squashfs_root_dir)], + true, + )?; + run_command( + "cp", + &[ + &pub_key_path, + &format!("{}/root/.ssh/authorized_keys", squashfs_root_dir), + ], + true, + )?; + + Ok(public_key) +} + +pub fn copy_ssh_keys(ssh_keys: &[String], squashfs_root_dir: &str) -> Result<()> { + run_command( + "mkdir", + &["-p", &format!("{}/root/.ssh", squashfs_root_dir)], + true, + )?; + + let auth_keys_path = "/tmp/authorized_keys"; + let mut auth_keys_file = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&auth_keys_path) + .map_err(|e| anyhow::anyhow!("Failed to open authorized_keys file: {}", e))?; + + for key in ssh_keys { + use std::io::Write; + writeln!(auth_keys_file, "{}", key) + .map_err(|e| anyhow::anyhow!("Failed to write to authorized_keys file: {}", e))?; + } + + run_command( + "cp", + &[ + &auth_keys_path, + &format!("{}/root/.ssh/authorized_keys", squashfs_root_dir), + ], + true, + )?; + std::fs::remove_file(&auth_keys_path) + .map_err(|e| anyhow::anyhow!("Failed to remove temporary authorized_keys file: {}", e))?; + Ok(()) } diff --git a/crates/firecracker-state/migrations/20250917153615_add_ssh_keys.sql b/crates/firecracker-state/migrations/20250917153615_add_ssh_keys.sql new file mode 100644 index 0000000..f9ab44d --- /dev/null +++ b/crates/firecracker-state/migrations/20250917153615_add_ssh_keys.sql @@ -0,0 +1,3 @@ +-- Add migration script here +ALTER TABLE virtual_machines +ADD COLUMN ssh_keys TEXT; \ No newline at end of file diff --git a/crates/firecracker-state/src/entity/virtual_machine.rs b/crates/firecracker-state/src/entity/virtual_machine.rs index 2e669dc..405646b 100644 --- a/crates/firecracker-state/src/entity/virtual_machine.rs +++ b/crates/firecracker-state/src/entity/virtual_machine.rs @@ -19,6 +19,7 @@ pub struct VirtualMachine { pub vmlinux: Option, pub rootfs: Option, pub bootargs: Option, + pub ssh_keys: Option, #[serde(with = "chrono::serde::ts_seconds")] pub created_at: DateTime, #[serde(with = "chrono::serde::ts_seconds")] diff --git a/crates/firecracker-state/src/lib.rs b/crates/firecracker-state/src/lib.rs index 2028a75..287a0b2 100644 --- a/crates/firecracker-state/src/lib.rs +++ b/crates/firecracker-state/src/lib.rs @@ -52,6 +52,21 @@ pub async fn create_connection_pool() -> Result, Error> { )) .await?; + match pool + .execute(include_str!( + "../migrations/20250917153615_add_ssh_keys.sql" + )) + .await + { + Ok(_) => (), + Err(e) => { + if e.to_string().contains("duplicate column name: ssh_keys") { + } else { + return Err(anyhow!("Failed to apply migration: {}", e)); + } + } + } + sqlx::query("PRAGMA journal_mode=WAL") .execute(&pool) .await?; diff --git a/crates/firecracker-state/src/repo/virtual_machine.rs b/crates/firecracker-state/src/repo/virtual_machine.rs index a703e0d..ddd517c 100644 --- a/crates/firecracker-state/src/repo/virtual_machine.rs +++ b/crates/firecracker-state/src/repo/virtual_machine.rs @@ -64,8 +64,9 @@ pub async fn create(pool: &Pool, vm: VirtualMachine) -> Result, vm: VirtualMachine) -> Result Result<(), Error> { - println!( - "Are you sure you want to reset? This will remove all ext4 files. Type '{}' to confirm:", - "yes".bright_green() - ); + let name = options + .api_socket + .trim_start_matches("/tmp/firecracker-") + .trim_end_matches(".sock") + .to_string(); + + if options.api_socket.is_empty() { + println!( + "Are you sure you want to reset? This will remove all *.img files. Type '{}' to confirm:", + "yes".bright_green() + ); + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .map_err(|e| Error::msg(format!("Failed to read input: {}", e)))?; + let input = input.trim(); + + if input != "yes" { + println!("Reset cancelled."); + return Ok(()); + } + + stop(Some(name)).await?; + + let app_dir = crate::config::get_config_dir()?; + let img_file = glob(format!("{}/*.img", app_dir).as_str()) + .map_err(|e| Error::msg(format!("Failed to find img file: {}", e)))?; + + for file in img_file { + if let Ok(path) = file { + run_command("rm", &[path.to_str().unwrap_or_default()], true)?; + } + } + + println!("[+] Reset complete. All *.img files have been removed."); + println!( + "[+] You can now run '{}' to start a new Firecracker MicroVM.", + "fireup".bright_green() + ); + return Ok(()); + } + + println!("Are you sure you want to reset the VM {}? This will remove its associated {} file. Type '{}' to confirm:", name.cyan(), "*.img".cyan(), "yes".bright_green()); let mut input = String::new(); std::io::stdin() .read_line(&mut input) .map_err(|e| Error::msg(format!("Failed to read input: {}", e)))?; let input = input.trim(); - if input != "yes" { println!("Reset cancelled."); return Ok(()); } - let name = options - .api_socket - .trim_start_matches("/tmp/firecracker-") - .trim_end_matches(".sock") - .to_string(); + let pool = firecracker_state::create_connection_pool().await?; - stop(Some(name)).await?; + let vm = repo::virtual_machine::find_by_api_socket(&pool, &options.api_socket).await?; + if vm.is_none() { + println!("[!] No virtual machine found with name: {}", name); + process::exit(1); + } - let app_dir = crate::config::get_config_dir()?; - let ext4_file = glob(format!("{}/*.ext4", app_dir).as_str()) - .map_err(|e| Error::msg(format!("Failed to find ext4 file: {}", e)))?; + let vm = vm.unwrap(); + stop(Some(vm.name.clone())).await?; - for file in ext4_file { - if let Ok(path) = file { - std::fs::remove_file(path) - .map_err(|e| Error::msg(format!("Failed to remove file: {}", e)))?; - } + if let Some(rootfs) = &vm.rootfs { + run_command("rm", &[rootfs], true)?; } - println!("[+] Reset complete. All ext4 files have been removed."); + println!("[+] Reset complete. Associated *.img files have been removed."); println!( "[+] You can now run '{}' to start a new Firecracker MicroVM.", "fireup".bright_green() diff --git a/crates/firecracker-up/src/cmd/start.rs b/crates/firecracker-up/src/cmd/start.rs index 1ee3d78..b16937a 100644 --- a/crates/firecracker-up/src/cmd/start.rs +++ b/crates/firecracker-up/src/cmd/start.rs @@ -43,6 +43,9 @@ pub async fn start(name: &str) -> Result<(), Error> { api_socket: vm.api_socket, mac_address: vm.mac_address, etcd, + ssh_keys: vm + .ssh_keys + .map(|keys| keys.split(',').map(|s| s.to_string()).collect()), }) .await?; diff --git a/crates/firecracker-up/src/cmd/up.rs b/crates/firecracker-up/src/cmd/up.rs index d459644..19f3216 100644 --- a/crates/firecracker-up/src/cmd/up.rs +++ b/crates/firecracker-up/src/cmd/up.rs @@ -78,7 +78,11 @@ pub async fn up(options: VmOptions) -> Result<(), Error> { } } - firecracker_prepare::prepare(options.clone().into(), options.vmlinux.clone())?; + firecracker_prepare::prepare( + options.clone().into(), + options.vmlinux.clone(), + options.ssh_keys.clone(), + )?; firecracker_vm::setup(&options, pid, vm_id).await?; Ok(()) } diff --git a/crates/firecracker-up/src/main.rs b/crates/firecracker-up/src/main.rs index 2a68522..32c7ca7 100644 --- a/crates/firecracker-up/src/main.rs +++ b/crates/firecracker-up/src/main.rs @@ -240,6 +240,9 @@ async fn main() -> Result<()> { .get_one::("mac-address") .cloned() .unwrap_or(default_mac); + let ssh_keys = args + .get_one::("ssh-keys") + .map(|s| s.split(',').map(|s| s.trim().to_string()).collect()); let options = VmOptions { debian: args.get_one::("debian").copied(), alpine: args.get_one::("alpine").copied(), @@ -263,6 +266,7 @@ async fn main() -> Result<()> { api_socket, mac_address, etcd: None, + ssh_keys, }; up(options).await? } @@ -280,8 +284,11 @@ async fn main() -> Result<()> { ssh(pool, name).await? } Some(("reset", args)) => { - let name = args.get_one::("name").cloned().unwrap(); - let api_socket = format!("/tmp/firecracker-{}.sock", name); + let name = args.get_one::("name").cloned(); + let api_socket = match name { + Some(name) => format!("/tmp/firecracker-{}.sock", name), + None => String::from(""), + }; reset(VmOptions { api_socket, ..Default::default() @@ -348,6 +355,9 @@ async fn main() -> Result<()> { .get_one::("mac-address") .cloned() .unwrap_or(default_mac); + let ssh_keys = matches + .get_one::("ssh-keys") + .map(|s| s.split(',').map(|s| s.trim().to_string()).collect()); let options = VmOptions { debian: Some(debian), @@ -372,6 +382,7 @@ async fn main() -> Result<()> { api_socket, mac_address, etcd: None, + ssh_keys, }; up(options).await? } diff --git a/crates/firecracker-vm/src/lib.rs b/crates/firecracker-vm/src/lib.rs index db2254b..aadae45 100644 --- a/crates/firecracker-vm/src/lib.rs +++ b/crates/firecracker-vm/src/lib.rs @@ -165,6 +165,7 @@ pub async fn setup(options: &VmOptions, pid: u32, vm_id: Option) -> Resu vmlinux: Some(kernel), rootfs: Some(rootfs), bootargs: options.bootargs.clone(), + ssh_keys: options.ssh_keys.as_ref().map(|keys| keys.join(",")), ..Default::default() }, ) @@ -190,6 +191,7 @@ pub async fn setup(options: &VmOptions, pid: u32, vm_id: Option) -> Resu vmlinux: Some(kernel), rootfs: Some(rootfs), bootargs: options.bootargs.clone(), + ssh_keys: options.ssh_keys.as_ref().map(|keys| keys.join(",")), ..Default::default() }, ) diff --git a/crates/firecracker-vm/src/types.rs b/crates/firecracker-vm/src/types.rs index a9f2e4f..020d45f 100644 --- a/crates/firecracker-vm/src/types.rs +++ b/crates/firecracker-vm/src/types.rs @@ -27,6 +27,7 @@ pub struct VmOptions { pub api_socket: String, pub mac_address: String, pub etcd: Option, + pub ssh_keys: Option>, } impl From for VmOptions { @@ -55,6 +56,7 @@ impl From for VmOptions { api_socket: vm.api_socket.unwrap_or(FIRECRACKER_SOCKET.into()), mac_address: vm.mac.unwrap_or(FC_MAC.into()), etcd: config.etcd.clone(), + ssh_keys: vm.ssh_keys.clone(), } } }