diff --git a/crates/lib/src/bootloader.rs b/crates/lib/src/bootloader.rs index be1d5882b..1e1157ef8 100644 --- a/crates/lib/src/bootloader.rs +++ b/crates/lib/src/bootloader.rs @@ -105,7 +105,12 @@ pub(crate) fn install_via_bootupd( println!("Installing bootloader via bootupd"); // Build the bootupctl arguments - let mut bootupd_args: Vec<&str> = vec!["backend", "install", "--write-uuid"]; + let mut bootupd_args: Vec<&str> = vec!["backend", "install"]; + if configopts.bootupd_skip_boot_uuid { + bootupd_args.push("--with-static-configs") + } else { + bootupd_args.push("--write-uuid"); + } if let Some(v) = verbose { bootupd_args.push(v); } diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index 3536c14cb..75c151e0d 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -368,6 +368,11 @@ pub(crate) struct InstallConfigOpts { /// The stateroot name to use. Defaults to `default`. #[clap(long)] pub(crate) stateroot: Option, + + /// Don't pass --write-uuid to bootupd during bootloader installation. + #[clap(long)] + #[serde(default)] + pub(crate) bootupd_skip_boot_uuid: bool, } #[derive(Debug, Default, Clone, clap::Parser, Serialize, Deserialize, PartialEq, Eq)] @@ -1512,7 +1517,7 @@ async fn verify_target_fetch( /// Preparation for an install; validates and prepares some (thereafter immutable) global state. async fn prepare_install( - config_opts: InstallConfigOpts, + mut config_opts: InstallConfigOpts, source_opts: InstallSourceOpts, target_opts: InstallTargetOpts, mut composefs_options: InstallComposefsOpts, @@ -1637,8 +1642,17 @@ async fn prepare_install( } let install_config = config::load_config()?; - if install_config.is_some() { + if let Some(ref config) = install_config { tracing::debug!("Loaded install configuration"); + // Merge config file values into config_opts (CLI takes precedence) + // Only apply config file value if CLI didn't explicitly set it + if !config_opts.bootupd_skip_boot_uuid { + config_opts.bootupd_skip_boot_uuid = config + .bootupd + .as_ref() + .and_then(|b| b.skip_boot_uuid) + .unwrap_or(false); + } } else { tracing::debug!("No install configuration found"); } diff --git a/crates/lib/src/install/config.rs b/crates/lib/src/install/config.rs index d62e51c88..3c42abd58 100644 --- a/crates/lib/src/install/config.rs +++ b/crates/lib/src/install/config.rs @@ -61,6 +61,16 @@ pub(crate) struct BasicFilesystems { /// Configuration for ostree repository pub(crate) type OstreeRepoOpts = ostree_ext::repo_options::RepoOptions; +/// Configuration options for bootupd, responsible for setting up the bootloader. +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub(crate) struct Bootupd { + /// Whether to skip writing the boot partition UUID to the bootloader configuration. + /// When true, bootupd is invoked with `--with-static-configs` instead of `--write-uuid`. + /// Defaults to false (UUIDs are written by default). + pub(crate) skip_boot_uuid: Option, +} + /// The serialized `[install]` section #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename = "install", rename_all = "kebab-case", deny_unknown_fields)] @@ -85,6 +95,8 @@ pub(crate) struct InstallConfiguration { pub(crate) root_mount_spec: Option, /// Mount specification for the /boot filesystem. pub(crate) boot_mount_spec: Option, + /// Bootupd configuration + pub(crate) bootupd: Option, } fn merge_basic(s: &mut Option, o: Option, _env: &EnvProperties) { @@ -142,6 +154,13 @@ impl Mergeable for OstreeRepoOpts { } } +impl Mergeable for Bootupd { + /// Apply any values in other, overriding any existing values in `self`. + fn merge(&mut self, other: Self, env: &EnvProperties) { + merge_basic(&mut self.skip_boot_uuid, other.skip_boot_uuid, env) + } +} + impl Mergeable for InstallConfiguration { /// Apply any values in other, overriding any existing values in `self`. fn merge(&mut self, other: Self, env: &EnvProperties) { @@ -160,6 +179,7 @@ impl Mergeable for InstallConfiguration { merge_basic(&mut self.stateroot, other.stateroot, env); merge_basic(&mut self.root_mount_spec, other.root_mount_spec, env); merge_basic(&mut self.boot_mount_spec, other.boot_mount_spec, env); + self.bootupd.merge(other.bootupd, env); if let Some(other_kargs) = other.kargs { self.kargs .get_or_insert_with(Default::default) @@ -731,4 +751,62 @@ boot-mount-spec = "" assert_eq!(install.root_mount_spec.as_deref().unwrap(), ""); assert_eq!(install.boot_mount_spec.as_deref().unwrap(), ""); } + + #[test] + fn test_parse_bootupd_skip_boot_uuid() { + // Test parsing true + let c: InstallConfigurationToplevel = toml::from_str( + r#"[install.bootupd] +skip-boot-uuid = true +"#, + ) + .unwrap(); + assert_eq!( + c.install.unwrap().bootupd.unwrap().skip_boot_uuid.unwrap(), + true + ); + + // Test parsing false + let c: InstallConfigurationToplevel = toml::from_str( + r#"[install.bootupd] +skip-boot-uuid = false +"#, + ) + .unwrap(); + assert_eq!( + c.install.unwrap().bootupd.unwrap().skip_boot_uuid.unwrap(), + false + ); + + // Test default (not specified) is None + let c: InstallConfigurationToplevel = toml::from_str( + r#"[install] +root-fs-type = "xfs" +"#, + ) + .unwrap(); + assert!(c.install.unwrap().bootupd.is_none()); + } + + #[test] + fn test_merge_bootupd_skip_boot_uuid() { + let env = EnvProperties { + sys_arch: "x86_64".to_string(), + }; + let mut install: InstallConfiguration = toml::from_str( + r#"[bootupd] +skip-boot-uuid = false +"#, + ) + .unwrap(); + let other = InstallConfiguration { + bootupd: Some(Bootupd { + skip_boot_uuid: Some(true), + }), + ..Default::default() + }; + install.merge(other, &env); + // skip_boot_uuid should be overridden to true + assert_eq!(install.bootupd.unwrap().skip_boot_uuid.unwrap(), true); + } } diff --git a/docs/src/man/bootc-install-config.5.md b/docs/src/man/bootc-install-config.5.md index 985892ebe..a6f5156c5 100644 --- a/docs/src/man/bootc-install-config.5.md +++ b/docs/src/man/bootc-install-config.5.md @@ -54,6 +54,14 @@ Configuration options for the ostree repository. There is one valid field: Boot Loader Spec entries, except for the default entry. This is useful for configuring arguments that should only apply to non-default deployments. +# bootupd + +Configuration options for bootupd, responsible of setting up the bootloader. +There is only one valid field: +- `skip-boot-uuid`: A boolean that controls whether to skip writing partition UUIDs + to the bootloader configuration. When `true`, bootupd is invoked with `--with-static-configs` + instead of `--write-uuid`. Defaults to `false` (UUIDs are written by default). + # Examples ```toml