Prepare, configure, and manage Firecracker microVMs in seconds!
virtualization linux microvm firecracker
at main 472 lines 20 kB view raw
1use anyhow::Result; 2use clap::{arg, Arg, Command}; 3use firecracker_vm::{constants::BRIDGE_DEV, mac::generate_unique_mac, types::VmOptions}; 4use owo_colors::OwoColorize; 5 6use crate::cmd::{ 7 cp::cp, down::down, exec::exec, init::init, inspect::inspect_microvm, logs::logs, 8 ps::list_all_instances, reset::reset, rm::remove, serve::serve, ssh::ssh, start::start, 9 status::status, stop::stop, up::up, 10}; 11 12pub mod cmd; 13pub mod command; 14pub mod config; 15pub mod date; 16pub mod ssh; 17 18fn cli() -> Command { 19 let banner = format!( 20 "{}", 21 r#" 22 _______ __ __ 23 / ____(_)_______ / / / /___ 24 / /_ / / ___/ _ \/ / / / __ \ 25 / __/ / / / / __/ /_/ / /_/ / 26 /_/ /_/_/ \___/\____/ .___/ 27 /_/ 28"# 29 .yellow() 30 ); 31 32 Command::new("fireup") 33 .version(env!("CARGO_PKG_VERSION")) 34 .about(&banner) 35 .subcommand( 36 Command::new("init") 37 .about("Create a new MicroVM configuration `fire.toml` in the current directory"), 38 ) 39 .subcommand( 40 Command::new("ps") 41 .alias("list") 42 .arg(arg!(-a --all "Show all Firecracker MicroVM instances").default_value("false")) 43 .about("List all Firecracker MicroVM instances"), 44 ) 45 .subcommand( 46 Command::new("start") 47 .arg(arg!(<name> "Name of the Firecracker MicroVM to start").required(true)) 48 .arg( 49 Arg::new("tailscale-auth-key") 50 .long("tailscale-auth-key") 51 .value_name("TAILSCALE_AUTH_KEY") 52 .help("Tailscale auth key to connect the VM to a Tailscale network"), 53 ) 54 .about("Start Firecracker MicroVM"), 55 ) 56 .subcommand( 57 Command::new("stop") 58 .arg(arg!([name] "Name of the Firecracker MicroVM to stop").required(false)) 59 .about("Stop Firecracker MicroVM"), 60 ) 61 .subcommand( 62 Command::new("restart") 63 .arg(arg!(<name> "Name of the Firecracker MicroVM to restart").required(true)) 64 .arg( 65 Arg::new("tailscale-auth-key") 66 .long("tailscale-auth-key") 67 .value_name("TAILSCALE_AUTH_KEY") 68 .help("Tailscale auth key to connect the VM to a Tailscale network"), 69 ) 70 .about("Restart Firecracker MicroVM"), 71 ) 72 .subcommand( 73 Command::new("up") 74 .arg(arg!(--debian "Prepare Debian MicroVM").default_value("false")) 75 .arg(arg!(--alpine "Prepare Alpine MicroVM").default_value("false")) 76 .arg(arg!(--nixos "Prepare NixOS MicroVM").default_value("false")) 77 .arg(arg!(--fedora "Prepare Fedora MicroVM").default_value("false")) 78 .arg(arg!(--gentoo "Prepare Gentoo MicroVM").default_value("false")) 79 .arg(arg!(--slackware "Prepare Slackware MicroVM").default_value("false")) 80 .arg(arg!(--opensuse "Prepare OpenSUSE MicroVM").default_value("false")) 81 .arg( 82 Arg::new("opensuse-tumbleweed") 83 .help("Prepare OpenSUSE Tumbleweed MicroVM") 84 .action(clap::ArgAction::SetTrue), 85 ) 86 .arg(arg!(--almalinux "Prepare AlmaLinux MicroVM").default_value("false")) 87 .arg(arg!(--rockylinux "Prepare RockyLinux MicroVM").default_value("false")) 88 .arg(arg!(--archlinux "Prepare ArchLinux MicroVM").default_value("false")) 89 .arg(arg!(--ubuntu "Prepare Ubuntu MicroVM").default_value("true")) 90 .arg(arg!(--vcpu <n> "Number of vCPUs")) 91 .arg(arg!(--memory <m> "Memory size in MiB")) 92 .arg(arg!(--vmlinux <path> "Path to the kernel image")) 93 .arg(arg!(--rootfs <path> "Path to the root filesystem image")) 94 .arg(arg!(--bridge <name> "Name of the bridge interface").default_value(BRIDGE_DEV)) 95 .arg(arg!(--tap <name> "Name of the tap interface").default_value("")) 96 .arg( 97 Arg::new("mac-address") 98 .long("mac-address") 99 .value_name("MAC") 100 .help("MAC address for the network interface"), 101 ) 102 .arg( 103 Arg::new("api-socket") 104 .long("api-socket") 105 .value_name("path") 106 .help("Path to the Firecracker API socket"), 107 ) 108 .arg( 109 Arg::new("boot-args") 110 .long("boot-args") 111 .value_name("ARGS") 112 .help("Override boot arguments"), 113 ) 114 .arg( 115 Arg::new("ssh-keys") 116 .long("ssh-keys") 117 .value_name("SSH_KEYS") 118 .help("Comma-separated list of SSH public keys to add to the VM"), 119 ) 120 .arg( 121 Arg::new("tailscale-auth-key") 122 .long("tailscale-auth-key") 123 .value_name("TAILSCALE_AUTH_KEY") 124 .help("Tailscale auth key to connect the VM to a Tailscale network"), 125 ) 126 .about("Start a new Firecracker MicroVM"), 127 ) 128 .subcommand(Command::new("down").about("Stop Firecracker MicroVM")) 129 .subcommand( 130 Command::new("status") 131 .arg(arg!([name] "Name of the Firecracker MicroVM to check status").required(false)) 132 .about("Check the status of Firecracker MicroVM"), 133 ) 134 .subcommand( 135 Command::new("logs") 136 .arg( 137 arg!(-f --follow "Follow the logs") 138 .short('f') 139 .long("follow") 140 .default_value("false"), 141 ) 142 .about("View the logs of the Firecracker MicroVM"), 143 ) 144 .subcommand( 145 Command::new("ssh") 146 .arg(arg!([name] "Name of the Firecracker MicroVM to SSH into")) 147 .about("SSH into the Firecracker MicroVM"), 148 ) 149 .subcommand( 150 Command::new("reset") 151 .arg(arg!([name] "Name of the Firecracker MicroVM to reset").required(false)) 152 .about("Reset the Firecracker MicroVM"), 153 ) 154 .subcommand( 155 Command::new("rm") 156 .arg(arg!(<name> "Name or ID of the Firecracker MicroVM to delete").required(true)) 157 .about("Delete the Firecracker MicroVM"), 158 ) 159 .subcommand( 160 Command::new("serve") 161 .about("Start fireup HTTP API server") 162 .arg(arg!(--host <host> "Host to bind the server")) 163 .arg(arg!(--port <port> "Port to bind the server")), 164 ) 165 .subcommand( 166 Command::new("inspect") 167 .arg(arg!(<name> "Name or ID of the Firecracker MicroVM to inspect").required(true)) 168 .about("Inspect the Firecracker MicroVM details"), 169 ) 170 .subcommand( 171 Command::new("exec") 172 .arg( 173 arg!(<name> "Name of the Firecracker MicroVM to execute command in") 174 .required(true), 175 ) 176 .arg( 177 Arg::new("args") 178 .help("Command and arguments to execute inside the MicroVM") 179 .required(true) 180 .num_args(1..), 181 ) 182 .about("Execute a command inside the Firecracker MicroVM"), 183 ) 184 .subcommand( 185 Command::new("cp") 186 .arg(arg!(<source> "Source file path").required(true)) 187 .arg(arg!(<destination> "Destination file path").required(true)) 188 .about("Copy files to/from the Firecracker MicroVM"), 189 ) 190 .arg(arg!(--debian "Prepare Debian MicroVM").default_value("false")) 191 .arg(arg!(--alpine "Prepare Alpine MicroVM").default_value("false")) 192 .arg(arg!(--nixos "Prepare NixOS MicroVM").default_value("false")) 193 .arg(arg!(--fedora "Prepare Fedora MicroVM").default_value("false")) 194 .arg(arg!(--gentoo "Prepare Gentoo MicroVM").default_value("false")) 195 .arg(arg!(--slackware "Prepare Slackware MicroVM").default_value("false")) 196 .arg(arg!(--opensuse "Prepare OpenSUSE MicroVM").default_value("false")) 197 .arg( 198 Arg::new("opensuse-tumbleweed") 199 .long("opensuse-tumbleweed") 200 .help("Prepare OpenSUSE Tumbleweed MicroVM") 201 .action(clap::ArgAction::SetTrue), 202 ) 203 .arg(arg!(--almalinux "Prepare AlmaLinux MicroVM").default_value("false")) 204 .arg(arg!(--rockylinux "Prepare RockyLinux MicroVM").default_value("false")) 205 .arg(arg!(--archlinux "Prepare ArchLinux MicroVM").default_value("false")) 206 .arg(arg!(--ubuntu "Prepare Ubuntu MicroVM").default_value("true")) 207 .arg(arg!(--vcpu <n> "Number of vCPUs")) 208 .arg(arg!(--memory <m> "Memory size in MiB")) 209 .arg(arg!(--vmlinux <path> "Path to the kernel image")) 210 .arg(arg!(--rootfs <path> "Path to the root filesystem image")) 211 .arg(arg!(--bridge <name> "Name of the bridge interface").default_value(BRIDGE_DEV)) 212 .arg(arg!(--tap <name> "Name of the tap interface").default_value("")) 213 .arg( 214 Arg::new("mac-address") 215 .long("mac-address") 216 .value_name("MAC") 217 .help("MAC address for the network interface"), 218 ) 219 .arg( 220 Arg::new("api-socket") 221 .long("api-socket") 222 .value_name("path") 223 .help("Path to the Firecracker API socket"), 224 ) 225 .arg( 226 Arg::new("boot-args") 227 .long("boot-args") 228 .value_name("ARGS") 229 .help("Override boot arguments"), 230 ) 231 .arg( 232 Arg::new("ssh-keys") 233 .long("ssh-keys") 234 .value_name("SSH_KEYS") 235 .help("Comma-separated list of SSH public keys to add to the VM"), 236 ) 237 .arg( 238 Arg::new("tailscale-auth-key") 239 .long("tailscale-auth-key") 240 .value_name("TAILSCALE_AUTH_KEY") 241 .help("Tailscale auth key to connect the VM to a Tailscale network"), 242 ) 243} 244 245#[tokio::main] 246async fn main() -> Result<()> { 247 let matches = cli().get_matches(); 248 249 let pool = firecracker_state::create_connection_pool().await?; 250 let vm_name = names::Generator::default().next().unwrap(); 251 let default_socket = format!("/tmp/firecracker-{}.sock", vm_name); 252 let default_mac = generate_unique_mac(); 253 254 match matches.subcommand() { 255 Some(("init", _)) => init()?, 256 Some(("ps", args)) => { 257 let all = args.get_one::<bool>("all").copied().unwrap_or(false); 258 list_all_instances(all).await?; 259 } 260 Some(("stop", args)) => { 261 let name = args.get_one::<String>("name").cloned().unwrap(); 262 stop(&name).await?; 263 } 264 Some(("start", args)) => { 265 let name = args.get_one::<String>("name").cloned().unwrap(); 266 let tailscale_auth_key = args.get_one::<String>("tailscale-auth-key").cloned(); 267 start(&name, tailscale_auth_key).await?; 268 } 269 Some(("restart", args)) => { 270 let name = args.get_one::<String>("name").cloned().unwrap(); 271 let tailscale_auth_key = args.get_one::<String>("tailscale-auth-key").cloned(); 272 stop(&name).await?; 273 start(&name, tailscale_auth_key).await?; 274 } 275 Some(("up", args)) => { 276 let vcpu = matches 277 .get_one::<String>("vcpu") 278 .map(|s| s.parse::<u16>().unwrap()) 279 .unwrap_or(num_cpus::get() as u16); 280 let memory = matches 281 .get_one::<String>("memory") 282 .map(|s| s.parse::<u16>().unwrap()) 283 .unwrap_or(512); 284 let vmlinux = matches.get_one::<String>("vmlinux").cloned(); 285 let rootfs = matches.get_one::<String>("rootfs").cloned(); 286 let bootargs = matches.get_one::<String>("boot-args").cloned(); 287 let bridge = args.get_one::<String>("bridge").cloned().unwrap(); 288 let tap = args.get_one::<String>("tap").cloned().unwrap(); 289 let api_socket = args 290 .get_one::<String>("api-socket") 291 .cloned() 292 .unwrap_or(default_socket); 293 let mac_address = args 294 .get_one::<String>("mac-address") 295 .cloned() 296 .unwrap_or(default_mac); 297 let ssh_keys = args 298 .get_one::<String>("ssh-keys") 299 .map(|s| s.split(',').map(|s| s.trim().to_string()).collect()); 300 let tailscale_auth_key = args.get_one::<String>("tailscale-auth-key").cloned(); 301 let options = VmOptions { 302 debian: args.get_one::<bool>("debian").copied(), 303 alpine: args.get_one::<bool>("alpine").copied(), 304 ubuntu: args.get_one::<bool>("ubuntu").copied(), 305 nixos: args.get_one::<bool>("nixos").copied(), 306 fedora: args.get_one::<bool>("fedora").copied(), 307 gentoo: args.get_one::<bool>("gentoo").copied(), 308 slackware: args.get_one::<bool>("slackware").copied(), 309 opensuse: args.get_one::<bool>("opensuse").copied(), 310 opensuse_tumbleweed: args.get_one::<bool>("opensuse-tumbleweed").copied(), 311 almalinux: args.get_one::<bool>("almalinux").copied(), 312 rockylinux: args.get_one::<bool>("rockylinux").copied(), 313 archlinux: args.get_one::<bool>("archlinux").copied(), 314 vcpu, 315 memory, 316 vmlinux, 317 rootfs, 318 bootargs, 319 bridge, 320 tap, 321 api_socket, 322 mac_address, 323 etcd: None, 324 ssh_keys, 325 tailscale: tailscale_auth_key.map(|key| fire_config::TailscaleOptions { 326 auth_key: Some(key), 327 }), 328 }; 329 up(options).await? 330 } 331 Some(("down", _)) => down().await?, 332 Some(("status", args)) => { 333 let name = args.get_one::<String>("name").cloned(); 334 status(name).await?; 335 } 336 Some(("logs", args)) => { 337 let follow = args.get_one::<bool>("follow").copied().unwrap_or(false); 338 logs(follow)?; 339 } 340 Some(("ssh", args)) => { 341 let name = args.get_one::<String>("name").cloned(); 342 ssh(pool, name).await? 343 } 344 Some(("reset", args)) => { 345 let name = args.get_one::<String>("name").cloned(); 346 let api_socket = match name { 347 Some(name) => format!("/tmp/firecracker-{}.sock", name), 348 None => String::from(""), 349 }; 350 reset(VmOptions { 351 api_socket, 352 ..Default::default() 353 }) 354 .await? 355 } 356 Some(("rm", args)) => { 357 let name = args.get_one::<String>("name").cloned().unwrap(); 358 remove(&name).await? 359 } 360 Some(("serve", _)) => serve().await?, 361 Some(("inspect", args)) => { 362 let name = args.get_one::<String>("name").cloned().unwrap(); 363 inspect_microvm(&name).await?; 364 } 365 Some(("exec", args)) => { 366 let name = args.get_one::<String>("name").cloned().unwrap(); 367 let cmd_args: Vec<String> = args 368 .get_many::<String>("args") 369 .unwrap() 370 .map(|s| s.to_string()) 371 .collect(); 372 exec(&name, cmd_args).await?; 373 } 374 Some(("cp", args)) => { 375 let source = args.get_one::<String>("source").cloned().unwrap(); 376 let destination = args.get_one::<String>("destination").cloned().unwrap(); 377 cp(&source, &destination).await?; 378 } 379 _ => { 380 let debian = matches.get_one::<bool>("debian").copied().unwrap_or(false); 381 let alpine = matches.get_one::<bool>("alpine").copied().unwrap_or(false); 382 let nixos = matches.get_one::<bool>("nixos").copied().unwrap_or(false); 383 let ubuntu = matches.get_one::<bool>("ubuntu").copied().unwrap_or(false); 384 let fedora = matches.get_one::<bool>("fedora").copied().unwrap_or(false); 385 let gentoo = matches.get_one::<bool>("gentoo").copied().unwrap_or(false); 386 let slackware = matches 387 .get_one::<bool>("slackware") 388 .copied() 389 .unwrap_or(false); 390 let opensuse = matches 391 .get_one::<bool>("opensuse") 392 .copied() 393 .unwrap_or(false); 394 let opensuse_tumbleweed = matches 395 .get_one::<bool>("opensuse-tumbleweed") 396 .copied() 397 .unwrap_or(false); 398 let almalinux = matches 399 .get_one::<bool>("almalinux") 400 .copied() 401 .unwrap_or(false); 402 let rockylinux = matches 403 .get_one::<bool>("rockylinux") 404 .copied() 405 .unwrap_or(false); 406 let archlinux = matches 407 .get_one::<bool>("archlinux") 408 .copied() 409 .unwrap_or(false); 410 411 let vcpu = matches 412 .get_one::<String>("vcpu") 413 .map(|s| s.parse::<u16>().unwrap()) 414 .unwrap_or(num_cpus::get() as u16); 415 416 let memory = matches 417 .get_one::<String>("memory") 418 .map(|s| s.parse::<u16>().unwrap()) 419 .unwrap_or(if nixos { 2048 } else { 512 }); 420 421 let vmlinux = matches.get_one::<String>("vmlinux").cloned(); 422 let rootfs = matches.get_one::<String>("rootfs").cloned(); 423 let bootargs = matches.get_one::<String>("boot-args").cloned(); 424 let bridge = matches.get_one::<String>("bridge").cloned().unwrap(); 425 let tap = matches.get_one::<String>("tap").cloned().unwrap(); 426 let api_socket = matches 427 .get_one::<String>("api-socket") 428 .cloned() 429 .unwrap_or(default_socket); 430 let mac_address = matches 431 .get_one::<String>("mac-address") 432 .cloned() 433 .unwrap_or(default_mac); 434 let ssh_keys = matches 435 .get_one::<String>("ssh-keys") 436 .map(|s| s.split(',').map(|s| s.trim().to_string()).collect()); 437 let tailscale_auth_key = matches.get_one::<String>("tailscale-auth-key").cloned(); 438 439 let options = VmOptions { 440 debian: Some(debian), 441 alpine: Some(alpine), 442 ubuntu: Some(ubuntu), 443 nixos: Some(nixos), 444 fedora: Some(fedora), 445 gentoo: Some(gentoo), 446 slackware: Some(slackware), 447 opensuse: Some(opensuse), 448 opensuse_tumbleweed: Some(opensuse_tumbleweed), 449 almalinux: Some(almalinux), 450 rockylinux: Some(rockylinux), 451 archlinux: Some(archlinux), 452 vcpu, 453 memory, 454 vmlinux, 455 rootfs, 456 bootargs, 457 bridge, 458 tap, 459 api_socket, 460 mac_address, 461 etcd: None, 462 ssh_keys, 463 tailscale: tailscale_auth_key.map(|key| fire_config::TailscaleOptions { 464 auth_key: Some(key), 465 }), 466 }; 467 up(options).await? 468 } 469 } 470 471 Ok(()) 472}