diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index 00a49581e..4d6cace28 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -562,9 +562,15 @@ impl State { } fn stateroot(&self) -> &str { + // CLI takes precedence over config file self.config_opts .stateroot .as_deref() + .or_else(|| { + self.install_config + .as_ref() + .and_then(|c| c.stateroot.as_deref()) + }) .unwrap_or(ostree_ext::container::deploy::STATEROOT_DEFAULT) } } @@ -2233,7 +2239,12 @@ pub(crate) async fn install_to_filesystem( // We support overriding the mount specification for root (i.e. LABEL vs UUID versus // raw paths). // We also support an empty specification as a signal to omit any mountspec kargs. - let root_info = if let Some(s) = fsopts.root_mount_spec { + // CLI takes precedence over config file. + let config_root_mount_spec = state + .install_config + .as_ref() + .and_then(|c| c.root_mount_spec.as_ref()); + let root_info = if let Some(s) = fsopts.root_mount_spec.as_ref().or(config_root_mount_spec) { RootMountInfo { mount_spec: s.to_string(), kargs: Vec::new(), @@ -2314,7 +2325,12 @@ pub(crate) async fn install_to_filesystem( let device_info = bootc_blockdev::partitions_of(Utf8Path::new(&backing_device))?; let rootarg = format!("root={}", root_info.mount_spec); - let mut boot = if let Some(spec) = fsopts.boot_mount_spec { + // CLI takes precedence over config file. + let config_boot_mount_spec = state + .install_config + .as_ref() + .and_then(|c| c.boot_mount_spec.as_ref()); + let mut boot = if let Some(spec) = fsopts.boot_mount_spec.as_ref().or(config_boot_mount_spec) { // An empty boot mount spec signals to omit the mountspec kargs // See https://github.com/bootc-dev/bootc/issues/1441 if spec.is_empty() { diff --git a/crates/lib/src/install/config.rs b/crates/lib/src/install/config.rs index 4d3f13293..41a77ef53 100644 --- a/crates/lib/src/install/config.rs +++ b/crates/lib/src/install/config.rs @@ -78,6 +78,13 @@ pub(crate) struct InstallConfiguration { pub(crate) match_architectures: Option>, /// Ostree repository configuration pub(crate) ostree: Option, + /// The stateroot name to use. Defaults to `default` + pub(crate) stateroot: Option, + /// Source device specification for the root filesystem. + /// For example, `UUID=2e9f4241-229b-4202-8429-62d2302382e1` or `LABEL=rootfs`. + pub(crate) root_mount_spec: Option, + /// Mount specification for the /boot filesystem. + pub(crate) boot_mount_spec: Option, } fn merge_basic(s: &mut Option, o: Option, _env: &EnvProperties) { @@ -150,6 +157,9 @@ impl Mergeable for InstallConfiguration { merge_basic(&mut self.block, other.block, env); self.filesystem.merge(other.filesystem, env); self.ostree.merge(other.ostree, env); + 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); if let Some(other_kargs) = other.kargs { self.kargs .get_or_insert_with(Default::default) @@ -630,6 +640,7 @@ bls-append-except-default = "console=ttyS0" let other = InstallConfiguration { ostree: Some(OstreeRepoOpts { bls_append_except_default: Some("console=tty0".to_string()), + ..Default::default() }), ..Default::default() }; @@ -639,4 +650,85 @@ bls-append-except-default = "console=ttyS0" "console=tty0" ); } + + #[test] + fn test_parse_stateroot() { + let c: InstallConfigurationToplevel = toml::from_str( + r#"[install] +stateroot = "custom" +"#, + ) + .unwrap(); + assert_eq!(c.install.unwrap().stateroot.unwrap(), "custom"); + } + + #[test] + fn test_merge_stateroot() { + let env = EnvProperties { + sys_arch: "x86_64".to_string(), + }; + let mut install: InstallConfiguration = toml::from_str( + r#"stateroot = "original" +"#, + ) + .unwrap(); + let other = InstallConfiguration { + stateroot: Some("newroot".to_string()), + ..Default::default() + }; + install.merge(other, &env); + assert_eq!(install.stateroot.unwrap(), "newroot"); + } + + #[test] + fn test_parse_mount_specs() { + let c: InstallConfigurationToplevel = toml::from_str( + r#"[install] +root-mount-spec = "LABEL=rootfs" +boot-mount-spec = "UUID=abcd-1234" +"#, + ) + .unwrap(); + let install = c.install.unwrap(); + assert_eq!(install.root_mount_spec.unwrap(), "LABEL=rootfs"); + assert_eq!(install.boot_mount_spec.unwrap(), "UUID=abcd-1234"); + } + + #[test] + fn test_merge_mount_specs() { + let env = EnvProperties { + sys_arch: "x86_64".to_string(), + }; + let mut install: InstallConfiguration = toml::from_str( + r#"root-mount-spec = "UUID=old" +boot-mount-spec = "UUID=oldboot" +"#, + ) + .unwrap(); + let other = InstallConfiguration { + root_mount_spec: Some("LABEL=newroot".to_string()), + ..Default::default() + }; + install.merge(other, &env); + // root_mount_spec should be overridden + assert_eq!(install.root_mount_spec.as_deref().unwrap(), "LABEL=newroot"); + // boot_mount_spec should remain unchanged + assert_eq!(install.boot_mount_spec.as_deref().unwrap(), "UUID=oldboot"); + } + + /// Empty mount specs are valid and signal to omit mount kargs entirely. + /// See https://github.com/bootc-dev/bootc/issues/1441 + #[test] + fn test_parse_empty_mount_specs() { + let c: InstallConfigurationToplevel = toml::from_str( + r#"[install] +root-mount-spec = "" +boot-mount-spec = "" +"#, + ) + .unwrap(); + let install = c.install.unwrap(); + assert_eq!(install.root_mount_spec.as_deref().unwrap(), ""); + assert_eq!(install.boot_mount_spec.as_deref().unwrap(), ""); + } } diff --git a/docs/src/man/bootc-install-config.5.md b/docs/src/man/bootc-install-config.5.md index 2080e53a7..985892ebe 100644 --- a/docs/src/man/bootc-install-config.5.md +++ b/docs/src/man/bootc-install-config.5.md @@ -25,6 +25,14 @@ The `install` section supports these subfields: - `kargs`: An array of strings; this will be appended to the set of kernel arguments. - `match_architectures`: An array of strings; this filters the install config. - `ostree`: See below. +- `stateroot`: The stateroot name to use. Defaults to `default`. +- `root-mount-spec`: A string specifying the root filesystem mount specification. + For example, `UUID=2e9f4241-229b-4202-8429-62d2302382e1` or `LABEL=rootfs`. + If not provided, the UUID of the target filesystem will be used. + An empty string signals to omit boot mount kargs entirely. +- `boot-mount-spec`: A string specifying the /boot filesystem mount specification. + If not provided and /boot is a separate mount, its UUID will be used. + An empty string signals to omit boot mount kargs entirely. # filesystem @@ -51,8 +59,13 @@ Configuration options for the ostree repository. There is one valid field: ```toml [install.filesystem.root] type = "xfs" + [install] kargs = ["nosmt", "console=tty0"] +stateroot = "myos" +root-mount-spec = "LABEL=rootfs" +boot-mount-spec = "UUID=abcd-1234" + [install.ostree] bls-append-except-default = 'grub_users=""' ```