diff --git a/dstack/dstack-util/src/system_setup.rs b/dstack/dstack-util/src/system_setup.rs index 5e22d319e..f9844bd57 100644 --- a/dstack/dstack-util/src/system_setup.rs +++ b/dstack/dstack-util/src/system_setup.rs @@ -2302,6 +2302,45 @@ fn kms_rpc_url(base: &str) -> String { } } +/// Whether `device` carries any signature. Only blkid's "nothing found" (exit +/// 2) counts as no, since a wrong "no" would reformat the application's data. +fn has_filesystem_signature(device: &str) -> bool { + Command::new("blkid") + .args(["-p", device]) + .output() + .map_or(true, |out| out.status.code() != Some(2)) +} + +#[cfg(test)] +mod data_disk_tests { + use super::has_filesystem_signature; + use std::process::Command; + + #[test] + fn a_blank_device_has_no_filesystem_signature() { + if ["blkid", "mkfs.ext4"] + .iter() + .any(|tool| Command::new(tool).arg("-V").output().is_err()) + { + eprintln!("skipping: blkid or mkfs.ext4 is not installed"); + return; + } + let dir = tempfile::tempdir().expect("failed to create temp dir"); + let image = dir.path().join("disk.img"); + std::fs::write(&image, vec![0_u8; 8 << 20]).expect("failed to create image"); + let image = image.to_string_lossy(); + assert!(!has_filesystem_signature(&image)); + let mkfs = Command::new("mkfs.ext4") + .args(["-F", "-q", &image]) + .status(); + assert!( + mkfs.is_ok_and(|status| status.success()), + "mkfs.ext4 failed" + ); + assert!(has_filesystem_signature(&image)); + } +} + impl<'a> Stage0<'a> { fn host_api(&self) -> HostApi { HostApi::new( @@ -2656,16 +2695,31 @@ impl<'a> Stage0<'a> { let disk_initialized = self.is_disk_initialized(opts); - if !disk_initialized { + // A first boot interrupted between luksFormat and mkfs leaves a LUKS + // header with nothing behind it. Initialize it rather than failing, and + // rebooting, on every later boot. + let has_filesystem = if opts.storage_encrypted && disk_initialized { + info!("Mounting encrypted data disk"); + self.open_encrypted_volume(disk_crypt_key, name, opts.storage_discard)?; + let has_filesystem = has_filesystem_signature(&fs_dev); + if !has_filesystem { + warn!("no filesystem behind the LUKS header, initializing the data disk"); + } + has_filesystem + } else { + disk_initialized + }; + + if !has_filesystem { self.vmm .notify_q("boot.progress", "initializing data disk") .await; - if opts.storage_encrypted { + if !opts.storage_encrypted { + info!("Skipping disk encryption as requested by kernel cmdline"); + } else if !disk_initialized { info!("Setting up disk encryption"); self.luks_setup(disk_crypt_key, name, opts.storage_discard)?; - } else { - info!("Skipping disk encryption as requested by kernel cmdline"); } match opts.storage_fs { @@ -2695,10 +2749,7 @@ impl<'a> Stage0<'a> { .notify_q("boot.progress", "mounting data disk") .await; - if opts.storage_encrypted { - info!("Mounting encrypted data disk"); - self.open_encrypted_volume(disk_crypt_key, name, opts.storage_discard)?; - } else { + if !opts.storage_encrypted { info!("Mounting unencrypted data disk"); }