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

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6cfb4be
cli/install: Add `composefs` option to `InstallToDiskOpts`
Johan-Liebert1 May 7, 2025
d03feb2
cli: Handle compatibility with latest composefs-rs
Johan-Liebert1 May 7, 2025
6be5560
install: Pull composefs repository
Johan-Liebert1 May 7, 2025
b3d1952
install/composefs: Write boot entries
Johan-Liebert1 May 13, 2025
83227f5
Grub wip
Johan-Liebert1 May 16, 2025
97b902c
Use discoverable partition specification for rootfs UUID
Johan-Liebert1 May 20, 2025
151963e
Bump composefs-rs
Johan-Liebert1 May 20, 2025
200d669
install/composefs: Update composefs install options
Johan-Liebert1 Jun 11, 2025
dbcce3f
install/composefs: Introduce flag for bls/uki boot
Johan-Liebert1 Jun 11, 2025
79f3957
install/composefs: Get image and transport `source_imgref`
Johan-Liebert1 Jun 11, 2025
42632ce
install/composefs: Return result from opts.validate
Johan-Liebert1 Jun 17, 2025
eac8e4d
install/composefs: Handle kargs for BLS
Johan-Liebert1 Jun 17, 2025
edcf721
install/composefs: Put UKI in the ESP
Johan-Liebert1 Jun 17, 2025
af8fa4e
cli/composefs: Implement `status` cmd for compoesfs booted system
Johan-Liebert1 Jun 20, 2025
770a57b
install/composefs: Create origin file
Johan-Liebert1 Jun 24, 2025
8e68e45
install/composefs: Move state to `/state/deploy/<image_id>`
Johan-Liebert1 Jun 26, 2025
8d9b923
cli: Implement `bootc status` for composefs native booted system
Johan-Liebert1 Jun 26, 2025
4383a35
install/composefs: Implement `bootc upgrade`
Johan-Liebert1 Jun 30, 2025
3c7a2e6
cli/composefs: Show staged/rollback deployments
Johan-Liebert1 Jul 1, 2025
ab8cdc9
composefs/install: Write entries as staged
Johan-Liebert1 Jul 2, 2025
6098379
cli/composefs: Implement `bootc switch`
Johan-Liebert1 Jul 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 50 additions & 56 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

126 changes: 118 additions & 8 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,24 @@ use fn_error_context::context;
use indoc::indoc;
use ostree::gio;
use ostree_container::store::PrepareResult;
use ostree_ext::composefs::fsverity;
use ostree_ext::composefs::fsverity::{self, FsVerityHashValue};
use ostree_ext::container as ostree_container;
use ostree_ext::container_utils::ostree_booted;
use ostree_ext::container_utils::{composefs_booted, ostree_booted};
use ostree_ext::keyfileext::KeyFileExt;
use ostree_ext::ostree;
use schemars::schema_for;
use serde::{Deserialize, Serialize};

use crate::deploy::RequiredHostSpec;
use crate::install::{
pull_composefs_repo, setup_composefs_bls_boot, setup_composefs_uki_boot, write_composefs_state,
BootSetupType, BootType,
};
use crate::lints;
use crate::progress_jsonl::{ProgressWriter, RawProgressFd};
use crate::spec::Host;
use crate::spec::ImageReference;
use crate::status::composefs_deployment_status;
use crate::utils::sigpolicy_from_opt;

/// Shared progress options
Expand Down Expand Up @@ -747,6 +752,51 @@ fn prepare_for_write() -> Result<()> {
Ok(())
}

#[context("Upgrading composefs")]
async fn upgrade_composefs(_opts: UpgradeOpts) -> Result<()> {
// TODO: IMPORTANT Have all the checks here that `bootc upgrade` has for an ostree booted system

let host = composefs_deployment_status()
.await
.context("Getting composefs deployment status")?;

// TODO: IMPORTANT We need to check if any deployment is staged and get the image from that
let imgref = host
.spec
.image
.as_ref()
.ok_or_else(|| anyhow::anyhow!("No image source specified"))?;

// let booted_image = host
// .status
// .booted
// .ok_or(anyhow::anyhow!("Could not find booted image"))?
// .image
// .ok_or(anyhow::anyhow!("Could not find booted image"))?;

// tracing::debug!("booted_image: {booted_image:#?}");
// tracing::debug!("imgref: {imgref:#?}");

// let digest = booted_image
// .digest()
// .context("Getting digest for booted image")?;

let (repo, entries, id) = pull_composefs_repo(&imgref.transport, &imgref.image).await?;

let Some(entry) = entries.into_iter().next() else {
anyhow::bail!("No boot entries!");
};

match BootType::from(&entry) {
BootType::Bls => setup_composefs_bls_boot(BootSetupType::Upgrade, repo, &id, entry),
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry),
}?;

write_composefs_state(&Utf8PathBuf::from("/sysroot"), id, imgref, true)?;

Ok(())
}

/// Implementation of the `bootc upgrade` CLI command.
#[context("Upgrading")]
async fn upgrade(opts: UpgradeOpts) -> Result<()> {
Expand Down Expand Up @@ -860,9 +910,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
Ok(())
}

/// Implementation of the `bootc switch` CLI command.
#[context("Switching")]
async fn switch(opts: SwitchOpts) -> Result<()> {
fn imgref_for_switch(opts: &SwitchOpts) -> Result<ImageReference> {
let transport = ostree_container::Transport::try_from(opts.transport.as_str())?;
let imgref = ostree_container::ImageReference {
transport,
Expand All @@ -871,6 +919,56 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
let sigverify = sigpolicy_from_opt(opts.enforce_container_sigpolicy);
let target = ostree_container::OstreeImageReference { sigverify, imgref };
let target = ImageReference::from(target);

return Ok(target);
}

#[context("Composefs Switching")]
async fn switch_composefs(opts: SwitchOpts) -> Result<()> {
let target = imgref_for_switch(&opts)?;
// TODO: Handle in-place

let host = composefs_deployment_status()
.await
.context("Getting composefs deployment status")?;

let new_spec = {
let mut new_spec = host.spec.clone();
new_spec.image = Some(target.clone());
new_spec
};

if new_spec == host.spec {
println!("Image specification is unchanged.");
return Ok(());
}

let Some(target_imgref) = new_spec.image else {
anyhow::bail!("Target image is undefined")
};

let (repo, entries, id) =
pull_composefs_repo(&target_imgref.transport, &target_imgref.image).await?;

let Some(entry) = entries.into_iter().next() else {
anyhow::bail!("No boot entries!");
};

match BootType::from(&entry) {
BootType::Bls => setup_composefs_bls_boot(BootSetupType::Upgrade, repo, &id, entry),
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade, repo, &id, entry),
}?;

write_composefs_state(&Utf8PathBuf::from("/sysroot"), id, &target_imgref, true)?;

Ok(())
}

/// Implementation of the `bootc switch` CLI command.
#[context("Switching")]
async fn switch(opts: SwitchOpts) -> Result<()> {
let target = imgref_for_switch(&opts)?;

let prog: ProgressWriter = opts.progress.try_into()?;

// If we're doing an in-place mutation, we shortcut most of the rest of the work here
Expand Down Expand Up @@ -1084,8 +1182,20 @@ impl Opt {
async fn run_from_opt(opt: Opt) -> Result<()> {
let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
match opt {
Opt::Upgrade(opts) => upgrade(opts).await,
Opt::Switch(opts) => switch(opts).await,
Opt::Upgrade(opts) => {
if composefs_booted()? {
upgrade_composefs(opts).await
} else {
upgrade(opts).await
}
}
Opt::Switch(opts) => {
if composefs_booted()? {
switch_composefs(opts).await
} else {
switch(opts).await
}
}
Opt::Rollback(opts) => rollback(opts).await,
Opt::Edit(opts) => edit(opts).await,
Opt::UsrOverlay => usroverlay().await,
Expand Down Expand Up @@ -1199,7 +1309,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
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

println!("{digest}");
Ok(())
}
Expand Down
Loading