Skip to content

feat(bundle): Carry hull, built from a pinned commit - #40

Open
cmainas wants to merge 1 commit into
mainfrom
feat/ship-hull
Open

cmainas wants to merge 1 commit into
mainfrom
feat/ship-hull

Conversation

@cmainas

@cmainas cmainas commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

brig is moving to drive Linux sandboxes through hull, as on macOS (brig-sh/hull#105). hull on Linux boots through this bundle's urunc, monitors and guest assets, but it publishes no Linux release, so the bundle now builds it from a pinned commit, the way it builds urunc.

nerdctl stays, and stays the default: the bundled brig v0.5.0 drives only nerdctl on Linux, and brig still refuses egress policies, routes, network: isolated and published ports on hull on Linux, so the shipped profiles would not run there yet. BRIG_RUNTIME=hull opts in.

Changes

  • Build hull. brig-sh/hull at fb41d4a, fetched by SHA and refused if the checkout differs, built static (CGO_ENABLED=0) with no telemetry endpoint, and recorded in pins.env as HULL_REF/HULL_PINNED. HULL_REF/HULL_BRANCH work like urunc's. stock leaves it out, as it leaves out hvi.
  • Launcher picks the runtime's binary. BRIG_RUNTIME_BIN was always the bundle's nerdctl, so BRIG_RUNTIME=hull would have driven nerdctl with hull's command line. It now follows BRIG_RUNTIME, and a bundle without hull names no binary for it.
  • Launcher exports NETCONFPATH. hull reads only the variable, so a root install's hull would look in /etc/cni/net.d and miss the bundle's bridge. The value is the directory nerdctl.toml already names.
  • Tests and CI. tests/source-pins.sh covers hull's pin; CI checks the recorded pin, that hull --version ends in linux/<arch>, and what the launcher exports for each runtime.
  • README. hull in the tree and the component table, a short "hull beside nerdctl" section, and how its pin moves with urunc's.

Testing

  • tests/*.sh all pass, including the new hull cases. The cgo check fails with CGO_ENABLED=0 removed.
  • shellcheck 0.9.0, CI's version, is clean on the same files CI lints.
  • The brig-env.sh generation code, run for plain and rootless bundles with and without hull, exports what the new CI step expects in all twelve cases.
  • hull built from fb41d4a with the recipe's checkout and make linux, but with the host's Go 1.26.8 rather than the golang:1.26.8 container: a static binary whose --version ends in linux/amd64).
  • Not run: a full build-bundle.sh build, the docker step of the hull build, and an install. CI's bundle job is the first full build.

Follow-ups

  • urunc 5c6bc7060 (feat/linux-hvi-v050-socket) is what hull's Linux network gateway needs. Moving to it waits for a hull-assets release built from it, since check-assets.sh refuses a mismatch.
  • The uninstaller does not remove hull's per-instance state (/var/log/hull, ~/.local/state/hull), and nix/runtime.nix does not carry hull.

brig is to drive Linux sandboxes through hull, as it does on macOS
(brig-sh/hull#105). hull on Linux is a containerd client that boots
through urunc, so it needs this bundle's urunc, monitors and guest
assets, but hull publishes no Linux release to fetch. So the build
makes it from brig-sh/hull at a pinned commit, the way it makes urunc:
fetched by SHA, refused if the checkout is anything else, recorded in
pins.env as HULL_REF and HULL_PINNED, and checked by CI. hull is pure
Go, so it is a static cross compile with no telemetry endpoint. stock
leaves it out, as it leaves out hvi.

nerdctl stays, and stays brig's runtime: the brig this bundle carries
drives only nerdctl on Linux, and brig refuses an egress policy, a
route, an isolated network and a published port on hull on Linux, so
the shipped profiles would not run there yet. BRIG_RUNTIME=hull opts
in.

Two launcher changes make that opt-in work. BRIG_RUNTIME_BIN now
follows BRIG_RUNTIME: it was always the bundle's nerdctl, and brig
handed that path with BRIG_RUNTIME=hull drives nerdctl with hull's
command line. A bundle without hull names no binary for it. And the
launcher exports NETCONFPATH, the directory nerdctl.toml names: hull
reads only the variable, and without it a root install's hull looks in
/etc/cni/net.d and does not find the bundle's bridge network.

hull's pin follows urunc's: hull checks the urunc keys it writes
against one urunc commit, 3c00b69 at fb41d4a, which is this bundle's.

Refs: brig-sh/hull#105
Signed-off-by: Charalampos Mainas <cmainas@nofire.ai>
@cmainas
cmainas requested a review from ananos November 12, 2026 13:57
@cmainas

cmainas commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator Author

@claude-fleet review

@claude-fleet

claude-fleet Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Review task queued.

Task: 21a2dc0d-f392-42fe-b8df-62dbd496b925
Commit: ea1fbed
Model: claude-opus-5 (effort medium)
Executor: ci-16gb-fsn1-1
Status: done. Review posted.

@claude-fleet claude-fleet Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the hull pin against the real repo (cloned brig-sh/hull at fb41d4a), ran the pin tests and the cgo revert check.

Approve: one nit.

Findings:

  • nit, scripts/build-bundle.sh:1495 — the reason given for exporting NETCONFPATH is wrong; hull does find an installed bundle's net.d on its own

Verified:

  • make linux LINUX_ARCH=<arch> exists and writes dist/hull_linux_<arch>, which is what the build copies out — hull Makefile:126-131 vs scripts/build-bundle.sh:1105.
  • No TELEMETRY_ENDPOINT means an empty endpoint, not a default one — hull Makefile:66-68; the test also fails if the flag appears (tests/source-pins.sh:140).
  • hull is pure Go, so CGO_ENABLED=0 with no --platform is a real cross build — Makefile:130 passes GOOS/GOARCH to go build.
  • The pin is fetched by SHA and a different checkout is refused — checkout_source scripts/build-bundle.sh:990-1008, exercised by tests/source-pins.sh:132-177.
  • HULL_REF/HULL_BRANCH behave as urunc's: branch without ref refused (scripts/build-bundle.sh:158-160), non-full-SHA refused by the new shared require_sha (scripts/build-bundle.sh:170-180), empty ref builds the tip with a warning.
  • "hull checks the urunc keys against one urunc commit, which has to be this bundle's urunc" — internal/urunckeys/go.mod requires urunc v0.8.2-0.20261029205741-3c00b69ff9ac, equal to URUNC_REF_DEFAULT (scripts/build-bundle.sh:131).
  • The launcher's exports do reach hull: NETCONFPATH (hull cmd/hull/net_linux.go:536), CONTAINERD_ADDRESS and CONTAINERD_NAMESPACE (internal/containerd/containerd.go:49,53, read via consts), BRIG_BOOT_ASSETS (internal/bootassets/bootassets.go).
  • NETCONFPATH is the directory nerdctl.toml names, in both layouts: root/plain $ETC_DIR/cni/net.d (scripts/build-bundle.sh:1418 vs 1470, 1486), rootless non-root $XDG_CONFIG_HOME/brig/cni/net.d (scripts/build-bundle.sh:670 vs 1477), which is also exactly hull's rootlessBrigNetConfDir.
  • hull --version ends in linux/<arch>), so the CI pattern holds — hull internal/buildinfo/buildinfo.go:167-176.
  • BRIG_RUNTIME_BIN now follows BRIG_RUNTIME and still defers to the environment; a bundle without hull emits no hull) branch (scripts/build-bundle.sh:1504-1513). [ "$HULL_INSTALLED" = true ] && install … (scripts/build-bundle.sh:1233) is safe under set -e: a failing non-final command of an AND list does not exit the shell.

Checks run:

  • sh tests/source-pins.sh → 12 ok, no failures.
  • Revert check: dropped -e CGO_ENABLED=0 from scripts/build-bundle.sh:1103 → FAIL: hull was built with cgo; file restored, tree clean.
  • git ls-remote https://github.com/brig-sh/hull HEAD → fb41d4a05d4a…, i.e. the pin is the current tip of main, not a commit behind it.

Smaller things:

  • hull's --snapshotter flag has no environment source (hull cmd/hull/run_linux.go:92-95), so the launcher's CONTAINERD_SNAPSHOTTER applies to nerdctl only and a BRIG_RUNTIME=hull run on a plain bundle will not pick up devmapper. Nothing to change here, but it is the same class of gap as the NETCONFPATH one and may be worth a line in the README's hull section.
  • The bundled hull will report its version as dev: the by-SHA --depth 1 fetch carries no tags, so the Makefile's git describe is empty (hull Makefile:25) and buildinfo falls back to "dev" (internal/buildinfo/buildinfo.go:81). The commit is still in the string and in pins.env, and the CI assertion only looks at the platform suffix, so this is cosmetic.
  • README.md:310 still says the bundle's hvi v0.1.0-rc.2 (commit 2ed8f61) is "the one hull ships on macOS". At the newly pinned hull commit, hvi.pin names 0.1.0-rc.1 / 65570c7. hull does no runtime hvi version check, so this is a stale sentence rather than a build problem, but now that hull is pinned the claim is checkable.
  • For --variant stock, pins.env gets HULL_PINNED= (scripts/build-bundle.sh:1605 with HULL_PINNED=""), where URUNC_PINNED is always true or false. A consumer that reads the field gets an empty value rather than a boolean. CI only builds generic-boot, so nothing checks it.
  • brig-env.sh is the one generated script the lint job does not sh -n (ci.yml:22-50 covers the uninstaller, the rootless setup and brig-ctl), and the new case/esac is now assembled from three heredocs. The new launcher step sources it, which covers it in practice.

Not checked:

  • A full scripts/build-bundle.sh run, the docker hull build, and an install: the bundle job is the first real exercise, as the description says.
  • Whether urunc 5c6bc7060 is what hull's Linux network gateway needs, and the claims about brig v0.5.0 refusing egress policies, routes, network: isolated and published ports on hull on Linux — both live in other repos.
  • hull's behaviour at runtime on Linux beyond the environment variables above (gateway, assets verification, instance state paths).

Not checked:

  • A full scripts/build-bundle.sh run, the docker hull build and an install (CI's bundle job is the first full build)
  • Whether urunc 5c6bc7060 is what hull's Linux network gateway needs (other repo)
  • brig v0.5.0's refusal of egress policies, routes, network: isolated and published ports on hull on Linux (other repo)
  • hull's runtime behaviour on Linux beyond the environment variables the launcher sets

Task 21a2dc0d-f392-42fe-b8df-62dbd496b925 on ci-16gb-fsn1-1, model claude-opus-5, effort medium.

Comment thread scripts/build-bundle.sh
# devmapper needs the system thin pool, so a rootless stack stays on overlayfs.
export CONTAINERD_SNAPSHOTTER="\${CONTAINERD_SNAPSHOTTER:-$SNAPSHOTTER}"
export CNI_PATH="\${CNI_PATH:-$CNI_DIR}"
# NETCONFPATH, set above, is the directory nerdctl.toml names. nerdctl reads

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The comment's reason for exporting NETCONFPATH is wrong: hull does not fall back to /etc/cni/net.d for an installed bundle. hull's cniConfig walks bootassets.BundleDirs() first and uses

/etc/cni/net.d when it exists, where BundleDirs is /var/lib/brig/data (plus ~/.local/share/brig/data for a non-root euid) -- i.e. exactly the default root and user install prefixes -- and it then prefers $XDG_CONFIG_HOME/brig/cni/net.d for a rootless user.
Failure: no runtime failure; the export is correct and matches nerdctl.toml. The claim is what is wrong, and a later reader judging a change against it (for instance, dropping the export or assuming hull has no bundle discovery) would be reasoning from a false premise.
Evidence: hull at the pinned fb41d4a, cmd/hull/net_linux.go:507-539 (bundleDirs loop, then rootlessBrigNetConfDir, then $NETCONFPATH) and internal/bootassets/bundle_linux.go:34-57 (systemBundle = /var/lib/brig/data). The same wording is in the PR description.
Fix: say that hull's own discovery covers the default prefixes and that the export is what makes a retargeted install (install.sh --prefix) work, and is read in preference to that discovery.

@ananos ananos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @cmainas, nice to have hull in the bundle! The pin, the static build and the pins.env plumbing follow urunc's recipe, which is the right call. Two things to fix before this goes in, both inline:

  • The hull the bundle builds reports v0.0.0-..., and brig reads that as an old hull.
  • After eval "$(brig-ctl env)", BRIG_RUNTIME=hull keeps the nerdctl binary.

Two smaller ones, fine as follow-ups: nothing checks that HULL_REF and URUNC_REF agree (the README says they must, so a check against internal/urunckeys/go.mod after the checkout would make it true), and golang:1.26.8 is a tag rather than a digest, as for urunc. Also, hull does find an installed bundle's etc/cni/net.d on its own (net_linux.go, bundleDirs), so the NETCONFPATH comment should say the export keeps hull and nerdctl.toml on one directory, not that hull reads only the variable.

Comment thread scripts/build-bundle.sh
hsrc="$TMP_DIR/hull-src"
HULL_PINNED=true
[ -n "$HULL_REF" ] || HULL_PINNED=false
h_commit="$(checkout_source "$HULL_REPO" "$HULL_BRANCH" "$HULL_REF" "$hsrc")"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a depth-1 fetch with no tags, so Go stamps the module as a pseudo-version, and hull's buildinfo prefers that over its git describe fallback. The CI run of this PR shows it: hull v0.0.0-20261111171032-fb41d4a05d4a (fb41d4a, 2026-11-11, go1.26.8, linux/arm64).

brig parses that as 0.0.x, older than rc23, so with BRIG_RUNTIME=hull it stops pinning boot assets by digest and falls back to tag verification with a weaker-verification alert (hullVersionPinsDigest, wrap/verify.go). brig doctor would also list the runtime as hull 0.0.0.

We should fetch the tags (or enough history for git describe --tags --long --match 'v[0-9]*' to find one) before make linux, and have the CI step assert that hull --version does not start with hull v0.0.0.

Comment thread scripts/build-bundle.sh
CFG
if [ "$HULL_INSTALLED" = true ]; then
cat >> "$STAGE/etc/brig-env.sh" <<CFG
hull) export BRIG_RUNTIME_BIN="\${BRIG_RUNTIME_BIN:-$BIN_DIR/hull}" ;;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${BRIG_RUNTIME_BIN:-...} keeps an inherited value whatever runtime set it. The README's flow hits this: eval "$(brig-ctl env)" exports BRIG_RUNTIME_BIN=.../nerdctl, and a later BRIG_RUNTIME=hull brig doctor reaches brig with that path, so brig drives nerdctl with hull's command line. Nothing re-reads this file in between. The CI check sources it under env -i, so it cannot see this.

Two ways out, maybe both: say in the README that BRIG_RUNTIME goes before the eval (or pass BRIG_RUNTIME_BIN too), and have this arm replace an inherited bin that equals the other runtime's default. A CI case that sources the file twice, default then BRIG_RUNTIME=hull, would lock it in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants