Skip to content

Commit f3d2d4e

Browse files
committed
sysext: Add fast path dev flow
For most work on `bootc upgrade` or `bootc switch`, rebuilding the full container image is unnecessary. This adds a fast-path workflow that builds just the bootc binary into a systemd-sysext and overlays it onto /usr in a persistent bcvk VM via virtiofs. For more info see the updated Justfile. There's still some TODOs here - e.g. we need to support running the TMT tests this way, etc. Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent 3ec055f commit f3d2d4e

File tree

7 files changed

+573
-0
lines changed

7 files changed

+573
-0
lines changed

CONTRIBUTING.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,31 @@ For the local case, check out [cstor-dist](https://github.com/cgwalters/cstor-di
104104
Another alternative is mounting via virtiofs (see e.g. [this PR to bcvk](https://github.com/bootc-dev/bcvk/pull/16)).
105105
If you're using libvirt, see [this document](https://libvirt.org/kbase/virtiofs.html).
106106

107+
#### Using sysext for fast iteration
108+
109+
For the fastest development cycle when working on the bootc client
110+
(e.g. `bootc upgrade`, `bootc switch`), you can use the sysext-based
111+
workflow. This builds the bootc binary via a container, shares it into
112+
a persistent VM via virtiofs, and overlays it onto `/usr` using
113+
systemd-sysext (~30s rebuild cycle):
114+
115+
```bash
116+
# Build sysext and launch a persistent dev VM
117+
just bcvk up
118+
119+
# After editing code, rebuild and refresh the overlay (~30s)
120+
just bcvk sync
121+
122+
# SSH into the VM — bootc is your dev build
123+
just bcvk ssh bootc status
124+
125+
# When done
126+
just bcvk down
127+
```
128+
129+
The sysext overlay means `bootc` on the VM's PATH is your dev build.
130+
Run `just bcvk` to list all available commands.
131+
107132
#### Running bootc against a live environment
108133

109134
If your development environment host is also a bootc system (e.g. a

Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,23 @@ ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}
9999
# Build RPM directly from source, using cached target directory
100100
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome RPM_VERSION="${pkgversion}" /src/contrib/packaging/build-rpm
101101

102+
# Build a systemd-sysext containing just the bootc binary.
103+
# Skips RPM machinery entirely for fast incremental rebuilds.
104+
FROM buildroot as sysext
105+
RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \
106+
--mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome <<EORUN
107+
set -xeuo pipefail
108+
cargo build --bin bootc
109+
mkdir -p /out/bootc/usr/bin /out/bootc/usr/lib/extension-release.d
110+
cp target/debug/bootc /out/bootc/usr/bin/
111+
cat > /out/bootc/usr/lib/extension-release.d/extension-release.bootc <<EOF
112+
ID=_any
113+
EXTENSION_RELOAD_MANAGER=1
114+
EOF
115+
echo "Fast sysext created (binary only):"
116+
find /out/bootc -type f
117+
EORUN
118+
102119
# This image signs systemd-boot using our key, and writes the resulting binary into /out
103120
FROM tools as sdboot-signed
104121
# The secureboot key and cert are passed via Justfile

Justfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
# -> cargo xtask
1111
# --------------------------------------------------------------------
1212

13+
mod bcvk 'bcvk.just'
14+
1315
# Configuration variables (override via environment or command line)
1416
# Example: BOOTC_base=quay.io/fedora/fedora-bootc:42 just build
1517

@@ -336,6 +338,16 @@ build-units:
336338
eval $(just _git-build-vars)
337339
podman build {{base_buildargs}} --build-arg=SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} --build-arg=pkgversion=${VERSION} --target units -t localhost/bootc-units .
338340

341+
# ============================================================================
342+
# Development VM workflow (sysext-based)
343+
# ============================================================================
344+
345+
# Build a systemd-sysext via the container build (binary only, for fast iteration)
346+
[group('dev')]
347+
sysext:
348+
contrib/packaging/build-container-stage sysext target/sysext \
349+
{{base_buildargs}} $(just _local-deps-args)
350+
339351
# ============================================================================
340352
# Internal helpers (prefixed with _)
341353
# ============================================================================
@@ -359,6 +371,13 @@ _git-build-vars:
359371
echo "SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}"
360372
echo "VERSION=${VERSION}"
361373

374+
_local-deps-args:
375+
#!/bin/bash
376+
set -euo pipefail
377+
if [[ -z "{{no_auto_local_deps}}" ]]; then
378+
cargo xtask local-rust-deps
379+
fi
380+
362381
_keygen:
363382
./hack/generate-secureboot-keys
364383

bcvk.just

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# bcvk development VM management
2+
#
3+
# The dev binary is overlaid onto /usr via systemd-sysext. After
4+
# rebuilding with `just sysext`, run `just bcvk sync` to
5+
# refresh the overlay (~30s total cycle).
6+
#
7+
# Usage:
8+
# just bcvk up # Build sysext + launch persistent VM
9+
# just bcvk sync # Rebuild sysext + refresh overlay (~30s)
10+
# just bcvk ssh # SSH into the VM
11+
# just bcvk ephemeral # Ephemeral VM (full image, destroyed on exit)
12+
13+
base_img := env("BOOTC_base_img", "localhost/bootc")
14+
15+
# List available recipes
16+
[private]
17+
default:
18+
@just --list bcvk
19+
20+
# Run an ephemeral VM from the latest build and SSH in (destroyed on exit)
21+
[group('ephemeral')]
22+
ephemeral:
23+
just build
24+
bcvk ephemeral run-ssh {{base_img}}
25+
26+
# Launch persistent development VM with sysext
27+
[group('vm')]
28+
up:
29+
just sysext
30+
cargo xtask bcvk vm
31+
32+
# Rebuild sysext and verify the new binary in the running VM
33+
[group('vm')]
34+
sync:
35+
just sysext
36+
cargo xtask bcvk sync
37+
38+
# SSH into development VM (interactive shell if no args given)
39+
[group('vm')]
40+
ssh *ARGS:
41+
cargo xtask bcvk ssh {{ARGS}}
42+
43+
# Stop and remove development VM
44+
[group('vm')]
45+
down:
46+
cargo xtask bcvk down
47+
48+
# Show development VM status
49+
[group('vm')]
50+
status:
51+
cargo xtask bcvk status
52+
53+
# Watch development VM logs
54+
[group('vm')]
55+
logs:
56+
cargo xtask bcvk logs
57+
58+
# Show sysext status in development VM
59+
[group('vm')]
60+
sysext-status:
61+
cargo xtask bcvk ssh systemd-sysext status
62+
63+
# Restart development VM
64+
[group('vm')]
65+
restart:
66+
#!/bin/bash
67+
set -euo pipefail
68+
echo "Restarting development VM..."
69+
cargo xtask bcvk ssh -- sudo systemctl reboot || true
70+
sleep 5
71+
echo "Waiting for VM to come back up..."
72+
for i in {1..30}; do
73+
if cargo xtask bcvk ssh -- echo "VM is up" 2>/dev/null; then
74+
echo "VM is back online!"
75+
break
76+
fi
77+
echo "Waiting... (attempt $i/30)"
78+
sleep 2
79+
done
80+
81+
# Clean up all development resources (VM + sysext)
82+
[group('vm')]
83+
clean:
84+
cargo xtask bcvk clean
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/bash
2+
# Build a Dockerfile stage and extract /out to a versioned subdirectory.
3+
# Usage: build-container-stage <stage> <output-dir> [podman-build-args...]
4+
#
5+
# Each build creates a new timestamped directory inside <output-dir>
6+
# (e.g. output-dir/bootc-1234567890/) and prints the version name to
7+
# stdout on the last line. A "current" symlink is updated to point at
8+
# the new version. Old versions are pruned (keeping the 2 most recent)
9+
# so the previous version remains valid for any active overlay.
10+
set -euo pipefail
11+
12+
stage="${1:?Usage: build-container-stage <stage> <output-dir> [podman-build-args...]}"
13+
output_dir="${2:?Usage: build-container-stage <stage> <output-dir> [podman-build-args...]}"
14+
shift 2
15+
16+
image_tag="localhost/bootc-${stage}"
17+
18+
podman build -t "${image_tag}" --target="${stage}" "$@" .
19+
20+
mkdir -p "${output_dir}"
21+
22+
# Extract into a versioned subdirectory, using the image build timestamp
23+
# so the version name reflects when the binary was actually built.
24+
version="bootc-$(podman inspect --format '{{.Created.Unix}}' "${image_tag}")"
25+
version_dir="${output_dir}/${version}"
26+
mkdir -p "${version_dir}"
27+
podman run --rm "${image_tag}" tar -C /out -cf - . | tar -C "${version_dir}" -xvf -
28+
chmod -R a+rX "${version_dir}"
29+
30+
# Update the "current" symlink atomically
31+
ln -sfn "${version}" "${output_dir}/current.tmp"
32+
mv -Tf "${output_dir}/current.tmp" "${output_dir}/current"
33+
34+
# Prune old versions, keeping the 2 most recent
35+
ls -1dt "${output_dir}"/bootc-[0-9]* 2>/dev/null | tail -n +3 | while read -r old; do
36+
rm -rf "${old}"
37+
done
38+
39+
# Print the version name so callers (xtask) can use it
40+
echo "sysext-version=${version}"

0 commit comments

Comments
 (0)