Skip to content

How a deployment works

Karim Vergnes edited this page Oct 22, 2024 · 2 revisions

The deployment process for a system extension managed by ostree-sysext can be broken down into the following steps:

Warning

Unless mentioned otherwise, all steps below are assumed to occur in a sandbox separate from the host's mount namespace.

1. Auditing the combined system

Each plugin on the system gets called via the check_compatible() function, in order to ascertain that the resulting stack of extensions does not give rise to conflicts.

Note

Example: A hypothetical rpm plugin is called.

The rpm plugin scans the package database for the base system, and for each extension, and checks that there are no conflicts.

If different versions of the same package are provided, and do not break the dependency graph, the rpm plugin votes with CompatVote.WARN. This blocks the deployment process, but leaves the possibility to use --force.

If the dependency graph fails, or another fatal error is caused, the rpm plugin votes with CompatVote.VETO. This blocks the deployment process and cannot be circumvented.

2. Constructing the deployment commit

Once approval is obtained, ostree-sysext builds the layout for the deployment commit, which keeps track of all applied extensions, under /run/ostree/extensions.

staged/            # Set of layered extensions
  ext-id  -> /ostree/extensions/ext-id/deploy/<hash>.0
state/             # Files dependent on the whole layered set, filled by the next step

3. Building stateful files

Certain files need to be generated using the whole set of layered extensions as reference, and cannot be included in one specific extension. Those can be built by plugins during the deployment process, then linked to in the resulting set.

Note

Example 1: The hypothetical rpm plugin.

RPM uses SQLite as a database to track installed packages. This is a robust, concurrent format but it would not make sense under layering.

In our example, the rpm plugin builds a new SQLite database by merging entries from the base system and extensions' respective databases, then writes the result to /run/ostree/extensions/state/rpmdb.sqlite.

Note

Example 2: The initramfs plugin.

The initramfs is a system component that can depend on files brought from system extensions, such as Plymouth boot themes.

In our example, the initramfs plugin calls dracut on the merged extensions set, ensuring that it sees files provided by individual extensions. The result is written to /run/ostree/extensions/state/initramfs.img.

4. Writing the deployment commit

The /run/ostree/extensions directory is captured into an OSTree commit ref: ostree-sysext/<osname>/0 and checked out under /ostree/deploy/<osname>/extensions/deploy/<hash>.0, and the checkout is referred to by a symbolic link /ostree/deploy/<osname>/deploy/<hash>.0.extensions, ensuring the extensions are restored when the system is booted.

5. Applying the new deployment

Our new commit is checked out to /run/ostree/extensions on the host system. This has the effect of updating stateful files built above.

Important

The implementation details for this are subject to change. One approach would be to consider /run/ostree/extensions/state as a system extension in and of itself.

Symbolic links (or composefs mount points) under /run/extensions are updated to reflect the new extension set.

6. Refreshing systemd-sysext

systemd-sysext refresh is called, which scans /run/extensions and applies our new extension set. The deployment is now finished.