From d4a58e6d55ddca8bd554e0195a86b6b44fb57ffd Mon Sep 17 00:00:00 2001 From: Joseph Marrero Corchado Date: Sun, 13 Sep 2026 18:09:13 -0400 Subject: [PATCH 1/4] loader-entries: Replace a source's options in place Re-applying an unchanged source staged a new deployment whenever other kargs followed its own on the options line: the old options were removed and the new ones appended at the end, so the line changed. That happens as soon as a second source or e.g. `rpm-ostree kargs` adds something after it, which is TuneD re-applying its profile. Replace the options where the old ones were. This also keeps kernel argument order stable, which matters where the last occurrence wins. Generated-by: AI I am knowledgeable in this problem domain and reviewed it carefully. Signed-off-by: Joseph Marrero Corchado --- crates/lib/src/loader_entries.rs | 87 +++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/crates/lib/src/loader_entries.rs b/crates/lib/src/loader_entries.rs index 3f193da392..3038457f65 100644 --- a/crates/lib/src/loader_entries.rs +++ b/crates/lib/src/loader_entries.rs @@ -11,7 +11,7 @@ use anyhow::{Context, Result, ensure}; use fn_error_context::context; -use linux_kernel_cmdline::utf8::{Cmdline, CmdlineOwned}; +use linux_kernel_cmdline::utf8::{Cmdline, CmdlineOwned, Parameter}; use ostree::{gio, glib}; use ostree_ext::ostree; use std::collections::BTreeMap; @@ -85,34 +85,50 @@ fn extract_source_options_from_bls(content: &str) -> 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); + let current = Cmdline::from(current_options); + let old_params: Vec = source_options + .get(&**target_source) + .map(|old| old.iter().collect()) + .unwrap_or_default(); + let new_cmdline = new_options.filter(|v| !v.is_empty()).map(Cmdline::from); + let new_params: Vec = new_cmdline + .iter() + .flat_map(|c| c.iter()) + .map(|p| p.to_string()) + .collect(); + + let mut merged: Vec = Vec::new(); + let mut inserted = false; + for param in current.iter() { + if old_params.contains(¶m) { + // First old parameter: emit the new ones here; drop the rest. + if !inserted { + merged.extend(new_params.iter().cloned()); + inserted = true; + } + continue; } + merged.push(param.to_string()); } - - // 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); - } + if !inserted { + merged.extend(new_params); } - merged + CmdlineOwned::from(merged.join(" ")) } /// Read x-options-source-* keys from the staged deployment data file. @@ -556,7 +572,40 @@ x-options-source-admin nohz=full ], "tuned", Some("nohz=full"), - "root=UUID=abc rw rd.driver.pre=vfio-pci nohz=full", + "root=UUID=abc rw nohz=full rd.driver.pre=vfio-pci", + ), + ( + "re-applying an unchanged source is a no-op even when followed by other options", + "root=UUID=abc rw nohz=on rcu_nocbs=2-7 cross1=a rpmarg=yes", + &[ + ("tuned", "nohz=on rcu_nocbs=2-7"), + ("crosstest", "cross1=a"), + ], + "tuned", + Some("nohz=on rcu_nocbs=2-7"), + "root=UUID=abc rw nohz=on rcu_nocbs=2-7 cross1=a rpmarg=yes", + ), + ( + "updating a source keeps its position", + "root=UUID=abc rw nohz=on rcu_nocbs=2-7 cross1=a rpmarg=yes", + &[ + ("tuned", "nohz=on rcu_nocbs=2-7"), + ("crosstest", "cross1=a"), + ], + "tuned", + Some("nohz=on skew_tick=1"), + "root=UUID=abc rw nohz=on skew_tick=1 cross1=a rpmarg=yes", + ), + ( + "removing a source keeps the order of the rest", + "root=UUID=abc rw nohz=on cross1=a rcu_nocbs=2-7 rpmarg=yes", + &[ + ("tuned", "nohz=on rcu_nocbs=2-7"), + ("crosstest", "cross1=a"), + ], + "tuned", + None, + "root=UUID=abc rw cross1=a rpmarg=yes", ), ]; From 00895baf18e24625195ae65d9f3c4307329732f6 Mon Sep 17 00:00:00 2001 From: Joseph Marrero Corchado Date: Sun, 13 Sep 2026 18:41:34 -0400 Subject: [PATCH 2/4] kargs: Build on the staged deployment's kargs when replacing it Upgrade and switch computed kargs from the booted deployment, so a change that was only staged (`loader-entries set-options-for-source`, `rpm-ostree kargs`) was silently dropped when the staged deployment was replaced -- e.g. TuneD applying a profile and an automatic `bootc upgrade` running before the reboot. rpm-ostree deliberately builds on the pending deployment so operations chain; do the same. Image-provided kargs are unaffected, since the kargs.d diff is applied relative to whichever deployment we build on. Generated-by: AI I am knowledgeable in this problem domain and reviewed it carefully. Signed-off-by: Joseph Marrero Corchado --- crates/lib/src/bootc_kargs.rs | 24 +++++++++++++++++------- crates/lib/src/deploy.rs | 5 ++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/crates/lib/src/bootc_kargs.rs b/crates/lib/src/bootc_kargs.rs index 6d1883576f..884cac0aae 100644 --- a/crates/lib/src/bootc_kargs.rs +++ b/crates/lib/src/bootc_kargs.rs @@ -194,9 +194,10 @@ fn get_kargs_from_ostree( Ok(ret) } -/// Compute the kernel arguments for the new deployment. This starts from the booted -/// karg, but applies the diff between the bootc karg files in /usr/lib/bootc/kargs.d -/// between the booted deployment and the new one. +/// Compute the kernel arguments for the new deployment. This starts from the kargs +/// of the deployment being replaced -- the staged one if there is one, otherwise the +/// merge (booted) deployment -- and applies the diff between the bootc karg files in +/// /usr/lib/bootc/kargs.d of that deployment and the new one. pub(crate) fn get_kargs( sysroot: &Storage, merge_deployment: &Deployment, @@ -207,16 +208,25 @@ pub(crate) fn get_kargs( let repo = &ostree.repo(); let sys_arch = std::env::consts::ARCH; - // Get the kargs used for the merge in the bootloader config - let mut kargs = ostree::Deployment::bootconfig(merge_deployment) + // A staged deployment carries kargs changes that are not in the booted + // entry yet (e.g. from `bootc loader-entries set-options-for-source` or + // `rpm-ostree kargs`). Staging replaces it, so build on it rather than + // silently discarding those changes. + let staged = ostree + .staged_deployment() + .filter(|s| s.stateroot() == merge_deployment.stateroot()); + let base_deployment = staged.as_ref().unwrap_or(merge_deployment); + + // Get the kargs used for the base deployment in the bootloader config + let mut kargs = ostree::Deployment::bootconfig(base_deployment) .and_then(|bootconfig| { ostree::BootconfigParser::get(&bootconfig, "options") .map(|options| Cmdline::from(options.to_string())) }) .unwrap_or_default(); - // Get the kargs in kargs.d of the merge - let merge_root = &crate::utils::deployment_fd(ostree, merge_deployment)?; + // Get the kargs in kargs.d of the base deployment + let merge_root = &crate::utils::deployment_fd(ostree, base_deployment)?; let existing_kargs = get_kargs_in_root(merge_root, sys_arch)?; // Get the kargs in kargs.d of the pending image diff --git a/crates/lib/src/deploy.rs b/crates/lib/src/deploy.rs index b361a3b79f..bad7acf84f 100644 --- a/crates/lib/src/deploy.rs +++ b/crates/lib/src/deploy.rs @@ -904,9 +904,8 @@ async fn deploy( lock_finalization: bool, ) -> Result { // Compute the kernel argument overrides. In practice today this API is always expecting - // a merge deployment. The kargs code also always looks at the booted root (which - // is a distinct minor issue, but not super important as right now the install path - // doesn't use this API). + // a merge deployment; the kargs code builds on the staged deployment instead when + // there is one, so that a pending kargs change is not lost. let (stateroot, override_kargs) = match &from { MergeState::MergeDeployment(deployment) => { let kargs = crate::bootc_kargs::get_kargs(sysroot, &deployment, image)?; From a0e5b1c1997eb5b0d33c92cd614fa78c000a109e Mon Sep 17 00:00:00 2001 From: Joseph Marrero Corchado Date: Tue, 21 Jul 2026 13:15:02 -0400 Subject: [PATCH 3/4] tests: Add cross-consumer staging scenario to loader-entries-source Cover bootc staging source-tracked kargs and rpm-ostree re-staging on the same boot: the x-options-source-* keys must survive, which is the fallback fixed in ostreedev/ostree#3611. Also cover the two bootc fixes before this: re-applying an unchanged source is a no-op, and a `bootc switch` on top of a staged source removal keeps the removal. Along the way, find the booted BLS entry by its ostree= karg instead of taking the last one, and expect a removed source to leave an empty tombstone key, since set-options-for-source never deletes keys. Generated-by: AI I am knowledgeable in this problem domain and reviewed it carefully. Signed-off-by: Joseph Marrero Corchado --- .../booted/test-loader-entries-source.nu | 96 ++++++++++++++++--- 1 file changed, 85 insertions(+), 11 deletions(-) diff --git a/tmt/tests/booted/test-loader-entries-source.nu b/tmt/tests/booted/test-loader-entries-source.nu index 423f80a97c..df868496dd 100644 --- a/tmt/tests/booted/test-loader-entries-source.nu +++ b/tmt/tests/booted/test-loader-entries-source.nu @@ -18,9 +18,17 @@ # 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) +# 11. Cross-consumer staging (bootc stages source kargs, then rpm-ostree +# re-stages on the same boot via kargs --append; x-options-source-* +# keys must survive in the replacement staged deployment via the +# "previously-staged fallback" path in ostree_sysroot_stage_tree_with_options) +# 12. Re-applying an unchanged source is a no-op even when other kargs follow +# its own on the options line, and a `bootc switch` staged on top of a +# pending source removal keeps that removal # # Requires ostree with bootconfig-extra support (>= 2026.1). # See: https://github.com/ostreedev/ostree/pull/3570 +# See: https://github.com/ostreedev/ostree/pull/3611 # See: https://github.com/bootc-dev/bootc/issues/899 use std assert use tap.nu @@ -36,16 +44,24 @@ 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). +# Read x-options-source-* keys from the booted BLS entry, found by +# matching the ostree= karg from /proc/cmdline against each entry's +# options line. Entry numbering does not track which one is booted. 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 booted_ostree = parse_cmdline | where { |k| $k starts-with "ostree=" } | first + let entries = glob /boot/loader/entries/ostree-*.conf + let booted = $entries | where { |e| + open $e | lines | any { |line| ($line starts-with "options ") and ($line | str contains $booted_ostree) } } - let entry = open ($entries | last) - $entry | lines | where { |line| $line starts-with "x-options-source-" } + if ($booted | length) != 1 { + error make { msg: $"Expected exactly one BLS entry for ($booted_ostree), found ($booted | length)" } + } + open ($booted | first) | lines | where { |line| $line starts-with "x-options-source-" } +} + +# Value of an x-options-source-* line, or "" for a tombstoned source. +def source_key_value [line: string] { + $line | str replace --regex '^x-options-source-\S+\s*' '' | str trim } # Save the current system kargs (root=, ostree=, rw, etc.) for later comparison @@ -191,24 +207,73 @@ def fourth_boot [] { # Then remove it with no --options bootc loader-entries set-options-for-source --source dracut + # -- Cross-consumer staging -- + # Simulate the scenario where bootc stages source-tracked kargs and + # then rpm-ostree re-stages on the same boot (e.g., appending an + # unrelated karg). The replacement staged deployment created by + # rpm-ostree must inherit the x-options-source-* keys from the + # previously-staged deployment via ostree's fallback path. + bootc loader-entries set-options-for-source --source crosstest --options "cross1=a cross2=b" + let st = bootc status --json | from json + assert ($st.status.staged != null) "crosstest should stage a deployment" + + # Now rpm-ostree appends an unrelated karg, creating a NEW staged + # deployment that replaces the one bootc just created. + rpm-ostree kargs --append=rpmarg=yes + + # A staged deployment should still exist after rpm-ostree re-staged + let st = bootc status --json | from json + assert ($st.status.staged != null) "deployment should still be staged after rpm-ostree kargs" + print "ok: cross-consumer staging set up (bootc then rpm-ostree)" + tmt-reboot } def fifth_boot [] { - # Verify dracut cleared, tuned preserved + # -- Verify cross-consumer staging results -- + # After fourth_boot: bootc staged crosstest source kargs, then + # rpm-ostree re-staged with --append=rpmarg=yes. Both the + # source-tracked kargs AND the rpm-ostree karg must be present. let cmdline = parse_cmdline + assert ("cross1=a" in $cmdline) "crosstest cross1=a should survive rpm-ostree re-staging" + assert ("cross2=b" in $cmdline) "crosstest cross2=b should survive rpm-ostree re-staging" + assert ("rpmarg=yes" in $cmdline) "rpm-ostree rpmarg=yes should be present" + + # Verify the crosstest source key exists in BLS + let source_keys = read_bls_source_keys + let crosstest_keys = $source_keys | where { |line| $line starts-with "x-options-source-crosstest" } + assert (($crosstest_keys | length) > 0) "x-options-source-crosstest BLS key must survive rpm-ostree re-staging" + let crosstest_line = $crosstest_keys | first + assert ($crosstest_line | str contains "cross1=a") "crosstest source key should contain cross1=a" + assert ($crosstest_line | str contains "cross2=b") "crosstest source key should contain cross2=b" + + # Verify tuned source also survived the cross-consumer staging + assert ("nohz=on" in $cmdline) "tuned nohz=on should survive cross-consumer staging" + assert ("rcu_nocbs=2-7" in $cmdline) "tuned rcu_nocbs=2-7 should survive cross-consumer staging" + print "ok: cross-consumer staging preserved all source kargs and rpm-ostree karg" + + # Verify dracut cleared (from fourth_boot removal) 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 -- + # Note: crosstest and rpmarg are still present in the booted deployment, + # but the idempotent check is only about the tuned source. Since tuned + # already has "nohz=on rcu_nocbs=2-7", bootc should detect no change + # and not stage a new deployment. 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" + # Clean up cross-consumer kargs now that the idempotent test has passed. + # These stage a deployment, and the image switch below re-stages on top + # of it: the switch must build on the staged kargs, not the booted ones, + # or this removal would be silently undone (verified in sixth_boot). + bootc loader-entries set-options-for-source --source crosstest + rpm-ostree kargs --delete=rpmarg=yes + # -- 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 @@ -246,10 +311,19 @@ def sixth_boot [] { 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 cross-consumer kargs were cleaned up in fifth_boot + assert ("cross1=a" not-in $cmdline) "crosstest kargs should be gone after cleanup" + assert ("cross2=b" not-in $cmdline) "crosstest kargs should be gone after cleanup" + assert ("rpmarg=yes" not-in $cmdline) "rpm-ostree rpmarg should be gone after cleanup" + # 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" + # Removing a source tombstones its key (empty value) rather than deleting it + let crosstest_keys = $source_keys | where { |line| $line starts-with "x-options-source-crosstest" } + assert (($crosstest_keys | length) == 1) "crosstest source key should remain as a tombstone after cleanup" + assert ((source_key_value ($crosstest_keys | first)) == "") "crosstest source key should be empty after cleanup" print "ok: staged deployment interaction preserved both image and source kargs" # Verify system kargs still intact From 2d9edec9a6e4615a068d619901d4cf1a59def1c0 Mon Sep 17 00:00:00 2001 From: Joseph Marrero Corchado Date: Sun, 13 Sep 2026 21:07:20 -0400 Subject: [PATCH 4/4] docs: Describe source-tracked kernel arguments kernel-arguments.md still said bootc has no API for per-machine kernel arguments. Describe `loader-entries set-options-for-source`: the ownership keys, how a call is processed, how upgrade/switch and rpm-ostree interact with it, and what TuneD does with it, with a link to the ostree documentation for how the keys survive staging. Generated-by: AI I am knowledgeable in this problem domain and reviewed it carefully. Signed-off-by: Joseph Marrero Corchado --- docs/src/building/kernel-arguments.md | 127 +++++++++++++++++- ...loader-entries-set-options-for-source.8.md | 2 + 2 files changed, 123 insertions(+), 6 deletions(-) diff --git a/docs/src/building/kernel-arguments.md b/docs/src/building/kernel-arguments.md index 2d619fc491..55b6675d31 100644 --- a/docs/src/building/kernel-arguments.md +++ b/docs/src/building/kernel-arguments.md @@ -78,22 +78,137 @@ mount -o remount,rw /boot # tool to edit /boot/loader/entries ``` -At the current time, `bootc` does not itself offer -an API to manipulate kernel arguments maintained per-machine. +`bootc loader-entries set-options-for-source` manages per-machine +kernel arguments on behalf of a named *source*; see +[Source-tracked kernel arguments](#source-tracked-kernel-arguments) below. -Other projects such as `rpm-ostree` do, via e.g. `rpm-ostree kargs`, +Other projects such as `rpm-ostree` do too, via e.g. `rpm-ostree kargs`, which is just a frontend for editing the bootloader configuration files. Note an important detail is that `rpm-ostree kargs` always creates a new deployment. -`rpm-ostree kargs` and bootc will interoperate as they both -use the ostree backend today, and any kernel arguments changed -via that mechanism will persist across upgrades. +`rpm-ostree kargs` and bootc interoperate as they both use the ostree +backend today: any kernel arguments changed via either mechanism persist +across upgrades, and each builds on the deployment it replaces, so a +change staged by one is not lost when the other stages next. It is currently undefined behavior to remove kernel arguments locally that are included in the base image via `/usr/lib/bootc/kargs.d`. +## Source-tracked kernel arguments + +A tool that adds kernel arguments — TuneD applying a profile is the +motivating case — needs to later replace or remove exactly the arguments +it added, without keeping state in `/etc` (which may be transient). +`bootc loader-entries set-options-for-source` records ownership next to +the arguments themselves, in the BLS entry: + +``` +options root=UUID=... rw ostree=/ostree/boot.0/... console=ttyS0 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 +``` + +* `options` is the kernel command line and the ground truth. +* `x-options-source-NAME` is bookkeeping: the arguments source `NAME` + currently owns. Bootloaders ignore unknown keys. Names are limited to + `[A-Za-z0-9_-]+`. +* A source key with an **empty value is a tombstone** ("this source owns + nothing"). bootc never deletes a key, it clears it. + +```bash +# set or replace TuneD's arguments +bootc loader-entries set-options-for-source --source tuned --options "nohz=full isolcpus=1-3" +# remove them +bootc loader-entries set-options-for-source --source tuned +``` + +Both stage a new deployment (unless nothing would change); the arguments +take effect on the next boot. See +`man bootc-loader-entries-set-options-for-source`. + +### How a call is processed + +```mermaid +flowchart TD + s([set-options-for-source --source S --options O]) --> v[validate S] + v --> base{Staged deployment exists?} + base -- yes --> b1[base = staged deployment
commit, origin, options from it] + base -- no --> b2[base = booted deployment] + b1 --> disc1[sources = names from the booted entry,
values from the staged bootconfig,
plus names only in the staged data] + b2 --> disc2[sources = parse the booted entry] + disc1 --> merge + disc2 --> merge[merged = options with S's old
arguments replaced in place by O] + merge --> idem{merged == options
and old S == O?} + idem -- yes --> noop([No changes needed]) + idem -- no --> set[On the booted deployment's bootconfig:
clear every known source key,
re-set all sources except S,
set S = O if given] + set --> stage[stage_tree_with_options
merge = booted, commit/origin = base,
override_kernel_argv = merged] +``` + +* **Base vs. merge deployment.** The commit, origin and current + `options` come from the *staged* deployment when one exists, so a + pending `bootc upgrade` is kept. The merge deployment (for the `/etc` + merge, and where the source keys are written) is always the booted one. +* **In-place replacement.** The source's arguments are replaced where + they were, so re-applying an unchanged source is byte-identical and + a no-op even when other arguments follow, and the relative order of + arguments is preserved (it matters where the last occurrence wins). +* **The full set is written on every call**, tombstones included. This + is what lets ostree carry the keys correctly through staging. + +### How the keys survive staging + +A staged deployment has no BLS entry until finalization at shutdown, and +any later staging in the same boot — `bootc upgrade`, `rpm-ostree kargs`, +another `set-options-for-source` — replaces it. ostree carries the +`x-options-source-*` keys across that gap in the staged deployment data +(`bootconfig-extra`) and decides which set to carry when a staging is +replaced; that mechanism, and the contract bootc follows as an "aware" +caller, are documented in +[Extension BLS keys and staged deployments](https://ostreedev.github.io/ostree/bootconfig-extra/) +on the ostree side. It requires ostree 2026.1 (2026.5 for the case where +another tool re-stages in the same boot); bootc checks the version at +runtime. + +### Interaction with `bootc upgrade` / `switch` + +```mermaid +flowchart TD + s([upgrade / switch]) --> base{Staged deployment in the
same stateroot exists?} + base -- yes --> b1[base = staged deployment] + base -- no --> b2[base = booted deployment] + b1 --> k[kargs = base's options line] + b2 --> k + k --> old[old = kargs.d of base's tree] + old --> new{new image has
/usr/lib/bootc/kargs.d?} + new -- no --> add[kargs += old] --> stage + new -- yes --> diff[remove old - new from kargs,
add new - old to kargs] --> stage[stage_tree_with_options
override_kernel_argv = kargs] +``` + +Upgrading builds on the staged deployment's arguments when there is one, +so a source change (or an `rpm-ostree kargs` change) staged earlier in +the same boot survives the upgrade; only the `kargs.d` *diff* between the +two images is applied on top. The upgrade path never touches source keys; +ostree carries them forward. + +### Interaction with `rpm-ostree` and direct edits + +`rpm-ostree kargs` likewise builds on the pending deployment, so the two +can be interleaved in either order. It does not know about source keys; +ostree carries them. If something edits `options` directly (for example +`rpm-ostree kargs --delete` of an argument a source owns), the source's +record is stale until that source is next written — a subsequent +`set-options-for-source` for it simply finds nothing to remove. + +### Consumers + +TuneD (2.27+ with the bootc bootloader support) uses `--source tuned`, +declaring its profile's full argument set on every apply and clearing it +on unapply. On images with a transient `/etc`, set TuneD's +`profile_mode` to `manual` in the image so it does not auto-select a +different profile on each boot and clear the administrator's arguments. + ## Injecting default arguments into custom kernels The Linux kernel supports building in arguments into the kernel 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 index 238e402042..18af114630 100644 --- 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 @@ -39,6 +39,8 @@ preserving the pending upgrade while layering the kargs change on top. 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. +ostree >= 2026.5 is needed for the source keys to survive when another +tool (e.g. `rpm-ostree kargs`) re-stages before the reboot. # EXAMPLES