Skip to content

composefs: Add support for android boot (both regular and ukiboot) - #2490

Merged
jmarrero merged 14 commits into
bootc-dev:mainfrom
alexlarsson:aboot-support
Oct 22, 2026
Merged

jmarrero merged 14 commits into
bootc-dev:mainfrom
alexlarsson:aboot-support

Conversation

@alexlarsson

Copy link
Copy Markdown
Contributor

Add experimental aboot support to the composefs backend. This supports both
Android boot v2 images on systems with boot_a/boot_b partitions and no ESP,
and ukiboot images on systems with an ESP.

The series adds bootc container aboot to build boot artifacts, teaches
container inspect and composefs installation to recognize them, and implements
slot-aware status, updates, rollback, reconciliation, and garbage collection.
Updates are staged without writing a boot partition; at shutdown, bootc
verifies the artifacts and uses aboot-deploy to flash the inactive slot.

There is also some generic changes:

  • Include dtb in deploy digest
  • Show queued rollbacks in bootc status.

This is marked draft atm, because it pulls in the composefs-rs branch from composefs/composefs-rs#399. Once that is landed we should do a release and update bootc to that instead.

I have follow-on work to use this to build images, which I have used to test stuff:

I was able with this to use a Dockerfile to build Fedora 44 images that boot on x86_64/aarch64 with ukiboot, and aarch64 with u-boot with android boot support. I think we can later add some integration test for this, but atm its a bit painful until all the other dependency changes have landed.

@github-actions github-actions Bot added area/install Issues related to `bootc install` area/documentation Updates to the documentation labels Sep 23, 2026
@bootc-bot
bootc-bot Bot requested a review from cgwalters September 23, 2026 17:38
Comment thread crates/lib/src/bootc_composefs/boot.rs Resolved
@cgwalters

Copy link
Copy Markdown
Collaborator

Copying over composefs/composefs-rs#399 (comment) - would have been nice to have a rough sketch of a design draft for this linked and gather a bit of consensus on it.

Could have basically just been fe1ea1a#diff-8df22b9d26bdc5a49b7c7557b59e35583771fb6bd56bb735479048f176cec622 filed as an issue?

It may still be worth filing one as the right place to discuss high level things as opposed to implementation code concerns.

@alexlarsson

Copy link
Copy Markdown
Contributor Author

@cgwalters I've done this work as much to learn about the composefs bootc backend as to do the work, because I had not looked at it before. So, it would have been hard for me to come up with the design up front. However, I agree that we need a highlevel discussion, and that will fit better in an issue, then we can do the code nitpicking here later. I'll type one up to get us started.

@alexlarsson

Copy link
Copy Markdown
Contributor Author

I moved the independent stuff to separate MRs and rebased this.

@alexlarsson

Copy link
Copy Markdown
Contributor Author

I rebased on master and fixed some CI failures. I also changed things around so now we just always auto-detect the backend, no need to hand-specify a bootloader.

alexlarsson added a commit to composefs/composefs-rs that referenced this pull request Sep 28, 2026
This changes some APIs, and we need the corresponding changes in
bootc from: bootc-dev/bootc#2490

We'll re-enable this once that is merged.

Signed-off-by: Alexander Larsson <alexl@redhat.com>
@alexlarsson
alexlarsson marked this pull request as ready for review September 28, 2026 20:22
@bootc-bot
bootc-bot Bot requested a review from jeckersb September 28, 2026 20:23
@alexlarsson

Copy link
Copy Markdown
Contributor Author

I updated this to the composefs-rs 0.9.3 release and removed the draft marking.
I think this is good enough as an initial experimental version at least.

cgwalters-bot pushed a commit to cgwalters-forge/composefs-rs that referenced this pull request Sep 29, 2026
This changes some APIs, and we need the corresponding changes in
bootc from: bootc-dev/bootc#2490

We'll re-enable this once that is merged.

Signed-off-by: Alexander Larsson <alexl@redhat.com>
@alexlarsson
alexlarsson force-pushed the aboot-support branch 3 times, most recently from 72e8eec to 0d03ff7 Compare September 30, 2026 10:53
@github-actions github-actions Bot added the area/ostree Issues related to ostree label Sep 30, 2026
@alexlarsson
alexlarsson force-pushed the aboot-support branch 4 times, most recently from 05705fe to f39c6af Compare September 30, 2026 11:19
@alexlarsson

Copy link
Copy Markdown
Contributor Author

Rebased on main

This is needed for aboot support.

Signed-off-by: Alexander Larsson <alexl@redhat.com>
Add the Aboot boot type and map composefs-boot aboot entries to it.

Reject aboot operations before modifying bootloader state until the
deployment and lifecycle paths are implemented.

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
An addon can sort before the primary UKI and supply the wrong kernel
version and command line. Ignore addon candidates when selecting the
kernel; addon installation continues to use the separate boot-entry
inventory.

Images with only addons now report no kernel; addons alongside vmlinuz
no longer force UKI backend selection.

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
This extends KernelType with a new Aboot version, and extends the
reporting of "bootc container inspect" to give aboot info as well.

Note: We're not recognizing traditional ostree aboot setups with an
aboot.img in /usr/lib/modules/$kver, because we will be treating these
differently, enforcing the composefs backend. Also, we want the
artifacts in /boot so that we can sign them without having that
affect the boot transformed digest.

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
This moves the code for kernel selection, composefs digest
calculation, and command-line assembly so aboot builders can reuse it
later.

Note: This is just a code motion and no significant beheviour is
changed (other than maybe some specific error message).

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
Kernel arguments from the image or --karg can contain stale composefs
digests. Apply the computed arguments after those inputs, replacing
existing values and removing duplicates so these are not duplicated,
which could cause confusion.

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
This uses aboot-update to build Android boot or ukiboot images,
similar to the "container ukify" command.

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
This just moves some code out, which will make later aboot changes
easier to do.

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
Pick (or allow selecting) the right bootloader for aboot images, and
install EFI files needed in case of ukiboot. Command lines in
immutable images are validated.

This changes how bootloader "none" is handled in the composefs
backend. Before it was not supported, but now it's is used for android
boot aboot images, where the bootloader is not on disk but in the board.

We also need to be more careful when picking the primary boot entry,
becase discover enumerates Type2 entries before the aboot, but we
might have non-bootable Type2 entries (like addons), and we don't want
those to cover an aboot entry.

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
Implement staging, finalization, and boot-time reconciliation for aboot
deployments. The boot time reconciliation is done in the
bootc-aboot-reconcile.service which is enbled by the bootc generator if needed
(and starts once writable state is available). The reconciliation includes active
slot tracking to ensure we're not GC:ing bootable images.

Updates stage the boot image and optional vbmeta alongside the deployment and
record their hashes in persistent pending state. At shutdown it, verifies the
payloads, records the attempt, invalidates the non-booted slot mapping, and
invokes aboot-deploy which will write the image to the next free boot partition.

On the following boot, use the active deployment to determine whether the attempt
succeeded. Clean up successful attempts while retaining failed candidates without
automatically retrying them.

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
Document /boot-only discovery, immutable artifact command lines,
artifact-selected disk layouts, and persistent A/B update behavior.

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
Let repart callers collect and use definitions in a chosen directory,
preparing for layouts with additional required partitions.

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
Derive Android or ukiboot disk layouts from the source image's boot
artifact when installing the running container image. With
--source-imgref, skip artifact-based layout selection rather than
inferring it from the installation host.

Validate repart definitions and the pulled artifact against the selected
layout, then initialize both slots. Keep the legacy OSTree installation
path unchanged.

Assisted-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
Exercise the native aboot installer through the existing bcvk and TMT
runner on x86_64. Derive the image from the OSTree test rootfs so
backend selection must follow the boot artifact, and verify the
default disk layout, both payload slots, and boot completion. After
that we try an update and rollback.

Note: This manually installs bcvk 0.21.0, this can be dropped once
bootc-dev/actions has been updated.

Generated-by: AI
Signed-off-by: Alexander Larsson <alexl@redhat.com>
@alexlarsson

alexlarsson commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor Author

I see the CI failure fix was merged, lemme rebase

@alexlarsson

Copy link
Copy Markdown
Contributor Author

Ok, now all the tests passed. Any chance we could get some more review and feedback on this? It kinda blocks a bunch of more work on using this on the automotive side.

@jmarrero jmarrero self-assigned this Oct 21, 2026

@jmarrero-bot jmarrero-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few non-blocking notes on the aboot code, inline.

Generated-by: https://github.com/jmarrero-bot#llms

));

state.record_attempt(deployment)?;
if let Some(slot) = booted_slot {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things here, neither blocking for an experimental feature:

  • When booted_slot is None (no or ambiguous androidboot.slot_suffix) this still records the attempt and runs aboot-deploy, but the docs ("requires an unambiguous booted slot") say it shouldn't. Bail before record_attempt() instead?
  • invalidate_other() drops the rollback slot's mapping before aboot-deploy has written anything. If it then fails without writing (say the binary is missing), the old rollback drops out of bootc status and out of the GC roots, but its slot still holds a bootable payload. And the way out of the resulting attempted state (bootc delete-deployment on the staged deployment) runs a pruning GC right away, which then deletes that rollback's state. Checking for aboot-deploy up front, or restoring the mapping when the spawn fails, would avoid that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, these are all very weird situations where the system is basically hosed from the point of view of aboot.

We have at times during development had systems which didn't properly record a boot slot, and having those act as a single-slot system has helped us make progress. So, basically I would like to leave all the slot handling magic (or at least as much as we can) to the aboot-deploy call, rather than trying to guess how it will behave. So, I think the first issue explained here is good for this development setup. In a real production environment it should not matter anyway.

For the second issue, yes, we could potentially loose and old rollback slot, but that will only happen if the current image is broken (no aboot-deploy), and I'm not sure how interesting that specific error case is. I guess you could maybe roll back to the old commit that had aboot-deploy? Any later error we can't tell if the state is safe to keep anyway. I'll add an up-front check for aboot-deploy, somewhere early in the update codepath (probably before finalize).

origin,
verity,
missing_verity_allowed,
Bootloader::None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: this hardcodes Bootloader::None, so bootc status on a ukiboot host reports bootloader: none even though install picked ukiboot (image_bootloader()). The origin doesn't record the encoding today, so this probably needs the bootloader (or encoding) written at install/stage time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, lets add the bootloader to the origin file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we only ever use one bootloader on the host. I should report the host setting here, and then add some code to validate we always stay on the same bootloader on updates.

} else {
ensure!(
bootloader != Bootloader::None,
"Bootloader set to none is not supported with the composefs backend unless using an aboot artifact"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check used to be in InstallComposefsOpts validation, before anything touched the disk. Now it runs in setup_composefs_boot(), so install to-disk --composefs-backend --bootloader none with an ordinary image wipes and partitions the disk, pulls, and only then fails. At least for installs from the running container, prepare_install() already knows from find_kernel() whether the image has an aboot payload. Could the non-aboot case keep failing early there?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not worried about this. install to-disk generally runs in a VM during install. Delaying the error to after some loopback files are wiped isn't really a problem.

option and reconciles the attempted deployment. The previous slot becomes the rollback
deployment; `bootc rollback` queues it through `aboot-deploy --rollback`. `bootc status`
indicates when a rollback is queued. A failed boot is **not** automatically retried: bootc
retains the attempted update for inspection and will not stage another update until that

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest saying how that state gets resolved. As far as I can tell, after a failed flash or a firmware fallback the only way to clear attempted and unblock bootc upgrade/switch/rollback is the hidden bootc delete-deployment <id> on the staged deployment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just never let the failed attempt be retried or block an update. It should just be temporarily available for inspection.

@jmarrero
jmarrero dismissed cgwalters’s stale review October 22, 2026 00:02

Colin's request was addressed.

@jmarrero

Copy link
Copy Markdown
Contributor

Approving to unblock this; the inline comments can be follow-ups. The docs are explicit that this is experimental, but the CLI doesn't gate or mark it (bootc container aboot, --bootloader ukiboot; RELEASES.md asks for an experimental marker on the command or flag, as grub-cc has). Not sure if that's on purpose @alexlarsson? Either way, this is focused work that won't affect most users at the moment: I checked that grub and systemd-boot hosts are unchanged.

@alexlarsson

Copy link
Copy Markdown
Contributor Author

@jmarrero Thanks. Any chance you can merge this now? I'll work on the followup comments separately. But it is somewhate of a pain to keep this PR updated as it often conflicts with other unrelated changes.

@jmarrero
jmarrero merged commit 6af0131 into bootc-dev:main Oct 22, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/documentation Updates to the documentation area/install Issues related to `bootc install` area/ostree Issues related to ostree

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants