Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions crates/lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
63 changes: 63 additions & 0 deletions docs/src/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<digest>` 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:<digest>`).
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
Expand Down
26 changes: 0 additions & 26 deletions docs/src/man/bootc-composefs-finalize-staged.8.md

This file was deleted.

55 changes: 55 additions & 0 deletions docs/src/man/bootc-finalize-staged.service.5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# NAME

bootc-finalize-staged.service
Comment on lines +1 to +3
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The NAME section in a man page should follow the standard format: name - summary description. This allows tools like apropos to correctly index the service and provides a quick overview for users.

Suggested change
# NAME
bootc-finalize-staged.service
# NAME
bootc-finalize-staged.service - Finalize a staged composefs deployment at shutdown


# 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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

RENAME_EXCHANGE is a flag for the renameat2 system call, rather than a system call itself. Referring to the specific syscall is more technically accurate and consistent with the description in the internals documentation.

Suggested change
exchanged with `loader/entries` via a single `RENAME_EXCHANGE` syscall,
exchanged with loader/entries via a single renameat2(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

<!-- VERSION PLACEHOLDER -->
1 change: 0 additions & 1 deletion docs/src/man/bootc.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -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** | |

<!-- END GENERATED SUBCOMMANDS -->

Expand Down
Loading