#!/usr/bin/env bash # yams/scripts/local-ci/package-lane.sh # # One-command local lane: build the Linux packages in a cached Docker builder, # then install + smoke-test them in clean systemd containers. This is the local # equivalent of the release CI package job. # # build : run scripts/build-deb.sh inside docker/local-ci/debian-package.Dockerfile # -> produces yams-*.deb and yams-*.rpm into the build dir # validate: run scripts/local-ci/package-validate.sh against those artifacts # # Requirements on host: # - Docker on PATH, on a Linux host (or Docker Desktop's Linux VM). # - The validate phase needs privileged systemd-in-docker (cgroups). On macOS # Docker Desktop the build phase works; the validate phase may not boot # systemd -- use --build-only there and run validate on a Linux runner. # # Examples: # bash scripts/local-ci/package-lane.sh # build + validate (deb + rpm) # bash scripts/local-ci/package-lane.sh --only deb # deb lane only # bash scripts/local-ci/package-lane.sh --only all # deb + rpm + Arch lanes # bash scripts/local-ci/package-lane.sh --build-only # just produce packages # bash scripts/local-ci/package-lane.sh --validate-only # reuse existing packages # bash scripts/local-ci/package-lane.sh --no-cache # rebuild the builder image set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" IMAGE="yams/ci-debian-package:trixie" DOCKERFILE="${REPO_ROOT}/docker/local-ci/debian-package.Dockerfile" ARCH_IMAGE="yams/ci-arch-package:latest" ARCH_DOCKERFILE="${REPO_ROOT}/docker/local-ci/arch-package.Dockerfile" BUILD_DIR="build/release-pkg" # relative to repo root; kept separate from dev build/ ONLY="linux" DO_BUILD=1 DO_VALIDATE=1 NO_CACHE=0 VERSION="" log() { printf '\033[1;34m[lane]\033[0m %s\n' "$*"; } fail() { printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2; } usage() { sed -n '2,30p' "${BASH_SOURCE[0]}"; } # ── Arch build lane ─────────────────────────────────────────────────────── build_arch_lane() { local arch_platform="${ARCH_DOCKER_PLATFORM:-linux/amd64}" log "building cached Arch builder image ${ARCH_IMAGE} (${arch_platform})" arch_build_args=(-f "${ARCH_DOCKERFILE}" -t "${ARCH_IMAGE}") [ "${NO_CACHE}" -eq 1 ] && arch_build_args+=(--no-cache) arch_build_args+=("${REPO_ROOT}") docker build --platform="${arch_platform}" "${arch_build_args[@]}" log "building Arch packages (version=${VERSION}) into ${BUILD_DIR}" mkdir -p "${ABS_BUILD_DIR}" docker run --rm \ --platform="${arch_platform}" \ -v "${REPO_ROOT}:/workspace/yams" \ -v yams-conan-cache-arch:/root/.conan2 \ -w /workspace \ -e RELEASE_CHANNEL=nightly \ -e GIT_REF="" \ -e BUILD_DIR="${BUILD_DIR}" \ -e MESON_EXTRA_ARGS="${MESON_EXTRA_ARGS:-}" \ -e CONAN_EXTRA_OPTS="${CONAN_EXTRA_OPTS:-}" \ -e HOST_UID="$(id -u)" \ -e HOST_GID="$(id -g)" \ "${ARCH_IMAGE}" \ bash -lc "set -euo pipefail; /workspace/yams/scripts/build-arch-pkg.sh build; rc=\$?; chown -R \"\${HOST_UID}:\${HOST_GID}\" \"/workspace/yams/${BUILD_DIR}\" 2>/dev/null || true; exit \$rc" log "built Arch artifacts:" find "${ABS_BUILD_DIR}" -maxdepth 4 -type f \( -name 'yams-*.pkg.tar.zst' -o -name 'yams.db' -o -name 'yams.files' \) -print } # ── dispatch ────────────────────────────────────────────────────────────── while [ "$#" -gt 0 ]; do case "$1" in --only) ONLY="$2" shift 2 ;; --build-dir) BUILD_DIR="$2" shift 2 ;; --version) VERSION="$2" shift 2 ;; --build-only) DO_VALIDATE=0 shift ;; --validate-only) DO_BUILD=0 shift ;; --no-cache) NO_CACHE=1 shift ;; -h | --help) usage exit 0 ;; *) fail "unknown argument: $1" usage exit 2 ;; esac done command -v docker >/dev/null 2>&1 || { fail "docker not found on PATH" exit 2 } # Resolve a version for the build (build-deb.sh packages only for tag/channel # builds, so the build phase passes RELEASE_CHANNEL=nightly to force packaging). if [ -z "${VERSION}" ]; then VERSION="$(git -C "${REPO_ROOT}" describe --tags --abbrev=7 2>/dev/null || echo 0.0.0-dev)" fi ABS_BUILD_DIR="${BUILD_DIR}" case "${ABS_BUILD_DIR}" in /*) : ;; *) ABS_BUILD_DIR="${REPO_ROOT}/${BUILD_DIR}" ;; esac if [ "${DO_BUILD}" -eq 1 ]; then case "${ONLY}" in arch) build_arch_lane ;; *) log "building cached builder image ${IMAGE}" build_args=(-f "${DOCKERFILE}" -t "${IMAGE}") [ "${NO_CACHE}" -eq 1 ] && build_args+=(--no-cache) build_args+=("${REPO_ROOT}") docker build "${build_args[@]}" log "building packages (version=${VERSION}) into ${BUILD_DIR}" mkdir -p "${ABS_BUILD_DIR}" # Run the real build inside the container; chown artifacts back to the host # user so the build dir isn't left root-owned on the bind mount. # Persist the Conan cache across runs so dependencies aren't rebuilt from # source every time (the container itself is --rm). BASE_VERSION is left unset # so build-deb.sh derives a digit-leading version (dpkg requires that). docker run --rm \ -v "${REPO_ROOT}:/workspace/yams" \ -v yams-conan-cache:/root/.conan2 \ -w /workspace \ -e RELEASE_CHANNEL=nightly \ -e GIT_REF="" \ -e BUILD_DIR="${BUILD_DIR}" \ -e MESON_EXTRA_ARGS="${MESON_EXTRA_ARGS:-}" \ -e CONAN_EXTRA_OPTS="${CONAN_EXTRA_OPTS:-}" \ -e HOST_UID="$(id -u)" \ -e HOST_GID="$(id -g)" \ "${IMAGE}" \ bash -lc '/workspace/yams/scripts/build-deb.sh; rc=$?; chown -R "${HOST_UID}:${HOST_GID}" "/workspace/yams/'"${BUILD_DIR}"'" 2>/dev/null || true; exit $rc' log "built artifacts:" find "${ABS_BUILD_DIR}" -maxdepth 1 -type f \( -name 'yams-*.deb' -o -name 'yams-*.rpm' \) -print if [ "${ONLY}" = "all" ]; then build_arch_lane fi ;; esac fi if [ "${DO_VALIDATE}" -eq 1 ]; then log "validating packages from ${BUILD_DIR}" bash "${SCRIPT_DIR}/package-validate.sh" --build-dir "${ABS_BUILD_DIR}" --only "${ONLY}" fi log "done"