diff --git a/crates/kit/src/sshcred.rs b/crates/kit/src/credentials.rs similarity index 96% rename from crates/kit/src/sshcred.rs rename to crates/kit/src/credentials.rs index 6c62f1ddf..961540dd3 100644 --- a/crates/kit/src/sshcred.rs +++ b/crates/kit/src/credentials.rs @@ -1,110 +1,11 @@ -//! SSH credential injection for bootc VMs -//! -//! Injects SSH public keys into VMs via systemd credentials using either SMBIOS -//! firmware variables (preferred) or kernel command-line arguments. Creates systemd -//! tmpfiles.d configuration to set up SSH access during VM boot. +//! Systemd credential injection for bootc VMs //! +//! Provides functions for injecting configuration into VMs via systemd credentials +//! using SMBIOS firmware variables (preferred) or kernel command-line arguments. +//! Supports SSH keys, mount units, environment configuration, and AF_VSOCK setup. use color_eyre::Result; -/// Generate SMBIOS credential string for root SSH access -/// -/// Creates a systemd credential for QEMU's SMBIOS interface. Preferred method -/// as it keeps credentials out of kernel command line and boot logs. -/// -/// Returns a string for use with `qemu -smbios type=11,value="..."` -pub fn smbios_cred_for_root_ssh(pubkey: &str) -> Result { - let k = key_to_root_tmpfiles_d(pubkey); - let encoded = data_encoding::BASE64.encode(k.as_bytes()); - let r = format!("io.systemd.credential.binary:tmpfiles.extra={encoded}"); - Ok(r) -} - -/// Generate kernel command-line argument for root SSH access -/// -/// Creates a systemd credential for kernel command-line delivery. Less secure -/// than SMBIOS method as credentials are visible in /proc/cmdline and boot logs. -/// -/// Returns a string for use in kernel boot parameters. -pub fn karg_for_root_ssh(pubkey: &str) -> Result { - let k = key_to_root_tmpfiles_d(pubkey); - let encoded = data_encoding::BASE64.encode(k.as_bytes()); - let r = format!("systemd.set_credential_binary=tmpfiles.extra:{encoded}"); - Ok(r) -} - -/// Convert SSH public key to systemd tmpfiles.d configuration -/// -/// Generates configuration to create `/root/.ssh` directory (0750) and -/// `/root/.ssh/authorized_keys` file (700) with the Base64-encoded SSH key. -/// Uses `f+~` to append to existing authorized_keys files. -pub fn key_to_root_tmpfiles_d(pubkey: &str) -> String { - let buf = data_encoding::BASE64.encode(pubkey.as_bytes()); - format!("d /root/.ssh 0750 - - -\nf+~ /root/.ssh/authorized_keys 700 - - - {buf}\n") -} - -/// Generate SMBIOS credentials for STORAGE_OPTS configuration -/// -/// Creates a systemd unit that conditionally appends STORAGE_OPTS to /etc/environment -/// (for PAM sessions including SSH), plus a dropin to ensure it runs. -/// -/// Returns a vector with: -/// 1. The unit itself (systemd.extra-unit) -/// 2. A dropin for sysinit.target to pull in the unit -pub fn smbios_creds_for_storage_opts() -> Result> { - // Create systemd unit that conditionally appends to /etc/environment - let unit_content = r#"[Unit] -Description=Setup STORAGE_OPTS for bcvk -DefaultDependencies=no -Before=systemd-user-sessions.service - -[Service] -Type=oneshot -ExecStart=/bin/sh -c 'grep -q STORAGE_OPTS /etc/environment || echo STORAGE_OPTS=additionalimagestore=/run/host-container-storage >> /etc/environment' -RemainAfterExit=yes -"#; - let encoded_unit = data_encoding::BASE64.encode(unit_content.as_bytes()); - let unit_cred = format!( - "io.systemd.credential.binary:systemd.extra-unit.bcvk-storage-opts.service={encoded_unit}" - ); - - // Create dropin for sysinit.target to pull in our unit - let dropin_content = "[Unit]\nWants=bcvk-storage-opts.service\n"; - let encoded_dropin = data_encoding::BASE64.encode(dropin_content.as_bytes()); - let dropin_cred = format!( - "io.systemd.credential.binary:systemd.unit-dropin.sysinit.target~bcvk-storage={encoded_dropin}" - ); - - Ok(vec![unit_cred, dropin_cred]) -} - -/// Generate tmpfiles.d lines for STORAGE_OPTS in systemd contexts -/// -/// Configures STORAGE_OPTS for: -/// - /etc/environment.d/: systemd user manager and user services -/// - /etc/systemd/system.conf.d/: system-level systemd services -pub fn storage_opts_tmpfiles_d_lines() -> String { - concat!( - "f /etc/environment.d/90-bcvk-storage.conf 0644 root root - STORAGE_OPTS=additionalimagestore=/run/host-container-storage\n", - "d /etc/systemd/system.conf.d 0755 root root -\n", - "f /etc/systemd/system.conf.d/90-bcvk-storage.conf 0644 root root - [Manager]\\nDefaultEnvironment=STORAGE_OPTS=additionalimagestore=/run/host-container-storage\n" - ).to_string() -} - -/// Generate SMBIOS credential string for AF_VSOCK systemd notification socket -/// -/// Creates a systemd credential that configures systemd to send notifications -/// via AF_VSOCK instead of the default Unix socket. This enables host-guest -/// communication for debugging VM boot sequences. -/// -/// Returns a string for use with `qemu -smbios type=11,value="..."` -pub fn smbios_cred_for_vsock_notify(host_cid: u32, port: u32) -> String { - format!( - "io.systemd.credential:vmm.notify_socket=vsock-stream:{}:{}", - host_cid, port - ) -} - /// Convert a guest mount path to a systemd unit name /// /// Systemd requires mount unit names to match the mount path with: @@ -174,6 +75,7 @@ pub fn generate_mount_unit(virtiofs_tag: &str, guest_path: &str, readonly: bool) /// 2. A dropin for local-fs.target that wants this mount unit /// /// Returns a vector of SMBIOS credential strings +#[allow(dead_code)] pub fn smbios_creds_for_mount_unit( virtiofs_tag: &str, guest_path: &str, @@ -199,6 +101,105 @@ pub fn smbios_creds_for_mount_unit( Ok(vec![mount_cred, dropin_cred]) } +/// Generate SMBIOS credential string for AF_VSOCK systemd notification socket +/// +/// Creates a systemd credential that configures systemd to send notifications +/// via AF_VSOCK instead of the default Unix socket. This enables host-guest +/// communication for debugging VM boot sequences. +/// +/// Returns a string for use with `qemu -smbios type=11,value="..."` +pub fn smbios_cred_for_vsock_notify(host_cid: u32, port: u32) -> String { + format!( + "io.systemd.credential:vmm.notify_socket=vsock-stream:{}:{}", + host_cid, port + ) +} + +/// Generate SMBIOS credentials for STORAGE_OPTS configuration +/// +/// Creates a systemd unit that conditionally appends STORAGE_OPTS to /etc/environment +/// (for PAM sessions including SSH), plus a dropin to ensure it runs. +/// +/// Returns a vector with: +/// 1. The unit itself (systemd.extra-unit) +/// 2. A dropin for sysinit.target to pull in the unit +pub fn smbios_creds_for_storage_opts() -> Result> { + // Create systemd unit that conditionally appends to /etc/environment + let unit_content = r#"[Unit] +Description=Setup STORAGE_OPTS for bcvk +DefaultDependencies=no +Before=systemd-user-sessions.service + +[Service] +Type=oneshot +ExecStart=/bin/sh -c 'grep -q STORAGE_OPTS /etc/environment || echo STORAGE_OPTS=additionalimagestore=/run/host-container-storage >> /etc/environment' +RemainAfterExit=yes +"#; + let encoded_unit = data_encoding::BASE64.encode(unit_content.as_bytes()); + let unit_cred = format!( + "io.systemd.credential.binary:systemd.extra-unit.bcvk-storage-opts.service={encoded_unit}" + ); + + // Create dropin for sysinit.target to pull in our unit + let dropin_content = "[Unit]\nWants=bcvk-storage-opts.service\n"; + let encoded_dropin = data_encoding::BASE64.encode(dropin_content.as_bytes()); + let dropin_cred = format!( + "io.systemd.credential.binary:systemd.unit-dropin.sysinit.target~bcvk-storage={encoded_dropin}" + ); + + Ok(vec![unit_cred, dropin_cred]) +} + +/// Generate tmpfiles.d lines for STORAGE_OPTS in systemd contexts +/// +/// Configures STORAGE_OPTS for: +/// - /etc/environment.d/: systemd user manager and user services +/// - /etc/systemd/system.conf.d/: system-level systemd services +pub fn storage_opts_tmpfiles_d_lines() -> String { + concat!( + "f /etc/environment.d/90-bcvk-storage.conf 0644 root root - STORAGE_OPTS=additionalimagestore=/run/host-container-storage\n", + "d /etc/systemd/system.conf.d 0755 root root -\n", + "f /etc/systemd/system.conf.d/90-bcvk-storage.conf 0644 root root - [Manager]\\nDefaultEnvironment=STORAGE_OPTS=additionalimagestore=/run/host-container-storage\n" + ).to_string() +} + +/// Generate SMBIOS credential string for root SSH access +/// +/// Creates a systemd credential for QEMU's SMBIOS interface. Preferred method +/// as it keeps credentials out of kernel command line and boot logs. +/// +/// Returns a string for use with `qemu -smbios type=11,value="..."` +pub fn smbios_cred_for_root_ssh(pubkey: &str) -> Result { + let k = key_to_root_tmpfiles_d(pubkey); + let encoded = data_encoding::BASE64.encode(k.as_bytes()); + let r = format!("io.systemd.credential.binary:tmpfiles.extra={encoded}"); + Ok(r) +} + +/// Generate kernel command-line argument for root SSH access +/// +/// Creates a systemd credential for kernel command-line delivery. Less secure +/// than SMBIOS method as credentials are visible in /proc/cmdline and boot logs. +/// +/// Returns a string for use in kernel boot parameters. +#[allow(dead_code)] +pub fn karg_for_root_ssh(pubkey: &str) -> Result { + let k = key_to_root_tmpfiles_d(pubkey); + let encoded = data_encoding::BASE64.encode(k.as_bytes()); + let r = format!("systemd.set_credential_binary=tmpfiles.extra:{encoded}"); + Ok(r) +} + +/// Convert SSH public key to systemd tmpfiles.d configuration +/// +/// Generates configuration to create `/root/.ssh` directory (0750) and +/// `/root/.ssh/authorized_keys` file (700) with the Base64-encoded SSH key. +/// Uses `f+~` to append to existing authorized_keys files. +pub fn key_to_root_tmpfiles_d(pubkey: &str) -> String { + let buf = data_encoding::BASE64.encode(pubkey.as_bytes()); + format!("d /root/.ssh 0750 - - -\nf+~ /root/.ssh/authorized_keys 700 - - - {buf}\n") +} + #[cfg(test)] mod tests { use data_encoding::BASE64; diff --git a/crates/kit/src/libvirt/run.rs b/crates/kit/src/libvirt/run.rs index 28edb4ffc..edf973b69 100644 --- a/crates/kit/src/libvirt/run.rs +++ b/crates/kit/src/libvirt/run.rs @@ -736,9 +736,9 @@ fn process_bind_mounts( domain_builder = domain_builder.with_virtiofs_filesystem(virtiofs_fs); // Generate SMBIOS credential for mount unit (without dropin) - let unit_name = crate::sshcred::guest_path_to_unit_name(&bind_mount.guest_path); + let unit_name = crate::credentials::guest_path_to_unit_name(&bind_mount.guest_path); let mount_unit_content = - crate::sshcred::generate_mount_unit(&tag, &bind_mount.guest_path, readonly); + crate::credentials::generate_mount_unit(&tag, &bind_mount.guest_path, readonly); let encoded_mount = data_encoding::BASE64.encode(mount_unit_content.as_bytes()); let mount_cred = format!("io.systemd.credential.binary:systemd.extra-unit.{unit_name}={encoded_mount}"); @@ -916,13 +916,13 @@ fn create_libvirt_domain_from_disk( // Generate SMBIOS credential for SSH key injection and systemd environment configuration // Combine SSH key setup and storage opts for systemd contexts - let mut tmpfiles_content = crate::sshcred::key_to_root_tmpfiles_d(&public_key_content); - tmpfiles_content.push_str(&crate::sshcred::storage_opts_tmpfiles_d_lines()); + let mut tmpfiles_content = crate::credentials::key_to_root_tmpfiles_d(&public_key_content); + tmpfiles_content.push_str(&crate::credentials::storage_opts_tmpfiles_d_lines()); let encoded = data_encoding::BASE64.encode(tmpfiles_content.as_bytes()); let smbios_cred = format!("io.systemd.credential.binary:tmpfiles.extra={encoded}"); // Generate SMBIOS credentials for storage opts unit (handles /etc/environment for PAM/SSH) - let storage_opts_creds = crate::sshcred::smbios_creds_for_storage_opts()?; + let storage_opts_creds = crate::credentials::smbios_creds_for_storage_opts()?; let memory = parse_memory_to_mb(&opts.memory.memory)?; @@ -1064,9 +1064,9 @@ fn create_libvirt_domain_from_disk( // Generate mount unit for automatic mounting at /run/host-container-storage let guest_mount_path = "/run/host-container-storage"; - let unit_name = crate::sshcred::guest_path_to_unit_name(guest_mount_path); + let unit_name = crate::credentials::guest_path_to_unit_name(guest_mount_path); let mount_unit_content = - crate::sshcred::generate_mount_unit("hoststorage", guest_mount_path, true); + crate::credentials::generate_mount_unit("hoststorage", guest_mount_path, true); let encoded_mount = data_encoding::BASE64.encode(mount_unit_content.as_bytes()); let mount_cred = format!("io.systemd.credential.binary:systemd.extra-unit.{unit_name}={encoded_mount}"); diff --git a/crates/kit/src/main.rs b/crates/kit/src/main.rs index 815f092c3..ef5bf3e1d 100644 --- a/crates/kit/src/main.rs +++ b/crates/kit/src/main.rs @@ -13,6 +13,7 @@ mod cli_json; mod common_opts; mod container_entrypoint; pub(crate) mod containerenv; +mod credentials; mod domain_list; mod envdetect; mod ephemeral; @@ -29,8 +30,6 @@ mod qemu_img; mod run_ephemeral; mod run_ephemeral_ssh; mod ssh; -#[allow(dead_code)] -mod sshcred; mod status_monitor; mod supervisor_status; pub(crate) mod systemd; diff --git a/crates/kit/src/qemu.rs b/crates/kit/src/qemu.rs index a057ebe1b..c483c6710 100644 --- a/crates/kit/src/qemu.rs +++ b/crates/kit/src/qemu.rs @@ -845,7 +845,7 @@ impl RunningQemu { let creds = sd_notification .as_ref() .map(|sd| { - let cred = crate::sshcred::smbios_cred_for_vsock_notify(2, sd.port.port()); + let cred = crate::credentials::smbios_cred_for_vsock_notify(2, sd.port.port()); vec![cred] }) .unwrap_or_default(); diff --git a/crates/kit/src/run_ephemeral.rs b/crates/kit/src/run_ephemeral.rs index f1d157800..ed1b9fd04 100644 --- a/crates/kit/src/run_ephemeral.rs +++ b/crates/kit/src/run_ephemeral.rs @@ -788,6 +788,8 @@ pub(crate) async fn run_impl(opts: RunEphemeralOpts) -> Result<()> { // Process host mounts and prepare virtiofsd instances for each using async manager let mut additional_mounts = Vec::new(); + // Collect mount unit credentials to inject via SMBIOS instead of writing to filesystem + let mut mount_unit_smbios_creds = Vec::new(); debug!( "Checking for host mounts directory: /run/host-mounts exists = {}", @@ -798,8 +800,7 @@ pub(crate) async fn run_impl(opts: RunEphemeralOpts) -> Result<()> { Utf8Path::new("/run/systemd-units").exists() ); - let target_unitdir = "/run/source-image/etc/systemd/system"; - + let mut mount_unit_names = Vec::new(); if Utf8Path::new("/run/host-mounts").exists() { for entry in fs::read_dir("/run/host-mounts")? { let entry = entry?; @@ -832,73 +833,48 @@ pub(crate) async fn run_impl(opts: RunEphemeralOpts) -> Result<()> { }; additional_mounts.push((virtiofsd_config, tag.clone())); - // Create individual .mount unit for this virtiofs mount + // Generate mount unit via SMBIOS credentials instead of writing to filesystem let mount_point = format!("/run/virtiofs-mnt-{}", mount_name_str); - - // Use systemd-escape to properly escape the mount path - let escaped_path = Command::new("systemd-escape") - .args(["-p", &mount_point]) - .output() - .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string()) - .unwrap_or_else(|_| { - // Fallback if systemd-escape is not available - mount_point - .replace("/", "-") - .trim_start_matches('-') - .to_string() - }); - - let mount_unit_name = format!("{}.mount", escaped_path); - let mount_options = if is_readonly { "ro" } else { "defaults" }; - - let mount_unit_content = format!( - r#"[Unit] -Description=Mount virtiofs {} -DefaultDependencies=no -After=systemd-remount-fs.service -Before=local-fs.target shutdown.target - -[Mount] -What={} -Where={} -Type=virtiofs -Options={} - -[Install] -WantedBy=local-fs.target -"#, - mount_name_str, tag, mount_point, mount_options + let unit_name = crate::credentials::guest_path_to_unit_name(&mount_point); + let mount_unit_content = + crate::credentials::generate_mount_unit(&tag, &mount_point, is_readonly); + let encoded_mount = data_encoding::BASE64.encode(mount_unit_content.as_bytes()); + + // Create SMBIOS credential for the mount unit + let mount_cred = format!( + "io.systemd.credential.binary:systemd.extra-unit.{unit_name}={encoded_mount}" ); + mount_unit_smbios_creds.push(mount_cred); - let mount_unit_path = format!("{target_unitdir}/{mount_unit_name}"); - fs::write(&mount_unit_path, mount_unit_content) - .with_context(|| format!("Failed to write mount unit to {}", mount_unit_path))?; - - // Enable the mount unit by creating symlink in local-fs.target.wants/ - let wants_dir = format!("{target_unitdir}/local-fs.target.wants"); - fs::create_dir_all(&wants_dir)?; - let wants_link = format!("{}/{}", wants_dir, mount_unit_name); - let relative_target = format!("../{}", mount_unit_name); - std::os::unix::fs::symlink(&relative_target, &wants_link)?; - - // Create mount point directory in the image - let image_mount_point = format!("/run/source-image{}", mount_point); - fs::create_dir_all(&image_mount_point).ok(); + // Collect unit name for the local-fs.target dropin + mount_unit_names.push(unit_name.clone()); debug!( - "Generated mount unit: {} (enabled in local-fs.target)", - mount_unit_name + "Generated SMBIOS credential for mount unit: {} ({})", + unit_name, mode ); } } + // If we have mount units, create a single dropin for local-fs.target + if !mount_unit_names.is_empty() { + let wants_list = mount_unit_names.join(" "); + let dropin_content = format!("[Unit]\nWants={}\n", wants_list); + let encoded_dropin = data_encoding::BASE64.encode(dropin_content.as_bytes()); + let dropin_cred = format!( + "io.systemd.credential.binary:systemd.unit-dropin.local-fs.target~bcvk-mounts={encoded_dropin}" + ); + mount_unit_smbios_creds.push(dropin_cred); + debug!( + "Created local-fs.target dropin for {} mount units", + mount_unit_names.len() + ); + } + // Handle --execute: pipes will be created when adding to qemu_config later // No need to create files anymore as we're using pipes - let default_wantsdir = format!("{target_unitdir}/default.target.wants"); - fs::create_dir_all(&default_wantsdir)?; - - // Create systemd unit to stream journal to virtio-serial device + // Create systemd unit to stream journal to virtio-serial device via SMBIOS credential let journal_stream_unit = r#"[Unit] Description=Stream systemd journal to host via virtio-serial DefaultDependencies=no @@ -916,17 +892,23 @@ RestartSec=1s [Install] WantedBy=sysinit.target "#; - let journal_unit_path = format!("{target_unitdir}/bcvk-journal-stream.service"); - tokio::fs::write(&journal_unit_path, journal_stream_unit).await?; - debug!("Created journal streaming unit at {journal_unit_path}"); - - // Enable the journal streaming unit - let sysinit_wantsdir = format!("{target_unitdir}/sysinit.target.wants"); - tokio::fs::create_dir_all(&sysinit_wantsdir).await?; - let journal_wants_link = format!("{sysinit_wantsdir}/bcvk-journal-stream.service"); - tokio::fs::symlink("../bcvk-journal-stream.service", &journal_wants_link).await?; - debug!("Enabled journal streaming unit in sysinit.target.wants"); + let encoded_journal = data_encoding::BASE64.encode(journal_stream_unit.as_bytes()); + let journal_cred = format!( + "io.systemd.credential.binary:systemd.extra-unit.bcvk-journal-stream.service={encoded_journal}" + ); + mount_unit_smbios_creds.push(journal_cred); + debug!("Generated SMBIOS credential for journal streaming unit"); + + // Create dropin for sysinit.target to enable journal streaming + let journal_dropin = "[Unit]\nWants=bcvk-journal-stream.service\n"; + let encoded_dropin = data_encoding::BASE64.encode(journal_dropin.as_bytes()); + let dropin_cred = format!( + "io.systemd.credential.binary:systemd.unit-dropin.sysinit.target~bcvk-journal={encoded_dropin}" + ); + mount_unit_smbios_creds.push(dropin_cred); + debug!("Created sysinit.target dropin to enable journal streaming unit"); + // Create execute units via SMBIOS credentials if needed match opts.common.execute.as_slice() { [] => {} elts => { @@ -946,8 +928,7 @@ StandardError=inherit service_content.push_str(&format!("ExecStart={elt}\n")); } - let service_finish = format!( - r#"[Unit] + let service_finish = r#"[Unit] Description=Execute Script Service Completion After=bootc-execute.service Requires=dev-virtio\\x2dports-executestatus.device @@ -957,24 +938,34 @@ Type=oneshot ExecStart=systemctl show bootc-execute ExecStart=systemctl poweroff StandardOutput=file:/dev/virtio-ports/executestatus -"# - ); +"#; - let service_path = format!("{target_unitdir}/bootc-execute.service"); - fs::write(service_path, service_content)?; - let service_path = format!("{target_unitdir}/bootc-execute-finish.service"); - fs::write(service_path, service_finish)?; + // Inject execute units via SMBIOS credentials + let encoded_execute = data_encoding::BASE64.encode(service_content.as_bytes()); + let execute_cred = format!( + "io.systemd.credential.binary:systemd.extra-unit.bootc-execute.service={encoded_execute}" + ); + mount_unit_smbios_creds.push(execute_cred); - for svc in ["bootc-execute.service", "bootc-execute-finish.service"] { - let wants_link = format!("{default_wantsdir}/{svc}"); - debug!("Creating execute service symlink: {}", &wants_link); - std::os::unix::fs::symlink(format!("../{svc}"), wants_link)?; - } + let encoded_finish = data_encoding::BASE64.encode(service_finish.as_bytes()); + let finish_cred = format!( + "io.systemd.credential.binary:systemd.extra-unit.bootc-execute-finish.service={encoded_finish}" + ); + mount_unit_smbios_creds.push(finish_cred); + + // Create dropin for default.target to enable execute services + let execute_dropin = + "[Unit]\nWants=bootc-execute.service bootc-execute-finish.service\n"; + let encoded_dropin = data_encoding::BASE64.encode(execute_dropin.as_bytes()); + let dropin_cred = format!( + "io.systemd.credential.binary:systemd.unit-dropin.default.target~bcvk-execute={encoded_dropin}" + ); + mount_unit_smbios_creds.push(dropin_cred); + debug!("Generated SMBIOS credentials for execute units"); } } - // Copy systemd units if provided (after mount units have been generated) - // Also inject if we created mount units that need to be copied + // Copy systemd units if provided (for --systemd-units-dir option) inject_systemd_units()?; // Prepare main virtiofsd config for the source image (will be spawned by QEMU) @@ -1003,7 +994,7 @@ StandardOutput=file:/dev/virtio-ports/executestatus let key_pair = crate::ssh::generate_default_keypair()?; // Create credential and add to kernel args let pubkey = std::fs::read_to_string(key_pair.public_key_path.as_path())?; - let credential = crate::sshcred::smbios_cred_for_root_ssh(&pubkey)?; + let credential = crate::credentials::smbios_cred_for_root_ssh(&pubkey)?; qemu_config.add_smbios_credential(credential); } // Build kernel command line @@ -1059,22 +1050,30 @@ StandardOutput=file:/dev/virtio-ports/executestatus "swap".into(), crate::to_disk::Format::Raw, ); - let svc = format!( - r#"[Unit] + + // Create swap unit via SMBIOS credential + let svc = r#"[Unit] Description=bcvk ephemeral swap [Swap] What=/dev/disk/by-id/virtio-swap Options= -"# - ); - +"#; let service_name = r#"dev-disk-by\x2did-virtio\x2dswap.swap"#; - let service_path = format!("{target_unitdir}/{service_name}"); - fs::write(&service_path, svc)?; + let encoded_swap = data_encoding::BASE64.encode(svc.as_bytes()); + let swap_cred = format!( + "io.systemd.credential.binary:systemd.extra-unit.{service_name}={encoded_swap}" + ); + mount_unit_smbios_creds.push(swap_cred); - let wants_link = format!("{default_wantsdir}/{service_name}"); - std::os::unix::fs::symlink(format!("../{service_name}"), wants_link)?; + // Create dropin for default.target to enable swap + let swap_dropin = format!("[Unit]\nWants={service_name}\n"); + let encoded_dropin = data_encoding::BASE64.encode(swap_dropin.as_bytes()); + let dropin_cred = format!( + "io.systemd.credential.binary:systemd.unit-dropin.default.target~bcvk-swap={encoded_dropin}" + ); + mount_unit_smbios_creds.push(dropin_cred); + debug!("Generated SMBIOS credential for swap unit"); tmp_swapfile = Some(tmpf); } @@ -1204,6 +1203,13 @@ Options= })?; }; + // Add all SMBIOS credentials for mount units, journal, and execute services + let cred_count = mount_unit_smbios_creds.len(); + for cred in mount_unit_smbios_creds { + qemu_config.add_smbios_credential(cred); + } + debug!("Added {} SMBIOS credentials to QEMU config", cred_count); + debug!("Starting QEMU with systemd debugging enabled"); // Spawn QEMU with all virtiofsd processes handled internally diff --git a/crates/kit/src/to_disk.rs b/crates/kit/src/to_disk.rs index 434aff932..3d85e67af 100644 --- a/crates/kit/src/to_disk.rs +++ b/crates/kit/src/to_disk.rs @@ -226,6 +226,14 @@ impl ToDiskOpts { rm /var/lib/containers -rf ln -sr /var/tmp/containers /var/lib/containers + # Ensure virtiofs mount is available (fallback for older systemd without SMBIOS support) + AIS=/run/virtiofs-mnt-hoststorage/ + if ! mountpoint -q ${AIS} &>/dev/null; then + echo "virtiofs mount not found at ${AIS}, mounting manually..." + mkdir -p ${AIS} + mount -t virtiofs mount_hoststorage ${AIS} -o ro + fi + echo "Starting bootc installation..." echo "Source image: {SOURCE_IMGREF}" echo "Additional args: {BOOTC_ARGS}" @@ -237,7 +245,6 @@ impl ToDiskOpts { # Execute bootc installation, having the outer podman pull from # the virtiofs store on the host, as well as the inner bootc. - AIS=/run/virtiofs-mnt-hoststorage/ export STORAGE_OPTS=additionalimagestore=${AIS} podman run --rm -i ${tty} --privileged --pid=host --net=none -v /sys:/sys:ro \ -v /var/lib/containers:/var/lib/containers -v /dev:/dev -v ${AIS}:${AIS} --security-opt label=type:unconfined_t \