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
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ jobs:
esac
grep -q "^ROOTLESS=$want$" "$p" \
|| { echo "pins.env does not say ROOTLESS=$want"; exit 1; }
# 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; }
done

- uses: actions/upload-artifact@v4
with:
Expand Down
18 changes: 11 additions & 7 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ now done and is what `install.sh` and `scripts/build-bundle.sh` produce.
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@feat/unchanged_containers`, CGO static, so native per arch
under qemu/binfmt), urunit (from `NOFireAI/urunit@urunit_agent`, C static), and
the brig `container-initrd`. 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: on amd64 from
(from `urunc-dev/urunc` at commit `0818ff1`, 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
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: on amd64 from
the bunny Cloud-Hypervisor kernel image
(`harbor.nbfc.io/nubificus/bunny/linux-kernel-cloud-hypervisor`), on arm64 from
`ghcr.io/nofireai/hull-assets`.
Expand Down
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,8 @@ 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` @ `feat/unchanged_containers` | CGO-static, built in a Go container |
| urunit | built from `NOFireAI/urunit` @ `urunit_agent` | C-static; goes into the initrd |
| urunc, containerd-shim-urunc-v2 | built from `urunc-dev/urunc` at commit `0818ff1` (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 |
| container-initrd | built from the above | assembled for brig, not fetched |
| guest kernel (amd64) | `harbor.nbfc.io/nubificus/bunny/linux-kernel-cloud-hypervisor` | fetched; extracted from the bunny image's `/.boot/kernel` |
| guest kernel (arm64) | `ghcr.io/nofireai/hull-assets` | fetched; the same kernel hull and brig use |
Expand All @@ -284,6 +284,37 @@ the three that do not:
Then it generates the config files, systemd units, `brig-ctl` and the
uninstaller, and packs the whole `/var/lib/brig/data` tree.

### urunc and urunit are pinned to a commit

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. The urunit pin is what
v0.1.0-rc6 to rc8 shipped.

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
build of another commit takes it from the environment:

```console
$ URUNC_REF=<40-character sha> scripts/build-bundle.sh --arch amd64 --version dev
```

To build a branch tip on purpose, set the ref empty. The build then warns in its
log and records `URUNC_PINNED=false` in `pins.env`:

```console
$ URUNC_REF= URUNC_BRANCH=feat/unchanged_containers scripts/build-bundle.sh --arch amd64 --version dev
```

A branch given without a ref is refused, since it is unclear which of the two
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
Expand Down
15 changes: 9 additions & 6 deletions docs/variants.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ by `build-bundle.sh` and packed into the tarball. The installer just unpacks the
### The runtime is built, then packed

No urunc release publishes a binary that boots a generic container. So the build
compiles one, from `urunc-dev/urunc` at branch `feat/unchanged_containers`, in a
compiles one, from `urunc-dev/urunc` at the commit `build-bundle.sh` pins
(`0818ff1`, on branch `feat/unchanged_containers-exec-fixes`), in a
`golang:1.26.4` container:

```console
Expand Down Expand Up @@ -102,7 +103,7 @@ assembles a brig initrd, with urunc's
- `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 branch `urunit_agent`;
`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`.
Expand Down Expand Up @@ -177,10 +178,12 @@ refuses a stock binary, rather than printing a table of zeroes.
build from; upstream is better.
3. Settled: the guest kernel is fetched — on amd64 from the bunny
Cloud-Hypervisor kernel image, on arm64 from `hull-assets` by the same tags
hull uses; the runtime is built from `urunc-dev/urunc` at
`feat/unchanged_containers`; and
the initrd is built for brig from `NOFireAI/urunit` at `urunit_agent` plus
urunc's own `packaging/container-initrd`, so its agent matches the shim.
hull uses; the runtime is built from `urunc-dev/urunc` at a pinned commit
(`0818ff1`, 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?
5. Is `introspection` a variant at all, or `generic-boot` plus an opt-in flag?
Expand Down
122 changes: 107 additions & 15 deletions scripts/build-bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,52 @@ done
# bundle is cut for. BRIG_VERSION=latest still works for a local build.
BRIG_VERSION="${BRIG_VERSION:-v0.2.0}"
BRIG_REPO="${BRIG_REPO:-brig-sh/brig}"
# urunc and urunit have no release that carries what brig needs, so they are
# built from source, and the source is pinned to one commit each. A branch tip
# moves with every push, so it is not a release input: two builds of one bundle
# version would carry different code. Moving a pin is a reviewed one-line
# change to a *_REF_DEFAULT below, plus its *_BRANCH_DEFAULT when the commit
# comes from another branch.
#
# *_BRANCH names the branch the pinned commit comes from. It is recorded in
# pins.env, and it is what gets built when the matching *_REF is set empty
# (URUNC_REF= scripts/build-bundle.sh ...): the tip, with a warning in the
# build log and *_PINNED=false in pins.env. An unset *_REF takes the pin
# below, which is why these use ${VAR-default} and not ${VAR:-default}.
#
# urunc: feat/unchanged_containers plus the two urunit-agent exec fixes,
# urunc-dev/urunc#1059 and #1060. The agent in the initrd is built from this
# checkout too.
URUNC_REPO="${URUNC_REPO:-urunc-dev/urunc}"
URUNC_BRANCH="${URUNC_BRANCH:-feat/unchanged_containers}"
URUNC_BRANCH_DEFAULT=feat/unchanged_containers-exec-fixes
URUNC_REF_DEFAULT=0818ff104781087a12a75059062c3407a8b97c8a
URUNC_GO_IMAGE="${URUNC_GO_IMAGE:-golang:1.26.4}"
# urunit: the init in the initrd. What v0.1.0-rc6 to rc8 shipped.
URUNIT_REPO="${URUNIT_REPO:-NOFireAI/urunit}"
URUNIT_BRANCH="${URUNIT_BRANCH:-urunit_agent}"
URUNIT_BRANCH_DEFAULT=urunit_agent
URUNIT_REF_DEFAULT=71bfdeefb7bced121c4e75afa97550523af34152
# A branch given without a ref would otherwise build the pinned commit and
# ignore the branch, so ask which one was meant.
if [ -n "${URUNC_BRANCH:-}" ] && [ "$URUNC_BRANCH" != "$URUNC_BRANCH_DEFAULT" ] && [ -z "${URUNC_REF+set}" ]; then
fatal "URUNC_BRANCH=$URUNC_BRANCH is set but URUNC_REF is not. Set URUNC_REF= to build that branch's tip, or URUNC_REF=<commit> to build one commit from it."
fi
if [ -n "${URUNIT_BRANCH:-}" ] && [ "$URUNIT_BRANCH" != "$URUNIT_BRANCH_DEFAULT" ] && [ -z "${URUNIT_REF+set}" ]; then
fatal "URUNIT_BRANCH=$URUNIT_BRANCH is set but URUNIT_REF is not. Set URUNIT_REF= to build that branch's tip, or URUNIT_REF=<commit> to build one commit from it."
fi
URUNC_BRANCH="${URUNC_BRANCH:-$URUNC_BRANCH_DEFAULT}"
URUNC_REF="${URUNC_REF-$URUNC_REF_DEFAULT}"
URUNIT_BRANCH="${URUNIT_BRANCH:-$URUNIT_BRANCH_DEFAULT}"
URUNIT_REF="${URUNIT_REF-$URUNIT_REF_DEFAULT}"
# The checkout compares HEAD with the ref, so a ref is a full SHA: a short one,
# a tag or a branch name would never match.
for r in "URUNC_REF=$URUNC_REF" "URUNIT_REF=$URUNIT_REF"; do
v="${r#*=}"
[ -n "$v" ] || continue
case "$v" in
*[!0-9a-f]*) fatal "${r%%=*}='$v' is not a commit: pin a full 40-character SHA" ;;
esac
[ "${#v}" -eq 40 ] || fatal "${r%%=*}='$v' is not a commit: pin a full 40-character SHA"
done
# Static musl busybox for the initrd, per arch. Extracted from this image so we
# do not depend on busybox.net, which publishes 1.35.0 for x86_64 only.
BUSYBOX_IMAGE="${BUSYBOX_IMAGE:-busybox:1.36.1-musl}"
Expand Down Expand Up @@ -849,6 +890,43 @@ verify() {
fi
}

# checkout_source <repo> <branch> <ref> <dir>: put github.com/<repo> into <dir>
# at exactly the commit <ref>, or at the tip of <branch> when <ref> is empty,
# and print the commit it checked out. The commit is fetched by its SHA: a
# --depth 1 --branch clone stops containing a pinned commit as soon as the
# branch moves past it. GitHub serves any reachable commit by SHA, a pull
# request's head included.
checkout_source() {
cs_repo="$1"; cs_branch="$2"; cs_ref="$3"; cs_dir="$4"
# stdout carries the commit and nothing else.
git init -q "$cs_dir" >&2
git -C "$cs_dir" remote add origin "https://github.com/$cs_repo" >&2
if [ -n "$cs_ref" ]; then
git -C "$cs_dir" fetch -q --depth 1 origin "$cs_ref" >&2 \
|| fatal "could not fetch $cs_repo@$cs_ref"
else
git -C "$cs_dir" fetch -q --depth 1 origin "refs/heads/$cs_branch" >&2 \
|| fatal "could not fetch the $cs_branch branch of $cs_repo"
fi
git -C "$cs_dir" -c advice.detachedHead=false checkout -q FETCH_HEAD >&2
cs_head="$(git -C "$cs_dir" rev-parse HEAD)"
if [ -n "$cs_ref" ] && [ "$cs_head" != "$cs_ref" ]; then
fatal "$cs_repo: checked out $cs_head, not the pinned $cs_ref"
fi
echo "$cs_head"
}

# pinned_note <name> <repo> <branch> <ref> <commit>: log what was checked out,
# and warn when it was a branch tip.
pinned_note() {
if [ -n "$4" ]; then
info "$1: checked out $2@$5 (pinned; from branch $3)"
else
info "WARNING: $1: built $2@$5, the tip of $3 at build time. It is not pinned,"
info "WARNING: and a rebuild of this bundle version may carry different code."
fi
}

info "building $NAME (variant $VARIANT)"
info "fetching components"

Expand All @@ -861,15 +939,17 @@ if [ "$VARIANT" = "stock" ] && [ -n "$URUNC_VERSION_STOCK" ]; then
verify "$DL/containerd-shim-urunc-v2" "" "containerd-shim-urunc-v2_static_$ARCH"
URUNC_SOURCE="$URUNC_REPO@$URUNC_VERSION_STOCK"
URUNC_REF="$URUNC_VERSION_STOCK"
URUNC_PINNED=true
else
command -v docker >/dev/null 2>&1 || fatal "docker is required to build urunc from $URUNC_REPO@$URUNC_BRANCH"
command -v docker >/dev/null 2>&1 || fatal "docker is required to build urunc from $URUNC_REPO"
command -v git >/dev/null 2>&1 || fatal "git is required to build urunc from source"
info "building urunc from $URUNC_REPO@$URUNC_BRANCH ($URUNC_GO_IMAGE, linux/$ARCH)"
src="$TMP_DIR/urunc-src"
git clone --depth 1 --branch "$URUNC_BRANCH" "https://github.com/$URUNC_REPO" "$src" 2>/dev/null \
|| git clone "https://github.com/$URUNC_REPO" "$src"
( cd "$src" && git checkout "$URUNC_BRANCH" 2>/dev/null ) || true
URUNC_REF="$(cd "$src" && git rev-parse HEAD)"
URUNC_PINNED=true
[ -n "$URUNC_REF" ] || URUNC_PINNED=false
u_commit="$(checkout_source "$URUNC_REPO" "$URUNC_BRANCH" "$URUNC_REF" "$src")"
pinned_note urunc "$URUNC_REPO" "$URUNC_BRANCH" "$URUNC_REF" "$u_commit"
URUNC_REF="$u_commit"
info "building urunc $URUNC_REF ($URUNC_GO_IMAGE, linux/$ARCH)"
# --platform builds native inside a target-arch container (needs binfmt for
# a cross build). The static urunc binary is CGO, so this is not a Go cross
# compile.
Expand All @@ -888,16 +968,20 @@ fi
# brig builds its own initrd rather than shipping hull-assets': it execs into a
# guest through urunit-agent, which has to match the urunc shim's protocol, so
# the agent is built from the same urunc checkout ($src) as the shim above.
URUNIT_REF=""
if [ "$VARIANT" != "stock" ]; then
URUNIT_PINNED=""
if [ "$VARIANT" = "stock" ]; then
# stock boots the unikernel's own init, so there is no urunit to build.
URUNIT_REF=""
else
command -v docker >/dev/null 2>&1 || fatal "docker is required to build the brig initrd"
[ -d "${src:-}" ] || fatal "the urunc checkout is needed to build the initrd (build urunc from source)"
info "building urunit from $URUNIT_REPO@$URUNIT_BRANCH (linux/$ARCH)"
usrc="$TMP_DIR/urunit-src"
git clone --depth 1 --branch "$URUNIT_BRANCH" "https://github.com/$URUNIT_REPO" "$usrc" 2>/dev/null \
|| git clone "https://github.com/$URUNIT_REPO" "$usrc"
( cd "$usrc" && git checkout "$URUNIT_BRANCH" 2>/dev/null ) || true
URUNIT_REF="$(cd "$usrc" && git rev-parse HEAD)"
URUNIT_PINNED=true
[ -n "$URUNIT_REF" ] || URUNIT_PINNED=false
ut_commit="$(checkout_source "$URUNIT_REPO" "$URUNIT_BRANCH" "$URUNIT_REF" "$usrc")"
pinned_note urunit "$URUNIT_REPO" "$URUNIT_BRANCH" "$URUNIT_REF" "$ut_commit"
URUNIT_REF="$ut_commit"
info "building urunit $URUNIT_REF (linux/$ARCH)"
docker run --rm --platform "linux/$ARCH" -v "$usrc":/u -w /u alpine:3.20 \
sh -c "apk add --no-cache build-base linux-headers make musl-dev >/dev/null && make static" \
|| fatal "urunit build failed"
Expand Down Expand Up @@ -1359,9 +1443,11 @@ URUNC_VERSION=$URUNC_SOURCE
URUNC_REPO=$URUNC_REPO
URUNC_BRANCH=$URUNC_BRANCH
URUNC_REF=$URUNC_REF
URUNC_PINNED=$URUNC_PINNED
URUNIT_REPO=$([ "$VARIANT" = "stock" ] && echo "" || echo "$URUNIT_REPO")
URUNIT_BRANCH=$([ "$VARIANT" = "stock" ] && echo "" || echo "$URUNIT_BRANCH")
URUNIT_REF=$URUNIT_REF
URUNIT_PINNED=$URUNIT_PINNED
INITRD_SOURCE=$([ "$VARIANT" = "stock" ] && echo "" || echo "built:$URUNC_REF")
PREFIX=$PREFIX
DATA_DIR=$DATA_DIR
Expand All @@ -1383,6 +1469,12 @@ ASSETS_REPO=$([ "$KERNEL_FROM_ASSETS" = true ] && echo "$ASSETS_REPO" || echo ""
ASSETS_VERSION=$([ "$KERNEL_FROM_ASSETS" = true ] && echo "$ASSETS_VERSION" || echo "")
ASSETS_URUNC_REF=$ASSETS_URUNC_REF
PINS
if [ "$URUNC_PINNED" = false ]; then
echo "# WARNING: urunc is the tip of $URUNC_BRANCH at build time, not a pinned commit." >> "$STAGE/pins.env"
fi
if [ "$URUNIT_PINNED" = false ]; then
echo "# WARNING: urunit is the tip of $URUNIT_BRANCH at build time, not a pinned commit." >> "$STAGE/pins.env"
fi

# Deterministic tar: sorted names, one fixed timestamp, root-owned, fixed
# directory and file modes, no extended headers. Fetched components are
Expand Down
Loading
Loading