···11+-- Add migration script here
22+CREATE TABLE IF NOT EXISTS virtual_machines (
33+ id VARCHAR(255) PRIMARY KEY,
44+ name VARCHAR(255) UNIQUE NOT NULL,
55+ status VARCHAR(255) NOT NULL,
66+ vcpu INT NOT NULL,
77+ memory INT NOT NULL,
88+ distro VARCHAR(255) NOT NULL,
99+ pid INT,
1010+ mac_address VARCHAR(255) NOT NULL,
1111+ bridge VARCHAR(255) NOT NULL,
1212+ tap VARCHAR(255) NOT NULL,
1313+ api_socket VARCHAR(255) UNIQUE NOT NULL,
1414+ project_dir VARCHAR(255) UNIQUE,
1515+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
1616+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
1717+);
···11use anyhow::{anyhow, Context, Result};
22use firecracker_prepare::Distro;
33+use firecracker_state::{entity::virtual_machine::VirtualMachine, repo};
34use owo_colors::OwoColorize;
45use std::fs;
56···1112mod dnsmasq;
1213mod firecracker;
1314mod guest;
1515+pub mod mac;
1416mod network;
1517pub mod types;
16181717-pub fn setup(options: &VmOptions) -> Result<()> {
1919+pub async fn setup(options: &VmOptions, pid: u32) -> Result<()> {
1820 let distro: Distro = options.clone().into();
1921 let app_dir = get_config_dir().with_context(|| "Failed to get configuration directory")?;
20222121- let logfile = format!("{}/firecracker.log", app_dir);
2323+ let name = options.api_socket
2424+ .split('/')
2525+ .last()
2626+ .ok_or_else(|| anyhow!("Failed to extract VM name from API socket path"))?
2727+ .replace("firecracker-", "")
2828+ .replace(".sock", "")
2929+ .to_string();
3030+ let name = match name.is_empty() {
3131+ true => names::Generator::default().next().unwrap(),
3232+ false => name,
3333+ };
3434+3535+ fs::create_dir_all(format!("{}/logs", app_dir))
3636+ .with_context(|| format!("Failed to create logs directory: {}", app_dir))?;
3737+3838+ let logfile = format!("{}/logs/firecracker-{}.log", app_dir, name);
2239 fs::File::create(&logfile)
2340 .with_context(|| format!("Failed to create log file: {}", logfile))?;
2441···7693 let arch = command::run_command("uname", &["-m"], false)?.stdout;
7794 let arch = String::from_utf8_lossy(&arch).trim().to_string();
7895 network::setup_network(options)?;
9696+ dnsmasq::setup_dnsmasq(options)?;
79978098 firecracker::configure(&logfile, &kernel, &rootfs, &arch, &options, distro)?;
819982100 if distro != Distro::NixOS {
8383- guest::configure_guest_network(&key_name)?;
101101+ let guest_ip = format!("{}.firecracker.local", name);
102102+ guest::configure_guest_network(&key_name, &guest_ip)?;
84103 }
104104+ let pool = firecracker_state::create_connection_pool().await?;
105105+ let distro = match distro {
106106+ Distro::Debian => "debian".into(),
107107+ Distro::Alpine => "alpine".into(),
108108+ Distro::NixOS => "nixos".into(),
109109+ Distro::Ubuntu => "ubuntu".into(),
110110+ };
111111+ repo::virtual_machine::create(pool, VirtualMachine {
112112+ vcpu: options.vcpu,
113113+ memory: options.memory,
114114+ api_socket: options.api_socket.clone(),
115115+ bridge: options.bridge.clone(),
116116+ tap: options.tap.clone(),
117117+ mac_address: options.mac_address.clone(),
118118+ name: name.clone(),
119119+ pid: Some(pid),
120120+ distro,
121121+ ..Default::default()
122122+ }).await?;
8512386124 println!("[✓] MicroVM booted and network is configured 🎉");
8712588126 println!("SSH into the VM using the following command:");
8989- println!("{}", "fireup ssh".bright_green());
127127+ println!("{} {}", "fireup ssh".bright_green(), name.bright_green());
9012891129 Ok(())
92130}
+45
crates/firecracker-vm/src/mac.rs
···11+use rand::Rng;
22+33+pub fn generate_unique_mac() -> String {
44+ let mut rng = rand::thread_rng();
55+66+ // First byte must have the least significant bit set to 0 (unicast)
77+ // and the second least significant bit set to 0 (globally unique)
88+ let first_byte = (rng.gen::<u8>() & 0xFC) | 0x02; // Set locally administered bit
99+1010+ // Generate the remaining 5 bytes
1111+ let mut mac_bytes = [0u8; 6];
1212+ mac_bytes[0] = first_byte;
1313+ for i in 1..6 {
1414+ mac_bytes[i] = rng.gen::<u8>();
1515+ }
1616+1717+ // Format as a MAC address (XX:XX:XX:XX:XX:XX)
1818+ format!(
1919+ "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
2020+ mac_bytes[0], mac_bytes[1], mac_bytes[2], mac_bytes[3], mac_bytes[4], mac_bytes[5]
2121+ )
2222+}
2323+2424+#[cfg(test)]
2525+mod tests {
2626+ use super::*;
2727+2828+ #[test]
2929+ fn test_generate_unique_mac() {
3030+ let mac1 = generate_unique_mac();
3131+ let mac2 = generate_unique_mac();
3232+3333+ // Check format (6 pairs of 2 hex digits separated by colons)
3434+ assert!(mac1.chars().count() == 17);
3535+ assert!(mac1.split(':').count() == 6);
3636+3737+ // Check that the MAC is locally administered (second bit of first byte is 1)
3838+ let first_byte = u8::from_str_radix(&mac1[0..2], 16).unwrap();
3939+ assert_eq!(first_byte & 0x02, 0x02);
4040+ assert_eq!(first_byte & 0x01, 0x00); // Unicast
4141+4242+ // Check uniqueness
4343+ assert_ne!(mac1, mac2);
4444+ }
4545+}