From 474402f545795895c611b941ff7d1d0d04f6b637 Mon Sep 17 00:00:00 2001 From: jbtrystram Date: Thu, 22 Jan 2026 15:33:40 +0100 Subject: [PATCH 1/2] install: Allow setting ostree stateroot in install config Support for configuring the stateroot name through the install configuration file under `[install.ostree]`. The CLI flag will override config file values, as for other options. Partial fix for https://github.com/bootc-dev/bootc/issues/1939 Assisted-by: Opencode (Claude Opus 4.5) Signed-off-by: jbtrystram --- crates/lib/src/install.rs | 6 +++++ crates/lib/src/install/config.rs | 33 ++++++++++++++++++++++++++ docs/src/man/bootc-install-config.5.md | 3 +++ 3 files changed, 42 insertions(+) diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index 00a49581e..542452f7d 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) } } diff --git a/crates/lib/src/install/config.rs b/crates/lib/src/install/config.rs index 4d3f13293..7f4299690 100644 --- a/crates/lib/src/install/config.rs +++ b/crates/lib/src/install/config.rs @@ -78,6 +78,8 @@ 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, } fn merge_basic(s: &mut Option, o: Option, _env: &EnvProperties) { @@ -150,6 +152,7 @@ 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); if let Some(other_kargs) = other.kargs { self.kargs .get_or_insert_with(Default::default) @@ -630,6 +633,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 +643,33 @@ 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"); + } } diff --git a/docs/src/man/bootc-install-config.5.md b/docs/src/man/bootc-install-config.5.md index 2080e53a7..5cdd9b048 100644 --- a/docs/src/man/bootc-install-config.5.md +++ b/docs/src/man/bootc-install-config.5.md @@ -25,6 +25,7 @@ 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`. # filesystem @@ -53,6 +54,8 @@ Configuration options for the ostree repository. There is one valid field: type = "xfs" [install] kargs = ["nosmt", "console=tty0"] +stateroot = "myos" + [install.ostree] bls-append-except-default = 'grub_users=""' ``` From 8e58002ff129633dbef3d331a0e2d3607ce345b6 Mon Sep 17 00:00:00 2001 From: jbtrystram Date: Thu, 22 Jan 2026 15:39:02 +0100 Subject: [PATCH 2/2] install: Allow root and boot mount-specs in config Allow configuring the root and boot filesystem mount specs via the install configuration file under [install]. As for other options, CLI arguments take precedence. For the to-existing-root flow, mount specs from config are ignored. Example configuration: ``` [install] root-mount-spec = "LABEL=rootfs" boot-mount-spec = "UUID=abcd-1234" ``` Fixes https://github.com/bootc-dev/bootc/issues/1939 Assisted-by: Opencode (Claude Opus 4.5) Signed-off-by: jbtrystram --- crates/lib/src/install.rs | 14 +++++- crates/lib/src/install/config.rs | 59 ++++++++++++++++++++++++++ docs/src/man/bootc-install-config.5.md | 10 +++++ 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index 542452f7d..4d6cace28 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -2239,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(), @@ -2320,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 7f4299690..41a77ef53 100644 --- a/crates/lib/src/install/config.rs +++ b/crates/lib/src/install/config.rs @@ -80,6 +80,11 @@ pub(crate) struct InstallConfiguration { 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) { @@ -153,6 +158,8 @@ impl Mergeable for InstallConfiguration { 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) @@ -672,4 +679,56 @@ stateroot = "custom" 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 5cdd9b048..985892ebe 100644 --- a/docs/src/man/bootc-install-config.5.md +++ b/docs/src/man/bootc-install-config.5.md @@ -26,6 +26,13 @@ The `install` section supports these subfields: - `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 @@ -52,9 +59,12 @@ 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=""'