diff --git a/.github/workflows/buildenv-diff.yml b/.github/workflows/buildenv-diff.yml new file mode 100644 index 0000000..9c3496e --- /dev/null +++ b/.github/workflows/buildenv-diff.yml @@ -0,0 +1,71 @@ +name: Buildenv Package Diff +on: + # When a PR bumps the kernel-buildenv digest pin (normally dependabot), show + # what actually changed between the two images: each image carries its own + # package manifest at /usr/share/buildenv/packages.tsv, so the diff is just + # two pulls and a compare, posted to the check summary for the reviewer. + pull_request: + paths: + - "Dockerfile" +permissions: + contents: read + packages: read +jobs: + diff: + name: package diff + runs-on: ubuntu-latest + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - name: checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v4 + - name: docker login ghcr.io + uses: Wandalen/wretry.action@e68c23e6309f2871ca8ae4763e7629b9c258e1ea # v3.8.0 + with: + action: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + with: | + registry: ghcr.io + username: "${{github.actor}}" + password: "${{secrets.GITHUB_TOKEN}}" + - name: diff package manifests + run: | + extract_ref() { + grep -oE 'ghcr\.io/edera-dev/kernel-buildenv[^[:space:]]*@sha256:[0-9a-f]+' "$1" | head -1 || true + } + git fetch --depth 1 origin "${{ github.base_ref }}" + git show FETCH_HEAD:Dockerfile > Dockerfile.base 2>/dev/null || : > Dockerfile.base + OLD_REF="$(extract_ref Dockerfile.base)" + NEW_REF="$(extract_ref Dockerfile)" + if [ -z "${NEW_REF}" ] || [ "${OLD_REF}" = "${NEW_REF}" ]; then + echo "kernel-buildenv pin unchanged; nothing to diff" + exit 0 + fi + MANIFEST=/usr/share/buildenv/packages.tsv + docker run --rm "${NEW_REF}" cat "${MANIFEST}" > packages-new.tsv + { + echo "## kernel-buildenv package changes" + echo "" + } >> "${GITHUB_STEP_SUMMARY}" + if [ -z "${OLD_REF}" ]; then + { + echo "New pin \`${NEW_REF}\` (no previous pin to diff against);" + echo "$(wc -l < packages-new.tsv) packages in the image." + } >> "${GITHUB_STEP_SUMMARY}" + exit 0 + fi + docker run --rm "${OLD_REF}" cat "${MANIFEST}" > packages-old.tsv + { + echo "\`${OLD_REF}\`" + echo "→ \`${NEW_REF}\`" + echo "" + if diff -u packages-old.tsv packages-new.tsv > packages.diff; then + echo "No package version changes (image metadata/base layer only)." + else + echo '```diff' + # Skip the +++/--- header noise; keep only the package changes. + grep -E '^[+-][^+-]' packages.diff + echo '```' + fi + } >> "${GITHUB_STEP_SUMMARY}" diff --git a/.github/workflows/buildenv.yml b/.github/workflows/buildenv.yml new file mode 100644 index 0000000..720093a --- /dev/null +++ b/.github/workflows/buildenv.yml @@ -0,0 +1,124 @@ +name: Build Environment Image +on: + # Rebuild on a schedule so toolchain updates land as deliberate, dated image + # releases (which dependabot then proposes as digest bumps in the kernel + # Dockerfile) instead of drifting silently into every kernel build. The + # cadence balances cache stability (each new image is a cold sccache build) + # against sitting too long on Debian toolchain fixes. + schedule: + - cron: "0 0 1,15 * *" + workflow_dispatch: + push: + branches: + - main + paths: + - "Dockerfile.buildenv" + - ".github/workflows/buildenv.yml" +permissions: + contents: read + packages: write + id-token: write +concurrency: + group: "buildenv-publish" +env: + IMAGE: ghcr.io/edera-dev/kernel-buildenv +jobs: + build: + name: "build ${{ matrix.arch }}" + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + platform: linux/amd64 + runner: ubuntu-latest + - arch: arm64 + platform: linux/arm64 + runner: ubuntu-24.04-arm + runs-on: "${{ matrix.runner }}" + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - name: checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v4 + - name: docker setup buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + - name: docker login ghcr.io + uses: Wandalen/wretry.action@e68c23e6309f2871ca8ae4763e7629b9c258e1ea # v3.8.0 + with: + action: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + with: | + registry: ghcr.io + username: "${{github.actor}}" + password: "${{secrets.GITHUB_TOKEN}}" + - name: build and push by digest + run: | + docker buildx build \ + -f Dockerfile.buildenv \ + --platform "${{ matrix.platform }}" \ + --metadata-file metadata.json \ + --output "type=image,name=${IMAGE},push-by-digest=true,name-canonical=true,push=true" \ + . + jq -r '."containerimage.digest"' metadata.json > "digest-${{ matrix.arch }}" + - name: extract package manifest + run: | + docker run --rm "${IMAGE}@$(cat digest-${{ matrix.arch }})" \ + cat /usr/share/buildenv/packages.tsv > "packages-${{ matrix.arch }}.tsv" + - name: upload digest + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: "buildenv-digest-${{ matrix.arch }}" + path: "digest-${{ matrix.arch }}" + if-no-files-found: error + compression-level: 0 + retention-days: 1 + - name: upload package manifest + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: "buildenv-packages-${{ matrix.arch }}" + path: "packages-${{ matrix.arch }}.tsv" + if-no-files-found: error + compression-level: 0 + merge: + name: merge and publish + needs: build + runs-on: ubuntu-latest + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + - name: install cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + - name: docker setup buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + - name: docker login ghcr.io + uses: Wandalen/wretry.action@e68c23e6309f2871ca8ae4763e7629b9c258e1ea # v3.8.0 + with: + action: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + with: | + registry: ghcr.io + username: "${{github.actor}}" + password: "${{secrets.GITHUB_TOKEN}}" + - name: download digests + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: "buildenv-digest-*" + merge-multiple: true + - name: assemble and tag manifest list + run: | + TAG="$(date -u +%Y%m%d)" + docker buildx imagetools create \ + -t "${IMAGE}:${TAG}" \ + -t "${IMAGE}:latest" \ + "${IMAGE}@$(cat digest-amd64)" \ + "${IMAGE}@$(cat digest-arm64)" + DIGEST="$(docker buildx imagetools inspect "${IMAGE}:${TAG}" --format '{{.Manifest.Digest}}')" + cosign sign --yes "${IMAGE}@${DIGEST}" + { + echo "published \`${IMAGE}:${TAG}\`" + echo "" + echo "pin as: \`${IMAGE}:${TAG}@${DIGEST}\`" + } >> "${GITHUB_STEP_SUMMARY}" diff --git a/Dockerfile.buildenv b/Dockerfile.buildenv new file mode 100644 index 0000000..fda9588 --- /dev/null +++ b/Dockerfile.buildenv @@ -0,0 +1,61 @@ +# Kernel build environment image, published as ghcr.io/edera-dev/kernel-buildenv. +# +# This exists so the toolchain is a deliberate, reviewed input to kernel builds +# rather than whatever the Debian archive serves on build day: sccache keys +# cache entries on the compiler binary's content hash, so an unplanned gcc +# update silently turns a build into a 100% cache miss. The image is rebuilt on +# a schedule (see .github/workflows/buildenv.yml); the kernel Dockerfile pins +# it by digest and dependabot proposes bumps as reviewable PRs. +FROM debian:bookworm@sha256:ed4fcc40bb1162b6d2d32e7bec15044d13963779abbe63f67f1cd62b06220519 + +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get install -y \ + build-essential squashfs-tools python3-yaml \ + patch diffutils sed mawk findutils zstd \ + python3 python3-packaging curl rsync cpio gpg grep \ + flex bison pahole libssl-dev libelf-dev bc kmod && \ + rm -rf /var/lib/apt/lists/* +ARG TARGETARCH +RUN if [ "${TARGETARCH}" = "amd64" ]; then \ + apt-get update && apt-get install -y linux-headers-amd64 g++-aarch64-linux-gnu gcc-aarch64-linux-gnu && rm -rf /var/lib/apt/lists/*; fi +RUN if [ "${TARGETARCH}" = "arm64" ]; then \ + apt-get update && apt-get install -y linux-headers-arm64 g++-x86-64-linux-gnu gcc-x86-64-linux-gnu && rm -rf /var/lib/apt/lists/*; fi + +ARG SCCACHE_VERSION=0.16.0 +ARG SCCACHE_SHA256_AMD64=aec995a83ad3dff3d14b6314e08858b7b73d35ca85a5bcf3d3a9ec07dee35588 +ARG SCCACHE_SHA256_ARM64=f73a5c39f96bb6ebb89cc7915cf182260d4cbf30765322c5e793d0fe8bd80784 +# The wrappers must reference the real compiler by absolute path: /usr/lib/sccache +# is first in PATH, so a bare compiler name would resolve back to the wrapper +# itself and recurse. +RUN case "${TARGETARCH}" in \ + amd64) SCCACHE_ARCH=x86_64 SCCACHE_SHA256="${SCCACHE_SHA256_AMD64}" ;; \ + arm64) SCCACHE_ARCH=aarch64 SCCACHE_SHA256="${SCCACHE_SHA256_ARM64}" ;; \ + *) echo "unsupported TARGETARCH ${TARGETARCH}" >&2; exit 1 ;; \ + esac && \ + SCCACHE_DIST="sccache-v${SCCACHE_VERSION}-${SCCACHE_ARCH}-unknown-linux-musl" && \ + curl -Lf -o /tmp/sccache.tar.gz "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/${SCCACHE_DIST}.tar.gz" && \ + echo "${SCCACHE_SHA256} /tmp/sccache.tar.gz" | sha256sum -c - && \ + tar -xz -C /tmp -f /tmp/sccache.tar.gz && \ + install -m 0755 "/tmp/${SCCACHE_DIST}/sccache" /usr/local/bin/sccache && \ + rm -rf /tmp/sccache.tar.gz "/tmp/${SCCACHE_DIST}" && \ + mkdir -p /usr/lib/sccache && \ + for compiler in cc c++ gcc g++ \ + x86_64-linux-gnu-gcc x86_64-linux-gnu-g++ \ + aarch64-linux-gnu-gcc aarch64-linux-gnu-g++; do \ + [ -x "/usr/bin/${compiler}" ] || continue; \ + printf '#!/bin/sh\nexec /usr/local/bin/sccache /usr/bin/%s "$@"\n' "${compiler}" >"/usr/lib/sccache/${compiler}"; \ + chmod 0755 "/usr/lib/sccache/${compiler}"; \ + done +ENV PATH="/usr/lib/sccache:${PATH}" + +RUN useradd -ms /bin/sh build + +# Self-describing package manifest: lets any two image digests be diffed +# without external records (docker run cat /usr/share/buildenv/packages.tsv). +# The buildenv-diff workflow uses this to summarize dependabot digest bumps. +RUN mkdir -p /usr/share/buildenv && \ + { dpkg-query -W -f '${Package}\t${Version}\t${Architecture}\n' | sort; \ + printf 'sccache\t%s\tgithub-release\n' "${SCCACHE_VERSION}"; } \ + >/usr/share/buildenv/packages.tsv + +LABEL org.opencontainers.image.source="https://github.com/edera-dev/linux-kernel-oci" +LABEL org.opencontainers.image.description="Edera kernel build environment (toolchain + sccache)"