From 1cfb5554ae8488b15055cb2388af054378aeff17 Mon Sep 17 00:00:00 2001 From: Joseph Marrero Corchado Date: Fri, 1 May 2026 12:21:20 -0400 Subject: [PATCH] Release 1.15.2 Resolves: #RHEL-172410 --- .gitignore | 2 + ...dd-set-options-for-source-for-source.patch | 1181 ----------------- bootc.spec | 6 +- sources | 4 +- 4 files changed, 5 insertions(+), 1188 deletions(-) delete mode 100644 0001-loader-entries-Add-set-options-for-source-for-source.patch diff --git a/.gitignore b/.gitignore index a612fe3..0f656d7 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,5 @@ /bootc-1.12.1.tar.zstd /bootc-1.15.1.tar.zstd /bootc-1.15.1-vendor.tar.zstd +/bootc-1.15.2.tar.zstd +/bootc-1.15.2-vendor.tar.zstd diff --git a/0001-loader-entries-Add-set-options-for-source-for-source.patch b/0001-loader-entries-Add-set-options-for-source-for-source.patch deleted file mode 100644 index 56a49a3..0000000 --- a/0001-loader-entries-Add-set-options-for-source-for-source.patch +++ /dev/null @@ -1,1181 +0,0 @@ -From 74f4fdd00e91cecd62714057eabf676de577bb7f Mon Sep 17 00:00:00 2001 -From: Joseph Marrero Corchado -Date: Tue, 14 Apr 2026 14:53:22 -0400 -Subject: [PATCH] loader-entries: Add `set-options-for-source` for - source-tracked kargs - -Add a new `bootc loader-entries set-options-for-source` command that -manages kernel arguments from independent sources (e.g. TuneD, admin) -by tracking ownership via `x-options-source-` extension keys in -BLS config files. This solves the problem of karg accumulation on bootc -systems with transient /etc, where tools like TuneD lose their state -files on reboot and cannot track which kargs they previously set. - -The command stages a new deployment with the updated kargs and source -keys. The kargs diff is computed by removing the old source's args and -adding the new ones, preserving all untracked options. Source keys -survive the staging roundtrip via ostree's `bootconfig-extra` -serialization (ostree >= 2026.1, version check active). - -Staged deployment handling: -- No staged deployment: stages based on the booted commit -- Staged deployment exists (e.g. from `bootc upgrade`): replaces it - using the staged commit and origin, preserving pending upgrades while - layering the source kargs change on top - -Includes a multi-reboot TMT integration test covering: input validation, -kargs surviving staging roundtrip, source replacement, multiple sources -coexisting (including pre-reboot), source removal, idempotency, system -kargs preservation, empty --options vs no --options, and staged -deployment interaction with bootc switch. - -Example usage: - bootc loader-entries set-options-for-source --source tuned \ - --options "isolcpus=1-3 nohz_full=1-3" - -Closes: https://github.com/bootc-dev/bootc/issues/899 -See: https://github.com/ostreedev/ostree/pull/3570 - -Assisted-by: OpenCode (Claude Opus 4.6) -Signed-off-by: Joseph Marrero Corchado ---- - crates/lib/src/cli.rs | 65 ++ - crates/lib/src/lib.rs | 1 + - crates/lib/src/loader_entries.rs | 573 ++++++++++++++++++ - ...loader-entries-set-options-for-source.8.md | 81 +++ - docs/src/man/bootc-loader-entries.8.md | 33 + - docs/src/man/bootc.8.md | 1 + - tmt/plans/integration.fmf | 8 + - .../booted/test-loader-entries-source.nu | 267 ++++++++ - tmt/tests/tests.fmf | 5 + - 9 files changed, 1034 insertions(+) - create mode 100644 crates/lib/src/loader_entries.rs - create mode 100644 docs/src/man/bootc-loader-entries-set-options-for-source.8.md - create mode 100644 docs/src/man/bootc-loader-entries.8.md - create mode 100644 tmt/tests/booted/test-loader-entries-source.nu - -diff --git a/crates/lib/src/cli.rs b/crates/lib/src/cli.rs -index b1752c2c..e4fa2a70 100644 ---- a/crates/lib/src/cli.rs -+++ b/crates/lib/src/cli.rs -@@ -716,6 +716,54 @@ pub(crate) enum InternalsOpts { - }, - } - -+/// Options for the `set-options-for-source` subcommand. -+#[derive(Debug, Parser, PartialEq, Eq)] -+pub(crate) struct SetOptionsForSourceOpts { -+ /// The name of the source that owns these kernel arguments. -+ /// -+ /// Must contain only alphanumeric characters, hyphens, or underscores. -+ /// Examples: "tuned", "admin", "bootc-kargs-d" -+ #[clap(long)] -+ pub(crate) source: String, -+ -+ /// The kernel arguments to set for this source. -+ /// -+ /// If not provided, the source is removed and its options are -+ /// dropped from the merged `options` line. -+ #[clap(long)] -+ pub(crate) options: Option, -+} -+ -+/// Operations on Boot Loader Specification (BLS) entries. -+/// -+/// These commands support managing kernel arguments from multiple independent -+/// sources (e.g., TuneD, admin, bootc kargs.d) by tracking argument ownership -+/// via `x-options-source-` extension keys in BLS config files. -+/// -+/// See -+#[derive(Debug, clap::Subcommand, PartialEq, Eq)] -+pub(crate) enum LoaderEntriesOpts { -+ /// Set or update the kernel arguments owned by a specific source. -+ /// -+ /// Each source's arguments are tracked via `x-options-source-` -+ /// keys in BLS config files. The `options` line is recomputed as the -+ /// merge of all tracked sources plus any untracked (pre-existing) options. -+ /// -+ /// This stages a new deployment with the updated kernel arguments. -+ /// -+ /// ## Examples -+ /// -+ /// Add TuneD kernel arguments: -+ /// bootc loader-entries set-options-for-source --source tuned --options "isolcpus=1-3 nohz_full=1-3" -+ /// -+ /// Update TuneD kernel arguments: -+ /// bootc loader-entries set-options-for-source --source tuned --options "isolcpus=0-7" -+ /// -+ /// Remove TuneD kernel arguments: -+ /// bootc loader-entries set-options-for-source --source tuned -+ SetOptionsForSource(SetOptionsForSourceOpts), -+} -+ - #[derive(Debug, clap::Subcommand, PartialEq, Eq)] - pub(crate) enum StateOpts { - /// Remove all ostree deployments from this system -@@ -821,6 +869,11 @@ pub(crate) enum Opt { - /// Stability: This interface may change in the future. - #[clap(subcommand, hide = true)] - Image(ImageOpts), -+ /// Operations on Boot Loader Specification (BLS) entries. -+ /// -+ /// Manage kernel arguments from multiple independent sources. -+ #[clap(subcommand)] -+ LoaderEntries(LoaderEntriesOpts), - /// Execute the given command in the host mount namespace - #[clap(hide = true)] - ExecInHostMountNamespace { -@@ -1899,6 +1952,18 @@ async fn run_from_opt(opt: Opt) -> Result<()> { - crate::install::install_finalize(&root_path).await - } - }, -+ Opt::LoaderEntries(opts) => match opts { -+ LoaderEntriesOpts::SetOptionsForSource(opts) => { -+ let storage = get_storage().await?; -+ let sysroot = storage.get_ostree()?; -+ crate::loader_entries::set_options_for_source_staged( -+ sysroot, -+ &opts.source, -+ opts.options.as_deref(), -+ )?; -+ Ok(()) -+ } -+ }, - Opt::ExecInHostMountNamespace { args } => { - crate::install::exec_in_host_mountns(args.as_slice()) - } -diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs -index 558ca871..69544ad6 100644 ---- a/crates/lib/src/lib.rs -+++ b/crates/lib/src/lib.rs -@@ -82,6 +82,7 @@ pub(crate) mod journal; - mod k8sapitypes; - mod kernel; - mod lints; -+mod loader_entries; - mod lsm; - pub(crate) mod metadata; - mod parsers; -diff --git a/crates/lib/src/loader_entries.rs b/crates/lib/src/loader_entries.rs -new file mode 100644 -index 00000000..0db9f875 ---- /dev/null -+++ b/crates/lib/src/loader_entries.rs -@@ -0,0 +1,573 @@ -+//! # Boot Loader Specification entry management -+//! -+//! This module implements support for merging disparate kernel argument sources -+//! into the single BLS entry `options` field. Each source (e.g., TuneD, admin, -+//! bootc kargs.d) can independently manage its own set of kernel arguments, -+//! which are tracked via `x-options-source-` extension keys in BLS config -+//! files. -+//! -+//! See -+//! See -+ -+use anyhow::{Context, Result, ensure}; -+use bootc_kernel_cmdline::utf8::{Cmdline, CmdlineOwned}; -+use fn_error_context::context; -+use ostree::{gio, glib}; -+use ostree_ext::ostree; -+use std::collections::BTreeMap; -+ -+/// The BLS extension key prefix for source-tracked options. -+const OPTIONS_SOURCE_KEY_PREFIX: &str = "x-options-source-"; -+ -+/// A validated source name (alphanumeric + hyphens + underscores, non-empty). -+/// -+/// This is a newtype wrapper around `String` that enforces validation at -+/// construction time. See . -+struct SourceName(String); -+ -+impl SourceName { -+ /// Parse and validate a source name. -+ fn parse(source: &str) -> Result { -+ ensure!(!source.is_empty(), "Source name must not be empty"); -+ ensure!( -+ source -+ .chars() -+ .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_'), -+ "Source name must contain only alphanumeric characters, hyphens, or underscores" -+ ); -+ Ok(Self(source.to_owned())) -+ } -+ -+ /// The BLS key for this source (e.g., `x-options-source-tuned`). -+ fn bls_key(&self) -> String { -+ format!("{OPTIONS_SOURCE_KEY_PREFIX}{}", self.0) -+ } -+} -+ -+impl std::ops::Deref for SourceName { -+ type Target = str; -+ fn deref(&self) -> &str { -+ &self.0 -+ } -+} -+ -+impl std::fmt::Display for SourceName { -+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -+ f.write_str(&self.0) -+ } -+} -+ -+/// Extract source options from BLS entry content. Parses `x-options-source-*` keys -+/// from the raw BLS text since the ostree BootconfigParser doesn't expose key iteration. -+fn extract_source_options_from_bls(content: &str) -> BTreeMap { -+ let mut sources = BTreeMap::new(); -+ for line in content.lines() { -+ let line = line.trim(); -+ let Some(rest) = line.strip_prefix(OPTIONS_SOURCE_KEY_PREFIX) else { -+ continue; -+ }; -+ let Some((source_name, value)) = rest.split_once(|c: char| c.is_ascii_whitespace()) else { -+ continue; -+ }; -+ let value = value.trim(); -+ if source_name.is_empty() || value.is_empty() { -+ continue; -+ } -+ sources.insert( -+ source_name.to_string(), -+ CmdlineOwned::from(value.to_string()), -+ ); -+ } -+ sources -+} -+ -+/// Compute the merged `options` line from all sources. -+/// -+/// The algorithm: -+/// 1. Start with the current options line -+/// 2. Remove all options that belong to the old value of the specified source -+/// 3. Add the new options for the specified source -+/// -+/// Options not tracked by any source are preserved as-is. -+fn compute_merged_options( -+ current_options: &str, -+ source_options: &BTreeMap, -+ target_source: &SourceName, -+ new_options: Option<&str>, -+) -> CmdlineOwned { -+ let mut merged = CmdlineOwned::from(current_options.to_owned()); -+ -+ // Remove old options from the target source (if it was previously tracked) -+ if let Some(old_source_opts) = source_options.get(&**target_source) { -+ for param in old_source_opts.iter() { -+ merged.remove_exact(¶m); -+ } -+ } -+ -+ // Add new options for the target source -+ if let Some(new_opts) = new_options.filter(|v| !v.is_empty()) { -+ let new_cmdline = Cmdline::from(new_opts); -+ for param in new_cmdline.iter() { -+ merged.add(¶m); -+ } -+ } -+ -+ merged -+} -+ -+/// Read x-options-source-* keys from the staged deployment data file. -+/// -+/// When a deployment is staged, ostree serializes any extension BLS keys into -+/// the "bootconfig-extra" field of the staged deployment GVariant at -+/// /run/ostree/staged-deployment. This function reads that file, extracts the -+/// bootconfig-extra dict, and returns all x-options-source-* entries. -+/// -+/// This is needed to discover sources set by previous calls to -+/// set-options-for-source in the same boot cycle, since the staged BLS entry -+/// doesn't exist on disk yet (finalization writes it at shutdown). -+fn read_staged_bootconfig_extra_sources( -+ sysroot: &ostree::Sysroot, -+) -> Result> { -+ let mut sources = BTreeMap::new(); -+ let sysroot_dir = crate::utils::sysroot_dir(sysroot)?; -+ -+ // The staged deployment data file is written by ostree during -+ // stage_tree_with_options() and lives under /run/ostree/. -+ let data = match sysroot_dir.open("run/ostree/staged-deployment") { -+ Ok(mut f) => { -+ let mut buf = Vec::new(); -+ std::io::Read::read_to_end(&mut f, &mut buf) -+ .context("Reading staged deployment data")?; -+ buf -+ } -+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(sources), -+ Err(e) => return Err(anyhow::Error::new(e).context("Opening staged deployment data")), -+ }; -+ -+ // The staged deployment file is a GVariant of type a{sv}. -+ let variant = glib::Variant::from_data_with_type(&data, glib::VariantTy::VARDICT); -+ let dict = glib::VariantDict::new(Some(&variant)); -+ -+ // Look up "bootconfig-extra" which is stored as a{ss} inside the a{sv} dict. -+ if let Some(extra) = dict.lookup_value("bootconfig-extra", None) { -+ // Handle both direct a{ss} and variant-wrapped a{ss} -+ let inner = if extra.type_().as_str() == "v" { -+ extra.child_value(0) -+ } else { -+ extra -+ }; -+ if inner.type_().as_str() == "a{ss}" { -+ for i in 0..inner.n_children() { -+ let entry = inner.child_value(i); -+ let key: String = entry.child_value(0).get().ok_or_else(|| { -+ anyhow::anyhow!("Unexpected type for key in bootconfig-extra entry") -+ })?; -+ let value: String = entry.child_value(1).get().ok_or_else(|| { -+ anyhow::anyhow!("Unexpected type for value in bootconfig-extra entry") -+ })?; -+ if let Some(name) = key.strip_prefix(OPTIONS_SOURCE_KEY_PREFIX) { -+ if !value.is_empty() { -+ sources.insert(name.to_string(), CmdlineOwned::from(value)); -+ } -+ } -+ } -+ } -+ } -+ -+ Ok(sources) -+} -+ -+/// Read the BLS entry file content for a deployment from /boot/loader/entries/. -+/// -+/// Returns `Ok(Some(content))` if the entry is found, `Ok(None)` if no matching -+/// entry exists, or `Err` if there's an I/O error. -+/// -+/// We match by checking the `options` line for the deployment's ostree path -+/// (which includes the stateroot, bootcsum, and bootserial). -+fn read_bls_entry_for_deployment( -+ sysroot: &ostree::Sysroot, -+ deployment: &ostree::Deployment, -+) -> Result> { -+ let sysroot_dir = crate::utils::sysroot_dir(sysroot)?; -+ let entries_dir = sysroot_dir -+ .open_dir("boot/loader/entries") -+ .context("Opening boot/loader/entries")?; -+ -+ // Build the expected ostree= value from the deployment to match against. -+ // The ostree= karg format is: /ostree/boot.N/$stateroot/$bootcsum/$bootserial -+ // where bootcsum is the boot checksum and bootserial is the serial among -+ // deployments sharing the same bootcsum (NOT the deployserial). -+ let stateroot = deployment.stateroot(); -+ let bootserial = deployment.bootserial(); -+ let bootcsum = deployment.bootcsum(); -+ let ostree_match = format!("/{stateroot}/{bootcsum}/{bootserial}"); -+ -+ for entry in entries_dir.entries_utf8()? { -+ let entry = entry?; -+ let file_name = entry.file_name()?; -+ -+ if !file_name.starts_with("ostree-") || !file_name.ends_with(".conf") { -+ continue; -+ } -+ let content = entries_dir -+ .read_to_string(&file_name) -+ .with_context(|| format!("Reading BLS entry {file_name}"))?; -+ // Match by parsing the ostree= karg from the options line and checking -+ // that its path ends with our deployment's stateroot/bootcsum/bootserial. -+ // A simple `contains` would be fragile (e.g., serial 0 vs 01). -+ if content.lines().any(|line| { -+ line.starts_with("options ") -+ && line.split_ascii_whitespace().any(|arg| { -+ arg.strip_prefix("ostree=") -+ .is_some_and(|path| path.ends_with(&ostree_match)) -+ }) -+ }) { -+ return Ok(Some(content)); -+ } -+ } -+ -+ Ok(None) -+} -+ -+/// Set the kernel arguments for a specific source via ostree staged deployment. -+/// -+/// If no staged deployment exists, this stages a new deployment based on -+/// the booted deployment's commit with the updated kargs. If a staged -+/// deployment already exists (e.g. from `bootc upgrade`), it is replaced -+/// with a new one using the staged commit and origin, preserving any -+/// pending upgrade while layering the source kargs change on top. -+/// -+/// The `x-options-source-*` keys survive the staging roundtrip via the -+/// ostree `bootconfig-extra` serialization: source keys are set on the -+/// merge deployment's in-memory bootconfig before staging, ostree inherits -+/// them during `stage_tree_with_options()`, serializes them into the staged -+/// GVariant, and restores them at shutdown during finalization. -+#[context("Setting options for source '{source}' (staged)")] -+pub(crate) fn set_options_for_source_staged( -+ sysroot: &ostree_ext::sysroot::SysrootLock, -+ source: &str, -+ new_options: Option<&str>, -+) -> Result<()> { -+ let source = SourceName::parse(source)?; -+ -+ // The bootconfig-extra serialization (preserving x-prefixed BLS keys through -+ // staged deployment roundtrips) was added in ostree 2026.1. Without it, -+ // source keys are silently dropped during finalization at shutdown. -+ if !ostree::check_version(2026, 1) { -+ anyhow::bail!("This feature requires ostree >= 2026.1 for bootconfig-extra support"); -+ } -+ -+ let booted = sysroot -+ .booted_deployment() -+ .ok_or_else(|| anyhow::anyhow!("Not booted into an ostree deployment"))?; -+ -+ // Determine the "base" deployment whose kargs and source keys we start from. -+ // If there's already a staged deployment (e.g. from `bootc upgrade`), we use -+ // its commit, origin, and kargs so we don't discard a pending upgrade. If no -+ // staged deployment exists, we use the booted deployment. -+ let staged = sysroot.staged_deployment(); -+ let base_deployment = staged.as_ref().unwrap_or(&booted); -+ -+ let bootconfig = ostree::Deployment::bootconfig(base_deployment) -+ .ok_or_else(|| anyhow::anyhow!("Base deployment has no bootconfig"))?; -+ -+ // Read current options from the base deployment's bootconfig. -+ let current_options = bootconfig -+ .get("options") -+ .map(|s| s.to_string()) -+ .unwrap_or_default(); -+ -+ // Read existing x-options-source-* keys. -+ // -+ let source_options = if staged.is_some() { -+ // For staged deployments, extract source keys from the in-memory bootconfig. -+ // We can't read a BLS file because it hasn't been written yet (finalization -+ // happens at shutdown). -+ // -+ // We discover sources from two places: -+ // 1. The booted BLS entry (sources that have been finalized in previous boots) -+ // 2. The staged bootconfig (sources set since last boot via prior calls to -+ // set-options-for-source that haven't been finalized yet) -+ // -+ // For (2), the staged bootconfig's extra keys are restored from the -+ // "bootconfig-extra" GVariant by ostree's _ostree_sysroot_reload_staged() -+ // during sysroot.load(). We probe the bootconfig for all source keys we -+ // can discover. -+ let mut sources = BTreeMap::new(); -+ -+ // First: discover from the booted BLS entry (already-finalized sources) -+ if let Some(bls_content) = -+ read_bls_entry_for_deployment(sysroot, &booted).context("Reading booted BLS entry")? -+ { -+ let booted_sources = extract_source_options_from_bls(&bls_content); -+ for name in booted_sources.keys() { -+ let key = format!("{OPTIONS_SOURCE_KEY_PREFIX}{name}"); -+ if let Some(val) = bootconfig.get(&key) { -+ sources.insert(name.clone(), CmdlineOwned::from(val.to_string())); -+ } -+ } -+ } -+ -+ // Second: discover from the staged bootconfig's extra keys. -+ // These are sources set by prior calls to set-options-for-source -+ // in this boot cycle (before any reboot). We read them from the -+ // staged deployment data file which contains the serialized -+ // bootconfig-extra GVariant. -+ let staged_sources = read_staged_bootconfig_extra_sources(sysroot)?; -+ for (name, value) in staged_sources { -+ sources.entry(name).or_insert(value); -+ } -+ -+ sources -+ } else { -+ // For booted deployments, parse the BLS file directly -+ let bls_content = read_bls_entry_for_deployment(sysroot, &booted) -+ .context("Reading booted BLS entry")? -+ .ok_or_else(|| anyhow::anyhow!("No BLS entry found for booted deployment"))?; -+ extract_source_options_from_bls(&bls_content) -+ }; -+ -+ // Compute merged options -+ let source_key = source.bls_key(); -+ let merged = compute_merged_options(¤t_options, &source_options, &source, new_options); -+ -+ // Check for idempotency: if nothing changed, skip staging. -+ // Compare the merged cmdline against the current one, and the source value. -+ let merged_str = merged.to_string(); -+ let is_options_unchanged = merged_str == current_options; -+ let is_source_unchanged = match (source_options.get(&*source), new_options) { -+ (Some(old), Some(new)) => &**old == new, -+ (None, None) | (None, Some("")) => true, -+ _ => false, -+ }; -+ -+ if is_options_unchanged && is_source_unchanged { -+ tracing::info!("No changes needed for source '{source}'"); -+ return Ok(()); -+ } -+ -+ // Use the base deployment's commit and origin so we don't discard a -+ // pending upgrade. The merge deployment is always the booted one (for -+ // /etc merge), but the commit/origin come from whichever deployment -+ // we're building on top of. -+ let stateroot = booted.stateroot(); -+ let merge_deployment = sysroot -+ .merge_deployment(Some(stateroot.as_str())) -+ .unwrap_or_else(|| booted.clone()); -+ -+ let origin = ostree::Deployment::origin(base_deployment) -+ .ok_or_else(|| anyhow::anyhow!("Base deployment has no origin"))?; -+ -+ let ostree_commit = base_deployment.csum(); -+ -+ // Update the source keys on the merge deployment's bootconfig BEFORE staging. -+ // The ostree patch (bootconfig-extra) inherits x-prefixed keys from the merge -+ // deployment's bootconfig during stage_tree_with_options(). By updating the -+ // merge deployment's in-memory bootconfig here, the updated source keys will -+ // be serialized into the staged GVariant and survive finalization at shutdown. -+ let merge_bootconfig = ostree::Deployment::bootconfig(&merge_deployment) -+ .ok_or_else(|| anyhow::anyhow!("Merge deployment has no bootconfig"))?; -+ -+ // Set all desired source keys on the merge bootconfig. -+ // First, clear any existing source keys that we know about by setting -+ // them to empty string. BootconfigParser has no remove() API, so "" -+ // acts as a tombstone. An empty x-options-source-* key is harmless: -+ // extract_source_options_from_bls will parse it as an empty value, -+ // and the idempotency check skips empty values (!val.is_empty()). -+ for name in source_options.keys() { -+ let key = format!("{OPTIONS_SOURCE_KEY_PREFIX}{name}"); -+ merge_bootconfig.set(&key, ""); -+ } -+ // Re-set the keys we want to keep (all except the one being removed) -+ for (name, value) in &source_options { -+ if name != &*source { -+ let key = format!("{OPTIONS_SOURCE_KEY_PREFIX}{name}"); -+ merge_bootconfig.set(&key, value); -+ } -+ } -+ // Set the new/updated source key (if not removing) -+ if let Some(opts_str) = new_options { -+ merge_bootconfig.set(&source_key, opts_str); -+ } -+ -+ // Build kargs as string slices for the ostree API -+ let kargs_strs: Vec = merged.iter_str().map(|s| s.to_string()).collect(); -+ let kargs_refs: Vec<&str> = kargs_strs.iter().map(|s| s.as_str()).collect(); -+ -+ let opts = ostree::SysrootDeployTreeOpts { -+ override_kernel_argv: Some(&kargs_refs), -+ ..Default::default() -+ }; -+ -+ sysroot.stage_tree_with_options( -+ Some(stateroot.as_str()), -+ &ostree_commit, -+ Some(&origin), -+ Some(&merge_deployment), -+ &opts, -+ gio::Cancellable::NONE, -+ )?; -+ -+ tracing::info!("Staged deployment with updated kargs for source '{source}'"); -+ -+ Ok(()) -+} -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ -+ #[test] -+ fn test_source_name_validation() { -+ // (input, should_succeed) -+ let cases = [ -+ ("tuned", true), -+ ("bootc-kargs-d", true), -+ ("my_source_123", true), -+ ("", false), -+ ("bad name", false), -+ ("bad/name", false), -+ ("bad.name", false), -+ ("foo@bar", false), -+ ]; -+ for (input, expect_ok) in cases { -+ let result = SourceName::parse(input); -+ assert_eq!( -+ result.is_ok(), -+ expect_ok, -+ "SourceName::parse({input:?}) should {}", -+ if expect_ok { "succeed" } else { "fail" } -+ ); -+ } -+ } -+ -+ #[test] -+ fn test_source_name_bls_key() { -+ let name = SourceName::parse("tuned").unwrap(); -+ assert_eq!(name.bls_key(), "x-options-source-tuned"); -+ } -+ -+ #[test] -+ fn test_extract_source_options_from_bls() { -+ let bls = "\ -+title Fedora Linux 43 -+version 6.8.0-300.fc40.x86_64 -+linux /vmlinuz-6.8.0 -+initrd /initramfs-6.8.0.img -+options root=UUID=abc rw nohz=full isolcpus=1-3 rd.driver.pre=vfio-pci -+x-options-source-tuned nohz=full isolcpus=1-3 -+x-options-source-dracut rd.driver.pre=vfio-pci -+"; -+ -+ let sources = extract_source_options_from_bls(bls); -+ assert_eq!(sources.len(), 2); -+ assert_eq!(&*sources["tuned"], "nohz=full isolcpus=1-3"); -+ assert_eq!(&*sources["dracut"], "rd.driver.pre=vfio-pci"); -+ } -+ -+ #[test] -+ fn test_extract_source_options_ignores_non_source_keys() { -+ let bls = "\ -+title Test -+version 1 -+linux /vmlinuz -+options root=UUID=abc -+x-unrelated-key some-value -+custom-key data -+"; -+ -+ let sources = extract_source_options_from_bls(bls); -+ assert!(sources.is_empty()); -+ } -+ -+ #[test] -+ fn test_extract_source_options_ignores_empty_values() { -+ // Empty value (tombstone) should be filtered out -+ let bls = "\ -+options root=UUID=abc -+x-options-source-tuned -+x-options-source-dracut -+x-options-source-admin nohz=full -+"; -+ -+ let sources = extract_source_options_from_bls(bls); -+ assert_eq!(sources.len(), 1); -+ assert_eq!(&*sources["admin"], "nohz=full"); -+ } -+ -+ #[test] -+ fn test_compute_merged_options() { -+ // Each case: (description, current_options, source_map, target_source, new_options, expected) -+ let cases: &[(&str, &str, &[(&str, &str)], &str, Option<&str>, &str)] = &[ -+ ( -+ "add new source", -+ "root=UUID=abc123 rw composefs=digest123", -+ &[], -+ "tuned", -+ Some("isolcpus=1-3 nohz_full=1-3"), -+ "root=UUID=abc123 rw composefs=digest123 isolcpus=1-3 nohz_full=1-3", -+ ), -+ ( -+ "update existing source", -+ "root=UUID=abc123 rw isolcpus=1-3 nohz_full=1-3", -+ &[("tuned", "isolcpus=1-3 nohz_full=1-3")], -+ "tuned", -+ Some("isolcpus=0-7"), -+ "root=UUID=abc123 rw isolcpus=0-7", -+ ), -+ ( -+ "remove source (None)", -+ "root=UUID=abc123 rw isolcpus=1-3 nohz_full=1-3", -+ &[("tuned", "isolcpus=1-3 nohz_full=1-3")], -+ "tuned", -+ None, -+ "root=UUID=abc123 rw", -+ ), -+ ( -+ "empty initial options", -+ "", -+ &[], -+ "tuned", -+ Some("isolcpus=1-3"), -+ "isolcpus=1-3", -+ ), -+ ( -+ "clear source with empty string", -+ "root=UUID=abc123 rw isolcpus=1-3", -+ &[("tuned", "isolcpus=1-3")], -+ "tuned", -+ Some(""), -+ "root=UUID=abc123 rw", -+ ), -+ ( -+ "preserves untracked options", -+ "root=UUID=abc123 rw quiet isolcpus=1-3", -+ &[("tuned", "isolcpus=1-3")], -+ "tuned", -+ Some("nohz=full"), -+ "root=UUID=abc123 rw quiet nohz=full", -+ ), -+ ( -+ "multiple sources, update one preserves others", -+ "root=UUID=abc rw isolcpus=1-3 rd.driver.pre=vfio-pci", -+ &[ -+ ("tuned", "isolcpus=1-3"), -+ ("dracut", "rd.driver.pre=vfio-pci"), -+ ], -+ "tuned", -+ Some("nohz=full"), -+ "root=UUID=abc rw rd.driver.pre=vfio-pci nohz=full", -+ ), -+ ]; -+ -+ for (desc, current, source_entries, target, new_opts, expected) in cases { -+ let mut sources = BTreeMap::new(); -+ for (name, value) in *source_entries { -+ sources.insert(name.to_string(), CmdlineOwned::from(value.to_string())); -+ } -+ let source = SourceName::parse(target).unwrap(); -+ let result = compute_merged_options(current, &sources, &source, *new_opts); -+ assert_eq!(&*result, *expected, "case: {desc}"); -+ } -+ } -+} -diff --git a/docs/src/man/bootc-loader-entries-set-options-for-source.8.md b/docs/src/man/bootc-loader-entries-set-options-for-source.8.md -new file mode 100644 -index 00000000..238e4020 ---- /dev/null -+++ b/docs/src/man/bootc-loader-entries-set-options-for-source.8.md -@@ -0,0 +1,81 @@ -+# NAME -+ -+bootc-loader-entries-set-options-for-source - Set or update the kernel arguments owned by a specific source -+ -+# SYNOPSIS -+ -+bootc loader-entries set-options-for-source **--source** *NAME* [**--options** *"KARGS"*] -+ -+# DESCRIPTION -+ -+Set or update the kernel arguments owned by a specific source. Each -+source's arguments are tracked via `x-options-source-` extension -+keys in BLS config files on `/boot`. The `options` line is recomputed -+as the merge of all tracked sources plus any untracked (pre-existing) -+options. -+ -+This command stages a new deployment with the updated kernel arguments. -+Changes take effect on the next reboot. -+ -+When a staged deployment already exists (e.g. from `bootc upgrade`), -+it is replaced using the staged deployment's commit and origin, -+preserving the pending upgrade while layering the kargs change on top. -+ -+# OPTIONS -+ -+ -+**--source**=*SOURCE* -+ -+ The name of the source that owns these kernel arguments -+ -+**--options**=*OPTIONS* -+ -+ The kernel arguments to set for this source -+ -+ -+ -+# REQUIREMENTS -+ -+This command requires ostree >= 2026.1 with `bootconfig-extra` support -+for preserving extension BLS keys through staged deployment roundtrips. -+On older ostree versions, the command will exit with an error. -+ -+# EXAMPLES -+ -+Add TuneD kernel arguments: -+ -+ bootc loader-entries set-options-for-source --source tuned \ -+ --options "isolcpus=1-3 nohz_full=1-3" -+ -+Update TuneD kernel arguments (replaces previous values): -+ -+ bootc loader-entries set-options-for-source --source tuned \ -+ --options "isolcpus=0-7" -+ -+Remove all kernel arguments owned by TuneD: -+ -+ bootc loader-entries set-options-for-source --source tuned -+ -+Multiple sources can coexist independently: -+ -+ bootc loader-entries set-options-for-source --source tuned \ -+ --options "nohz=full isolcpus=1-3" -+ bootc loader-entries set-options-for-source --source dracut \ -+ --options "rd.driver.pre=vfio-pci" -+ -+# KNOWN LIMITATIONS -+ -+Source keys set by prior calls in the same boot cycle (before any reboot) -+are discovered by reading the staged deployment data file at -+`/run/ostree/staged-deployment`. If this file is missing or cannot be -+parsed, sources from prior calls may not be discovered, potentially -+orphaning their kargs. In practice this should not occur, as the file is -+managed by ostree and always present when a staged deployment exists. -+ -+# SEE ALSO -+ -+**bootc**(8), **bootc-loader-entries**(8) -+ -+# VERSION -+ -+ -diff --git a/docs/src/man/bootc-loader-entries.8.md b/docs/src/man/bootc-loader-entries.8.md -new file mode 100644 -index 00000000..623cc40b ---- /dev/null -+++ b/docs/src/man/bootc-loader-entries.8.md -@@ -0,0 +1,33 @@ -+# NAME -+ -+bootc-loader-entries - Operations on Boot Loader Specification (BLS) entries -+ -+# SYNOPSIS -+ -+bootc loader-entries *COMMAND* -+ -+# DESCRIPTION -+ -+Manage kernel arguments from multiple independent sources by tracking -+argument ownership via `x-options-source-` extension keys in BLS -+config files. -+ -+This solves the problem of kernel argument accumulation on bootc systems -+with transient `/etc`, where tools like TuneD lose their state files on -+reboot and cannot track which kargs they previously set. -+ -+ -+ -+ -+# COMMANDS -+ -+**set-options-for-source** -+: Set or update the kernel arguments owned by a specific source. -+ -+# SEE ALSO -+ -+**bootc**(8), **bootc-loader-entries-set-options-for-source**(8) -+ -+# VERSION -+ -+ -diff --git a/docs/src/man/bootc.8.md b/docs/src/man/bootc.8.md -index 99e673af..d543d049 100644 ---- a/docs/src/man/bootc.8.md -+++ b/docs/src/man/bootc.8.md -@@ -33,6 +33,7 @@ pulled and `bootc upgrade`. - | **bootc usr-overlay** | Add a transient overlayfs on `/usr` | - | **bootc install** | Install the running container to a target | - | **bootc container** | Operations which can be executed as part of a container build | -+| **bootc loader-entries** | Operations on Boot Loader Specification (BLS) entries | - | **bootc composefs-finalize-staged** | | - - -diff --git a/tmt/plans/integration.fmf b/tmt/plans/integration.fmf -index ce746945..8a485097 100644 ---- a/tmt/plans/integration.fmf -+++ b/tmt/plans/integration.fmf -@@ -246,4 +246,12 @@ execute: - test: - - /tmt/tests/tests/test-40-install-karg-delete - extra-fixme_skip_if_composefs: true -+ -+/plan-42-loader-entries-source: -+ summary: Test bootc loader-entries set-options-for-source -+ discover: -+ how: fmf -+ test: -+ - /tmt/tests/tests/test-42-loader-entries-source -+ extra-fixme_skip_if_composefs: true - # END GENERATED PLANS -diff --git a/tmt/tests/booted/test-loader-entries-source.nu b/tmt/tests/booted/test-loader-entries-source.nu -new file mode 100644 -index 00000000..6a6223bc ---- /dev/null -+++ b/tmt/tests/booted/test-loader-entries-source.nu -@@ -0,0 +1,267 @@ -+# number: 42 -+# tmt: -+# summary: Test bootc loader-entries set-options-for-source -+# duration: 30m -+# -+# This test verifies the source-tracked kernel argument management via -+# bootc loader-entries set-options-for-source. It covers: -+# 1. Input validation (invalid/empty source names) -+# 2. Adding source-tracked kargs and verifying they appear in /proc/cmdline -+# 3. Kargs and x-options-source-* BLS keys surviving the staging roundtrip -+# 4. Source replacement semantics (old kargs removed, new ones added) -+# 5. Multiple sources coexisting independently -+# 6. Source removal (--source without --options clears all owned kargs) -+# 7. Idempotent operation (no changes when kargs already match) -+# 8. Existing system kargs (root=, ostree=, etc.) preserved through changes -+# 9. --options "" (empty string) clears kargs without removing the source -+# 10. Staged deployment interaction (bootc switch + set-options-for-source -+# preserves the pending image switch) -+# -+# Requires ostree with bootconfig-extra support (>= 2026.1). -+# See: https://github.com/ostreedev/ostree/pull/3570 -+# See: https://github.com/bootc-dev/bootc/issues/899 -+use std assert -+use tap.nu -+ -+def parse_cmdline [] { -+ open /proc/cmdline | str trim | split row " " -+} -+ -+# Read x-options-source-* keys from the booted BLS entry. -+# The booted deployment always has the highest version number, -+# so we pick the last entry when sorted by filename (ostree-N.conf). -+def read_bls_source_keys [] { -+ let entries = glob /boot/loader/entries/ostree-*.conf | sort -+ if ($entries | length) == 0 { -+ error make { msg: "No BLS entries found" } -+ } -+ let entry = open ($entries | last) -+ $entry | lines | where { |line| $line starts-with "x-options-source-" } -+} -+ -+# Save the current system kargs (root=, ostree=, rw, etc.) for later comparison -+def save_system_kargs [] { -+ let cmdline = parse_cmdline -+ # Filter to well-known system kargs that must never be lost -+ # Note: ostree= is excluded because its value changes between deployments -+ # (boot version counter, bootcsum). It's managed by ostree's -+ # install_deployment_kernel() and always regenerated during finalization. -+ let system_kargs = $cmdline | where { |k| -+ (($k starts-with "root=") or ($k == "rw") or ($k starts-with "console=")) -+ } -+ $system_kargs | to json | save -f /var/bootc-test-system-kargs.json -+} -+ -+def load_system_kargs [] { -+ open /var/bootc-test-system-kargs.json -+} -+ -+def first_boot [] { -+ tap begin "loader-entries set-options-for-source" -+ -+ # Save system kargs for later verification -+ save_system_kargs -+ -+ # -- Input validation -- -+ -+ # Invalid source name (spaces) -+ let r = do -i { bootc loader-entries set-options-for-source --source "bad name" --options "foo=bar" } | complete -+ assert ($r.exit_code != 0) "spaces in source name should fail" -+ -+ # Invalid source name (special chars) -+ let r = do -i { bootc loader-entries set-options-for-source --source "foo@bar" --options "foo=bar" } | complete -+ assert ($r.exit_code != 0) "special chars in source name should fail" -+ -+ # Empty source name -+ let r = do -i { bootc loader-entries set-options-for-source --source "" --options "foo=bar" } | complete -+ assert ($r.exit_code != 0) "empty source name should fail" -+ -+ # Valid name with underscores/dashes -+ bootc loader-entries set-options-for-source --source "my_custom-src" --options "testvalid=1" -+ # Clear it immediately (no --options = remove source) -+ bootc loader-entries set-options-for-source --source "my_custom-src" -+ -+ # -- Add source kargs (multiple sources before reboot) -- -+ bootc loader-entries set-options-for-source --source tuned --options "nohz=full isolcpus=1-3" -+ bootc loader-entries set-options-for-source --source admin --options "quiet" -+ -+ # Verify deployment is staged -+ let st = bootc status --json | from json -+ assert ($st.status.staged != null) "deployment should be staged" -+ -+ print "ok: validation and initial staging" -+ tmt-reboot -+} -+ -+def second_boot [] { -+ # Verify kargs survived the staging roundtrip -+ let cmdline = parse_cmdline -+ assert ("nohz=full" in $cmdline) "nohz=full should be in cmdline after reboot" -+ assert ("isolcpus=1-3" in $cmdline) "isolcpus=1-3 should be in cmdline after reboot" -+ -+ # Verify both sources staged in first_boot survived -+ assert ("quiet" in $cmdline) "admin quiet karg should be in cmdline after reboot" -+ print "ok: multiple sources staged before reboot both survived" -+ -+ # Verify system kargs were preserved -+ let system_kargs = load_system_kargs -+ for karg in $system_kargs { -+ assert ($karg in $cmdline) $"system karg '($karg)' must be preserved" -+ } -+ print "ok: system kargs preserved" -+ -+ # Verify x-options-source-* keys in BLS entry -+ let source_keys = read_bls_source_keys -+ let tuned_key = $source_keys | where { |line| $line starts-with "x-options-source-tuned" } -+ assert (($tuned_key | length) > 0) "x-options-source-tuned should be in BLS entry" -+ let tuned_line = $tuned_key | first -+ assert ($tuned_line | str contains "nohz=full") "tuned source key should contain nohz=full" -+ assert ($tuned_line | str contains "isolcpus=1-3") "tuned source key should contain isolcpus=1-3" -+ let admin_key = $source_keys | where { |line| $line starts-with "x-options-source-admin" } -+ assert (($admin_key | length) > 0) "x-options-source-admin should be in BLS entry" -+ print "ok: kargs and source keys survived reboot" -+ -+ # Clean up admin source before continuing with replacement test -+ bootc loader-entries set-options-for-source --source admin -+ -+ # -- Source replacement: new kargs replace old ones -- -+ bootc loader-entries set-options-for-source --source tuned --options "nohz=on rcu_nocbs=2-7" -+ -+ tmt-reboot -+} -+ -+def third_boot [] { -+ # Verify replacement worked -+ let cmdline = parse_cmdline -+ assert ("nohz=full" not-in $cmdline) "old nohz=full should be gone" -+ assert ("isolcpus=1-3" not-in $cmdline) "old isolcpus=1-3 should be gone" -+ assert ("nohz=on" in $cmdline) "new nohz=on should be present" -+ assert ("rcu_nocbs=2-7" in $cmdline) "new rcu_nocbs=2-7 should be present" -+ # Admin source was removed in second_boot -+ assert ("quiet" not-in $cmdline) "admin quiet should be gone after removal" -+ -+ # Verify system kargs still preserved after replacement -+ let system_kargs = load_system_kargs -+ for karg in $system_kargs { -+ assert ($karg in $cmdline) $"system karg '($karg)' must survive replacement" -+ } -+ print "ok: source replacement persisted, system kargs preserved" -+ -+ # -- Multiple sources coexist -- -+ bootc loader-entries set-options-for-source --source dracut --options "rd.driver.pre=vfio-pci" -+ -+ tmt-reboot -+} -+ -+def fourth_boot [] { -+ # Verify both sources persisted -+ let cmdline = parse_cmdline -+ assert ("nohz=on" in $cmdline) "tuned nohz=on should still be present" -+ assert ("rcu_nocbs=2-7" in $cmdline) "tuned rcu_nocbs=2-7 should still be present" -+ assert ("rd.driver.pre=vfio-pci" in $cmdline) "dracut karg should be present" -+ -+ # Verify both source keys in BLS -+ let source_keys = read_bls_source_keys -+ let tuned_keys = $source_keys | where { |line| $line starts-with "x-options-source-tuned" } -+ let dracut_keys = $source_keys | where { |line| $line starts-with "x-options-source-dracut" } -+ assert (($tuned_keys | length) > 0) "tuned source key should exist" -+ assert (($dracut_keys | length) > 0) "dracut source key should exist" -+ print "ok: multiple sources coexist" -+ -+ # -- Clear source with empty --options "" (different from no --options) -- -+ # --options "" should remove the kargs but the key can remain with empty value -+ bootc loader-entries set-options-for-source --source dracut --options "" -+ # dracut kargs should be removed from pending deployment -+ let st = bootc status --json | from json -+ assert ($st.status.staged != null) "empty options should still stage a deployment" -+ print "ok: --options '' clears kargs" -+ -+ # Now also test no --options (remove the source entirely) -+ # First re-add dracut so we can test removal -+ bootc loader-entries set-options-for-source --source dracut --options "rd.driver.pre=vfio-pci" -+ # Then remove it with no --options -+ bootc loader-entries set-options-for-source --source dracut -+ -+ tmt-reboot -+} -+ -+def fifth_boot [] { -+ # Verify dracut cleared, tuned preserved -+ let cmdline = parse_cmdline -+ assert ("rd.driver.pre=vfio-pci" not-in $cmdline) "dracut karg should be gone" -+ assert ("nohz=on" in $cmdline) "tuned nohz=on should still be present" -+ assert ("rcu_nocbs=2-7" in $cmdline) "tuned rcu_nocbs=2-7 should still be present" -+ print "ok: source clear persisted" -+ -+ # -- Idempotent: same kargs again should be a no-op -- -+ bootc loader-entries set-options-for-source --source tuned --options "nohz=on rcu_nocbs=2-7" -+ # Should not stage a new deployment (idempotent) -+ let st = bootc status --json | from json -+ assert ($st.status.staged == null) "idempotent call should not stage a deployment" -+ print "ok: idempotent operation" -+ -+ # -- Staged deployment interaction -- -+ # Build a derived image and switch to it (this stages a deployment). -+ # Then call set-options-for-source on top. The staged deployment should -+ # be replaced with one that has the new image AND the source kargs. -+ bootc image copy-to-storage -+ -+ let td = mktemp -d -+ $"FROM localhost/bootc -+RUN echo source-test-marker > /usr/share/source-test-marker.txt -+" | save $"($td)/Dockerfile" -+ podman build -t localhost/bootc-source-test $"($td)" -+ -+ bootc switch --transport containers-storage localhost/bootc-source-test -+ let st = bootc status --json | from json -+ assert ($st.status.staged != null) "switch should stage a deployment" -+ -+ # Now add source kargs on top of the staged switch -+ bootc loader-entries set-options-for-source --source tuned --options "nohz=on rcu_nocbs=2-7 skew_tick=1" -+ -+ # Verify a deployment is still staged (it was replaced, not removed) -+ let st = bootc status --json | from json -+ assert ($st.status.staged != null) "deployment should still be staged after set-options-for-source" -+ -+ tmt-reboot -+} -+ -+def sixth_boot [] { -+ # Verify the image switch landed (the derived image's marker file exists) -+ assert ("/usr/share/source-test-marker.txt" | path exists) "derived image marker should exist" -+ print "ok: image switch preserved" -+ -+ # Verify the source kargs also landed -+ let cmdline = parse_cmdline -+ assert ("nohz=on" in $cmdline) "tuned nohz=on should be present" -+ assert ("rcu_nocbs=2-7" in $cmdline) "tuned rcu_nocbs=2-7 should be present" -+ assert ("skew_tick=1" in $cmdline) "tuned skew_tick=1 should be present" -+ -+ # Verify source key in BLS -+ let source_keys = read_bls_source_keys -+ let tuned_key = $source_keys | where { |line| $line starts-with "x-options-source-tuned" } -+ assert (($tuned_key | length) > 0) "tuned source key should exist after staged interaction" -+ print "ok: staged deployment interaction preserved both image and source kargs" -+ -+ # Verify system kargs still intact -+ let system_kargs = load_system_kargs -+ let cmdline = parse_cmdline -+ for karg in $system_kargs { -+ assert ($karg in $cmdline) $"system karg '($karg)' must survive staged interaction" -+ } -+ print "ok: system kargs preserved through all phases" -+ -+ tap ok -+} -+ -+def main [] { -+ match $env.TMT_REBOOT_COUNT? { -+ null | "0" => first_boot, -+ "1" => second_boot, -+ "2" => third_boot, -+ "3" => fourth_boot, -+ "4" => fifth_boot, -+ "5" => sixth_boot, -+ $o => { error make { msg: $"Unexpected TMT_REBOOT_COUNT ($o)" } }, -+ } -+} -diff --git a/tmt/tests/tests.fmf b/tmt/tests/tests.fmf -index 0d081d5e..bce8dadd 100644 ---- a/tmt/tests/tests.fmf -+++ b/tmt/tests/tests.fmf -@@ -153,3 +153,8 @@ check: - summary: Test bootc install --karg-delete - duration: 30m - test: nu booted/test-install-karg-delete.nu -+ -+/test-42-loader-entries-source: -+ summary: Test bootc loader-entries set-options-for-source -+ duration: 30m -+ test: nu booted/test-loader-entries-source.nu --- -2.53.0 - diff --git a/bootc.spec b/bootc.spec index 9007531..82e676a 100644 --- a/bootc.spec +++ b/bootc.spec @@ -22,7 +22,7 @@ %endif Name: bootc -Version: 1.15.1 +Version: 1.15.2 Release: %{autorelease} Summary: Bootable container system @@ -39,10 +39,6 @@ URL: https://github.com/bootc-dev/bootc Source0: %{url}/releases/download/v%{version}/bootc-%{version}.tar.zstd Source1: %{url}/releases/download/v%{version}/bootc-%{version}-vendor.tar.zstd -# Backport https://github.com/bootc-dev/bootc/pull/2114 -# This can be safely removed in any release > 1.15.1 -Patch0: 0001-loader-entries-Add-set-options-for-source-for-source.patch - # https://fedoraproject.org/wiki/Changes/EncourageI686LeafRemoval ExcludeArch: %{ix86} diff --git a/sources b/sources index 0d9daf4..ef70ef2 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ -SHA512 (bootc-1.15.1.tar.zstd) = afd7deeb1f34b8d023675a8d8cc40eb5802b7ec541269db93488cac9776ccd967d9d4737a39ae445d4cd78b57fd8a236e0b6f0bc0d447f207361828a2e2ca4c4 -SHA512 (bootc-1.15.1-vendor.tar.zstd) = dad48260d88e1a278aee8721c9985b1b3ff47474c64876f335a05b3e93af259b28b387a056394b09b3dd2ae42db2c141140f77aac6c3c887262f73a24671f125 +SHA512 (bootc-1.15.2-vendor.tar.zstd) = 0be448c56d7a7311d8186e5478f2a322d8c20bc9e98c7398dd9116964210958a6033cf05fb41541a6a32a5b365f62ca0a7055a8c71871e6f2cea0620f0c99fba +SHA512 (bootc-1.15.2.tar.zstd) = 9ca23fcde1ba8440b91794178b8e56764a3ab8a799618c86f3ecbc32dd09b7f06ee048a8e473c9c62aeae74719e82515933bb4c2fffd608741f6d92e1060b5e2