-
Notifications
You must be signed in to change notification settings - Fork 131
WIP: composefs branch #1444
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
cgwalters
wants to merge
24
commits into
main
Choose a base branch
from
composefs-backend
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
WIP: composefs branch #1444
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
260c20e
WIP: composefs backend
Johan-Liebert1 24d0e98
Rework composefs_booted to use kernel cmdline
cgwalters d3a66c9
composefs/status: Read UKI entries to check for queued rollback
Johan-Liebert1 9694943
parser/grub: Use String instead of &str
Johan-Liebert1 8820dad
composefs/rollback: Handle UKI rollback
Johan-Liebert1 78e3fc8
composefs/state: Use atomic writes for origin and staged deployment f…
Johan-Liebert1 7e489dd
composefs/boot/bls: Handle duplicate VMLinuz + Initrd
Johan-Liebert1 c2ed79b
parser/bls: `impl Display` for BLSConfig
Johan-Liebert1 67590ce
lib/composefs: Centralize constants
Johan-Liebert1 52f2b5c
composefs/state: Name state directory `default`
Johan-Liebert1 2c51b6a
parser/bls: Add tests for bls parser
Johan-Liebert1 eefddd2
install/composefs/uki: Write only staged + booted menuentry on upgrade
Johan-Liebert1 d263a1f
rollback/composefs: Print whether we are reverting the queued rollback
Johan-Liebert1 6d33975
refactor: Pass boot dir to boot entry readers
Johan-Liebert1 ef3f885
test: Add tests for reading boot entries
Johan-Liebert1 224f838
Drop duplicate bls_config
cgwalters ffbc273
install: Use read_file from composefs-boot
cgwalters 2afa715
install: Fix cargo fmt
cgwalters 2ff7a13
status: Use constant for composefs
cgwalters 6d5ead8
status: Enhance composefs cmdline parsing to handle ?
cgwalters 42e44bb
composefs-backend: store boot assets in ESP during install
p5 b15b7b0
Post-rebase fixups:
jeckersb 24bf572
composefs/install/bls: Fix empty version in config
Johan-Liebert1 023be10
composefs/install: Copy /etc contents to state
Johan-Liebert1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub(crate) mod state; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
use std::process::Command; | ||
|
||
use anyhow::{Context, Result}; | ||
use bootc_utils::CommandRunExt; | ||
use camino::Utf8PathBuf; | ||
use fn_error_context::context; | ||
|
||
use rustix::{ | ||
fs::{open, Mode, OFlags, CWD}, | ||
mount::{unmount, UnmountFlags}, | ||
path::Arg, | ||
}; | ||
|
||
/// Mounts an EROFS image and copies the pristine /etc to the deployment's /etc | ||
#[context("Copying etc")] | ||
pub(crate) fn copy_etc_to_state( | ||
sysroot_path: &Utf8PathBuf, | ||
erofs_id: &String, | ||
state_path: &Utf8PathBuf, | ||
) -> Result<()> { | ||
let sysroot_fd = open( | ||
sysroot_path.as_std_path(), | ||
OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC, | ||
Mode::empty(), | ||
) | ||
.context("Opening sysroot")?; | ||
|
||
let composefs_fd = bootc_initramfs_setup::mount_composefs_image(&sysroot_fd, &erofs_id, false)?; | ||
|
||
let tempdir = tempfile::tempdir().context("Creating tempdir")?; | ||
|
||
bootc_initramfs_setup::mount_at_wrapper(composefs_fd, CWD, tempdir.path())?; | ||
|
||
// TODO: Replace this with a function to cap_std_ext | ||
let cp_ret = Command::new("cp") | ||
.args([ | ||
"-a", | ||
&format!("{}/etc/.", tempdir.path().as_str()?), | ||
&format!("{state_path}/etc/."), | ||
]) | ||
.run_capture_stderr(); | ||
|
||
// Unmount regardless of copy succeeding | ||
unmount(tempdir.path(), UnmountFlags::DETACH).context("Unmounting composefs")?; | ||
|
||
cp_ret | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,11 +28,16 @@ use ostree_ext::sysroot::SysrootLock; | |
use schemars::schema_for; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
use crate::deploy::RequiredHostSpec; | ||
use crate::deploy::{composefs_rollback, 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_booted, composefs_deployment_status}; | ||
use crate::utils::sigpolicy_from_opt; | ||
|
||
/// Shared progress options | ||
|
@@ -903,6 +908,69 @@ 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, fs) = pull_composefs_repo(&imgref.transport, &imgref.image).await?; | ||
|
||
let Some(entry) = entries.into_iter().next() else { | ||
anyhow::bail!("No boot entries!"); | ||
}; | ||
|
||
let boot_type = BootType::from(&entry); | ||
let mut boot_digest = None; | ||
|
||
match boot_type { | ||
BootType::Bls => { | ||
boot_digest = Some(setup_composefs_bls_boot( | ||
BootSetupType::Upgrade(&fs), | ||
repo, | ||
&id, | ||
entry, | ||
)?) | ||
} | ||
|
||
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade(&fs), repo, &id, entry)?, | ||
}; | ||
|
||
write_composefs_state( | ||
&Utf8PathBuf::from("/sysroot"), | ||
id, | ||
imgref, | ||
true, | ||
boot_type, | ||
boot_digest, | ||
)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Implementation of the `bootc upgrade` CLI command. | ||
#[context("Upgrading")] | ||
async fn upgrade(opts: UpgradeOpts) -> Result<()> { | ||
|
@@ -1016,9 +1084,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, | ||
|
@@ -1027,6 +1093,73 @@ 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
#[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, fs) = | ||
pull_composefs_repo(&"docker".into(), &target_imgref.image).await?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
let Some(entry) = entries.into_iter().next() else { | ||
anyhow::bail!("No boot entries!"); | ||
}; | ||
|
||
let boot_type = BootType::from(&entry); | ||
let mut boot_digest = None; | ||
|
||
match boot_type { | ||
BootType::Bls => { | ||
boot_digest = Some(setup_composefs_bls_boot( | ||
BootSetupType::Upgrade(&fs), | ||
repo, | ||
&id, | ||
entry, | ||
)?) | ||
} | ||
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade(&fs), repo, &id, entry)?, | ||
}; | ||
|
||
write_composefs_state( | ||
&Utf8PathBuf::from("/sysroot"), | ||
id, | ||
&target_imgref, | ||
true, | ||
boot_type, | ||
boot_digest, | ||
)?; | ||
|
||
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 | ||
|
@@ -1118,21 +1251,25 @@ async fn switch(opts: SwitchOpts) -> Result<()> { | |
/// Implementation of the `bootc rollback` CLI command. | ||
#[context("Rollback")] | ||
async fn rollback(opts: RollbackOpts) -> Result<()> { | ||
let sysroot = &get_storage().await?; | ||
let ostree = sysroot.get_ostree()?; | ||
crate::deploy::rollback(sysroot).await?; | ||
if composefs_booted()?.is_some() { | ||
composefs_rollback().await? | ||
} else { | ||
let sysroot = &get_storage().await?; | ||
let ostree = sysroot.get_ostree()?; | ||
crate::deploy::rollback(sysroot).await?; | ||
|
||
if opts.soft_reboot.is_some() { | ||
// Get status of rollback deployment to check soft-reboot capability | ||
let host = crate::status::get_status_require_booted(ostree)?.2; | ||
|
||
handle_soft_reboot( | ||
opts.soft_reboot, | ||
host.status.rollback.as_ref(), | ||
"rollback", | ||
|| soft_reboot_rollback(ostree), | ||
)?; | ||
} | ||
if opts.soft_reboot.is_some() { | ||
// Get status of rollback deployment to check soft-reboot capability | ||
let host = crate::status::get_status_require_booted(ostree)?.2; | ||
|
||
handle_soft_reboot( | ||
opts.soft_reboot, | ||
host.status.rollback.as_ref(), | ||
"rollback", | ||
|| soft_reboot_rollback(ostree), | ||
)?; | ||
} | ||
}; | ||
|
||
if opts.apply { | ||
crate::reboot::reboot()?; | ||
|
@@ -1282,8 +1419,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()?.is_some() { | ||
upgrade_composefs(opts).await | ||
} else { | ||
upgrade(opts).await | ||
} | ||
} | ||
Opt::Switch(opts) => { | ||
if composefs_booted()?.is_some() { | ||
switch_composefs(opts).await | ||
} else { | ||
switch(opts).await | ||
} | ||
} | ||
Opt::Rollback(opts) => rollback(opts).await, | ||
Opt::Edit(opts) => edit(opts).await, | ||
Opt::UsrOverlay => usroverlay().await, | ||
|
@@ -1424,8 +1573,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> { | |
FsverityOpts::Enable { path } => { | ||
let fd = | ||
std::fs::File::open(&path).with_context(|| format!("Reading {path}"))?; | ||
// Note this is not robust to forks, we're not using the _maybe_copy variant | ||
fsverity::enable_verity_with_retry::<fsverity::Sha256HashValue>(&fd)?; | ||
fsverity::enable_verity_raw::<fsverity::Sha256HashValue>(&fd)?; | ||
Ok(()) | ||
} | ||
}, | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/// composefs= paramter in kernel cmdline | ||
pub const COMPOSEFS_CMDLINE: &str = "composefs"; | ||
|
||
/// Directory to store transient state, such as staged deployemnts etc | ||
pub(crate) const COMPOSEFS_TRANSIENT_STATE_DIR: &str = "/run/composefs"; | ||
/// File created in /run/composefs to record a staged-deployment | ||
pub(crate) const COMPOSEFS_STAGED_DEPLOYMENT_FNAME: &str = "staged-deployment"; | ||
|
||
/// Absolute path to composefs-native state directory | ||
pub(crate) const STATE_DIR_ABS: &str = "/sysroot/state/deploy"; | ||
/// Relative path to composefs-native state directory. Relative to /sysroot | ||
pub(crate) const STATE_DIR_RELATIVE: &str = "state/deploy"; | ||
/// Relative path to the shared 'var' directory. Relative to /sysroot | ||
pub(crate) const SHARED_VAR_PATH: &str = "state/os/default/var"; | ||
|
||
/// Section in .origin file to store boot related metadata | ||
pub(crate) const ORIGIN_KEY_BOOT: &str = "boot"; | ||
/// Whether the deployment was booted with BLS or UKI | ||
pub(crate) const ORIGIN_KEY_BOOT_TYPE: &str = "boot_type"; | ||
/// Key to store the SHA256 sum of vmlinuz + initrd for a deployment | ||
pub(crate) const ORIGIN_KEY_BOOT_DIGEST: &str = "digest"; | ||
|
||
/// Filename for `loader/entries` | ||
pub(crate) const BOOT_LOADER_ENTRIES: &str = "entries"; | ||
/// Filename for staged boot loader entries | ||
pub(crate) const STAGED_BOOT_LOADER_ENTRIES: &str = "entries.staged"; | ||
/// Filename for rollback boot loader entries | ||
pub(crate) const ROLLBACK_BOOT_LOADER_ENTRIES: &str = STAGED_BOOT_LOADER_ENTRIES; | ||
|
||
/// Filename for grub user config | ||
pub(crate) const USER_CFG: &str = "user.cfg"; | ||
/// Filename for staged grub user config | ||
pub(crate) const USER_CFG_STAGED: &str = "user.cfg.staged"; | ||
/// Filename for rollback grub user config | ||
pub(crate) const USER_CFG_ROLLBACK: &str = USER_CFG_STAGED; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of
tempdir.path().as_str()?
could cause an error if the temporary directory path contains non-UTF-8 characters. While this is uncommon on many systems, it's safer to handle this potential failure explicitly. You can usecamino::Utf8Path::from_path
to convert thestd::path::Path
to acamino::Utf8Path
and handle theOption
result.