Repository navigation
UKI Addons Support - #2448
UKI Addons Support#2448Johan-Liebert1 wants to merge 23 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.
Not ready yet, but close. I re-ran our tests on 4942382 in VMs (same CI variant). B1 and B3 as reported are fixed, and so is B2 in the scenario I reported, but B2 still reproduces in two nearby cases and the GC part of B3 now fails differently. Each fix is a line or two (inline):
- With no live
loader/addons, staging returns before clearing a staleaddons.staged, so an abandoned deployment's global still lands on the next one. - The carry-forward set still counts staged globals.
- Seeding the staged dir makes GC see every live global twice while something is staged, so
delete-deploymentof the rollback fails with ENOENT.
Also, re-staging now silently undoes a remove ... global made while staged. S2 to S5 and N1 from the first review are unchanged, and deferring the status listing is fine. plan-51 passes. Details, VM logs and tmt tests are in the gist: https://gist.github.com/jmarrero-bot/2191254aacdd3bc3c5c517b1a0c9ea1d#file-1-rereview-2448-49423828-md
Generated-by: https://github.com/jmarrero-bot#llms
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>
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>
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>
Global UKI addons are removed once no deployment's image references them, but that removal happened silently and only after the addon was already orphaned, too late for the user to act. During `bootc upgrade/switch`, warn about installed global addons that the newly staged image no longer provides, i.e. they'll be GC'd in the next upgrade/switch. Extend the UKI GC test to assert the notice fires when switching to an image that drops a global addon, and does not fire when the image still provides it. Tests are written by LLM Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
- Make sure to remove the staged global addons dir when a staged deployment is deleted or before restaging a deployment - Make sure to delete the staged global addons dir when rolling back a deployment similar to what we do with BLS entries - Fix boot aftifact GC by handling a case where we could have the same UKI addons present more than once in `/boot` Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
4942382 to
4524504
Compare
jmarrero-bot
left a comment
There was a problem hiding this comment.
Close, but not quite ready. All three issues from the last round are fixed at 4524504, confirmed in VMs (same CI variant) and by our tmt tests. Three things remain before merge (inline):
- On classic GRUB + UKI, finalize looks for
loader/addons.stagedin/bootinstead of on the ESP, so a staged global addon is never applied. Reproduced in a VM. - A pruning GC (which
upgradeanddelete-deploymentrun) unlinks addon objects a pruned deployment shared with the retained ones. Addons are usually identical from one image to the next, so afterwardsuki-addon addfrom the booted image fails with ENOENT. The loop predates this PR, butaddis new. Reproduced in a VM. - The rebase brought back the old "addons are only installed by
bootc install" bullet in bootc-composefs.7.md, which contradicts the new one.
Should fix, or as a follow-up: re-staging still silently undoes a global remove made while staged (inline). The composefs= rejection tests pass on a hash parse error, not on the checks they're meant to cover: ${CFS_DIGEST} is single-quoted, and bootc compute-composefs-digest isn't a command (inline). S2 to S5 and N1 from the first review are unchanged. Also, the pull path only reads composefs= from the main UKI, so the "composefs= in a scoped addon" case that 451cd36 adds can't actually be installed or switched to.
plan-51 passes here and in CI. The only red CI job, centos-9 ostree (plan-32-multi-device-esp), looks unrelated and is being re-run. Details, VM logs and tests are in the gist: https://gist.github.com/jmarrero-bot/2191254aacdd3bc3c5c517b1a0c9ea1d#file-2-rereview-2448-45245047-md
Generated-by: https://github.com/jmarrero-bot#llms
| let boot_dir = storage.require_boot_dir()?; | ||
|
|
||
| // Un-stage Global UKI Addons (if present) as the penultimate step | ||
| if boot_dir.exists(STAGED_GLOBAL_UKI_ADDONS_DIR) { |
There was a problem hiding this comment.
On classic GRUB, boot_dir is /boot, not the ESP, but staging and uki-addon add write loader/addons.staged on the ESP. So on GRUB + UKI a staged global addon is never applied.
Reproduced with an image built with BOOTC_bootloader=grub BOOTC_boot_type=uki: switch --global-uki-addon foo, reboot. foo isn't on the cmdline, addons.staged is still on the ESP, and uki-addon list --verbose keeps showing it as staged. Using storage.require_esp()?.fd here should fix it. The GRUB branch in rollback.rs also needs the addons.staged cleanup that the BLS branch got.
| Ok(stat) => { | ||
| objects_bytes += stat.st_size as u64; | ||
| } | ||
| Err(e) if e.kind() == std::io::ErrorKind::NotFound => { |
There was a problem hiding this comment.
This loop unlinks every object in an orphan's /boot without checking whether a retained deployment uses the same object, and addons are usually byte-identical from one image to the next. The loop predates this PR, but uki-addon add now reads these objects.
Reproduced: A, B and C ship identical foo (global) and sc (scoped) addons. With C booted, B the rollback and A no longer in the boot entries, bootc internals composefs-gc --prune-repo (which upgrade and delete-deployment also run) removes 15 objects. After that, uki-addon remove sc scoped + add sc scoped fails with Opening UKI Addon at boot/EFI/Linux/<kver>.efi.extra.d/sc.addon.efi: No such file or directory, the same happens for foo global, and bootc internals fsck still reports ok. Leaving out the objects that any live deployment's /boot still references (booted, staged, rollback, pinned) would fix it. The ENOENT skip covers duplicates within one image, but not this case.
| /// an upgrade/switch | ||
| #[context("Staging Global UKI Addons")] | ||
| fn stage_global_uki_addons(esp_fd: &Dir) -> Result<()> { | ||
| // Remove any previously staged addons |
There was a problem hiding this comment.
From the last round, still open: since staging always rebuilds addons.staged from the live dir, a uki-addon remove <name> global done while a deployment is staged gets silently undone by the next upgrade/switch. By reading, adds made while staged get dropped too, now that the carry-forward filter excludes staged addons.
Reproduced: stage C, remove baz global, switch to C2, and baz is back in addons.staged and active after the reboot. If that's intended, add/remove should at least say the change only applies to the deployment staged right now.
| - UKI addons (`--uki-addon`). Addons are only installed by `bootc install`: | ||
| they aren't updated on upgrade, garbage collected, or reverted on | ||
| rollback. | ||
| - UKI addons (--uki-addon, --global-uki-addon). On upgrade and switch, |
There was a problem hiding this comment.
The rebase brought back the old bullet just above (lines 126-128: "Addons are only installed by bootc install: they aren't updated on upgrade, garbage collected, or reverted on rollback"), which contradicts this one. 4942382 had removed it.
|
|
||
| # /run/target is taken from tap make_uki_containerfile 'FROM base as sealed-uki' | ||
| CFS_DIGEST=$\(bootc compute-composefs-digest /run/target\) | ||
| ukify build --cmdline 'composefs=${CFS_DIGEST}' --output /out/loader/addons/global-cfs-cmdline.addon.efi |
There was a problem hiding this comment.
Two problems here, and the same at line 136. bootc compute-composefs-digest isn't a command (it's bootc container compute-composefs-digest), so CFS_DIGEST comes out empty. And ${CFS_DIGEST} is in single quotes, so the addon gets the literal string anyway.
So the switches at lines 84 and 149 do fail, but on parsing composefs= hash: got 13 hex chars, expected 128, not on the global-addon or duplicate-composefs= checks they're meant to test. I checked both in a VM with these same addon commands. Asserting on stderr would catch this.
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