From f71a343f957ad02e740462e01a65032bdcba86c9 Mon Sep 17 00:00:00 2001 From: Joel Capitao Date: Tue, 6 Jan 2026 18:42:50 +0100 Subject: [PATCH] install: support configuring sysroot.bls-append-except-default Add a new [install.ostree] configuration section to allow setting the ostree sysroot.bls-append-except-default option during installation. Closes: https://github.com/bootc-dev/bootc/issues/1710 Signed-off-by: Joel Capitao Co-authored-by: Jean-Baptiste Trystram Assisted-by: Claude (Sonnet 4) --- crates/lib/src/install.rs | 16 +++++- crates/lib/src/install/config.rs | 67 +++++++++++++++++++++++ crates/ostree-ext/src/lib.rs | 1 + crates/ostree-ext/src/repo_options.rs | 30 ++++++++++ crates/tests-integration/src/container.rs | 16 ++++++ docs/src/man/bootc-install-config.5.md | 13 ++++- 6 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 crates/ostree-ext/src/repo_options.rs diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index 105e2b3c1..00a49581e 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -795,7 +795,21 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result crate::lsm::ensure_dir_labeled(rootfs_dir, "boot", None, 0o755.into(), sepolicy)?; } - for (k, v) in DEFAULT_REPO_CONFIG.iter() { + // Build the list of ostree repo config options: defaults + install config + let ostree_opts = state + .install_config + .as_ref() + .and_then(|c| c.ostree.as_ref()) + .into_iter() + .flat_map(|o| o.to_config_tuples()); + + let repo_config: Vec<_> = DEFAULT_REPO_CONFIG + .iter() + .copied() + .chain(ostree_opts) + .collect(); + + for (k, v) in repo_config.iter() { Command::new("ostree") .args(["config", "--repo", "ostree/repo", "set", k, v]) .cwd_dir(rootfs_dir.try_clone()?) diff --git a/crates/lib/src/install/config.rs b/crates/lib/src/install/config.rs index bdeecb459..4d3f13293 100644 --- a/crates/lib/src/install/config.rs +++ b/crates/lib/src/install/config.rs @@ -58,6 +58,9 @@ pub(crate) struct BasicFilesystems { // pub(crate) esp: Option, } +/// Configuration for ostree repository +pub(crate) type OstreeRepoOpts = ostree_ext::repo_options::RepoOptions; + /// The serialized [install] section #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename = "install", rename_all = "kebab-case", deny_unknown_fields)] @@ -73,6 +76,8 @@ pub(crate) struct InstallConfiguration { pub(crate) kargs: Option>, /// Supported architectures for this configuration pub(crate) match_architectures: Option>, + /// Ostree repository configuration + pub(crate) ostree: Option, } fn merge_basic(s: &mut Option, o: Option, _env: &EnvProperties) { @@ -119,6 +124,17 @@ impl Mergeable for BasicFilesystems { } } +impl Mergeable for OstreeRepoOpts { + /// Apply any values in other, overriding any existing values in `self`. + fn merge(&mut self, other: Self, env: &EnvProperties) { + merge_basic( + &mut self.bls_append_except_default, + other.bls_append_except_default, + env, + ) + } +} + impl Mergeable for InstallConfiguration { /// Apply any values in other, overriding any existing values in `self`. fn merge(&mut self, other: Self, env: &EnvProperties) { @@ -133,6 +149,7 @@ impl Mergeable for InstallConfiguration { #[cfg(feature = "install-to-disk")] merge_basic(&mut self.block, other.block, env); self.filesystem.merge(other.filesystem, env); + self.ostree.merge(other.ostree, env); if let Some(other_kargs) = other.kargs { self.kargs .get_or_insert_with(Default::default) @@ -572,4 +589,54 @@ root-fs-type = "xfs" ) ); } + + #[test] + fn test_parse_ostree() { + let env = EnvProperties { + sys_arch: "x86_64".to_string(), + }; + + // Table-driven test cases for parsing bls-append-except-default + let parse_cases = [ + ("console=ttyS0", "console=ttyS0"), + ("console=ttyS0,115200n8", "console=ttyS0,115200n8"), + ("rd.lvm.lv=vg/root", "rd.lvm.lv=vg/root"), + ]; + for (input, expected) in parse_cases { + let toml_str = format!( + r#"[install.ostree] +bls-append-except-default = "{input}" +"# + ); + let c: InstallConfigurationToplevel = toml::from_str(&toml_str).unwrap(); + assert_eq!( + c.install + .unwrap() + .ostree + .unwrap() + .bls_append_except_default + .unwrap(), + expected + ); + } + + // Test merging: other config should override original + let mut install: InstallConfiguration = toml::from_str( + r#"[ostree] +bls-append-except-default = "console=ttyS0" +"#, + ) + .unwrap(); + let other = InstallConfiguration { + ostree: Some(OstreeRepoOpts { + bls_append_except_default: Some("console=tty0".to_string()), + }), + ..Default::default() + }; + install.merge(other, &env); + assert_eq!( + install.ostree.unwrap().bls_append_except_default.unwrap(), + "console=tty0" + ); + } } diff --git a/crates/ostree-ext/src/lib.rs b/crates/ostree-ext/src/lib.rs index 0c53ec618..5de91010f 100644 --- a/crates/ostree-ext/src/lib.rs +++ b/crates/ostree-ext/src/lib.rs @@ -47,6 +47,7 @@ pub mod ostree_prepareroot; pub mod refescape; #[doc(hidden)] pub mod repair; +pub mod repo_options; pub mod sysroot; pub mod tar; pub mod tokio_util; diff --git a/crates/ostree-ext/src/repo_options.rs b/crates/ostree-ext/src/repo_options.rs new file mode 100644 index 000000000..6def00f1a --- /dev/null +++ b/crates/ostree-ext/src/repo_options.rs @@ -0,0 +1,30 @@ +//! Configuration options for an ostree repository + +use serde::{Deserialize, Serialize}; + +/// Configuration options for an ostree repository. +/// +/// This struct represents configurable options for an ostree repository +/// that can be set via the `ostree config set` command. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case", default)] +pub struct RepoOptions { + /// Boot Loader Spec entries that should append arguments only for non-default entries. + /// + /// Corresponds to the `sysroot.bls-append-except-default` ostree config key. + #[serde(skip_serializing_if = "Option::is_none")] + pub bls_append_except_default: Option, +} + +impl RepoOptions { + /// Returns an iterator of (key, value) tuples for ostree repo configuration. + /// + /// Each tuple represents an ostree config key and its value, suitable for + /// passing to `ostree config set`. + pub fn to_config_tuples(&self) -> impl Iterator { + self.bls_append_except_default + .as_ref() + .map(|v| ("sysroot.bls-append-except-default", v.as_str())) + .into_iter() + } +} diff --git a/crates/tests-integration/src/container.rs b/crates/tests-integration/src/container.rs index 1429ca995..2063aac2e 100644 --- a/crates/tests-integration/src/container.rs +++ b/crates/tests-integration/src/container.rs @@ -102,9 +102,16 @@ pub(crate) fn test_bootc_install_config() -> Result<()> { } pub(crate) fn test_bootc_install_config_all() -> Result<()> { + #[derive(Deserialize)] + #[serde(rename_all = "kebab-case")] + struct TestOstreeConfig { + bls_append_except_default: Option, + } + #[derive(Deserialize)] struct TestInstallConfig { kargs: Vec, + ostree: Option, } let config_d = std::path::Path::new("/run/bootc/install/"); @@ -113,6 +120,8 @@ pub(crate) fn test_bootc_install_config_all() -> Result<()> { let content = indoc! {r#" [install] kargs = ["karg1=1", "karg2=2"] + [install.ostree] + bls-append-except-default = "grub_users=\"\"" "#}; std::fs::write(&test_toml_path, content)?; defer! { @@ -124,6 +133,13 @@ pub(crate) fn test_bootc_install_config_all() -> Result<()> { let config: TestInstallConfig = serde_json::from_str(&config).context("Parsing install config")?; assert_eq! {config.kargs, vec!["karg1=1".to_string(), "karg2=2".to_string(), "localtestkarg=somevalue".to_string(), "otherlocalkarg=42".to_string()]}; + assert_eq!( + config + .ostree + .as_ref() + .and_then(|o| o.bls_append_except_default.as_deref()), + Some("grub_users=\"\"") + ); Ok(()) } diff --git a/docs/src/man/bootc-install-config.5.md b/docs/src/man/bootc-install-config.5.md index b4343784f..2080e53a7 100644 --- a/docs/src/man/bootc-install-config.5.md +++ b/docs/src/man/bootc-install-config.5.md @@ -16,7 +16,7 @@ that can be overridden in a derived container image. This is the only defined toplevel table. -The `install` section supports two subfields: +The `install` section supports these subfields: - `block`: An array of supported `to-disk` backends enabled by this base container image; if not specified, this will just be `direct`. The only other supported value is `tpm2-luks`. @@ -24,6 +24,7 @@ The `install` section supports two subfields: - `filesystem`: See below. - `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. # filesystem @@ -37,6 +38,14 @@ There is one valid field: `type`: This can be any basic Linux filesystem with a `mkfs.$fstype`. For example, `ext4`, `xfs`, etc. +# ostree + +Configuration options for the ostree repository. There is one valid field: + +- `bls-append-except-default`: A string of kernel arguments that will be appended to + Boot Loader Spec entries, except for the default entry. This is useful for configuring + arguments that should only apply to non-default deployments. + # Examples ```toml @@ -44,6 +53,8 @@ There is one valid field: type = "xfs" [install] kargs = ["nosmt", "console=tty0"] +[install.ostree] +bls-append-except-default = 'grub_users=""' ``` # SEE ALSO