diff --git a/crates/lib/src/cli.rs b/crates/lib/src/cli.rs index e4fa2a70c..bb266c758 100644 --- a/crates/lib/src/cli.rs +++ b/crates/lib/src/cli.rs @@ -887,6 +887,7 @@ pub(crate) enum Opt { #[clap(subcommand)] #[clap(hide = true)] Internals(InternalsOpts), + #[clap(hide = true)] ComposefsFinalizeStaged, /// Diff current /etc configuration versus default #[clap(hide = true)] diff --git a/docs/src/internals.md b/docs/src/internals.md index 8d80da88d..bc9f4d983 100644 --- a/docs/src/internals.md +++ b/docs/src/internals.md @@ -55,6 +55,69 @@ Key paths: Implementation: `bootc_composefs` module in `bootc-lib`. +#### Deployment identification + +Each composefs deployment is identified by the SHA-512 fsverity digest +of its EROFS image. This digest appears in the kernel command line as +`composefs=` and is used throughout the codebase to distinguish +deployments. + +The status detection logic (`composefs_deployment_status_from()` in +`status.rs`) classifies deployments using two independent mechanisms: + +- **Booted**: the verity digest from each boot loader entry is compared + against the root filesystem's actual mount source (`composefs:`). + This is read from the mount table, not `/proc/cmdline`, so it remains + correct after a soft-reboot. +- **Staged**: compared against the `depl_id` field in + `/run/composefs/staged-deployment` (see below). +- **Rollback**: anything that is neither booted nor staged. + +#### Staging and finalization + +Unlike the ostree backend (which uses `ostree_sysroot_stage_tree_with_options()` +and the `ostree-finalize-staged.service`), the composefs backend manages +its own staging pipeline. The ostree sysroot is not initialized on +composefs-booted systems, so ostree staging APIs are not available. + +When `bootc upgrade` or `bootc switch` stages a new deployment, two +things happen: + +1. **Boot loader entries** are written to a staging directory rather than + the live directory. For BLS entries this is `loader/entries.staged/` + (alongside the live `loader/entries/`). For GRUB UKI configurations, + `user.cfg.staged` is written next to `user.cfg`. The staging directory + contains entries for both the new deployment and the current booted + deployment (for rollback). + +2. **A transient state file** is written to + `/run/composefs/staged-deployment` as JSON. This records the staged + deployment's verity digest (`depl_id`) and whether finalization is + locked (`finalization_locked`, set by `bootc upgrade --download-only`). + Because this is under `/run`, it does not survive + reboot — if the system reboots before finalization, the staged + deployment is effectively abandoned. + +**Finalization** happens at shutdown via `bootc-finalize-staged.service` +(an `ExecStop` action). The process is: + +1. Read `/run/composefs/staged-deployment`. If absent or marked + download-only, exit with no action. +2. Perform a three-way `/etc` merge: pristine `/etc` from the booted + EROFS image, the running system's `/etc`, and the new deployment's + `/etc`. +3. Atomically swap boot entries using `renameat2(RENAME_EXCHANGE)`: + `loader/entries.staged` ↔ `loader/entries`, then remove the old + staged directory. For UKI entries, the `.staged` suffix is removed + from files on the ESP. + +After finalization, the next boot picks up the new entries. If +finalization fails, the live entries are untouched and the system +boots the previous deployment. + +See also: [bootc-finalize-staged.service(5)](man/bootc-finalize-staged.service.5.md), +[Boot failure detection](boot-failure-detection.md). + ## Key Modules ### The Store Module diff --git a/docs/src/man/bootc-composefs-finalize-staged.8.md b/docs/src/man/bootc-composefs-finalize-staged.8.md deleted file mode 100644 index 569b88367..000000000 --- a/docs/src/man/bootc-composefs-finalize-staged.8.md +++ /dev/null @@ -1,26 +0,0 @@ -# NAME - -bootc-composefs-finalize-staged - TODO: Add description - -# SYNOPSIS - -bootc composefs-finalize-staged - -# DESCRIPTION - -TODO: Add description - - - - -# EXAMPLES - -TODO: Add practical examples showing how to use this command. - -# SEE ALSO - -**bootc**(8) - -# VERSION - - diff --git a/docs/src/man/bootc-finalize-staged.service.5.md b/docs/src/man/bootc-finalize-staged.service.5.md new file mode 100644 index 000000000..a9b89a180 --- /dev/null +++ b/docs/src/man/bootc-finalize-staged.service.5.md @@ -0,0 +1,55 @@ +# NAME + +bootc-finalize-staged.service + +# DESCRIPTION + +This service finalizes a staged composefs deployment at shutdown, making +it active for the next boot. It is the composefs equivalent of +`ostree-finalize-staged.service`. + +The service runs as `ExecStop=/usr/bin/bootc composefs-finalize-staged`, +meaning finalization happens during the shutdown/reboot sequence. It is +started when `bootc upgrade` or `bootc switch` stages a new deployment. + +The finalization process: + +1. Reads the staged deployment record from `/run/composefs/staged-deployment` + (a JSON file written during `bootc upgrade`/`switch`). If no staged + deployment exists, the service exits successfully with no action. + +2. If the staged deployment is marked download-only (from + `bootc upgrade --download-only`), exits without finalizing. + +3. Performs a three-way merge of `/etc`: the pristine `/etc` from the + currently booted EROFS image, the running system's `/etc` (which may + have local modifications), and the new deployment's `/etc`. + +4. Atomically swaps the boot loader entries: `loader/entries.staged` is + exchanged with `loader/entries` via a single `RENAME_EXCHANGE` syscall, + then the old staged directory is removed. For UKI entries, the `.staged` + suffix is removed from the UKI files on the ESP. + +After finalization, the next boot uses the new deployment's boot entries. +If finalization fails, the old boot entries remain in place and the system +boots the previous deployment. + +# DIAGNOSTICS + +Check the journal for finalization results from the previous boot: + +``` +journalctl -u bootc-finalize-staged.service -b -1 +``` + +There is not currently a `bootc-boot-complete.service` equivalent (unlike +the ostree backend's `ostree-boot-complete.service`). Monitoring the +journal is the recommended way to detect finalization failures. + +# SEE ALSO + +**bootc**(8), **bootc-upgrade**(8), **bootc-switch**(8) + +# VERSION + + diff --git a/docs/src/man/bootc.8.md b/docs/src/man/bootc.8.md index d543d0499..41859e4c9 100644 --- a/docs/src/man/bootc.8.md +++ b/docs/src/man/bootc.8.md @@ -34,7 +34,6 @@ pulled and `bootc upgrade`. | **bootc install** | Install the running container to a target | | **bootc container** | Operations which can be executed as part of a container build | | **bootc loader-entries** | Operations on Boot Loader Specification (BLS) entries | -| **bootc composefs-finalize-staged** | |