Skip to content

Composefs-native backend #1314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

Johan-Liebert1
Copy link
Collaborator

@Johan-Liebert1 Johan-Liebert1 commented May 13, 2025

Related to: #1190

A draft for installing and booting using composefs-rs.

  • Works with install to-disk with --composefs-native option
  • Works with install to-filesystem with --composefs-native option
  • Implementation for bootc status
  • Implementation for bootc switch, bootc upgrade and bootc rollback. Some edge cases need handling, such as rolling back while a deployment is staged. Currently staged deployment WILL be booted on the next reboot regardless of rolling back.

Issues:

  1. Issue with transport type continers-storage, viz, #117. Needs some ironing out. Done
  2. Bootc images need rebuilding with composfs-rs components, namely composfs-setup-root

I have a few logs being logged as warning, just for visibility. Will remove them when it's ready.
Same with composefs-rs patch in cargo.toml

@github-actions github-actions bot added the area/install Issues related to `bootc install` label May 13, 2025
Copy link
Collaborator

@cgwalters cgwalters left a comment

Choose a reason for hiding this comment

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

Thanks for working on this!!

.create_dir_all("composefs")
.context("Creating dir 'composefs'")?;

tracing::warn!("STATE: {state:#?}");
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd recommend dbg! for this type of stuff; it will build locally but not pass CI so it can't be accidentally merged.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I intend to remove all these logs. They were only for debugging purposes

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yep that makes sense, what I meant is that use dbg! locally. (Or use tracing::debug! and keep the tracing in there and just use the tracing selector to ensure you see what you want...we are really chatty with a big env RUST_LOG=debug unfortunately today, mostly in container fetches)

lib/src/cli.rs Outdated
@@ -1199,7 +1199,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
let fd =
std::fs::File::open(&path).with_context(|| format!("Reading {path}"))?;
let digest: fsverity::Sha256HashValue = fsverity::measure_verity(&fd)?;
let digest = hex::encode(digest);
let digest = digest.to_hex();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this should be squashed with the previous commit, and it may make sense to have a "bump composefs-rs" prep PR split out right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right, that makes sense. I'll put this in a separate PR

Copy link
Collaborator

Choose a reason for hiding this comment

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

Done in #1403

async fn install_to_filesystem_impl(
state: &State,
rootfs: &mut RootSetup,
cleanup: Cleanup,
composefs: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this would make sense as part of state

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was conflicted on where to put this. Thanks for the review. I'll add this to State

&imgstore,
)
.await?;
tracing::warn!(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment re dbg! or this one could also make sense as a tracing::debug! that gets left in right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, yes. We can keep this one as debug

@cgwalters
Copy link
Collaborator

bootc install to-disk --composefs

The branding here is a bit confusing because of course, bootc already uses composefs. Just the https://github.com/composefs/composefs/ version, and that is "indirectly" via ostree.

Bigger picture I think what we're trying to aim for here primarily is "sealed" images right? In that case then, one thing I think would make sense is that we don't even have an option to install - we determine what to do by inspecting the container.

It does of course make sense to eventually support unsealed images this way too...which will be extremely important for eventually migrating existing ostree systems. There's an enormous set of details there though.

So my proposal in a nutshell:

  • Let's call it "composefs native" storage or so (the branding here is again hard, open to other ideas)
  • Default to (i.e. require) composefs-native storage if we detect the image is a sealed UKI.
  • Make the opt-in flag called like --composefs-native or so for unsealed cases. That said, it could also make sense to add some sort of flag to the container image to do this instead; because I think most people who want to do this will always want to do it. It's basically similar to what we have with kernel arguments; they can be either baked into the container or applied out of band, and in the general case we need both.

@Johan-Liebert1
Copy link
Collaborator Author

Johan-Liebert1 commented May 15, 2025

Let's call it "composefs native" storage or so (the branding here is again hard, open to other ideas)

I think this works. Or we could have --composefs-native-experimental or something?

Default to (i.e. require) composefs-native storage if we detect the image is a sealed UKI.

Curious about this. Is there a reason we'd want composfs native storage in this case?

@cgwalters
Copy link
Collaborator

Curious about this. Is there a reason we'd want composfs native storage in this case?

Isn't the whole idea that we're aiming for the sealed case?

@Johan-Liebert1
Copy link
Collaborator Author

I'll be getting back to this PR next Monday. Been working on a separate issue this week...

@@ -403,6 +409,8 @@ pub(crate) fn install_create_rootfs(
"root",
opts.wipe,
mkfs_options.iter().copied(),
// TODO: Add cli option for this
Some(uuid::uuid!("6523f8ae-3eb1-4e2a-a05a-18b695ae656f")),
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like you chose the alpha architecture uuid; it'd make sense potentially to use the DPS UUIDs by default for to-disk but that will require some work.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@travier travier mentioned this pull request Jun 17, 2025
12 tasks
@cgwalters cgwalters changed the title Composefs backend Composefs-native backend Jun 25, 2025
Comment on lines 1550 to 1552
let esp_devices = [COREOS_ESP_PART_LABEL, ANACONDA_ESP_PART_LABEL]
.into_iter()
.map(|p| Path::new("/dev/disk/by-partlabel/").join(p));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Two things

  • We shouldn't use /dev/disk/by-partlabel because that's global state, in the general case we may be creating partitions on a loopback mounted device and don't want to touch any global state
  • There's a well known UUID for the ESP to use

Combining these, see https://github.com/coreos/bootupd/blob/92a8fe2326e35d893afbe464857408c403d9e7ea/src/blockdev.rs#L41 which perhaps we could move into this codebase and have bootupd use it (because bootupd already uses bootc).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, okay I'll update to not use by-partlabel

perhaps we could move into this codebase and have bootupd use it (because bootupd already uses bootc)

I found this little bit weird, as I feel like this should be the other way around. Is there a reason why bootc uses the bootupctl command rather than the bootup crate?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a reason why bootc uses the bootupctl command rather than the bootup crate?

Well...unless we took over that CLI, that'd mean we duplicate the code.

create_dir_all(&boot_dir).context("Failed to create boot dir")?;

// Add the user grug cfg
let grub_user_config = format!(
Copy link
Collaborator

Choose a reason for hiding this comment

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

In my ideal world grub related things live in bootupd, not in this project.

And even better if we can make this a static configuration...where grub basically implements the BLS spec and finds things in /EFI/Linux on its own right?

Copy link
Collaborator Author

@Johan-Liebert1 Johan-Liebert1 Jun 26, 2025

Choose a reason for hiding this comment

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

It makes sense to put this in bootupd, but upon discussions with @travier we're still debating on whether we want to push systemd-boot or not, so this is more or less temporary

Copy link
Contributor

Choose a reason for hiding this comment

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

This part of the GRUB config can not be generated by bootup as it is the deployment boot order in the UKI + GRUB case. See: containers/composefs-rs#38 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

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

It's not possible to use the BLS spec partial support in GRUB to boot UKI. It does not support the key that we need to do so, thus why we generate GRUB config snippets: ostreedev/ostree#2753 (comment)

Copy link
Collaborator

@cgwalters cgwalters left a comment

Choose a reason for hiding this comment

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

Some more comments


/// Returns true if the system appears to have been booted with composefs.
pub fn composefs_booted() -> io::Result<bool> {
let cmdline = std::fs::read_to_string("/proc/cmdline")?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should use a proper kernel argument parser in general. It's actually a bit complicated because of quoting. systemd has a good version of course, and we have one in C in ostree. It'd probably make sense to make a dedicated one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We could build our own parser? @travier suggested https://github.com/rust-bakery/nom for parsing Grub menuentries. Maybe we could use it for parsing kernel cmdline as well?

Copy link
Collaborator

@jeckersb jeckersb Jul 17, 2025

Choose a reason for hiding this comment

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

I have written what feels like a million various nom parsers for Advent of Code over the years, I could probably throw something together for kernel cmdline in short order. Seems to be well-documented at https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure, I'm good to make a new small internal crate for this. But I wouldn't try to just greenfield it though, but reference existing parsers. I think https://github.com/ostreedev/ostree/blob/main/src/libostree/ostree-kernel-args.c is correct, as is the systemd version.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry I forgot we already have https://github.com/bootc-dev/bootc/blob/main/lib/src/kernel.rs and to combine with the chat yes let's for now just take the code from https://github.com/containers/composefs-rs/blob/d5455222f282200913902f29c66d5f625ca5661f/crates/composefs-boot/src/cmdline.rs#L9 but what we IMO must do when parsing /proc/cmdline is use to_utf8_lossy or so so we don't bomb out if the kernel commandline somehow has non-UTF8 data.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 for a first pass, but skimming through the boot loader specification document there's a lot more to this. Probably enough so that it would make sense to spin this off into its own crate, I'm sure others would find it to be useful outside of bootc.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably enough so that it would make sense to spin this off into its own crate,

Yes I had the same thought, was one of the motivations for #1413

I'm sure others would find it to be useful outside of bootc.

Probably though I'd say we should probably not at the current time try to go all the way into making it a real published crate unless we had a reason to believe that there was another possible concrete user.

Copy link
Collaborator

@cgwalters cgwalters left a comment

Choose a reason for hiding this comment

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

Some more review

async fn initialize_composefs_repository(
state: &State,
root_setup: &RootSetup,
) -> Result<(Sha256Digest, impl FsVerityHashValue)> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should return the repository object too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I missed it

#[context("Setting up BLS boot")]
pub(crate) fn setup_composefs_bls_boot(
setup_type: BootSetupType,
// TODO: Make this generic
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah but you know the problem here is...it will basically infect most of the bootc codebase with a generic - the same way it did in composefs-rs. That's going to be super painful.

We had part of this debate in containers/composefs-rs#105 and I'm still kind of skeptical that generics make sense overall. (The especially important argument here is that we may want to defer until runtime i.e. via some repository configuration or other state which digest we use) and that will mean we basically double 70% (yes number made up) of the code generated.

A potential thing in the future is to support migrating from sha256 to sha512 for example, and then we'd need two instantations of Repository for each.

Copy link
Collaborator

Choose a reason for hiding this comment

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

}
}

pub(crate) fn parse_bls_config(input: &str) -> Result<BLSConfig> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This sort of thing I would normally impl TryFrom<&str> for BLSConfig. I think it makes it more obvious what is going on when you use it like let foo = BLSConfig::try_from(&some_str)?; Plus for free you also get the inverse of let foo: BLSConfig = some_str.try_into()?;

@cgwalters
Copy link
Collaborator

We had a live chat about this and decided to make a branch in this repo for it so multiple people can collaborate!

@cgwalters
Copy link
Collaborator

Created in https://github.com/bootc-dev/bootc/tree/composefs-backend
I have also sent an invite to @Johan-Liebert1 to have write access to the repository.

(That said we still need to convert to e.g. https://clowarden.io/ )

Instead of using `write_boot_simple` from composefs-rs, have custom
logic in bootc to write UKIs and Grub menuentries

Signed-off-by: Johan-Liebert1 <[email protected]>
Use `atomic_write` from `cap_std` crate
Get rid of `opeanat` crate
Call `fsync` after write to disk

Signed-off-by: Johan-Liebert1 <[email protected]>
@Johan-Liebert1 Johan-Liebert1 changed the base branch from main to composefs-backend July 24, 2025 13:06
@Johan-Liebert1 Johan-Liebert1 marked this pull request as ready for review July 29, 2025 11:22
@Johan-Liebert1 Johan-Liebert1 requested a review from cgwalters July 29, 2025 11:22
@Johan-Liebert1 Johan-Liebert1 force-pushed the composefs-backend branch 2 times, most recently from f068622 to 72bd34b Compare July 31, 2025 06:15
Remove `--boot` option as we can get it from the image itself.

Allow `--insecure` option to `--composefs-native` to make fsverity
validation optional in case the filesystem does not support it.

Signed-off-by: Johan-Liebert1 <[email protected]>
Parse the Grub menuentry file, `boot/grub2/user.cfg` to get a list of
bootable UKIs and figure out if a rollback is currently queued.

Signed-off-by: Johan-Liebert1 <[email protected]>
Returning a local reference to a `&str` is quite tricky with rust.
Update `title` and `chainloader`, the two dynamic fields in the grub
menuentry, to be `String` instead of `&str`

Signed-off-by: Johan-Liebert1 <[email protected]>
We parse the grub menuentries, get the rollback deployment then perform
the rollback, which basically consists of writing a new .staged
menuentry file then atomically swapping the staged and the current
menuentry.

Rollback while there is a staged deployment is still to be handled.

Signed-off-by: Johan-Liebert1 <[email protected]>
If two deployments have the same VMLinuz + Initrd then, we can use the
same binaries for both the deployments.

Before writing the BLS entries to disk we calculate the SHA256Sum
of VMLinuz + Initrd combo, then test if any other deployment has the
same SHA256Sum for the binaries. Store the hash in the origin file under
`boot -> hash` for future lookups.

Signed-off-by: Johan-Liebert1 <[email protected]>
Centralize all constants in a separate file

Signed-off-by: Johan-Liebert1 <[email protected]>
Instead of `/sysroot/state/os/fedora` use `/sysroot/state/os/default` as
the default state directory.

Signed-off-by: Johan-Liebert1 <[email protected]>
Instaed of writing all present menuentries, only write the menuentry for
switch/upgrade and the menuentry for the currently booted deployment.

Signed-off-by: Johan-Liebert1 <[email protected]>
This allows for easier testing

Signed-off-by: Pragyan Poudyal <[email protected]>
Add tests for functions `get_sorted_bls_boot_entries` and
`get_sorted_uki_boot_entries`

Signed-off-by: Pragyan Poudyal <[email protected]>
@cgwalters
Copy link
Collaborator

cgwalters commented Aug 1, 2025

Two things:

Copy link
Collaborator

@cgwalters cgwalters left a comment

Choose a reason for hiding this comment

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

Overall looks sane, we will definitely have some splitting/squashing/cleanup/testing to do as we target main though

@cgwalters cgwalters merged commit 18c5a1d into bootc-dev:composefs-backend Aug 1, 2025
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/install Issues related to `bootc install`
Projects
None yet
Development

Successfully merging this pull request may close these issues.

bootc composefs-native backend
4 participants