gershwin-developer — NextBSD build CI_

Add a fourth build target to gershwin-developer's build.yml — alongside FreeBSD, Arch, and Debian — that verifies Gershwin compiles on NextBSD. The trick we earlier lacked: don't boot the NextBSD image, chroot-build into its rootfs inside a FreeBSD VM. This is the job we scrapped two iterations ago, now actually feasible.

CI PLAN vmactions/freebsd-vm chroot build build-verify only

1Why this works now (and didn't before)

Our first attempt at a NextBSD CI job died on a real wall: vmactions can only boot its own prepared FreeBSD images (no custom-image input), and the NextBSD .img isn't provisioned for SSH/rsync — and it's launchd-based with no rc.d, so we couldn't even reliably get a shell to drive a build. We tried serial-console + FAT-disk plumbing and abandoned it as too fragile.

Researching the NextBSD ISO pipeline flipped the model. NextBSD itself never "runs" its image to build software into it — it chroots into the rootfs from a FreeBSD VM and builds there. We copy that exactly:

2Where it fits in build.yml

A new build-nextbsd-amd64 job, parallel to the existing three. The other jobs use a container (Arch/Debian) or vmactions/freebsd-vm directly with the repo synced in; NextBSD differs only in that the FreeBSD VM is a host for a chroot, and the rootfs is fetched from NextBSD's continuous release rather than provided by vmactions.

checkout gershwin-developer──▶ download NextBSD .img.zip──▶ FreeBSD VM: mount rootfs──▶ chroot: Bootstrap → Checkout → make install──▶ assert /System populated
JobEnvironmentHow sources arrive
build-freebsd-14-amd64vmactions/freebsd-vmrsynced into the VM, build runs in the VM
build-archlinux / build-debiancontainer:checked out into the container
build-nextbsd-amd64 (new)vmactions/freebsd-vm → chroot of NextBSD rootfsrsynced into the VM, then tar'd into the chroot

3The job

.github/workflows/build.yml — new job
  build-nextbsd-amd64:
    name: NextBSD x86_64
    runs-on: ubuntu-latest
    timeout-minutes: 240
    steps:
      - uses: actions/checkout@v4
      - uses: jlumbroso/free-disk-space@main          # rootfs + obj trees are multi-GB
      - name: Download latest NextBSD rootfs image
        env: { GH_TOKEN: "${{ github.token }}" }
        run: |
          URL=$(gh api repos/nextbsd-redux/nextbsd/releases/tags/continuous \
                --jq '.assets[]|select(.name|test("amd64.*\\.img\\.zip$")).browser_download_url' | head -1)
          curl -fL -o nextbsd.img.zip        "$URL"
          curl -fL -o nextbsd.img.zip.sha256 "$URL.sha256"
          sha256sum -c nextbsd.img.zip.sha256
          unzip -p nextbsd.img.zip '*.img' > nextbsd.img
      - name: Build Gershwin in the NextBSD rootfs (FreeBSD VM)
        uses: vmactions/freebsd-vm@v1
        with:
          release: '15.0'          # match the NEXTBSD kernel's FreeBSD base
          mem: 8192
          cpu: 4
          usesh: true
          sync: rsync
          prepare: pkg install -y bash git rsync
          run: |
            sh ./.github/scripts/nextbsd-chroot-build.sh "$PWD/nextbsd.img"

The chroot-build script

.github/scripts/nextbsd-chroot-build.sh (runs as root in the FreeBSD VM)
#!/bin/sh
set -eu
IMG=$1
WORK=$PWD/work; ROOTFS=$WORK/rootfs
mkdir -p "$ROOTFS"

# extract the NextBSD rootfs from the .img's freebsd-ufs/ROOTFS partition (p3)
md=$(mdconfig -a -t vnode -f "$IMG")
mount "/dev/${md}p3" /mnt                              # confirm index with: gpart show ${md}
tar -C /mnt -cf - . | tar -C "$ROOTFS" -xpf -          # roomy plain dir; img has only +1.5G headroom
umount /mnt; mdconfig -d -u "${md#md}"

# build Gershwin inside a chroot of that rootfs (net for pkg + git clone)
mount -t devfs devfs "$ROOTFS/dev"
cp /etc/resolv.conf "$ROOTFS/private/etc/resolv.conf"
mkdir -p "$ROOTFS/build"
tar -C "$PWD" --exclude ./work --exclude ./nextbsd.img -cf - . | tar -C "$ROOTFS/build" -xpf -
chroot "$ROOTFS" /bin/sh -eu -c '
    cd /build
    sh Library/Scripts/Bootstrap.sh        # pkg install nextbsd.txt prerequisites
    sh Library/Scripts/Checkout.sh         # clone the component repos
    make install                           # detect_platform() sees /usr/lib/system → NextBSD path
'
umount "$ROOTFS/dev"

test -d "$ROOTFS/System/Library" || { echo "FAIL: /System not populated"; exit 1; }
echo "NextBSD chroot build OK"

4The one make-or-break risk

Everything above hinges on a single assumption that must be proven on the first run:

Also confirm on first runExpected
UFS root partition index in the .imgp3 (p1 freebsd-boot, p2 efi, p3 ufs) — verify with gpart show
FreeBSD release matching the NEXTBSD kernel basefreebsd-version in the extracted rootfs
VM disk headroom for sources + objsfree-disk-space added; watch during the build

What this does and doesn't prove