Skip to content

Commit 0307111

Browse files
committed
unified-storage: implement the ostree three-store pipeline
The goal of unified storage is to make the same on-disk layer data simultaneously visible to containers-storage (for `podman run`), composefs (for the boot overlay), and ostree (for deployment tracking), using reflinks so layers are stored once regardless of how many stores reference them. This wires the pipeline into the ostree backend. When unified storage is enabled — either at install time via the `[install.storage]` config key, or post-install via `bootc image set-unified` — upgrades and switches route through pull_via_composefs: Stage 1: pull the image into bootc-owned containers-storage Stage 2: zero-copy reflink import into the composefs OCI repo Stage 3: synthesize an ostree commit from the composefs tree Whether unified storage is active is tracked by composefs/bootc.json (BootcRepoMeta) and the ostree repo config `[composefs] unified` key. This replaces the previous heuristic that checked per-image presence in containers-storage, which broke when switching to a new image reference. The install config gains a `storage.unified` key with three values: disabled (default), enabled (fail if reflinks unavailable), and enabled-with-copy (copy fallback). This lets an image opt into unified storage without threading a CLI flag through every installer. User-facing onboarding is a single verb with a required mode: bootc image set-unified <composefs|full> `composefs` enables the ostree↔composefs binding only: it is a flag-only operation that writes `[composefs] unified = true` and does not fetch, synthesize, or stage anything. The next `bootc upgrade` re-pulls through the composefs pipeline, synthesizes the commit, and stages a deployment; a reboot then activates bound-only mode. `full` additionally onboards the booted image into containers-storage so it is visible to `podman run`. `bootc image sync` reconciles every bootloader-pinned deployment into bootc's containers-storage on a unified system. For the binding to actually activate, the upgrade path had to learn that a classic (non-synthesized) booted commit must be re-staged even when the image's manifest digest is unchanged: the synthesized commit is a *different* ostree commit. Without this, `bootc upgrade` after `set-unified composefs` would report "No update available." and the binding could never take effect. `bootc internals fsck images` validates consistency across the stores. On a bound-only system the OS image lives in the composefs repo and is intentionally absent from containers-storage, so checks are driven from the live ostree deployments. A booted-but-not-yet-synthesized live image (the window between `set-unified composefs` and the activating reboot) is reported as `pending` with `ok: true` — an expected transient state, not a corruption. `--repair` restores a missing composefs tag when the objects are still present. The cstorage GC is extended to protect images that have composefs tags, since the composefs splitstreams reference the cstorage layer data; pruning one without the other would corrupt the repo. Assisted-by: OpenCode (Claude Opus 4.8) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent 6841360 commit 0307111

53 files changed

Lines changed: 5008 additions & 778 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ jobs:
158158
# These tests may mutate the system live so we can't run in parallel
159159
sudo bootc-integration-tests system-reinstall localhost/bootc --test-threads=1
160160
161+
# Unified storage case
162+
sudo podman build -t localhost/bootc-unified-storage -f ci/Containerfile.install-unified-storage
163+
sudo podman run --privileged --pid=host localhost/bootc-unified-storage bootc install to-existing-root --stateroot=unified-storage --acknowledge-destructive --skip-fetch-check
164+
# Verify unified storage was activated; composefs/bootc.json is written relative to
165+
# the target physical root (/target bind-mounted to host /), so the file appears at /composefs/bootc.json
166+
sudo test -f /composefs/bootc.json
167+
161168
# And the fsverity case
162169
sudo podman run --privileged --pid=host localhost/bootc-fsverity bootc install to-existing-root --stateroot=other \
163170
--acknowledge-destructive --skip-fetch-check

Justfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,27 @@ test-tmt-baseconfig baseconfig *ARGS:
290290
--seal-state={{seal_state}} \
291291
{{base_img}} readonly {{ARGS}}
292292

293+
# Run unified-storage baseconfig test (works with ostree variant, no composefs required)
294+
[group('testing')]
295+
test-tmt-baseconfig-unified-storage *ARGS:
296+
just baseconfigs=unified-storage build
297+
just baseconfigs=unified-storage _build-upgrade-image
298+
cargo xtask run-tmt \
299+
--env=BOOTC_baseconfigs=unified-storage \
300+
--upgrade-image={{upgrade_img}} \
301+
--bootloader={{bootloader}} \
302+
--filesystem={{filesystem}} \
303+
--boot-type={{boot_type}} \
304+
--seal-state={{seal_state}} \
305+
{{base_img}} readonly {{ARGS}}
306+
293307
# Run readonly tests for all standard baseconfigs
294308
[group('testing')]
295309
test-baseconfigs *ARGS:
296310
just test-tmt-baseconfig etc-transient {{ARGS}}
297311
just test-tmt-baseconfig root-transient {{ARGS}}
298312
just test-tmt-baseconfig var-volatile {{ARGS}}
313+
just test-tmt-baseconfig-unified-storage {{ARGS}}
299314

300315
# Run tmt tests on Fedora CoreOS
301316
[group('testing')]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Enable unified storage (composefs+ostree) at install time via image-embedded config
2+
FROM localhost/bootc-install
3+
RUN <<EORUN
4+
set -xeuo pipefail
5+
mkdir -p /usr/lib/bootc/install
6+
printf '[install.storage]\nunified = "enabled-with-copy"\n' > /usr/lib/bootc/install/00-storage.toml
7+
bootc container lint
8+
EORUN

contrib/packaging/inject-baseconfig

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,8 @@ if [ -z "${BASECONFIGS}" ]; then
1111
exit 0
1212
fi
1313

14-
# setup-root-conf.toml is composefs-specific; ostree uses prepare-root.conf
15-
# which has a different (INI) format and different option names.
16-
case "${VARIANT}" in
17-
composefs*)
18-
TARGET="/usr/lib/composefs/setup-root-conf.toml"
19-
;;
20-
*)
21-
echo "inject-baseconfig: baseconfigs not supported for variant '${VARIANT}'" >&2
22-
exit 1
23-
;;
24-
esac
25-
26-
mkdir -p "$(dirname "${TARGET}")"
27-
28-
# Split on commas and process each token
14+
# Split and process tokens; unified-storage is handled before the variant check
15+
# because it is backend-independent (works with both ostree and composefs).
2916
IFS=',' read -ra TOKENS <<< "${BASECONFIGS}"
3017
for raw_token in "${TOKENS[@]}"; do
3118
# Trim leading/trailing spaces
@@ -35,27 +22,50 @@ for raw_token in "${TOKENS[@]}"; do
3522
[ -z "${token}" ] && continue
3623

3724
case "${token}" in
38-
etc-transient)
39-
printf '[etc]\ntransient = true\n' >> "${TARGET}"
40-
;;
41-
root-transient)
42-
printf '[root]\ntransient = true\n' >> "${TARGET}"
43-
;;
44-
var-volatile)
45-
# Mount /var as a fresh tmpfs on every boot via systemd.volatile=state.
46-
# bootc-root-setup detects this karg in the initramfs and automatically
47-
# skips the /var state bind-mount, leaving /var as an empty directory
48-
# from the composefs image. systemd-fstab-generator then mounts a fresh
49-
# tmpfs there at local-fs.target. Using a plain tmpfs avoids the
50-
# overlayfs-on-overlayfs restriction that breaks tools like podman which
51-
# use overlayfs under /var/lib/containers.
52-
mkdir -p /usr/lib/bootc/kargs.d
53-
printf 'kargs = ["systemd.volatile=state"]\n' \
54-
> /usr/lib/bootc/kargs.d/50-var-volatile.toml
25+
unified-storage)
26+
# Write the bootc install config to enable unified storage at install time.
27+
# This is backend-independent and works with both ostree and composefs variants.
28+
mkdir -p /usr/lib/bootc/install
29+
printf '[install.storage]\nunified = "enabled-with-copy"\n' \
30+
> /usr/lib/bootc/install/00-storage.toml
5531
;;
5632
*)
57-
echo "Unknown baseconfig: ${token}" >&2
58-
exit 1
33+
# All other tokens require the composefs variant (they write to composefs-specific paths).
34+
# Validate variant here, not at the top, so unified-storage can run on any variant.
35+
case "${VARIANT}" in
36+
composefs*)
37+
TARGET="/usr/lib/composefs/setup-root-conf.toml"
38+
mkdir -p "$(dirname "${TARGET}")"
39+
;;
40+
*)
41+
echo "inject-baseconfig: baseconfig '${token}' not supported for variant '${VARIANT}'" >&2
42+
exit 1
43+
;;
44+
esac
45+
case "${token}" in
46+
etc-transient)
47+
printf '[etc]\ntransient = true\n' >> "${TARGET}"
48+
;;
49+
root-transient)
50+
printf '[root]\ntransient = true\n' >> "${TARGET}"
51+
;;
52+
var-volatile)
53+
# Mount /var as a fresh tmpfs on every boot via systemd.volatile=state.
54+
# bootc-root-setup detects this karg in the initramfs and automatically
55+
# skips the /var state bind-mount, leaving /var as an empty directory
56+
# from the composefs image. systemd-fstab-generator then mounts a fresh
57+
# tmpfs there at local-fs.target. Using a plain tmpfs avoids the
58+
# overlayfs-on-overlayfs restriction that breaks tools like podman which
59+
# use overlayfs under /var/lib/containers.
60+
mkdir -p /usr/lib/bootc/kargs.d
61+
printf 'kargs = ["systemd.volatile=state"]\n' \
62+
> /usr/lib/bootc/kargs.d/50-var-volatile.toml
63+
;;
64+
*)
65+
echo "Unknown baseconfig: ${token}" >&2
66+
exit 1
67+
;;
68+
esac
5969
;;
6070
esac
6171
done

0 commit comments

Comments
 (0)