Files
zeekayandHanzo Dev c814594092 The version gate asked the runtime for a string only its guest can say
`runsc --version` prints two lines, and -visor is in neither:

    runsc version <STABLE_VERSION>
    spec: <OCI spec version>

STABLE_VERSION is `git describe --always --tags` — tools/workspace_status.sh,
wired by .bazelrc, substituted through runsc/BUILD's x_defs. The spec line is
opencontainers/runtime-spec's. The -visor suffix lives in
pkg/sentry/kernel/version/version.go: it is the release the sandbox advertises
through uname(2), which only a process inside a running sandbox can read. Its
own comment says as much — the two builds "are told apart by asking the guest".

So the gate fails for every build that can exist. visor-20260919.0 is queued and
has never run; it would have compiled all of gVisor and died on the last line
before creating the release, reading as a broken fork rather than a bad grep.

Assert what the binary does report, and what a release needs anyway: that the
bytes carry the tag being published. At a tag, describe returns the tag name, so
the line is exactly `runsc version $TAG`. Everything that should be rejected
still is — VERSION_MISSING with no stamp, 0.0.0 or a bare commit with no tag in
reach, -dirty from a modified tree, and upstream, whose tags are release-*.

The bazelisk asset now names the pool it runs on. bazelisk publishes <os>-<arch>
with exactly the labels our runners carry, so the arm64 row cannot come back
downloading an amd64 bazel.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-19 10:19:16 -07:00

136 lines
6.3 KiB
YAML

name: publish
# A node must install the sandbox boundary from a VERIFIED artifact: every
# binary ships beside its sha512, and universe's install.sh pins those digests,
# so a tampered or truncated download aborts instead of running an unverified
# boundary.
#
# Until this lane existed, the visor on each node was built by hand from a
# working tree — 102MB nobody could reproduce, attribute to a commit, or check.
#
# It runs on OUR self-hosted pools, not GitHub's machines, and publishes with
# the token every run is given, so there is no credential here that can rot.
#
# IT LIVES IN .hanzo/workflows FOR THAT REASON. Under .github/ it is a workflow
# only GitHub Actions would run, and this estate has no runners there: the lane
# sat at .github/workflows/publish.yml, tag visor-20260911.0 was pushed at a
# commit that already carried it, and no release was ever created. A workflow in
# the wrong directory does not fail — it waits, which reads exactly like a lane
# nobody triggered.
on:
push:
tags: ['visor-*']
workflow_dispatch:
inputs:
tag:
description: An existing visor-* tag to publish
required: true
permissions:
contents: write
jobs:
binaries:
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
runs-on: linux-amd64
# aarch64 is NOT here yet, and that is a measurement: the runner image
# oci.hanzo.ai/hanzoai/git-runner:5 is a single manifest, so an arm64
# node cannot start it — its pods StartError and the job then sits
# queued forever, which looks like nothing happening. Add this arch
# back in the same commit that publishes a multi-arch runner image.
#
# - arch: aarch64
# runs-on: linux-arm64
runs-on: ${{ matrix.runs-on }}
env:
TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
# ONE bazel invocation for both, so the shim and the runtime it execs are
# always the same build. A shim paired with a runtime it was not built
# against fails at container start, after admission.
# bazel DIRECTLY, not `make copy`: the Makefile wraps bazel in a container
# and pushes to an upstream registry, so it needs a docker socket our
# runners do not have — it failed with `dial unix /var/run/docker.sock`
# before building anything.
#
# bazelisk rather than a pinned bazel, because the repo states its own
# version in .bazelversion and bazelisk is the one thing that reads it.
#
# Installed WITHOUT sudo, into a directory this job already owns. Runners
# execute inside the visor sandbox, where setuid is not honoured — sudo
# there fails with "nosuid", so any step that reaches for root fails
# before it does anything.
- name: Install bazelisk
run: |
set -eu
mkdir -p "$HOME/.local/bin"
# The RELEASE is pinned; no digest is asserted, because a checksum
# nobody verified out-of-band is theatre. What this lane actually
# proves is the binary it produces — the version gate below rejects
# anything not stamped with the tag being published.
#
# The asset is named after the pool: bazelisk publishes <os>-<arch>
# with exactly the labels our runners carry, so the download cannot
# be a different architecture from the machine that runs it.
curl -fsSL -o "$HOME/.local/bin/bazel" \
"https://github.com/bazelbuild/bazelisk/releases/download/v1.25.0/bazelisk-${{ matrix.runs-on }}"
chmod 0755 "$HOME/.local/bin/bazel"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
"$HOME/.local/bin/bazel" version
- name: Build
run: |
set -eu
bazel build //runsc:runsc //shim:containerd-shim-runsc-v1
mkdir -p bin
cp -L "$(find -L bazel-bin/runsc -type f -name runsc | head -1)" bin/runsc
cp -L "$(find -L bazel-bin -type f -name containerd-shim-runsc-v1 | head -1)" bin/containerd-shim-runsc-v1
# The runtime installs as `visor` — the name the containerd handler and the
# RuntimeClass both use. Asking it for its version is the cheapest proof the
# bytes are this tag: .bazelrc stamps STABLE_VERSION from
# `git describe --always --tags` through runsc/BUILD's x_defs, and cli.go
# prints it as `runsc version <stamp>`. At a tag, describe returns the tag
# name — so an unstamped build (VERSION_MISSING), a build with no tag in
# reach (0.0.0 or a bare commit) and a modified tree (-dirty) all fail here.
#
# It does NOT print -visor. That suffix is the guest kernel's uname release
# in pkg/sentry/kernel/version/version.go, which only a process inside a
# running sandbox can read, so grepping the runtime's own --version for it
# could never pass: the gate burned a whole gVisor build to fail on its
# last line, having published nothing.
- name: Name the bytes and prove they are this tag
run: |
set -eu
cd bin
mv runsc visor
chmod 0755 visor containerd-shim-runsc-v1
./visor --version | tee version.txt
grep -qx "runsc version $TAG" version.txt || { echo "FATAL: this binary is stamped $(head -1 version.txt), not $TAG"; exit 1; }
for f in visor containerd-shim-runsc-v1; do
sha512sum "$f" | awk '{print $1}' > "$f-${{ matrix.arch }}.sha512"
mv "$f" "$f-${{ matrix.arch }}"
done
ls -l
- name: Publish to the release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eu
gh release view "$TAG" >/dev/null 2>&1 || gh release create "$TAG" --title "$TAG" --notes "Verified visor binaries, built on our own runners."
cd bin
gh release upload "$TAG" \
"visor-${{ matrix.arch }}" "visor-${{ matrix.arch }}.sha512" \
"containerd-shim-runsc-v1-${{ matrix.arch }}" "containerd-shim-runsc-v1-${{ matrix.arch }}.sha512" \
--clobber
echo "published $(ls | tr '\n' ' ')"