Repository navigation
UKI Addons Support - #2448
UKI Addons Support#2448Johan-Liebert1 wants to merge 21 commits into
Conversation
2579e09 to
e31ce45
Compare
|
Can we install UKI addons as part of a deployment with |
| pub(crate) enum UkiAddonCliOpts { | ||
| /// List all installed UKI Addons | ||
| List { | ||
| /// Output in JSON format |
That's in the docs here right? |
|
Or well actually, I think what you may be asking about is external UKI addons (i.e. not included in the container image), in which case it would totally make sense to do. |
Yea I wasn't super clear but that was what I was thinking about 🙂 |
3422e57 to
4b63be2
Compare
cgwalters
left a comment
There was a problem hiding this comment.
How about bootc install --included-uki-addon foo --external-uki-addon /path/to/external.efi i.e. we always disambiguate between internal and external?
I don't understand what we mean by external here? I'm guessing it doesn't mean "global" addons |
|
external ➡️ #2448 (comment) |
bcafaf3 to
77db443
Compare
b3b9d76 to
3ca9715
Compare
c11883b to
ec66b3d
Compare
jmarrero-bot
left a comment
There was a problem hiding this comment.
These are findings from @jmarrero's review of 2aea32e that aren't already raised in existing threads. Inline comments carry their severity; additions to existing threads are posted as replies there. The rest, which isn't on diff lines:
Tests
- No unit tests for pure logic. Besides
gather_addons_from_dir(already asked in theuki_addon.rs:177thread), good table-driven candidates areparse_uki_cmdline(generic overRead + Seek), theget_*_addon_namehelpers, the unreferenced-global-addon calculation (inline incomposefs_gc; extract it), and the CLI and carry-over selection (inline insetup_composefs_uki_boot).
Docs
docs/src/bootc-composefs.7.md:126still says addons "are only installed bybootc install: they aren't updated on upgrade, garbage collected, or reverted on rollback". This PR changes the first two.- The conceptual docs were lost: 86b4d23 "Add docs for UKI Addons" now contains only a comment tweak, and the PR no longer touches any docs besides the man pages and
SUMMARY.md. The how-to (building addons, ESP layout) is gone, and so is the Secure Boot signing note requested in #2448 (comment).
Commit hygiene
- db64bbf fixes a clap panic introduced in c429341 in this same PR; please squash it. Its message also says the options are ignored on ostree, which 2aea32e then reverses.
- 6ad99fa "Remove addon dir if empty" has no body and is a follow-up to 87b533f; please squash it.
- 86b4d23 "Add docs for UKI Addons" is misnamed (see Docs) and has no body.
- 01d573e "tmt: Add tests" also changes the shipped
contrib/packaging/finalize-ukiwithout mentioning it, and has no body. - 2aea32e bundles three unrelated changes: the ostree bail, a behavior change (a missing addon becomes an error), and the
SUMMARY.mdman page links. Please split it. - f9ee463 is not rustfmt-clean on its own (
match setup_typeand{split across lines inboot.rs; later commits fix it).
Generated-by: https://github.com/jmarrero-bot#llms
| .. | ||
| } = uki_info; | ||
|
|
||
| for entry in entries_to_write { |
There was a problem hiding this comment.
Must fix. Staging an upgrade rewrites the global addons the booted deployment uses.
On upgrade/switch every installed global addon is carried forward (line 1906, UkiAddonType::Global => true). Any of them the new image ships are then overwritten in place in the shared loader/addons/ here, at stage time, before the boot entry and deployment state are written.
Scenario: A is booted with global addon foo v1, and image B ships foo v2. bootc upgrade --download-only to B replaces bootc_composefs-foo.addon.efi immediately. If the user reboots without applying B, A boots with foo v2. The same happens if the staged deployment is discarded, or if write_systemd_uki_config / write_composefs_state fails after this loop.
The TODO on GLOBAL_UKI_ADDONS_DIR covers rollback but not this. At minimum the global addon write should not happen for a deployment that may never be applied. Ideally globals are written at finalize time, or changing global addons only happens through an explicit bootc uki-addon add.
There was a problem hiding this comment.
Staging an upgrade rewrites the global addons the booted deployment uses.
yeah, that is the very nature of global UKI Addons. We have two options here
-
Have a
.stagedentry per addon to be updated, examplesloader/addons/bootc-composefs_global.efi.addon.stagedwhich we would rename exchange on finalize. This does require more syscalls depending on the number of addons being updated which means more points of failures -
Have
loader/addons.stagedsimilar to what we do with BLS config directory. This will require a copy of all existing UKI addons but is effectively less syscalls and less points of failures, but requires extra disk space
There was a problem hiding this comment.
or changing global addons only happens through an explicit bootc uki-addon add.
this will require at least two reboots to take effect, so not a good plan imo
There was a problem hiding this comment.
On a live call, we are leaning more towards the second option here
| } | ||
|
|
||
| let final_pe_path = pe_output_dir(&pe_type, mounted_efi.as_ref(), file_path, uki_id); | ||
| validate_uki_candidates(&composefs_candidates, boot_ids, Some(uki_name.into()))?; |
There was a problem hiding this comment.
Nit: when composefs= came from a scoped addon, uki_name here is the addon's file name, so print_uki_dumpfile_diff (line 210) looks for <addon>.addon.dump instead of ${kver}.dump and silently skips the diff.
| bootc switch --transport containers-storage --global-uki-addon global-cfs-cmdline localhost/bootc-uki-addons-2 | ||
| } | complete) | ||
|
|
||
| assert ($result_global.exit_code != 0) "Global addon with composefs= should be rejected" |
There was a problem hiding this comment.
Tests. The negative tests only check exit_code != 0 (here, and lines 128, 134, 212). "Duplicate composefs=" can't be told apart from a digest mismatch or a broken test image. The --addon-cmds heredoc also has no set -e, so a failing bootc compute-composefs-digest still yields a failing switch. Please match on the error message.
| " | ||
|
|
||
| let cmds = " | ||
| ukify build --cmdline 'johan=liebert' --output /out/${kver}.efi.extra.d/monster-cmdline.addon.efi |
There was a problem hiding this comment.
Tests. Unlike the later boots, this writes into ${kver}.efi.extra.d without mkdir -p. If ukify doesn't create the directory, no scoped addon is built (the heredoc has no set -e) and the "no addons included" check in second_boot passes trivially.
| assert ($failed.exit_code != 0) | ||
|
|
||
| # Adding an addon that carries a composefs= cmdline should be rejected. | ||
| let cfs_add = (do { bootc uki-addon add local-cfs-cmdline scoped } | complete) |
There was a problem hiding this comment.
Tests. A successful bootc uki-addon add ... scoped is never tested; only global add and this rejected scoped add are.
jmarrero
left a comment
There was a problem hiding this comment.
Added a few comments, and also passed down the review I did with Claude to my bot to double check before posting. @Johan-Liebert1 I am happy to work on these together in a meeting to iterate quickly.
Introduce a function to gather all currently installed addons, scoped and global. On upgrade/switch, gather all installed addons and if an addon with the same name is found in the upgrade image, update that particular addon automatically Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
We were partially supporting global addons, but they were lumped in with scoped/local addons. Add a new cli option to composefs installs called `--global-uki-addon` which would determine which global addon to install. Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Here is what we do now with UKI Addons - Accept `--uki-addon` and `--global-uki-addon` cli options for bootc switch/upgrade commands - If we find an installed addon with the same name as the one in the new image, we update it Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Assisted-by: AI Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Prefix global addon filenames with the bootc identifier in the ESP so we can distinguish bootc-managed global addons from third-party ones Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
2aea32e to
4649646
Compare
Add `bootc uki-addon` subcommand with three operations: - `bootc uki-addon list`: List installed UKI addons Supports `--json` for JSON output - `bootc uki-addon add <name> <global|scoped>`: Install an addon from the booted image onto the ESP - `bootc uki-addon remove <name> [deployment_id]`: Remove an addon Add Display and Serialize to UkiAddonType/UkiAddonsList Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
- Add GC tests for Addons - Add CLI tests Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Mark UKI Addons as experimental Assisted-by: Claude-Code (Opus) Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
UkiAddonOpts is now flattened into both upgrade and switch commands, which don't have a --composefs-backend flag. The `requires = "composefs_backend"` constraint causes a panic at clap validation time because the referenced argument doesn't exist in those command contexts. This is generally safe as the options are ignored for ostree installs anyway Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Extract cmdline parsing from write_pe_to_esp into parse_uki_cmdline so it runs for both the UKI and UKI addons. The `composefs=` parameter can now be found in the main UKI or a scoped addon - At most one composefs= cmdline across all PE binaries (UKI + addons). A second one is rejected even if the digest matches. - Global UKI addons must never contain `composefs=` cmdline - At least one `composefs=` cmdline must be found or the install fails Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Before removing a scoped addon, parse its PE binary and check for a composefs= kernel parameter. If found, early exit as removing that addon would make the system unbootable Global addons are not checked because `composefs=` is rejected at install time for global addons Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Test that bootc rejects composefs= in the wrong places - Build a global addon containing composefs= from `bootc compute-composefs-digest`, attempt switch with --global-uki-addon, assert failure - Build a scoped addon containing composefs= alongside the UKI (which already has it), attempt switch with --uki-addon, assert failure due to duplicate composefs= It's a shame that we can't test UKI Addon only cmdline without piling on a bunch of hacks since `bootc container ukify` unconditionally puts the cmdline inside the UKI Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
This option lists all the UKI Addons across all deployments. Also, it associates global addons with the deployments that reference them. It is useful for GC-ing global UKI Addons if no deployments refer to them Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Similar to how we GC UKIs and scoped UKI Addons, GC Global UKI addons if we have no EROFS images remaining that hold a reference to them, which means that the deployments that depended on the Global addons have been removed Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Reject an addon whose cmdline embeds a `composefs=` or `composefs.digest=` argument before copying it. An addon added this way must not be able to override the composefs digest. Write the addon with `atomic_replace_with` and fsync the destination directory, so a crash mid add can't leave a truncated PE on the ESP. Add a tmt test to verify. Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
`--uki-addon` was a no-op on ostree backend, now we bail if it's passed during ostree installs/upgrades Also, bail if we fail to find an addon in the image instead of just logging as info Update manpage links Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
We get referenced global UKI Addons by mounting non-bootable EROFS images and collecting all the addons referenced. This is a pretty expensive operation, so now we only do that if: - The system is booted via UKI, and - Global Addons are actually installed Also, update docs on how scoped and global addons are updated Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
4649646 to
8d197a9
Compare
|
@jmarrero A live chat would be helpful |
|
|
||
| match opts { | ||
| UkiAddonCliOpts::List { json } => { | ||
| let addons = list_installed_uki_addons(storage)?; |
There was a problem hiding this comment.
Here, we should show where a global addon came from
There was a problem hiding this comment.
Also have better docs for this
There was a problem hiding this comment.
It could be just a * after the add-on name. And we document what that means. Specially I think if you are carrying a global add-on that is security related it would be good for the admins to know it's about to be dropped once they get a new update and reboot.
|
Not a blocker to merge this but a good follow up would be to have bootc status --verbose print all the uki add-ons specifying which are scoped and which are global, as well as if any of them are scheduled to be dropped if they are not re-added on the next image. |
If we have a staged deployment present, then `uki-addon add/remove` should act on the staged deployment as users usually will expect the addon to take effect after reboot. If we add it to the booted deployment while we have a staged deployment, users won't get the addon after reboot as we'd reboot into the staged deployment Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
Create a staged directory for global uki addons `loader/addons.stage` in the ESP which we will atomically rename-exchange with the the actual addons directory `loader/addons` `uki-addon add`/`remove` already act on a staged deployment for scoped addons. Extend the same behaviour to global addons. When a deployment is staged, add/remove operate on the staged set at `loader/addons.staged` instead of the live directory. Document the staged global-addon removal behaviour in the man page and the composefs overview, and add a tmt tests. Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
uki-addon: Update addons on update/switch
Introduce a function to gather all currently installed addons, scoped
and global. On upgrade/switch, gather all installed addons and if an
addon with the same name is found in the upgrade image, update that
particular addon automatically
uki/addon: Support global addons
We were partially supporting global addons, but they were lumped in with
scoped/local addons. Add a new cli option to composefs installs called
--global-uki-addonwhich would determine which global addon toinstall.
cfs/upgrade/switch: Handle UKI Addons
Here is what we do now with UKI Addons
Accept
--uki-addonand--global-uki-addoncli options for bootcswitch/upgrade commands
If we find an installed addon with the same name as the one in the new
image, we update it
global-uki-addons: Add prefix to name
Prefix global addon filenames with the bootc identifier in the ESP so
we can distinguish bootc-managed global addons from third-party ones
uki-addon: Add CLI for managing UKI Addons
Add
bootc uki-addonsubcommand with three operations:bootc uki-addon list: List installed UKI addonsSupports
--jsonfor JSON outputbootc uki-addon add <name> <global|scoped>: Install an addon fromthe booted image onto the ESP
bootc uki-addon remove <name> [deployment_id]: Remove an addonAdd Display and Serialize to UkiAddonType/UkiAddonsList