Skip to content
Merged
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
39 changes: 28 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ jobs:
shell:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Check POSIX syntax
run: |
set -eu
sh -n install.sh
sh -n scripts/build-bundle.sh
sh -n scripts/build-bundle.sh scripts/fetch-hvi.sh
# The wrappers and uninstaller are generated into the tarball by
# build-bundle.sh, so lint the text it will write rather than waiting
# to find out on a host.
Expand Down Expand Up @@ -53,7 +53,7 @@ jobs:
- name: shellcheck
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck
shellcheck -s sh install.sh scripts/build-bundle.sh /tmp/brig-uninstall.sh \
shellcheck -s sh install.sh scripts/build-bundle.sh scripts/fetch-hvi.sh /tmp/brig-uninstall.sh \
/tmp/brig-rootless-setup.sh /tmp/brig-ctl tests/*.sh

- name: Script tests
Expand Down Expand Up @@ -97,7 +97,7 @@ jobs:
flag: '--rootless'
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Build a generic-boot bundle (${{ matrix.arch }}, ${{ matrix.rootless }})
env:
Expand Down Expand Up @@ -146,15 +146,32 @@ jobs:
# urunc and urunit are built from pinned commits, and the build
# fails if the checkout is anything else. This checks the record: the
# commits in pins.env are the ones the script pins.
for c in URUNC URUNIT; do
want="$(sed -n "s/^${c}_REF_DEFAULT=\([0-9a-f]\{40\}\)$/\1/p" scripts/build-bundle.sh)"
[ -n "$want" ] || { echo "no ${c}_REF_DEFAULT in build-bundle.sh"; exit 1; }
grep -E "^${c}_(REF|PINNED)=" "$p"
grep -qx "${c}_REF=$want" "$p" || { echo "pins.env does not carry ${c}_REF=$want"; exit 1; }
grep -qx "${c}_PINNED=true" "$p" || { echo "pins.env does not say ${c}_PINNED=true"; exit 1; }
want="$(sed -n 's/^URUNC_REF_DEFAULT=\([0-9a-f]\{40\}\)$/\1/p' scripts/build-bundle.sh)"
[ -n "$want" ] || { echo "no URUNC_REF_DEFAULT in build-bundle.sh"; exit 1; }
grep -E "^URUNC_(REF|PINNED)=" "$p"
grep -qx "URUNC_REF=$want" "$p" || { echo "pins.env does not carry URUNC_REF=$want"; exit 1; }
grep -qx "URUNC_PINNED=true" "$p" || { echo "pins.env does not say URUNC_PINNED=true"; exit 1; }
# hvi is fetched from the release the script pins; fetch-hvi.sh
# fails the build unless its signature and sums check out.
grep -E "^HVI_(VERSION|COMMIT)=" "$p"
for k in HVI_VERSION HVI_COMMIT; do
want="$(sed -n "s/^$k=\([0-9a-z.-]*\)$/\1/p" scripts/build-bundle.sh)"
[ -n "$want" ] || { echo "no $k in build-bundle.sh"; exit 1; }
grep -qx "$k=$want" "$p" || { echo "pins.env does not carry $k=$want"; exit 1; }
done
# urunit has no pin of its own here: the build reads it from the
# pinned urunc commit's packaging/container-initrd/urunit.pin. Read
# that file at the recorded urunc commit and compare.
urunc_repo="$(sed -n 's/^URUNC_REPO=//p' "$p")"
urunc_ref="$(sed -n 's/^URUNC_REF=//p' "$p")"
want="$(curl -fsSL "https://raw.githubusercontent.com/$urunc_repo/$urunc_ref/packaging/container-initrd/urunit.pin" \
| sed -n 's/^URUNIT_REF=\([0-9a-f]\{40\}\)$/\1/p')"
[ -n "$want" ] || { echo "$urunc_repo@$urunc_ref has no urunit.pin naming a commit"; exit 1; }
grep -E "^URUNIT_(REF|PINNED)=" "$p"
grep -qx "URUNIT_REF=$want" "$p" || { echo "pins.env does not carry URUNIT_REF=$want, urunc's pin"; exit 1; }
grep -qx "URUNIT_PINNED=true" "$p" || { echo "pins.env does not say URUNIT_PINNED=true"; exit 1; }

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: bundle-${{ matrix.arch }}-${{ matrix.rootless }}
path: dist/*
14 changes: 8 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
flag: '--rootless'
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Build the ${{ matrix.arch }} ${{ matrix.rootless }} bundle
env:
Expand All @@ -73,7 +73,7 @@ jobs:
--version "${{ needs.version.outputs.version }}" \
--variant generic-boot ${{ matrix.flag }} --out dist

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: bundle-${{ matrix.arch }}-${{ matrix.rootless }}
path: dist/*
Expand All @@ -88,11 +88,13 @@ jobs:
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
- uses: sigstore/cosign-installer@v3
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: v2.5.3

- name: Collect the per-arch bundles
uses: actions/download-artifact@v4
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: bundle-*
path: incoming
Expand Down Expand Up @@ -121,7 +123,7 @@ jobs:
--yes

- name: Attest build provenance
uses: actions/attest-build-provenance@v2
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: dist/brig-standalone-*.tar.gz

Expand Down
20 changes: 10 additions & 10 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ now done and is what `install.sh` and `scripts/build-bundle.sh` produce.
fetches nothing else. Because the config files carry the fixed layout, the paths
above are not configurable at install time.
- **Why three things are built (at release).** Most components are upstream release
artifacts. Three have no upstream release and are compiled in the build: urunc
(from `urunc-dev/urunc` at commit `0b1ef07`, on
`feat/unchanged_containers-exec-fixes`, CGO static, so native per arch under
qemu/binfmt), urunit (from `NOFireAI/urunit` at commit `71bfdee`, on
`urunit_agent`, C static), and the brig `container-initrd`. The two sources are
artifacts, hvi among them (`brig-sh/hvi-vmm` `v0.1.0-rc.1`, cosign-verified).
Three have no upstream release and are compiled in the build: urunc
(from `urunc-dev/urunc` at commit `3c00b69`, on
`feat/linux-hvi-v050`, CGO static, so native per arch under
qemu/binfmt), urunit (from `NOFireAI/urunit` at the commit urunc's
`urunit.pin` names, C static) and the brig `container-initrd`. The sources are
pinned to a commit, not to a branch: a branch tip moves with every push, so it
is not a release input. The README's build section says how to move a pin.
brig execs into a guest through an in-guest agent (`urunit-agent`) whose
protocol must match the urunc shim, so hull-assets' prebuilt initrd (hull's
agent) is not usable; the build assembles a brig initrd from urunit +
`urunit-agent` (from the same urunc commit as the shim) + busybox + urunc's
`container-init`. The kernel is still fetched, not built: from
brig execs into a guest through the agent in urunit, whose protocol must match
the urunc shim, so hull-assets' prebuilt initrd is not usable; the build
assembles a brig initrd of urunit alone, the early init, supervisor and agent
in one static binary, at the commit the shim's urunc pins. The kernel is still fetched, not built: from
`ghcr.io/nofireai/hull-assets` on both arches, by digest, once its cosign
signature checks out.
Because urunc and the initrd ship in one tarball built together, their agent
Expand Down
62 changes: 38 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ $ sha256sum -c checksums.txt --ignore-missing
/var/lib/brig/
data/ immutable, unpacked from the tarball:
bin/ brig, brigd, urunc, containerd-shim-urunc-v2, containerd, ctr,
runc, nerdctl, cosign, oras, firecracker, cloud-hypervisor,
solo5-hvt, solo5-spt, brig-ctl, brig-pool-up, brig-uninstall.sh
runc, nerdctl, cosign, oras, hvi, firecracker,
cloud-hypervisor, solo5-hvt, solo5-spt, brig-ctl,
brig-pool-up, brig-uninstall.sh
libexec/ virtiofsd
libexec/cni/ CNI plugins
etc/ urunc.toml, containerd.toml, nerdctl.toml, brig-env.sh,
Expand Down Expand Up @@ -280,8 +281,9 @@ the three that do not:
| Component | Source | In the tarball |
| --- | --- | --- |
| brig, brigd | `brig-sh/brig` release | fetched, verified against its signed `checksums.txt` |
| urunc, containerd-shim-urunc-v2 | built from `urunc-dev/urunc` at commit `0b1ef07` (branch `feat/unchanged_containers-exec-fixes`) | CGO-static, built in a Go container |
| urunit | built from `NOFireAI/urunit` at commit `71bfdee` (branch `urunit_agent`) | C-static; goes into the initrd |
| urunc, containerd-shim-urunc-v2 | built from `urunc-dev/urunc` at commit `3c00b69` (branch `feat/linux-hvi-v050`) | CGO-static, built in a Go container |
| urunit | built from `NOFireAI/urunit` at the commit urunc's `packaging/container-initrd/urunit.pin` names (`48a61bd`, branch `stack/10-acpi-power-button`) | C-static; the whole initrd |
| hvi | `brig-sh/hvi-vmm` release `v0.1.0-rc.1` (commit `65570c7`), the one hull ships on macOS | fetched, musl-static; its `SHA256SUMS` cosign-verified against hvi-vmm's release workflow, the tarball pinned by sha256 |
| container-initrd | built from the above | assembled for brig, not fetched |
| guest kernel | `ghcr.io/nofireai/hull-assets` | fetched by digest once its cosign signature checks out; the kernel hull and brig boot |
| monitors, virtiofsd | `urunc-dev/monitors-build` | fetched, pinned by sha256 |
Expand All @@ -296,16 +298,28 @@ uninstaller, and packs the whole `/var/lib/brig/data` tree.

A branch tip is not a release input. It moves with every push, so two builds
of one bundle version would carry different code. So `build-bundle.sh` pins
each of the two to one commit, `URUNC_REF_DEFAULT` and `URUNIT_REF_DEFAULT`.
The build fetches that commit by its SHA, fails if the checkout is anything
else, and records it in `pins.env` as `URUNC_REF` and `URUNIT_REF`. CI checks
that record on every bundle it builds.

The urunc pin is `feat/unchanged_containers` plus the two `urunit-agent` exec
fixes, urunc-dev/urunc#1059 and urunc-dev/urunc#1060. It also carries a commit
that sizes a container boot's vCPUs from the container's CPU limit, so
`brig run --cpus N` gives the guest N vCPUs. The urunit pin is what
v0.1.0-rc6 to rc8 shipped.
urunc to one commit, `URUNC_REF_DEFAULT`, and takes urunit from the commit that
urunc pins in `packaging/container-initrd/urunit.pin`, so the initrd always
matches the shim that talks to it. The build fetches each commit by its SHA,
fails if the checkout is anything else, and records it in `pins.env` as
`URUNC_REF` and `URUNIT_REF`. CI checks that record on every bundle it builds.
`URUNIT_REPO` and `URUNIT_REF` override the urunit pin for a one-off build.

The urunc pin is `feat/linux-hvi-v050`: `feat/unchanged_containers` with
the exec fixes, the hvi monitor, urunit's single binary as the initrd, the
overlay-block root (`BRIG_ROOTFS_MODE=overlay-block`) and stop signals handed to
the guest's init. It also sizes a container boot's vCPUs from the container's
CPU limit, so `brig run --cpus N` gives the guest N vCPUs.

The bundle ships hvi first in the monitor list and writes a `[monitors.hvi]`
section, but it names no default monitor: urunc boots whatever monitor the
`com.urunc.unikernel.hypervisor` annotation asks for, and brig sets that
annotation. A brig that knows hvi on Linux boots hvi unless `BRIG_HYPERVISOR`
names another monitor in the bundle (`cloud-hypervisor` stays installed for
that); an older brig keeps asking for cloud-hypervisor and gets it. The same
holds for the overlay-block root: the bundle's urunc understands
`com.urunc.unikernel.rootfsMode`, and brig sends it when
`BRIG_ROOTFS_MODE=overlay-block` is set.

Moving a pin is a reviewed one-line change to the `*_REF_DEFAULT` line, plus
its `*_BRANCH_DEFAULT` when the new commit comes from another branch. A one-off
Expand All @@ -327,22 +341,22 @@ was meant.

### Why the initrd is brig's own, not hull-assets'

brig execs into a guest through an in-guest agent, `urunit-agent`, whose wire
protocol (`pkg/agentproto`) is a contract with the urunc shim. The two agree only
when both are built from the same urunc commit, and hull-assets' prebuilt initrd
carries hull's agent. So `build-bundle.sh` builds a brig initrd with urunc's own
`packaging/container-initrd` tooling, from `urunit`, a `urunit-agent` built from
the same urunc commit as the shim, a static `busybox`, and urunc's
`container-init`. The urunc binary and the initrd's agent ship in one tarball, so
they always match. Only the kernel is fetched rather than built. It is generic,
brig execs into a guest through the exec agent in urunit, whose wire protocol
(`pkg/agentproto`) is a contract with the urunc shim. The two agree when urunit
is the commit the urunc checkout pins, and hull-assets' prebuilt initrd may
carry another. So `build-bundle.sh` builds a brig initrd with urunc's own
`packaging/container-initrd` tooling: `/urunit`, one static binary that is the
early init, the supervisor and the agent, with `/init` and `/vz-init` linked to
it. The urunc binary and the initrd's agent ship in one tarball, so they always
match. Only the kernel is fetched rather than built. It is generic,
and comes from hull-assets on both arches, checked against its signature.

Two properties of the initrd are checked when the build packs it and again when
the runtime boots it, because each fails in a way that does not name itself:
`container-initrd` must not be compressed (the runtime appends the container's
argv, environment and network identity to a copy of it, and a gzipped one accepts
the append and boots without it), and `busybox` must be built for the target
architecture, or it panics init on its first line.
the append and boots without it), and urunit must be built for the target
architecture, or the kernel cannot run init.

The boot assets land in `share/guest/` and the launcher points brig at them
through `BRIG_BOOT_ASSETS`, so brig uses the packed assets rather than fetching
Expand Down
55 changes: 26 additions & 29 deletions docs/variants.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ by `build-bundle.sh` and packed into the tarball. The installer just unpacks the

No urunc release publishes a binary that boots a generic container. So the build
compiles one, from `urunc-dev/urunc` at the commit `build-bundle.sh` pins
(`0b1ef07`, on branch `feat/unchanged_containers-exec-fixes`), in a
`golang:1.26.4` container:
(`3c00b69`, on branch `feat/linux-hvi-v050`), in a
`golang:1.26.8` container:

```console
# docker run --rm -v <urunc>:/app -w /app -e HOME=/tmp golang:1.26.4 \
# docker run --rm -v <urunc>:/app -w /app -e HOME=/tmp golang:1.26.8 \
sh -c "git config --global --add safe.directory /app && make static"
```

Expand Down Expand Up @@ -85,20 +85,16 @@ boots on macOS. `pins.env` records it as `KERNEL_SOURCE`, by the digest the buil
pulled. `share/guest/SHA256SUMS` holds the sha256 of the kernel and the initrd,
and each release publishes it as `<bundle>.boot-assets.sha256`: see the README.

**The initrd is not taken from hull-assets.** brig execs into a guest through an
in-guest agent, `urunit-agent`, whose wire protocol (`pkg/agentproto`) is a
contract with the urunc shim. The two agree only when both are built from the same
urunc commit, and hull-assets' prebuilt initrd carries hull's agent. So the build
assembles a brig initrd, with urunc's
`packaging/container-initrd/build-container-initrd.sh`, from four pieces:

- `container-init`, urunc's early-userspace script (mounts the shared rootfs,
stages urunit and the agent, `switch_root`s into urunit);
- `urunit`, the tiny C init that becomes PID 1, built static from
`NOFireAI/urunit` at the pinned commit `71bfdee` (branch `urunit_agent`);
- `urunit-agent`, built from the **same** urunc commit that produced the shim,
so the protocol matches by construction;
- a static `busybox`.
**The initrd is not taken from hull-assets.** brig execs into a guest through the
exec agent in urunit, whose wire protocol (`pkg/agentproto`) is a contract with
the urunc shim. The two agree when urunit is the commit the shim's urunc pins in
`packaging/container-initrd/urunit.pin`, and hull-assets' prebuilt initrd may
carry another. So the build assembles a brig initrd, with urunc's
`packaging/container-initrd/build-container-initrd.sh`, from one binary:

- `/urunit`, built static from `NOFireAI/urunit` at the commit urunc pins. It is
the early init (`/init` and `/vz-init` are links to it), the PID 1 supervisor
and the exec agent.

urunc and the initrd ship in one tarball, built together, so their agent commit
matches by construction. The installer just fetches and unpacks the tarball; it
Expand All @@ -111,8 +107,8 @@ runtime boots it, because each fails in a way that does not name itself:
argv, environment and network identity to a copy of it, and concatenated cpio
works only while both halves are uncompressed. A gzipped one accepts the append
and boots without it.
- `busybox` must be built for this architecture. Under the wrong one it exits 127
on every applet and panics init on the first line.
- `urunit` must be built for this architecture. Under the wrong one the kernel
cannot run init and panics.

The annotations that carry the pair are `com.urunc.unikernel.bootKernel` and
`com.urunc.unikernel.bootInitrd`, pointing into `share/guest/`. hull takes the
Expand Down Expand Up @@ -148,9 +144,8 @@ reaps it per container. `build-bundle.sh` would install it as `bin/sidecar`;
where its build comes from is unsettled.

**hvi is a fifth monitor from a different repo.** The matrix scores hvi beside
firecracker, but hvi is built in `brig-sh/hvi-vmm`, not in the monitors release
the build draws from. Adding it means either a second source in `build-bundle.sh`
or getting hvi into `monitors-build`.
firecracker, but hvi is released by `brig-sh/hvi-vmm`, not by the monitors
release the build draws from. `build-bundle.sh` fetches it as a second source.

## Running the matrix

Expand All @@ -171,12 +166,14 @@ refuses a stock binary, rather than printing a table of zeroes.
3. Settled: the guest kernel is fetched from `hull-assets`, by the same tags hull
uses, and checked against its signature; the runtime is built from
`urunc-dev/urunc` at a pinned commit
(`0b1ef07`, on `feat/unchanged_containers-exec-fixes`); and the initrd is
built for brig from `NOFireAI/urunit` at a pinned commit (`71bfdee`, on
`urunit_agent`) plus urunc's own `packaging/container-initrd`, so its agent
matches the shim. Both pins are commits because a branch tip moves with every
push; the README's build section says how to move one.
4. Does hvi join `monitors-build`, or does `build-bundle.sh` learn a second
source?
(`3c00b69`, on `feat/linux-hvi-v050`); and the initrd is built for
brig from `NOFireAI/urunit` at the commit that urunc pins, with urunc's own
`packaging/container-initrd`, so its agent matches the shim. Both are
commits because a branch tip moves with every push; the README's build
section says how to move one.
4. Settled: `build-bundle.sh` fetches hvi from the `brig-sh/hvi-vmm` release
hull ships on macOS (`v0.1.0-rc.1`). `scripts/fetch-hvi.sh` checks the
cosign signature on its `SHA256SUMS`, the pinned sha256 of the tarball and
the commit its manifest names.
5. Is `introspection` a variant at all, or `generic-boot` plus an opt-in flag?
Everything it adds is additive, which argues for the flag.
Loading