chore: add CI for FreeBSD

This commit is contained in:
Marcus Holland-Moritz 2025-08-09 08:16:17 +02:00
parent addc222511
commit 32ed073cb2
4 changed files with 320 additions and 0 deletions

33
.docker/build-freebsd.sh Normal file
View File

@ -0,0 +1,33 @@
#!/bin/sh
set -eux
export HOME=/root
export PATH=/usr/local/bin:/usr/local/sbin:/bin:/usr/bin:/sbin:/usr/sbin
export CCACHE_DIR=/ccache
export RUNNER_TEMP=/runner_temp
export RUNNER_WORKSPACE=/work
export GITHUB_RUN_NUMBER="${GITHUB_RUN_NUMBER:-0}"
export BUILD_MODE="${BUILD_MODE:-Release}"
export CMAKE_ARGS="${CMAKE_ARGS:-}"
# Source tarball path (NFS is nullfs-mounted from host)
SRC_TARBALL="/mnt/opensource/artifacts/dwarfs/cache/dwarfs-source-${GITHUB_RUN_NUMBER}.tar.zst"
test -r "${SRC_TARBALL}"
cd "$HOME"
rm -rf dwarfs dwarfs-*
tar xf "${SRC_TARBALL}"
ln -sfn dwarfs-* dwarfs
rm -rf "${RUNNER_TEMP}/build"
cmake --fresh \
-B"${RUNNER_TEMP}/build" \
-S"${HOME}/dwarfs" \
-DCMAKE_BUILD_TYPE="${BUILD_MODE}" \
-DWITH_TESTS=ON \
-DWITH_PXATTR=ON \
${CMAKE_ARGS}
cmake --build "${RUNNER_TEMP}/build" -j"$(sysctl -n hw.ncpu)"
ctest --test-dir "${RUNNER_TEMP}/build" --output-on-failure -j"$(sysctl -n hw.ncpu)"
cmake --build "${RUNNER_TEMP}/build" --target realclean

189
.github/scripts/freebsd_jail_build.sh vendored Normal file
View File

@ -0,0 +1,189 @@
#!/bin/sh
set -eu
# ----------------------------
# Config / Inputs (env-driven)
# ----------------------------
FREEBSD_VER_TAG="${FREEBSD_VERSION:-14_3}"
SNAP_LABEL="${FREEBSD_SNAPSHOT:-base-20250809}"
ZPOOL="${ZPOOL:-zroot}"
JAILS_DS="${JAILS_DS:-${ZPOOL}/z/jails}"
JAILS_MP="${JAILS_MP:-/z/jails}"
GITHUB_RUN_ID="${GITHUB_RUN_ID:-localrun}"
GITHUB_RUN_ATTEMPT="${GITHUB_RUN_ATTEMPT:-1}"
BUILD_MODE="${BUILD_MODE:-Release}"
CMAKE_ARGS="${CMAKE_ARGS:-}"
BASE_DS="${JAILS_DS}/${FREEBSD_VER_TAG}"
RUN_DS="${JAILS_DS}/ci-${FREEBSD_VER_TAG}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
RUN_MP="${JAILS_MP}/ci-${FREEBSD_VER_TAG}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
JAIL="ci_${FREEBSD_VER_TAG}_${GITHUB_RUN_ID}_${GITHUB_RUN_ATTEMPT}"
# ----------------------------
# Helpers
# ----------------------------
log() { printf '%s %s\n' "[freebsd-jail]" "$*" >&2; }
# Stop a jail if it's running; kill any lingering processes by JID if needed
stop_stale_jail() {
jname="$1"
if jls -j "$jname" >/dev/null 2>&1; then
log "Stopping stale jail: $jname"
if ! sudo jail -r "$jname" 2>/dev/null; then
JID="$(jls -j "$jname" jid -h 2>/dev/null || true)"
if [ -n "$JID" ]; then
sudo killall -j "$JID" -TERM 2>/dev/null || true
sleep 1
sudo killall -j "$JID" -KILL 2>/dev/null || true
fi
sudo jail -r "$jname" 2>/dev/null || true
fi
fi
}
# Unmount everything under a mountpoint (deepest-first)
umount_tree() {
rootmp="$1"
# List mounted paths under $rootmp, deepest-first
# shellcheck disable=SC2016
for m in $(mount | awk -v p="$rootmp" '$3 ~ ("^"p) { print length, $3 }' | sort -rn | cut -d" " -f2-); do
sudo umount -f "$m" 2>/dev/null || true
done
}
# Destroy a cloned dataset (after umount)
destroy_dataset() {
ds="$1"
if sudo zfs list -H "$ds" >/dev/null 2>&1; then
mp="$(sudo zfs get -H -o value mountpoint "$ds" 2>/dev/null || echo "")"
if [ -n "$mp" ] && [ "$mp" != "-" ]; then
umount_tree "$mp"
fi
log "Destroying dataset: $ds"
sudo zfs destroy -r "$ds"
fi
}
# Remove stale jail + dataset that match our run IDs
cleanup_stale_before_start() {
stop_stale_jail "$JAIL"
destroy_dataset "$RUN_DS"
}
# Full cleanup for traps: stop jail, unmount, destroy dataset
cleanup_on_exit() {
# Be idempotent; ignore errors
stop_stale_jail "$JAIL"
destroy_dataset "$RUN_DS"
}
# Generate an env file inside jail that exports safe GitHub env vars
write_gha_env() {
out="$1"
# Allow common CI vars; deny obvious secrets
# Adjust allow/deny as needed for your org.
ALLOW='GITHUB_* RUNNER_* CI ACTIONS_* INPUT_* MATRIX_* BUILD_MODE CMAKE_ARGS'
DENY='*TOKEN* *SECRET* *PASSWORD* *PASS* *KEY* *CERT* AWS_* AZURE_* GCP_*'
# Function to test name against allow/deny (POSIX sh compatible inline)
is_allowed() {
name="$1"
case "$name" in
GITHUB_*|RUNNER_*|CI|ACTIONS_*|INPUT_*|MATRIX_*|BUILD_MODE|CMAKE_ARGS) : ;;
*) return 1 ;;
esac
case "$name" in
*TOKEN*|*SECRET*|*PASSWORD*|*PASS*|*KEY*|*CERT*|AWS_*|AZURE_*|GCP_* ) return 1 ;;
esac
return 0
}
tmp="$(mktemp)"
{
echo "# Autogenerated; sourced by /root/build.sh"
# Ensure our two common knobs are present even if not in env
printf "export BUILD_MODE='%s'\n" "$BUILD_MODE"
# Escape single quotes in CMAKE_ARGS
esc_ca=$(printf "%s" "$CMAKE_ARGS" | sed "s/'/'\"'\"'/g")
printf "export CMAKE_ARGS='%s'\n" "$esc_ca"
env | while IFS='=' read -r name value; do
if is_allowed "$name"; then
esc=$(printf "%s" "$value" | sed "s/'/'\"'\"'/g")
printf "export %s='%s'\n" "$name" "$esc"
fi
done
} > "$tmp"
sudo mkdir -p "$(dirname "$out")"
sudo cp "$tmp" "$out"
sudo chmod 0644 "$out"
rm -f "$tmp"
}
# ----------------------------
# Trap: ensure cleanup on any exit
# ----------------------------
trap cleanup_on_exit EXIT INT TERM HUP
# ----------------------------
# Prep: nuke stale, then clone snapshot
# ----------------------------
cleanup_stale_before_start
log "Cloning ${BASE_DS}@${SNAP_LABEL} -> ${RUN_DS}"
sudo zfs clone "${BASE_DS}@${SNAP_LABEL}" "${RUN_DS}"
# ----------------------------
# Bootstrapping jail root: DNS + mounts
# ----------------------------
# DNS for networking inside jail
sudo mkdir -p "${RUN_MP}/etc"
sudo cp /etc/resolv.conf "${RUN_MP}/etc/resolv.conf"
[ -f /etc/hosts ] && sudo cp /etc/hosts "${RUN_MP}/etc/hosts"
[ -f /etc/nsswitch.conf ] && sudo cp /etc/nsswitch.conf "${RUN_MP}/etc/nsswitch.conf"
# Core mounts
sudo mount -t devfs devfs "${RUN_MP}/dev"
sudo mount -t fdescfs fdesc "${RUN_MP}/dev/fd"
sudo mount -t procfs proc "${RUN_MP}/proc"
sudo mount -t tmpfs tmpfs "${RUN_MP}/tmp"
# Workspace / temp / ccache / NFS from host
RUNNER_WORKSPACE="${RUNNER_WORKSPACE:-$PWD}"
RUNNER_TEMP="${RUNNER_TEMP:-/tmp/runner_temp}"
mkdir -p "${RUNNER_TEMP}" "${HOME}/.ccache"
sudo mkdir -p "${RUN_MP}/work" "${RUN_MP}/runner_temp" "${RUN_MP}/ccache" "${RUN_MP}/mnt/opensource"
sudo mount -t nullfs -o rw "${RUNNER_WORKSPACE}" "${RUN_MP}/work"
sudo mount -t nullfs -o rw "${RUNNER_TEMP}" "${RUN_MP}/runner_temp"
sudo mount -t nullfs -o rw "${HOME}/github-ccache" "${RUN_MP}/ccache"
sudo mount -t nullfs -o rw "/mnt/opensource" "${RUN_MP}/mnt/opensource"
# ----------------------------
# Start jail (FUSE enabled)
# ----------------------------
log "Starting jail: ${JAIL}"
sudo jail -c name="${JAIL}" host.hostname="${JAIL}" \
path="${RUN_MP}" persist \
mount.devfs devfs_ruleset=5 enforce_statfs=1 \
allow.mount allow.mount.fusefs \
allow.raw_sockets \
ip4=inherit ip6=inherit
# ----------------------------
# Inject env + build script, then run it
# ----------------------------
write_gha_env "${RUN_MP}/root/gha_env.sh"
sudo cp ".docker/build-freebsd.sh" "${RUN_MP}/root/build.sh"
sudo chmod +x "${RUN_MP}/root/build.sh"
# Run inside the jail with env sourced
log "Executing build inside jail…"
sudo jexec "${JAIL}" /bin/sh -lc '. /root/gha_env.sh; exec /root/build.sh'
log "Build finished successfully."
# Cleanup happens automatically via trap on EXIT.

67
.github/scripts/freebsd_setup_base.sh vendored Executable file
View File

@ -0,0 +1,67 @@
#!/bin/sh
set -euo pipefail
# ===== Config =====
FREEBSD_REL="${FREEBSD_REL:-14.3-RELEASE}"
FREEBSD_VER_TAG="${FREEBSD_VER_TAG:-14_3}"
SNAP_LABEL="${SNAP_LABEL:-base-$(date +%Y%m%d)}"
ZPOOL="${ZPOOL:-zroot}"
JAILS_DS="${JAILS_DS:-${ZPOOL}/z/jails}"
JAILS_MP="${JAILS_MP:-/z/jails}"
PKG_REPO_URL='pkg+http://pkg.FreeBSD.org/${ABI}/latest'
PKGS="boost-all brotli ccache cmake date double-conversion flac fuse fusefs-libs fusefs-libs3 glog gnulibiberty libarchive liblz4 mold ninja nlohmann-json openssl parallel-hashmap pkgconf range-v3 utf8cpp xxhash zstd ca_root_nss git"
# ===== Layout =====
sudo zfs list -H "${ZPOOL}" >/dev/null 2>&1 || { echo "No pool ${ZPOOL}"; exit 1; }
sudo zfs create -o mountpoint=/z "${ZPOOL}/z" >/dev/null 2>&1 || true
sudo zfs create -o mountpoint=/z/jails "${JAILS_DS}" >/dev/null 2>&1 || true
BASE_DS="${JAILS_DS}/${FREEBSD_VER_TAG}"
BASE_MP="${JAILS_MP}/${FREEBSD_VER_TAG}"
if ! sudo zfs list -H "${BASE_DS}" >/dev/null 2>&1; then
sudo zfs create -o mountpoint="${BASE_MP}" "${BASE_DS}"
fi
# ===== Fetch & extract base.txz =====
if [ ! -f "${BASE_MP}/.seeded" ]; then
TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT
fetch -o "${TMPDIR}/base.txz" "https://download.freebsd.org/releases/amd64/${FREEBSD_REL}/base.txz"
sudo tar -xpf "${TMPDIR}/base.txz" -C "${BASE_MP}"
sudo touch "${BASE_MP}/.seeded"
fi
# ===== Provide networking config to the chroot =====
sudo mkdir -p "${BASE_MP}/etc"
# copy DNS + name service config from host so pkg can resolve names
sudo cp /etc/resolv.conf "${BASE_MP}/etc/resolv.conf"
[ -f /etc/hosts ] && sudo cp /etc/hosts "${BASE_MP}/etc/hosts"
[ -f /etc/nsswitch.conf ] && sudo cp /etc/nsswitch.conf "${BASE_MP}/etc/nsswitch.conf"
# ===== Preinstall deps inside the tree =====
sudo mount -t devfs devfs "${BASE_MP}/dev"
sudo mount -t fdescfs fdesc "${BASE_MP}/dev/fd"
sudo mount -t procfs proc "${BASE_MP}/proc"
sudo chroot "${BASE_MP}" /bin/sh -lc "
set -e
env ASSUME_ALWAYS_YES=yes pkg bootstrap -f
mkdir -p /usr/local/etc/pkg/repos
echo 'FreeBSD: { url: \"${PKG_REPO_URL}\" }' > /usr/local/etc/pkg/repos/FreeBSD.conf
pkg update
pkg install -y ${PKGS}
mkdir -p /root/.ccache
"
sudo umount -f "${BASE_MP}/proc" || true
sudo umount -f "${BASE_MP}/dev/fd" || true
sudo umount -f "${BASE_MP}/dev" || true
# ===== Snapshot =====
sudo zfs snapshot "${BASE_DS}@${SNAP_LABEL}"
echo "Snapshot created: ${BASE_DS}@${SNAP_LABEL}"

View File

@ -490,3 +490,34 @@ jobs:
rm -rf dwarfs-*/
rm -f dwarfs-source-*.tar.zst
rm -f dwarfs
################################################################################
freebsd:
needs: package-source
runs-on:
- self-hosted
- freebsd
- ${{ matrix.arch }}
strategy:
matrix:
arch:
- amd64
build_mode:
- Release
- Debug
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: 'false'
fetch-depth: '0'
ref: ${{ github.ref }}
- name: Run Build
run: |
BUILD_MODE=${{ matrix.build_mode }} \
sh .github/scripts/freebsd_jail_build.sh