Update Flow

The full update flow from staging through reboot to a working new deploy.

Phase 1. Stage

Triggered by cache22-update, bootc upgrade, cache22-rebase, or bootc switch.

cache22-update
  -> bootc upgrade --check
     # Read-only check against the registry.
     # Exit if no new content.
  -> bootc upgrade
     -> bootc pulls layers from registry.
     -> bootc creates an ostree commit from the OCI image.
     -> ostree creates a deploy directory under
        /sysroot/ostree/deploy/<state>/deploy/<new-csum>.<idx>/
     -> The new deploy is in the "staged" slot.
        bootc status .status.staged != null
  -> /usr/libexec/cache22/refresh-pending-motd
     -> Reads bootc status, writes /run/motd.d/10-cache22-pending-reboot
        with the apply hint.
     -> Pops a desktop notification on graphical sessions.
  (returns)

At this point:

  • The staged deploy directory exists on disk.
  • ostree has staged metadata in /sysroot/ostree/repo/.
  • No BLS entry is written yet. No UKI is built yet.
  • The bootloader still points at the booted deploy.
  • The user’s existing system continues to run normally.

This is the standard bootc/ostree behavior. Finalize and UKI build are deferred to shutdown.

Phase 2. Apply (default path: full reboot or auto-pick)

Triggered by cache22-reboot (no flags) or systemctl reboot.

systemctl reboot
  (or cache22-reboot which exec's systemctl reboot for the hard path)
  -> systemd shutdown sequence begins.
  -> reboot.target activates.
  -> All units with Conflicts=final.target stop.
  -> ostree-finalize-staged.service stops.
     ExecStop runs in declaration order:
       1. /usr/bin/ostree admin finalize-staged
          -> Writes BLS entry to /boot/loader/entries/.
          -> Swaps active boot.X slot.
          -> Cleans up old deploys per retention policy.
       2. /usr/libexec/cache22/resign-uki  (from 50-cache22-uki.conf drop-in)
          -> For each live deploy, build the UKI from BLS entry.
          -> Sign with per-machine SB key + tpm-pcr11.key.
          -> Atomic-write to /efi/EFI/Linux/cache22-<csum>.efi.
          -> Re-sign sd-boot if newer.
          -> GC stale UKIs.
  -> systemd-reboot.service runs.
  -> Hardware reboots.

  Boot sequence:
  -> Firmware POST.
  -> Firmware loads sd-boot (signed).
  -> sd-boot enumerates UKIs in /efi/EFI/Linux/.
  -> sd-boot picks highest .osrel VERSION_ID UKI (the new deploy).
  -> sd-stub measures PCR 11 with this UKI's content.
  -> Kernel + initramfs run.
  -> initramfs:
     -> ostree-prepare-root reads ostree= karg from cmdline.
     -> Bind-mounts the new deploy as /sysroot.tmp.
     -> Sets up /etc bind-mount (writable on top of read-only deploy).
     -> Sets up other binds (/var, /sysroot, /boot).
     -> switch_root to /sysroot.tmp (which becomes the new /).
  -> systemd starts in the new root.
  -> ostree-remount.service runs:
     -> Various /var, /sysroot remounts.
     -> 50-cache22-etc-rw.conf drop-in's ExecStartPost runs:
        -> /usr/libexec/cache22/ensure-etc-writable (no-op on hard boot:
           /etc is already writable — the composefs overlay, or the
           initrd bind on the legacy backend).
  -> sysinit.target reached.
  -> systemd-tmpfiles-setup, sysusers, etc., run with writable /etc.
  -> multi-user.target reached.
  -> cache22-pending-motd.service runs:
     -> Sees no staged (the previously-staged is now booted).
     -> Removes the stale MOTD marker.
  -> User can log in.
  -> 2 minutes later: cache22-healthcheck.service runs:
     -> Runs scripts in /etc/cache22/healthcheck.d/required.d/.
     -> If all pass: reset bad-boots counter.
     -> If any fail: increment counter; if counter >= 3, bootc rollback + reboot.

Phase 2 alternative. kexec

Triggered by cache22-reboot --kexec, or by cache22-reboot (no flags) when KERNEL_CHANGE_STRATEGY=kexec.

cache22-reboot --kexec
  -> ostree admin finalize-staged
  -> /usr/libexec/cache22/resign-uki
  -> Pick the highest VERSION_ID UKI on /efi/EFI/Linux/.
  -> Extract .linux, .initrd, .cmdline from the UKI's PE sections.
  -> Re-sign the kernel with the per-machine SB key (so it works under
     kernel lockdown if enabled).
  -> kexec --load (stage the new kernel in kernel memory).
  -> systemctl kexec
     -> systemd shutdown sequence runs (ostree-finalize-staged ExecStop
        is a no-op since we already finalized; same for resign-uki).
     -> kexec_exec replaces the running kernel with the loaded one.
  -> New kernel runs initramfs.
  -> initramfs:
     -> ostree-prepare-root reads ostree= karg.
     -> Sets up the deploy and switch_roots.
  -> systemd starts in the new root.
  -> Normal boot sequence proceeds.

The kexec’d boot saves ~10-30 seconds compared to a full reboot (skips firmware POST). LUKS unlock requires a PCR 7 fallback keyslot to auto-unlock; otherwise the user is prompted.

Failure handling at each step

Step Failure mode Recovery
bootc upgrade Network failure, registry unavailable. Retry. No state change.
ostree admin finalize-staged at shutdown ostree internal error. Recorded in journal; system reboots without finalizing. Next reboot retries.
resign-uki at shutdown sbsign fails, sb-key-init missing key files. Recorded in journal; reboot proceeds. Old UKIs remain. New deploy is finalized but not bootable until the next resign-uki runs.
Boot of new deploy Kernel panic, ostree-prepare-root failure. sd-boot menu lets user pick the previous UKI (rollback). After 3 such cycles, cache22-healthcheck auto-rolls-back.
Health check failure Service fails one of the configured checks. Counter increments. After 3 consecutive failures, bootc rollback + reboot.

Idempotency

Most steps are idempotent and can be re-run:

  • ostree admin finalize-staged is a no-op when nothing is staged.
  • resign-uki rebuilds UKIs that already exist; the atomic-write ensures the previous UKI remains visible until the new one is fully on disk.
  • cache22-pending-motd.service writes or removes the marker based on current state; harmless to re-run.

This idempotency is what allows triggers like bootc-status-updated.target to fire frequently without causing churn.

See also