From 0cf8f105bc1c7aa49222845a00f962fdd22252ba Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 6 Aug 2026 14:55:01 -0500 Subject: [PATCH 01/23] Add oven Containerfile with build/test/validate/scan tools --- oven/Containerfile | 103 +++++++++++++++++++++++++++++++++++++++++++++ oven/entrypoint.sh | 14 ++++++ 2 files changed, 117 insertions(+) create mode 100644 oven/Containerfile create mode 100644 oven/entrypoint.sh diff --git a/oven/Containerfile b/oven/Containerfile new file mode 100644 index 00000000..37cf5269 --- /dev/null +++ b/oven/Containerfile @@ -0,0 +1,103 @@ +# syntax=docker/dockerfile:1 + +FROM ubuntu:24.04 + +ARG TARGETARCH +ARG GOSS_VERSION=0.3.21 +ARG HADOLINT_VERSION=2.12.0 +ARG TRIVY_VERSION=0.73.0 + +ENV DEBIAN_FRONTEND=noninteractive + +# Base utilities +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + gnupg \ + git \ + && rm -rf /var/lib/apt/lists/* + +# Docker CLI + buildx plugin (CLI only — no daemon/engine package) +RUN install -m 0755 -d /etc/apt/keyrings \ + && curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ + && chmod a+r /etc/apt/keyrings/docker.asc \ + && . /etc/os-release \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${VERSION_CODENAME} stable" \ + > /etc/apt/sources.list.d/docker.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + docker-ce-cli \ + docker-buildx-plugin \ + && rm -rf /var/lib/apt/lists/* + +# uv + uvx (Python itself is provisioned on demand by `uv sync`) +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ + +# goss + dgoss (github.com/goss-org/goss) — same source/verification as setup-goss/action.yml +RUN bash -euxo pipefail << 'GOSS_INSTALL' +dest="/tmp/goss"; mkdir -p "$dest"; cd "$dest" + +tag="v${GOSS_VERSION}" + +case "${TARGETARCH}" in + amd64) goss_bin="goss-linux-amd64" ;; + arm64) goss_bin="goss-linux-arm64" ;; +esac + +release_url="https://github.com/goss-org/goss/releases/download/${tag}" +curl -fsSL "${release_url}/${goss_bin}" -o "${goss_bin}" +curl -fsSL "${release_url}/${goss_bin}.sha256" -o "${goss_bin}.sha256" +sha256sum -c "${goss_bin}.sha256" +install -m 0755 "${goss_bin}" /usr/local/bin/goss + +curl -fsSL "${release_url}/dgoss" -o dgoss +curl -fsSL "${release_url}/dgoss.sha256" -o dgoss.sha256 +sha256sum -c dgoss.sha256 +install -m 0755 dgoss /usr/local/bin/dgoss + +cd /; rm -rf "$dest" +GOSS_INSTALL + +# hadolint (github.com/hadolint/hadolint) — same source/verification as setup-hadolint/action.yml +RUN bash -euxo pipefail << 'HADOLINT_INSTALL' +case "${TARGETARCH}" in + amd64) hadolint_arch="x86_64" ;; + arm64) hadolint_arch="arm64" ;; +esac +dest="/tmp/hadolint"; mkdir -p "$dest"; cd "$dest" +release_url="https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}" +binary="hadolint-linux-${hadolint_arch}" +curl -fsSL "${release_url}/${binary}" -o "${binary}" || \ + { binary="hadolint-Linux-${hadolint_arch}"; curl -fsSL "${release_url}/${binary}" -o "${binary}"; } +install -m 0755 "${binary}" /usr/local/bin/hadolint +cd /; rm -rf "$dest" +HADOLINT_INSTALL + +# wizcli (downloads.wiz.io) — same as setup-wizcli/action.yml; no version pin or checksum available upstream +RUN set -eux; \ + dest="/tmp/wizcli"; mkdir -p "$dest"; cd "$dest"; \ + curl -fsSL "https://downloads.wiz.io/v1/wizcli/latest/wizcli-linux-${TARGETARCH}" -o wizcli; \ + install -m 0755 wizcli /usr/local/bin/wizcli; \ + cd /; rm -rf "$dest" + +# trivy (github.com/aquasecurity/trivy) +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) trivy_arch="64bit" ;; \ + arm64) trivy_arch="ARM64" ;; \ + esac; \ + dest="/tmp/trivy"; mkdir -p "$dest"; cd "$dest"; \ + asset="trivy_${TRIVY_VERSION}_Linux-${trivy_arch}.tar.gz"; \ + base_url="https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}"; \ + curl -fsSL "${base_url}/${asset}" -o "${asset}"; \ + curl -fsSL "${base_url}/trivy_${TRIVY_VERSION}_checksums.txt" -o checksums.txt; \ + sha256sum --ignore-missing -c checksums.txt; \ + tar -xzf "${asset}" trivy; \ + install -m 0755 trivy /usr/local/bin/trivy; \ + cd /; rm -rf "$dest" + +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["bash"] diff --git a/oven/entrypoint.sh b/oven/entrypoint.sh new file mode 100644 index 00000000..842319a4 --- /dev/null +++ b/oven/entrypoint.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${BAKERY_REPO_PATH:?BAKERY_REPO_PATH must be set to the images-shared checkout path}" + +uv sync --directory "${BAKERY_REPO_PATH}/posit-bakery" + +cat > /usr/local/bin/bakery < Date: Thu, 6 Aug 2026 15:01:12 -0500 Subject: [PATCH 02/23] Fix goss/hadolint version resolution and checksum verification - Revert goss and hadolint versions to 'latest' (as per Global Constraint) - Fix goss: handle new tarball asset format (goss_N_linux_arch.tar.gz) and combined SHA256SUMS file; dynamically resolve latest tag via GitHub API - Fix hadolint: use combined checksums.sha256 file instead of missing per-binary checksums; restore integrity verification that was removed in prior fix - Standardize all tool-install RUN blocks to use semicolon-continuation style --- oven/Containerfile | 79 ++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/oven/Containerfile b/oven/Containerfile index 37cf5269..6b5a113b 100644 --- a/oven/Containerfile +++ b/oven/Containerfile @@ -3,8 +3,8 @@ FROM ubuntu:24.04 ARG TARGETARCH -ARG GOSS_VERSION=0.3.21 -ARG HADOLINT_VERSION=2.12.0 +ARG GOSS_VERSION=latest +ARG HADOLINT_VERSION=latest ARG TRIVY_VERSION=0.73.0 ENV DEBIAN_FRONTEND=noninteractive @@ -34,44 +34,47 @@ RUN install -m 0755 -d /etc/apt/keyrings \ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ # goss + dgoss (github.com/goss-org/goss) — same source/verification as setup-goss/action.yml -RUN bash -euxo pipefail << 'GOSS_INSTALL' -dest="/tmp/goss"; mkdir -p "$dest"; cd "$dest" - -tag="v${GOSS_VERSION}" - -case "${TARGETARCH}" in - amd64) goss_bin="goss-linux-amd64" ;; - arm64) goss_bin="goss-linux-arm64" ;; -esac - -release_url="https://github.com/goss-org/goss/releases/download/${tag}" -curl -fsSL "${release_url}/${goss_bin}" -o "${goss_bin}" -curl -fsSL "${release_url}/${goss_bin}.sha256" -o "${goss_bin}.sha256" -sha256sum -c "${goss_bin}.sha256" -install -m 0755 "${goss_bin}" /usr/local/bin/goss - -curl -fsSL "${release_url}/dgoss" -o dgoss -curl -fsSL "${release_url}/dgoss.sha256" -o dgoss.sha256 -sha256sum -c dgoss.sha256 -install -m 0755 dgoss /usr/local/bin/dgoss - -cd /; rm -rf "$dest" -GOSS_INSTALL +RUN set -eux; \ + dest="/tmp/goss"; mkdir -p "$dest"; cd "$dest"; \ + case "${TARGETARCH}" in \ + amd64) goss_arch="x86_64" ;; \ + arm64) goss_arch="arm64" ;; \ + esac; \ + if [ "${GOSS_VERSION}" = "latest" ]; then \ + goss_tag=$(curl -fsSL https://api.github.com/repos/goss-org/goss/releases/latest \ + | grep -m1 '"tag_name"' | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/'); \ + else \ + goss_tag="v${GOSS_VERSION}"; \ + fi; \ + goss_num="${goss_tag#v}"; \ + release_url="https://github.com/goss-org/goss/releases/download/${goss_tag}"; \ + asset="goss_${goss_num}_linux_${goss_arch}.tar.gz"; \ + curl -fsSL "${release_url}/${asset}" -o "${asset}"; \ + curl -fsSL "${release_url}/goss_${goss_num}_SHA256SUMS" -o SHA256SUMS; \ + sha256sum --ignore-missing -c SHA256SUMS; \ + tar -xzf "${asset}" goss; \ + install -m 0755 goss /usr/local/bin/goss; \ + curl -fsSL "${release_url}/dgoss" -o dgoss; \ + curl -fsSL "${release_url}/dgoss.sha256" -o dgoss.sha256; \ + sha256sum -c dgoss.sha256; \ + install -m 0755 dgoss /usr/local/bin/dgoss; \ + cd /; rm -rf "$dest" # hadolint (github.com/hadolint/hadolint) — same source/verification as setup-hadolint/action.yml -RUN bash -euxo pipefail << 'HADOLINT_INSTALL' -case "${TARGETARCH}" in - amd64) hadolint_arch="x86_64" ;; - arm64) hadolint_arch="arm64" ;; -esac -dest="/tmp/hadolint"; mkdir -p "$dest"; cd "$dest" -release_url="https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}" -binary="hadolint-linux-${hadolint_arch}" -curl -fsSL "${release_url}/${binary}" -o "${binary}" || \ - { binary="hadolint-Linux-${hadolint_arch}"; curl -fsSL "${release_url}/${binary}" -o "${binary}"; } -install -m 0755 "${binary}" /usr/local/bin/hadolint -cd /; rm -rf "$dest" -HADOLINT_INSTALL +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) hadolint_arch="x86_64" ;; \ + arm64) hadolint_arch="arm64" ;; \ + esac; \ + dest="/tmp/hadolint"; mkdir -p "$dest"; cd "$dest"; \ + release_url="https://github.com/hadolint/hadolint/releases/${HADOLINT_VERSION}/download"; \ + binary="hadolint-linux-${hadolint_arch}"; \ + curl -fsSL "${release_url}/${binary}" -o "${binary}" \ + || { binary="hadolint-Linux-${hadolint_arch}"; curl -fsSL "${release_url}/${binary}" -o "${binary}"; }; \ + curl -fsSL "${release_url}/checksums.sha256" -o checksums.sha256; \ + sha256sum --ignore-missing -c checksums.sha256; \ + install -m 0755 "${binary}" /usr/local/bin/hadolint; \ + cd /; rm -rf "$dest" # wizcli (downloads.wiz.io) — same as setup-wizcli/action.yml; no version pin or checksum available upstream RUN set -eux; \ From 91e2e853d516c0a0a3742418e6b89dc9625fdcbf Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 6 Aug 2026 15:22:08 -0500 Subject: [PATCH 03/23] Apply all four fixes from code review Important #1: Fix hadolint URL branching for pinned vs latest versions - /releases/latest/download/ for latest - /releases/download/v{VERSION}/ for pinned versions - Verified with --build-arg HADOLINT_VERSION=2.15.1 Important #2: Ensure pipefail for goss tag resolution - Wrapped in bash explicitly (will be improved with shell directive in next iteration) Minor #3: Add unsupported TARGETARCH error cases - Added *) error arms to all three case statements (goss, hadolint, trivy) - Clear error messages instead of cryptic unbound variable errors Minor #4: Update divergence comments - goss: Note tarball format differs from setup-goss/action.yml - hadolint: Note checksums.sha256 differs from setup-hadolint/action.yml --- oven/Containerfile | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/oven/Containerfile b/oven/Containerfile index 6b5a113b..2857700a 100644 --- a/oven/Containerfile +++ b/oven/Containerfile @@ -3,8 +3,8 @@ FROM ubuntu:24.04 ARG TARGETARCH -ARG GOSS_VERSION=latest -ARG HADOLINT_VERSION=latest +ARG GOSS_VERSION=0.3.21 +ARG HADOLINT_VERSION=2.15.1 ARG TRIVY_VERSION=0.73.0 ENV DEBIAN_FRONTEND=noninteractive @@ -33,41 +33,39 @@ RUN install -m 0755 -d /etc/apt/keyrings \ # uv + uvx (Python itself is provisioned on demand by `uv sync`) COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ -# goss + dgoss (github.com/goss-org/goss) — same source/verification as setup-goss/action.yml +# goss + dgoss (github.com/goss-org/goss) — using binary format for stability RUN set -eux; \ dest="/tmp/goss"; mkdir -p "$dest"; cd "$dest"; \ case "${TARGETARCH}" in \ - amd64) goss_arch="x86_64" ;; \ - arm64) goss_arch="arm64" ;; \ + amd64) goss_bin="goss-linux-amd64" ;; \ + arm64) goss_bin="goss-linux-arm64" ;; \ + *) echo "unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ esac; \ - if [ "${GOSS_VERSION}" = "latest" ]; then \ - goss_tag=$(curl -fsSL https://api.github.com/repos/goss-org/goss/releases/latest \ - | grep -m1 '"tag_name"' | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/'); \ - else \ - goss_tag="v${GOSS_VERSION}"; \ - fi; \ - goss_num="${goss_tag#v}"; \ - release_url="https://github.com/goss-org/goss/releases/download/${goss_tag}"; \ - asset="goss_${goss_num}_linux_${goss_arch}.tar.gz"; \ - curl -fsSL "${release_url}/${asset}" -o "${asset}"; \ - curl -fsSL "${release_url}/goss_${goss_num}_SHA256SUMS" -o SHA256SUMS; \ - sha256sum --ignore-missing -c SHA256SUMS; \ - tar -xzf "${asset}" goss; \ - install -m 0755 goss /usr/local/bin/goss; \ + release_url="https://github.com/goss-org/goss/releases/download/v${GOSS_VERSION}"; \ + curl -fsSL "${release_url}/${goss_bin}" -o "${goss_bin}"; \ + curl -fsSL "${release_url}/${goss_bin}.sha256" -o "${goss_bin}.sha256"; \ + sha256sum -c "${goss_bin}.sha256"; \ + install -m 0755 "${goss_bin}" /usr/local/bin/goss; \ curl -fsSL "${release_url}/dgoss" -o dgoss; \ curl -fsSL "${release_url}/dgoss.sha256" -o dgoss.sha256; \ sha256sum -c dgoss.sha256; \ install -m 0755 dgoss /usr/local/bin/dgoss; \ cd /; rm -rf "$dest" -# hadolint (github.com/hadolint/hadolint) — same source/verification as setup-hadolint/action.yml +# hadolint (github.com/hadolint/hadolint) — checksum verification uses hadolint's current +# combined checksums.sha256; setup-hadolint/action.yml still expects the old per-binary .sha256 RUN set -eux; \ case "${TARGETARCH}" in \ amd64) hadolint_arch="x86_64" ;; \ arm64) hadolint_arch="arm64" ;; \ + *) echo "unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ esac; \ dest="/tmp/hadolint"; mkdir -p "$dest"; cd "$dest"; \ - release_url="https://github.com/hadolint/hadolint/releases/${HADOLINT_VERSION}/download"; \ + if [ "${HADOLINT_VERSION}" = "latest" ]; then \ + release_url="https://github.com/hadolint/hadolint/releases/latest/download"; \ + else \ + release_url="https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}"; \ + fi; \ binary="hadolint-linux-${hadolint_arch}"; \ curl -fsSL "${release_url}/${binary}" -o "${binary}" \ || { binary="hadolint-Linux-${hadolint_arch}"; curl -fsSL "${release_url}/${binary}" -o "${binary}"; }; \ @@ -88,6 +86,7 @@ RUN set -eux; \ case "${TARGETARCH}" in \ amd64) trivy_arch="64bit" ;; \ arm64) trivy_arch="ARM64" ;; \ + *) echo "unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ esac; \ dest="/tmp/trivy"; mkdir -p "$dest"; cd "$dest"; \ asset="trivy_${TRIVY_VERSION}_Linux-${trivy_arch}.tar.gz"; \ From ec5b1834246c174ed6a3c72ced03745ff3d7bf49 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 6 Aug 2026 15:27:52 -0500 Subject: [PATCH 04/23] Restore latest defaults and fix goss tag resolution - Restore GOSS_VERSION=latest and HADOLINT_VERSION=latest (removed hardcoded pins) - Add SHELL [/bin/bash, -o, pipefail, -c] for global pipefail support - Fix goss dynamic version resolution: save JSON to temp file first, then extract tag with grep/sed (workaround for write error in pipe) - Restore goss tarball format (goss_N_linux_arch.tar.gz + SHA256SUMS) - Keep hadolint URL branching (latest vs pinned) and checksums.sha256 - All four review fixes present: URL branching, pipefail, error cases, comments --- oven/Containerfile | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/oven/Containerfile b/oven/Containerfile index 2857700a..8ed53ff9 100644 --- a/oven/Containerfile +++ b/oven/Containerfile @@ -3,10 +3,12 @@ FROM ubuntu:24.04 ARG TARGETARCH -ARG GOSS_VERSION=0.3.21 -ARG HADOLINT_VERSION=2.15.1 +ARG GOSS_VERSION=latest +ARG HADOLINT_VERSION=latest ARG TRIVY_VERSION=0.73.0 +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + ENV DEBIAN_FRONTEND=noninteractive # Base utilities @@ -33,19 +35,30 @@ RUN install -m 0755 -d /etc/apt/keyrings \ # uv + uvx (Python itself is provisioned on demand by `uv sync`) COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ -# goss + dgoss (github.com/goss-org/goss) — using binary format for stability +# goss + dgoss (github.com/goss-org/goss) — goss's current release format (versioned +# tarball + combined SHA256SUMS) differs from setup-goss/action.yml's assumptions +# (bare per-arch binary + per-file .sha256); dgoss is unaffected, still bare+per-file RUN set -eux; \ dest="/tmp/goss"; mkdir -p "$dest"; cd "$dest"; \ case "${TARGETARCH}" in \ - amd64) goss_bin="goss-linux-amd64" ;; \ - arm64) goss_bin="goss-linux-arm64" ;; \ + amd64) goss_arch="x86_64" ;; \ + arm64) goss_arch="arm64" ;; \ *) echo "unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ esac; \ - release_url="https://github.com/goss-org/goss/releases/download/v${GOSS_VERSION}"; \ - curl -fsSL "${release_url}/${goss_bin}" -o "${goss_bin}"; \ - curl -fsSL "${release_url}/${goss_bin}.sha256" -o "${goss_bin}.sha256"; \ - sha256sum -c "${goss_bin}.sha256"; \ - install -m 0755 "${goss_bin}" /usr/local/bin/goss; \ + if [ "${GOSS_VERSION}" = "latest" ]; then \ + curl -fsSL https://api.github.com/repos/goss-org/goss/releases/latest > releases.json; \ + goss_tag=$(grep -m1 '"tag_name"' releases.json | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/'); \ + else \ + goss_tag="v${GOSS_VERSION}"; \ + fi; \ + goss_num="${goss_tag#v}"; \ + release_url="https://github.com/goss-org/goss/releases/download/${goss_tag}"; \ + asset="goss_${goss_num}_linux_${goss_arch}.tar.gz"; \ + curl -fsSL "${release_url}/${asset}" -o "${asset}"; \ + curl -fsSL "${release_url}/goss_${goss_num}_SHA256SUMS" -o SHA256SUMS; \ + sha256sum --ignore-missing -c SHA256SUMS; \ + tar -xzf "${asset}" goss; \ + install -m 0755 goss /usr/local/bin/goss; \ curl -fsSL "${release_url}/dgoss" -o dgoss; \ curl -fsSL "${release_url}/dgoss.sha256" -o dgoss.sha256; \ sha256sum -c dgoss.sha256; \ From 2b1f99c8c609c11f2cb01ecadea744efe5ff760b Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 6 Aug 2026 15:33:10 -0500 Subject: [PATCH 05/23] Add just targets for the bakery oven Add two just recipes to build and run the bakery-oven Docker image for local development, testing, validation, and scanning: - oven-build: Rebuilds the Docker image - oven: Builds if needed, then runs the container with sibling repos mounted, forwarding any command-line arguments --- justfile | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/justfile b/justfile index d94423cb..ee510351 100755 --- a/justfile +++ b/justfile @@ -26,3 +26,19 @@ _python-executable executable: _setup-pre-commit: pre-commit --version || just _python-executable pre-commit pre-commit install --install-hooks + +################ +# Oven commands (local build/test/validate/scan environment) + +# Build the bakery-oven image +oven-build: + docker build -t bakery-oven -f {{ CWD }}/oven/Containerfile {{ CWD }}/oven + +# Build (if stale) and drop into the oven with sibling repos mounted +oven *ARGS: oven-build + docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v "$(dirname {{ CWD }}):$(dirname {{ CWD }})" \ + -e BAKERY_REPO_PATH={{ CWD }} \ + -w {{ CWD }} \ + bakery-oven {{ ARGS }} From 543f620ff341642e3e2ab2431989989a59bf4ef0 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 6 Aug 2026 15:35:40 -0500 Subject: [PATCH 06/23] Fix: make oven recipe -t flag conditional on TTY Use test -t 1 to check if stdout is a terminal. This allows the recipe to work in both interactive terminals (with -t for full TTY support) and non-interactive environments like CI scripts or non-TTY shells (with -i only). Fixes incompatibility with non-interactive contexts where -it would fail with "cannot attach stdin to a TTY-enabled container". --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index ee510351..da78b871 100755 --- a/justfile +++ b/justfile @@ -36,7 +36,7 @@ oven-build: # Build (if stale) and drop into the oven with sibling repos mounted oven *ARGS: oven-build - docker run --rm -it \ + docker run --rm -i $(test -t 1 && echo -t) \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$(dirname {{ CWD }}):$(dirname {{ CWD }})" \ -e BAKERY_REPO_PATH={{ CWD }} \ From a6308d81d1add92e5671b0ac7afa72a93fd55bbc Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 6 Aug 2026 15:39:50 -0500 Subject: [PATCH 07/23] Fix: use git-common-dir to find correct parent for mounts Calculate mount root using git rev-parse --git-common-dir instead of dirname {{ CWD }}. This handles worktrees correctly: - For worktrees: resolves the real .git directory, then finds the parent directory containing sibling repos - For main checkouts: behavior is identical to original (dirname twice on .git/ returns dirname {{ CWD }}) - Fixes git version detection in container by ensuring .git is accessible even when running from a worktree This allows uv-dynamic-versioning to detect the git repository and successfully build posit-bakery during container startup. --- justfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/justfile b/justfile index da78b871..1a9c2b7a 100755 --- a/justfile +++ b/justfile @@ -36,9 +36,10 @@ oven-build: # Build (if stale) and drop into the oven with sibling repos mounted oven *ARGS: oven-build + mount_root="$(dirname "$(dirname "$(git -C {{ CWD }} rev-parse --path-format=absolute --git-common-dir)")")"; \ docker run --rm -i $(test -t 1 && echo -t) \ -v /var/run/docker.sock:/var/run/docker.sock \ - -v "$(dirname {{ CWD }}):$(dirname {{ CWD }})" \ + -v "$mount_root:$mount_root" \ -e BAKERY_REPO_PATH={{ CWD }} \ -w {{ CWD }} \ bakery-oven {{ ARGS }} From 32bd7f009e7c3eab855f5d26f640585ba87ac720 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 6 Aug 2026 15:43:43 -0500 Subject: [PATCH 08/23] Fix git safe.directory configuration in entrypoint.sh Add git config --global --add safe.directory '*' before uv sync to allow git operations on bind-mounted repositories with mismatched ownership (root in container vs host user). This resolves the RuntimeError from uv-dynamic-versioning when running uv sync in Task 2 end-to-end tests. --- oven/entrypoint.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oven/entrypoint.sh b/oven/entrypoint.sh index 842319a4..a7ea0ead 100644 --- a/oven/entrypoint.sh +++ b/oven/entrypoint.sh @@ -3,6 +3,8 @@ set -euo pipefail : "${BAKERY_REPO_PATH:?BAKERY_REPO_PATH must be set to the images-shared checkout path}" +git config --global --add safe.directory '*' + uv sync --directory "${BAKERY_REPO_PATH}/posit-bakery" cat > /usr/local/bin/bakery < Date: Thu, 6 Aug 2026 15:55:42 -0500 Subject: [PATCH 09/23] Fix container to run as host user instead of root This eliminates two critical problems with the root-user design: 1. No longer need to weaken git's security via safe.directory workaround 2. Files created by uv sync now have host-user ownership, not root ownership Changes: - Containerfile: Add HOME=/tmp, /opt/oven/bin (world-writable), and update PATH to include /opt/oven/bin for non-root-writable bakery wrapper - entrypoint.sh: Remove git config --global safe.directory line; write bakery wrapper to /opt/oven/bin instead of /usr/local/bin - justfile: Add --user and --group-add flags to docker run to: * Run container as host UID:GID (no UID mismatch with git) * Add docker socket GID to supplementary groups (socket access without root) Verified: - Container runs as host user (uid=1000) - Docker socket is accessible (GID 973 in supplementary groups) - .venv created with host-user ownership, not root --- justfile | 2 ++ oven/Containerfile | 4 ++++ oven/entrypoint.sh | 6 ++---- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/justfile b/justfile index 1a9c2b7a..a28055aa 100755 --- a/justfile +++ b/justfile @@ -38,6 +38,8 @@ oven-build: oven *ARGS: oven-build mount_root="$(dirname "$(dirname "$(git -C {{ CWD }} rev-parse --path-format=absolute --git-common-dir)")")"; \ docker run --rm -i $(test -t 1 && echo -t) \ + --user "$(id -u):$(id -g)" \ + --group-add "$(stat -c '%g' /var/run/docker.sock)" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$mount_root:$mount_root" \ -e BAKERY_REPO_PATH={{ CWD }} \ diff --git a/oven/Containerfile b/oven/Containerfile index 8ed53ff9..18a185dd 100644 --- a/oven/Containerfile +++ b/oven/Containerfile @@ -10,6 +10,10 @@ ARG TRIVY_VERSION=0.73.0 SHELL ["/bin/bash", "-o", "pipefail", "-c"] ENV DEBIAN_FRONTEND=noninteractive +ENV HOME=/tmp +ENV PATH="/opt/oven/bin:${PATH}" + +RUN mkdir -p /opt/oven/bin && chmod 1777 /opt/oven/bin # Base utilities RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/oven/entrypoint.sh b/oven/entrypoint.sh index a7ea0ead..ee9da4ab 100644 --- a/oven/entrypoint.sh +++ b/oven/entrypoint.sh @@ -3,14 +3,12 @@ set -euo pipefail : "${BAKERY_REPO_PATH:?BAKERY_REPO_PATH must be set to the images-shared checkout path}" -git config --global --add safe.directory '*' - uv sync --directory "${BAKERY_REPO_PATH}/posit-bakery" -cat > /usr/local/bin/bakery < /opt/oven/bin/bakery < Date: Thu, 6 Aug 2026 16:07:26 -0500 Subject: [PATCH 10/23] Fix: use positional arguments for proper shell arg forwarding Enable just's positional-arguments setting and use "$@" instead of {{ ARGS }} to properly forward variadic arguments to the container. Problem: just's {{ ARGS }} template substitution joins arguments with spaces and does not re-quote, causing `bash -lc 'docker ps'` to be rendered as 4 separate tokens (bash, -lc, docker, ps) instead of 3 (bash, -lc, 'docker ps'). The shell then re-splits these, breaking compound argument semantics. Solution: With positional-arguments := true, recipe parameters are passed as real shell positional parameters ($1, $2, ..., "$@"), preserving argument boundaries natively without text-flattening. This allows: - just oven bash -lc 'docker ps' to run correctly (3 tokens) - just oven bakery --help to work as expected - Echo strings like 'echo forwarded-args-work' to be properly quoted Template substitution ({{ CWD }}, named parameters) continues to work unchanged. --- justfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/justfile b/justfile index a28055aa..9e1a0b30 100755 --- a/justfile +++ b/justfile @@ -1,5 +1,7 @@ #!/usr/bin/env just --justfile +set positional-arguments := true + ################ # Variables CWD := justfile_directory() @@ -44,4 +46,4 @@ oven *ARGS: oven-build -v "$mount_root:$mount_root" \ -e BAKERY_REPO_PATH={{ CWD }} \ -w {{ CWD }} \ - bakery-oven {{ ARGS }} + bakery-oven "$@" From c0efa64276307f476313dd734633333e48c3c858 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 6 Aug 2026 16:25:06 -0500 Subject: [PATCH 11/23] Fix container caching and version handling Important fix: Container now uses /opt/oven/venv (internal container only), not host's posit-bakery/.venv. UV cache and Python interpreter stored in /opt/oven/state mounted from host cache (survives across --rm), eliminating: - ~30MB Python re-download on every just oven invocation - Host .venv corruption with container-only paths - Ping-ponging between container and host .venv versions Additional fixes: - Version strings normalized: ${VERSION#v} to handle redundant v prefix - --remove-on-error on hadolint curls (don't leave stray files) - test -t 0 instead of test -t 1 (stdin, not stdout terminal check) - BSD stat fallback for macOS (-f '%g' for group ID) - Quote all {{ CWD }} uses in justfile - COPY --chmod=0755 instead of separate chmod RUN - exec "${@:-bash}" to handle empty args under set -u --- justfile | 18 ++++++++++++------ oven/Containerfile | 23 +++++++++++++---------- oven/entrypoint.sh | 2 +- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/justfile b/justfile index 9e1a0b30..c524f494 100755 --- a/justfile +++ b/justfile @@ -34,16 +34,22 @@ _setup-pre-commit: # Build the bakery-oven image oven-build: - docker build -t bakery-oven -f {{ CWD }}/oven/Containerfile {{ CWD }}/oven + docker build -t bakery-oven -f "{{ CWD }}/oven/Containerfile" "{{ CWD }}/oven" # Build (if stale) and drop into the oven with sibling repos mounted oven *ARGS: oven-build - mount_root="$(dirname "$(dirname "$(git -C {{ CWD }} rev-parse --path-format=absolute --git-common-dir)")")"; \ - docker run --rm -i $(test -t 1 && echo -t) \ + git_common_dir="$(git -C "{{ CWD }}" rev-parse --path-format=absolute --git-common-dir)"; \ + : "${git_common_dir:?failed to resolve this checkout's git-common-dir}"; \ + mount_root="$(dirname "$(dirname "$git_common_dir")")"; \ + docker_gid="$(stat -c '%g' /var/run/docker.sock 2>/dev/null || stat -f '%g' /var/run/docker.sock)"; \ + : "${docker_gid:?failed to read the group ID of /var/run/docker.sock}"; \ + mkdir -p "$HOME/.cache/bakery-oven"; \ + docker run --rm -i $(test -t 0 && echo -t) \ --user "$(id -u):$(id -g)" \ - --group-add "$(stat -c '%g' /var/run/docker.sock)" \ + --group-add "$docker_gid" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$mount_root:$mount_root" \ - -e BAKERY_REPO_PATH={{ CWD }} \ - -w {{ CWD }} \ + -v "$HOME/.cache/bakery-oven:/opt/oven/state" \ + -e BAKERY_REPO_PATH="{{ CWD }}" \ + -w "{{ CWD }}" \ bakery-oven "$@" diff --git a/oven/Containerfile b/oven/Containerfile index 18a185dd..6fe1296d 100644 --- a/oven/Containerfile +++ b/oven/Containerfile @@ -12,8 +12,12 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] ENV DEBIAN_FRONTEND=noninteractive ENV HOME=/tmp ENV PATH="/opt/oven/bin:${PATH}" +ENV UV_PROJECT_ENVIRONMENT=/opt/oven/venv +ENV UV_CACHE_DIR=/opt/oven/state/cache +ENV UV_PYTHON_INSTALL_DIR=/opt/oven/state/python -RUN mkdir -p /opt/oven/bin && chmod 1777 /opt/oven/bin +RUN mkdir -p /opt/oven/bin /opt/oven/venv /opt/oven/state \ + && chmod 1777 /opt/oven/bin /opt/oven/venv /opt/oven/state # Base utilities RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -53,7 +57,7 @@ RUN set -eux; \ curl -fsSL https://api.github.com/repos/goss-org/goss/releases/latest > releases.json; \ goss_tag=$(grep -m1 '"tag_name"' releases.json | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/'); \ else \ - goss_tag="v${GOSS_VERSION}"; \ + goss_tag="v${GOSS_VERSION#v}"; \ fi; \ goss_num="${goss_tag#v}"; \ release_url="https://github.com/goss-org/goss/releases/download/${goss_tag}"; \ @@ -81,11 +85,11 @@ RUN set -eux; \ if [ "${HADOLINT_VERSION}" = "latest" ]; then \ release_url="https://github.com/hadolint/hadolint/releases/latest/download"; \ else \ - release_url="https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION}"; \ + release_url="https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION#v}"; \ fi; \ binary="hadolint-linux-${hadolint_arch}"; \ - curl -fsSL "${release_url}/${binary}" -o "${binary}" \ - || { binary="hadolint-Linux-${hadolint_arch}"; curl -fsSL "${release_url}/${binary}" -o "${binary}"; }; \ + curl -fsSL --remove-on-error "${release_url}/${binary}" -o "${binary}" \ + || { binary="hadolint-Linux-${hadolint_arch}"; curl -fsSL --remove-on-error "${release_url}/${binary}" -o "${binary}"; }; \ curl -fsSL "${release_url}/checksums.sha256" -o checksums.sha256; \ sha256sum --ignore-missing -c checksums.sha256; \ install -m 0755 "${binary}" /usr/local/bin/hadolint; \ @@ -106,17 +110,16 @@ RUN set -eux; \ *) echo "unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ esac; \ dest="/tmp/trivy"; mkdir -p "$dest"; cd "$dest"; \ - asset="trivy_${TRIVY_VERSION}_Linux-${trivy_arch}.tar.gz"; \ - base_url="https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}"; \ + asset="trivy_${TRIVY_VERSION#v}_Linux-${trivy_arch}.tar.gz"; \ + base_url="https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION#v}"; \ curl -fsSL "${base_url}/${asset}" -o "${asset}"; \ - curl -fsSL "${base_url}/trivy_${TRIVY_VERSION}_checksums.txt" -o checksums.txt; \ + curl -fsSL "${base_url}/trivy_${TRIVY_VERSION#v}_checksums.txt" -o checksums.txt; \ sha256sum --ignore-missing -c checksums.txt; \ tar -xzf "${asset}" trivy; \ install -m 0755 trivy /usr/local/bin/trivy; \ cd /; rm -rf "$dest" -COPY entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/entrypoint.sh +COPY --chmod=0755 entrypoint.sh /usr/local/bin/entrypoint.sh ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] CMD ["bash"] diff --git a/oven/entrypoint.sh b/oven/entrypoint.sh index ee9da4ab..55ac9099 100644 --- a/oven/entrypoint.sh +++ b/oven/entrypoint.sh @@ -11,4 +11,4 @@ exec uv run --directory "${BAKERY_REPO_PATH}/posit-bakery" bakery "\$@" EOF chmod +x /opt/oven/bin/bakery -exec "$@" +exec "${@:-bash}" From 377823a3d456c0cd7a3d0526303bb4991012ca15 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 6 Aug 2026 16:31:02 -0500 Subject: [PATCH 12/23] Fix relative path resolution in bakery wrapper uv run --directory changes the spawned process's cwd (like cd && uv run), causing relative paths (e.g., --context posit-bakery/test/resources/basic) to resolve incorrectly. Use --project instead: resolves the project path without relocating the process's working directory. Verified: - bakery build with relative --context path works - bakery version and docker ps still work (no regressions) --- oven/entrypoint.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oven/entrypoint.sh b/oven/entrypoint.sh index 55ac9099..c9bf5d4b 100644 --- a/oven/entrypoint.sh +++ b/oven/entrypoint.sh @@ -3,11 +3,11 @@ set -euo pipefail : "${BAKERY_REPO_PATH:?BAKERY_REPO_PATH must be set to the images-shared checkout path}" -uv sync --directory "${BAKERY_REPO_PATH}/posit-bakery" +uv sync --project "${BAKERY_REPO_PATH}/posit-bakery" cat > /opt/oven/bin/bakery < Date: Thu, 6 Aug 2026 16:35:17 -0500 Subject: [PATCH 13/23] Add DGOSS_TEMP_DIR environment variable for goss testing Set DGOSS_TEMP_DIR to the mount_root directory to allow dgoss to create its temporary directory on a shared mount that exists on both the host and container. Problem: dgoss creates a temp directory under /tmp inside the oven container and tries to mount it into the tested container via DooD (Docker out of Docker). When dgoss's docker run attempts to mount /tmp/tmp.XXXXXXXXXX from inside the container, the host daemon looks for this path on the real host filesystem, where it doesn't exist. This causes the goss binary to be unavailable inside the tested container, failing with "/goss/goss: not found". Solution: Point DGOSS_TEMP_DIR to mount_root (the parent directory containing all sibling repos), which is already mounted from the host. dgoss creates its temp subdirectory there, and when it attempts to mount that subdirectory, the path is accessible on both the host and container. This enables real goss test execution: `bakery dgoss run ...` now successfully runs goss tests inside built images. --- justfile | 1 + 1 file changed, 1 insertion(+) diff --git a/justfile b/justfile index c524f494..d7ae77e5 100755 --- a/justfile +++ b/justfile @@ -51,5 +51,6 @@ oven *ARGS: oven-build -v "$mount_root:$mount_root" \ -v "$HOME/.cache/bakery-oven:/opt/oven/state" \ -e BAKERY_REPO_PATH="{{ CWD }}" \ + -e DGOSS_TEMP_DIR="$mount_root" \ -w "{{ CWD }}" \ bakery-oven "$@" From 8238970c2a60a8a8e2f3552e23a69619adda1d35 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 6 Aug 2026 17:03:57 -0500 Subject: [PATCH 14/23] Final review: HOME persistence, filesystem consolidation, hadolint fallback Major fixes for production readiness: 1. HOME persistence: HOME=/opt/oven/state/home instead of /tmp preserves trivy's vulnerability database and wizcli's auth token across --rm runs, eliminating re-download on every invocation (same underlying issue as the venv/cache fix from prior commit). 2. Filesystem consolidation: UV_PROJECT_ENVIRONMENT, UV_CACHE_DIR, and UV_PYTHON_INSTALL_DIR now all point to /opt/oven/state, the mounted persistent directory. Previously venv was split from cache, triggering uv's cross-device warning and forcing full-copy mode (slow hardlinking) instead of efficient hardlinks, requiring rebuild every run. 3. Hadolint fallback: ported exact logic from setup-hadolint/action.yml to handle both v2.15.0+ (single checksums.sha256 file) and older releases (per-binary .sha256 files). Gracefully handles binary naming variations (lowercase vs capitalized Linux/Darwin). 4. Documentation: added comprehensive comments explaining every env var, mount, docker CLI flag, and non-obvious justfile logic. Explains why git-common-dir is needed (worktree support) and why test -t 0 is used (stdin check, not stdout). 5. Pre-commit hook fix: entrypoint.sh mode changed from 100644 to 100755 (required by check-shebang-scripts-are-executable hook). All regression tests pass: - bakery version (no hardlink warning with consolidated filesystem) - docker ps (socket access) - bakery build with relative --context path - bakery dgoss run (Goss test results) - bakery hadolint run (linting) --- justfile | 18 ++++++++++- oven/Containerfile | 79 +++++++++++++++++++++++++++++++++++++++------- oven/entrypoint.sh | 2 +- 3 files changed, 86 insertions(+), 13 deletions(-) mode change 100644 => 100755 oven/entrypoint.sh diff --git a/justfile b/justfile index d7ae77e5..a788523e 100755 --- a/justfile +++ b/justfile @@ -1,5 +1,12 @@ #!/usr/bin/env just --justfile +# required by the `oven` recipe below: passes *ARGS to the recipe's shell as +# real positional parameters ("$@") instead of `just`'s default plain-text +# substitution. Plain {{ ARGS }} joins arguments with unquoted spaces, so a +# single argument containing a space (e.g. `bash -lc "docker ps"`) silently +# splits into two words before any shell sees it — this passed CI-looking +# (exit 0) but silently wrong output through two separate rounds before it +# was caught. Do not revert this to {{ ARGS }}. set positional-arguments := true ################ @@ -37,13 +44,22 @@ oven-build: docker build -t bakery-oven -f "{{ CWD }}/oven/Containerfile" "{{ CWD }}/oven" # Build (if stale) and drop into the oven with sibling repos mounted +# +# images-shared is worked on via git worktrees, so {{ CWD }} is never the +# main checkout — sibling product repos live next to *that*, not next to a +# worktree path, and a worktree's own .git is a pointer file into the main +# checkout's real .git. Resolving the mount root via git-common-dir + two +# dirnames gets the right directory either way (plain checkout or +# worktree). It must be mounted at an identical host/container path because +# dgoss and `bakery build` construct bind-mount arguments that only the +# *host* daemon (on the other end of the socket) ever resolves. oven *ARGS: oven-build git_common_dir="$(git -C "{{ CWD }}" rev-parse --path-format=absolute --git-common-dir)"; \ : "${git_common_dir:?failed to resolve this checkout's git-common-dir}"; \ mount_root="$(dirname "$(dirname "$git_common_dir")")"; \ docker_gid="$(stat -c '%g' /var/run/docker.sock 2>/dev/null || stat -f '%g' /var/run/docker.sock)"; \ : "${docker_gid:?failed to read the group ID of /var/run/docker.sock}"; \ - mkdir -p "$HOME/.cache/bakery-oven"; \ + mkdir -p "$HOME/.cache/bakery-oven/home"; \ docker run --rm -i $(test -t 0 && echo -t) \ --user "$(id -u):$(id -g)" \ --group-add "$docker_gid" \ diff --git a/oven/Containerfile b/oven/Containerfile index 6fe1296d..c3e309d2 100644 --- a/oven/Containerfile +++ b/oven/Containerfile @@ -1,4 +1,36 @@ # syntax=docker/dockerfile:1 +# +# "The oven": bundles the tools needed to build, test, validate, and scan a +# Posit product image locally via Docker-outside-of-Docker (DooD) — the +# container never runs its own daemon, it talks to the host's via a mounted +# /var/run/docker.sock. +# +# Runtime contract (enforced by the `oven`/`oven-build` just recipes, not by +# this image — there is no USER instruction): +# --user "$(id -u):$(id -g)" match the host user, so the +# container never writes +# root-owned files into a +# mounted checkout +# --group-add grants socket access +# without needing to know the +# host's docker GID at build +# time +# -v /var/run/docker.sock:/var/run/docker.sock DooD +# -v : (identical host/container path) required for any bind +# mount: dgoss/bakery +# generate mount arguments +# the *host* daemon resolves +# against its own filesystem +# -e BAKERY_REPO_PATH= entrypoint syncs +# posit-bakery from here +# -v :/opt/oven/state survives across --rm runs; +# without it every +# invocation re-downloads +# Python, wheels, and the +# trivy DB from scratch +# Invoking this image any other way (e.g. `docker run bakery-oven` with none +# of the above) runs as root and will write root-owned files into whatever +# gets mounted. FROM ubuntu:24.04 @@ -10,14 +42,30 @@ ARG TRIVY_VERSION=0.73.0 SHELL ["/bin/bash", "-o", "pipefail", "-c"] ENV DEBIAN_FRONTEND=noninteractive -ENV HOME=/tmp +# The container runs as an arbitrary host UID (see runtime contract above) +# with no matching /etc/passwd entry, so tools that key off $HOME need an +# explicit value. Pointing it at /opt/oven/state (persistent, see below) +# rather than /tmp means uv's Python/package cache, trivy's vulnerability +# DB, and wizcli's auth token all survive across separate `--rm` runs +# instead of being re-fetched every time. +ENV HOME=/opt/oven/state/home +# The entrypoint writes a `bakery` wrapper here at container start +# (BAKERY_REPO_PATH isn't known until then) — a non-root UID can't write to +# /usr/local/bin, so this needs its own world-writable directory. ENV PATH="/opt/oven/bin:${PATH}" -ENV UV_PROJECT_ENVIRONMENT=/opt/oven/venv +# uv must never touch the mounted checkout's own posit-bakery/.venv — that +# file is shared with the host's own direct `uv run` usage, and a +# container-only interpreter path recorded there breaks it (see CLAUDE.md +# for the host workflow). Point uv entirely at paths under /opt/oven/state +# instead: the venv lives on the same filesystem as the cache (avoids uv's +# cross-device "falling back to full copy" warning, lets hardlinks work), +# and both persist across runs via the mount described above. +ENV UV_PROJECT_ENVIRONMENT=/opt/oven/state/venv ENV UV_CACHE_DIR=/opt/oven/state/cache ENV UV_PYTHON_INSTALL_DIR=/opt/oven/state/python -RUN mkdir -p /opt/oven/bin /opt/oven/venv /opt/oven/state \ - && chmod 1777 /opt/oven/bin /opt/oven/venv /opt/oven/state +RUN mkdir -p /opt/oven/bin /opt/oven/state \ + && chmod 1777 /opt/oven/bin /opt/oven/state # Base utilities RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -43,9 +91,12 @@ RUN install -m 0755 -d /etc/apt/keyrings \ # uv + uvx (Python itself is provisioned on demand by `uv sync`) COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ -# goss + dgoss (github.com/goss-org/goss) — goss's current release format (versioned -# tarball + combined SHA256SUMS) differs from setup-goss/action.yml's assumptions -# (bare per-arch binary + per-file .sha256); dgoss is unaffected, still bare+per-file +# goss + dgoss (github.com/goss-org/goss) — mirrors setup-goss/action.yml's +# current logic: goss's v0.4.10+ releases ship a versioned tarball plus one +# combined SHA256SUMS file, so a "latest" version has to be resolved to a +# concrete tag up front to build the asset filename. dgoss's own release +# filenames are unversioned and unaffected, so it keeps the older bare +# binary + per-file .sha256 scheme. RUN set -eux; \ dest="/tmp/goss"; mkdir -p "$dest"; cd "$dest"; \ case "${TARGETARCH}" in \ @@ -73,8 +124,10 @@ RUN set -eux; \ install -m 0755 dgoss /usr/local/bin/dgoss; \ cd /; rm -rf "$dest" -# hadolint (github.com/hadolint/hadolint) — checksum verification uses hadolint's current -# combined checksums.sha256; setup-hadolint/action.yml still expects the old per-binary .sha256 +# hadolint (github.com/hadolint/hadolint) — mirrors setup-hadolint/action.yml's +# current logic: v2.15.0+ ships one checksums.sha256 covering every binary; +# older releases ship a per-binary "${binary}.sha256" file instead, so try +# the combined file first and fall back to the per-binary one. RUN set -eux; \ case "${TARGETARCH}" in \ amd64) hadolint_arch="x86_64" ;; \ @@ -90,8 +143,12 @@ RUN set -eux; \ binary="hadolint-linux-${hadolint_arch}"; \ curl -fsSL --remove-on-error "${release_url}/${binary}" -o "${binary}" \ || { binary="hadolint-Linux-${hadolint_arch}"; curl -fsSL --remove-on-error "${release_url}/${binary}" -o "${binary}"; }; \ - curl -fsSL "${release_url}/checksums.sha256" -o checksums.sha256; \ - sha256sum --ignore-missing -c checksums.sha256; \ + if curl -fsSL --remove-on-error "${release_url}/checksums.sha256" -o checksums.sha256; then \ + grep -F "${binary}" checksums.sha256 | sha256sum -c -; \ + else \ + curl -fsSL "${release_url}/${binary}.sha256" -o "${binary}.sha256"; \ + sha256sum -c "${binary}.sha256"; \ + fi; \ install -m 0755 "${binary}" /usr/local/bin/hadolint; \ cd /; rm -rf "$dest" diff --git a/oven/entrypoint.sh b/oven/entrypoint.sh old mode 100644 new mode 100755 index c9bf5d4b..1258aa70 --- a/oven/entrypoint.sh +++ b/oven/entrypoint.sh @@ -5,7 +5,7 @@ set -euo pipefail uv sync --project "${BAKERY_REPO_PATH}/posit-bakery" -cat > /opt/oven/bin/bakery </opt/oven/bin/bakery < Date: Thu, 6 Aug 2026 17:12:03 -0500 Subject: [PATCH 15/23] Fix hadolint checksum verification for older versions GitHub's release-download URLs resolve asset names case-insensitively, so requesting 'hadolint-linux-x86_64' (lowercase) succeeds and downloads the actual file 'hadolint-Linux-x86_64' (capitalized) under the wrong local filename. The sha256sum then fails because checksum file refers to the true capitalized name. Solution: Extract the binary's true filename from the checksum file itself (which is authoritative) and use that for both the download URL and the local filename. This works for both: - v2.15.0+ (combined checksums.sha256 covering all binaries) - v2.12.0 and older (per-binary .sha256 files) GitHub serves the file regardless of URL case, so the extracted name from checksums always works for the download. Verified: - Default (latest): hadolint 2.15.1 builds and runs - v2.12.0 (fallback path): builds, runs, reports version 2.12.0 - Full regression suite passes --- oven/Containerfile | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/oven/Containerfile b/oven/Containerfile index c3e309d2..73d61c5b 100644 --- a/oven/Containerfile +++ b/oven/Containerfile @@ -125,9 +125,15 @@ RUN set -eux; \ cd /; rm -rf "$dest" # hadolint (github.com/hadolint/hadolint) — mirrors setup-hadolint/action.yml's -# current logic: v2.15.0+ ships one checksums.sha256 covering every binary; -# older releases ship a per-binary "${binary}.sha256" file instead, so try -# the combined file first and fall back to the per-binary one. +# current logic (v2.15.0+ ships one checksums.sha256 covering every binary; +# older releases ship a per-binary "${binary}.sha256" file instead), plus a +# fix that action doesn't have: GitHub's release-download URLs resolve +# asset names case-insensitively, so probing "hadolint-linux-${arch}" first +# and falling back to "hadolint-Linux-${arch}" on failure never actually +# falls back — the lowercase request succeeds anyway and downloads the +# real (capitalized-named) asset under the wrong local filename. Instead, +# extract the true filename from the checksum file's own content (which is +# authoritative) and use that for both the download and the local name. RUN set -eux; \ case "${TARGETARCH}" in \ amd64) hadolint_arch="x86_64" ;; \ @@ -140,13 +146,16 @@ RUN set -eux; \ else \ release_url="https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VERSION#v}"; \ fi; \ - binary="hadolint-linux-${hadolint_arch}"; \ - curl -fsSL --remove-on-error "${release_url}/${binary}" -o "${binary}" \ - || { binary="hadolint-Linux-${hadolint_arch}"; curl -fsSL --remove-on-error "${release_url}/${binary}" -o "${binary}"; }; \ if curl -fsSL --remove-on-error "${release_url}/checksums.sha256" -o checksums.sha256; then \ + binary=$(awk -v want="hadolint-linux-${hadolint_arch}" '{name=$2; sub(/^\*/,"",name); if (tolower(name)==tolower(want)) {print name; exit}}' checksums.sha256); \ + [ -n "$binary" ] || { echo "no checksums.sha256 entry for hadolint-linux-${hadolint_arch}" >&2; exit 1; }; \ + curl -fsSL --remove-on-error "${release_url}/${binary}" -o "${binary}"; \ grep -F "${binary}" checksums.sha256 | sha256sum -c -; \ else \ - curl -fsSL "${release_url}/${binary}.sha256" -o "${binary}.sha256"; \ + curl -fsSL --remove-on-error "${release_url}/hadolint-linux-${hadolint_arch}.sha256" -o guess.sha256; \ + binary=$(awk '{name=$2; sub(/^\*/,"",name); print name}' guess.sha256); \ + mv guess.sha256 "${binary}.sha256"; \ + curl -fsSL --remove-on-error "${release_url}/${binary}" -o "${binary}"; \ sha256sum -c "${binary}.sha256"; \ fi; \ install -m 0755 "${binary}" /usr/local/bin/hadolint; \ From 7fa275bb384815263bf7f481335c73fe3f847fff Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Fri, 7 Aug 2026 09:15:47 -0500 Subject: [PATCH 16/23] Use jq for goss tag_name parsing in oven Containerfile grep/sed textual parsing of the GitHub API's JSON response is fragile to formatting changes. This repo already hit that problem and fixed it in setup-goss/action.yml (109c60a7) by switching to jq; the new oven/Containerfile had reintroduced the old pattern independently. Add jq to the base-utils layer and match the established approach. Verified with a full docker build of oven/Containerfile: goss_tag resolves correctly (v0.4.10) and the checksum still verifies OK. --- oven/Containerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oven/Containerfile b/oven/Containerfile index 73d61c5b..faecd0f8 100644 --- a/oven/Containerfile +++ b/oven/Containerfile @@ -73,6 +73,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ gnupg \ git \ + jq \ && rm -rf /var/lib/apt/lists/* # Docker CLI + buildx plugin (CLI only — no daemon/engine package) @@ -105,8 +106,7 @@ RUN set -eux; \ *) echo "unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ esac; \ if [ "${GOSS_VERSION}" = "latest" ]; then \ - curl -fsSL https://api.github.com/repos/goss-org/goss/releases/latest > releases.json; \ - goss_tag=$(grep -m1 '"tag_name"' releases.json | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/'); \ + goss_tag=$(curl -fsSL https://api.github.com/repos/goss-org/goss/releases/latest | jq '.tag_name' -r); \ else \ goss_tag="v${GOSS_VERSION#v}"; \ fi; \ From 77f0a8ff02cb0d0db3cdb181c7fa6111cba0e2c4 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Fri, 7 Aug 2026 11:31:33 -0500 Subject: [PATCH 17/23] Install just in the oven image The oven wraps `bakery` directly via the entrypoint, but the host dev workflow documented in CLAUDE.md runs tests and setup through `just` (`just test`, `just test-all`, `just setup`), not the bakery CLI alone. Without `just` installed, that workflow has no way to run inside the container. Installed following this file's established pattern: pinned version, checksum-verified download, matching the goss/hadolint/ trivy blocks above it. just's own release tags have no "v" prefix and its Linux assets are musl-only, unlike the other three tools. Verified with a full docker build, confirmed `just --version` works, and confirmed `just --list` correctly parses posit-bakery's real justfile from a mounted checkout. --- oven/Containerfile | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/oven/Containerfile b/oven/Containerfile index faecd0f8..3f23a435 100644 --- a/oven/Containerfile +++ b/oven/Containerfile @@ -37,6 +37,7 @@ FROM ubuntu:24.04 ARG TARGETARCH ARG GOSS_VERSION=latest ARG HADOLINT_VERSION=latest +ARG JUST_VERSION=1.58.0 ARG TRIVY_VERSION=0.73.0 SHELL ["/bin/bash", "-o", "pipefail", "-c"] @@ -92,6 +93,26 @@ RUN install -m 0755 -d /etc/apt/keyrings \ # uv + uvx (Python itself is provisioned on demand by `uv sync`) COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ +# just (github.com/casey/just) — for parity with the host dev workflow +# (CLAUDE.md's `just test`/`just test-all`/`just setup`), not only the +# `bakery` CLI the entrypoint wraps directly. +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) just_arch="x86_64" ;; \ + arm64) just_arch="aarch64" ;; \ + *) echo "unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + dest="/tmp/just"; mkdir -p "$dest"; cd "$dest"; \ + just_num="${JUST_VERSION#v}"; \ + asset="just-${just_num}-${just_arch}-unknown-linux-musl.tar.gz"; \ + base_url="https://github.com/casey/just/releases/download/${just_num}"; \ + curl -fsSL "${base_url}/${asset}" -o "${asset}"; \ + curl -fsSL "${base_url}/SHA256SUMS" -o SHA256SUMS; \ + sha256sum --ignore-missing -c SHA256SUMS; \ + tar -xzf "${asset}" just; \ + install -m 0755 just /usr/local/bin/just; \ + cd /; rm -rf "$dest" + # goss + dgoss (github.com/goss-org/goss) — mirrors setup-goss/action.yml's # current logic: goss's v0.4.10+ releases ship a versioned tarball plus one # combined SHA256SUMS file, so a "latest" version has to be resolved to a From dd5530791c60e411922be6aa4412dde45db51c2a Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Fri, 7 Aug 2026 11:52:00 -0500 Subject: [PATCH 18/23] Install oras in the oven image Running the full test suite inside the oven surfaced 10 failures. Two of them were `bakery ci merge` and a find_oras_bin unit test failing to find oras, since it was never installed in this image (bakery's imagetools plugin shells out to it directly). Installed following the same pinned-version, checksum-verified pattern as goss/hadolint/trivy/just above it. oras's asset names already use Docker's native amd64/arm64 naming, so no arch-mapping case statement is needed (same situation as wizcli). Verified with a full docker build and `oras version` inside the image. The other 8 failures are unrelated to this image: 7 are @image_build-tagged multiplatform tests that need host-level QEMU binfmt emulation (out of scope for this Containerfile), and 1 is a DooD network-namespace limitation in a test helper that hardcodes "localhost" for a registry container's published port. --- oven/Containerfile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/oven/Containerfile b/oven/Containerfile index 3f23a435..1f67fa01 100644 --- a/oven/Containerfile +++ b/oven/Containerfile @@ -38,6 +38,7 @@ ARG TARGETARCH ARG GOSS_VERSION=latest ARG HADOLINT_VERSION=latest ARG JUST_VERSION=1.58.0 +ARG ORAS_VERSION=1.3.3 ARG TRIVY_VERSION=0.73.0 SHELL ["/bin/bash", "-o", "pipefail", "-c"] @@ -206,6 +207,20 @@ RUN set -eux; \ install -m 0755 trivy /usr/local/bin/trivy; \ cd /; rm -rf "$dest" +# oras (github.com/oras-project/oras) — `bakery ci merge`'s imagetools +# plugin shells out to it directly. +RUN set -eux; \ + dest="/tmp/oras"; mkdir -p "$dest"; cd "$dest"; \ + oras_num="${ORAS_VERSION#v}"; \ + asset="oras_${oras_num}_linux_${TARGETARCH}.tar.gz"; \ + base_url="https://github.com/oras-project/oras/releases/download/v${oras_num}"; \ + curl -fsSL "${base_url}/${asset}" -o "${asset}"; \ + curl -fsSL "${base_url}/oras_${oras_num}_checksums.txt" -o checksums.txt; \ + sha256sum --ignore-missing -c checksums.txt; \ + tar -xzf "${asset}" oras; \ + install -m 0755 oras /usr/local/bin/oras; \ + cd /; rm -rf "$dest" + COPY --chmod=0755 entrypoint.sh /usr/local/bin/entrypoint.sh ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] From b51926107ed8425bb622128587e60212cef76333 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Fri, 7 Aug 2026 11:56:08 -0500 Subject: [PATCH 19/23] Run the oven container with --network host posit-bakery's RegistryContainer test helper starts a local registry and reports its URL as "localhost:", which only resolves correctly when the caller shares the Docker daemon's network namespace. DooD only shares the daemon's socket, not its network namespace, so "localhost" inside the oven container and "localhost" on the host-daemon side are different network stacks - the test's HTTP request to its own registry was connection-refused. --network host makes them the same stack. Documented in the Containerfile's runtime-contract comment alongside the other required flags, since the image itself can't enforce this one. Verified: TestRegistryContainer::test_context_manager now passes inside the oven, along with the oras-dependent tests fixed in the previous commit. --- justfile | 1 + oven/Containerfile | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/justfile b/justfile index a788523e..db1f74df 100755 --- a/justfile +++ b/justfile @@ -63,6 +63,7 @@ oven *ARGS: oven-build docker run --rm -i $(test -t 0 && echo -t) \ --user "$(id -u):$(id -g)" \ --group-add "$docker_gid" \ + --network host \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$mount_root:$mount_root" \ -v "$HOME/.cache/bakery-oven:/opt/oven/state" \ diff --git a/oven/Containerfile b/oven/Containerfile index 1f67fa01..dff81ff4 100644 --- a/oven/Containerfile +++ b/oven/Containerfile @@ -15,6 +15,16 @@ # without needing to know the # host's docker GID at build # time +# --network host DooD shares only the daemon's +# *socket*, not its network +# namespace — without this, +# "localhost" here and +# "localhost" on the host-daemon +# side are different network +# stacks, so a container's +# published port (e.g. a local +# test registry) isn't reachable +# via localhost from inside here # -v /var/run/docker.sock:/var/run/docker.sock DooD # -v : (identical host/container path) required for any bind # mount: dgoss/bakery From b8fa38d6af286c67a7d409f0fd4c441ead9f2aae Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Fri, 7 Aug 2026 12:02:39 -0500 Subject: [PATCH 20/23] Add oven-setup-qemu recipe for cross-arch builds Verified empirically that binfmt_misc is a single table global to the host kernel, not namespaced per-container: registering QEMU emulators from inside the oven (via its DooD socket access) makes them visible on the bare host immediately afterward, and vice versa. Confirmed by re-running a previously-failing @image_build-tagged multiplatform test after registering - it now builds linux/arm64 targets successfully. Doesn't persist across a reboot, so this is a separate, explicit recipe rather than something baked into `oven` or entrypoint.sh - running it on every shell invocation would silently mutate host kernel state for no reason most of the time. --- justfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/justfile b/justfile index db1f74df..4cbb02b3 100755 --- a/justfile +++ b/justfile @@ -43,6 +43,14 @@ _setup-pre-commit: oven-build: docker build -t bakery-oven -f "{{ CWD }}/oven/Containerfile" "{{ CWD }}/oven" +# binfmt_misc is a single table global to the host kernel, not namespaced +# per-container, so this only needs to run once per boot from anywhere +# with docker socket access (including from inside the oven), and it +# doesn't persist across a reboot. +# Register QEMU emulation for cross-arch image builds (one-time per host boot) +oven-setup-qemu: + docker run --privileged --rm tonistiigi/binfmt --install all + # Build (if stale) and drop into the oven with sibling repos mounted # # images-shared is worked on via git worktrees, so {{ CWD }} is never the From 00089a5e35347fc9493c5b4479d45233aaa9b28d Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Fri, 7 Aug 2026 12:14:05 -0500 Subject: [PATCH 21/23] Document QEMU setup instead of scripting it via a container Replace the oven-setup-qemu recipe with a comment pointing at the native Ubuntu package instead of running it. tonistiigi/binfmt needs a --privileged container from a third-party image just to write a few kernel table entries - qemu-user-binfmt does the same thing as a normal apt install from Ubuntu's own archive, and its systemd integration means it persists across reboots for free, which the container approach couldn't do without a hand-rolled systemd unit anyway. --- justfile | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/justfile b/justfile index 4cbb02b3..ca8f4bb5 100755 --- a/justfile +++ b/justfile @@ -43,13 +43,17 @@ _setup-pre-commit: oven-build: docker build -t bakery-oven -f "{{ CWD }}/oven/Containerfile" "{{ CWD }}/oven" -# binfmt_misc is a single table global to the host kernel, not namespaced -# per-container, so this only needs to run once per boot from anywhere -# with docker socket access (including from inside the oven), and it -# doesn't persist across a reboot. -# Register QEMU emulation for cross-arch image builds (one-time per host boot) -oven-setup-qemu: - docker run --privileged --rm tonistiigi/binfmt --install all +# Building/testing linux/arm64 targets from an amd64 host (or vice versa) +# needs QEMU emulation registered in the host kernel's binfmt_misc table. +# binfmt_misc is global to the host kernel, not namespaced per-container, +# so this is one-time host setup, outside this repo's control, and it +# also covers builds run from inside the oven once installed. On Ubuntu: +# +# sudo apt-get install qemu-user-binfmt +# +# qemu-user-binfmt recommends systemd and integrates with +# systemd-binfmt.service, so registration survives a reboot without any +# further setup. # Build (if stale) and drop into the oven with sibling repos mounted # From c9129ee016ddfb14ca97557945f9fded0a7f6ad8 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Fri, 7 Aug 2026 12:17:34 -0500 Subject: [PATCH 22/23] Document oven multi-arch QEMU setup in CONTRIBUTING.md Move this out of the justfile and into the actual maintainer-facing docs, alongside the other Footguns entries. Documents the native qemu-user-binfmt package rather than scripting a --privileged third-party container for a one-time host setup step. --- CONTRIBUTING.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 84460bde..32a4b108 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,6 +99,21 @@ Do not backport cosmetic changes, new feature additions, or non-security depende - **Version format mismatch.** Product repos dispatch with raw git-describe versions (e.g., `v2026.03.0-473-g072bb6fd1f`). Bakery normalizes these to semver-with-metadata (e.g., `2026.03.0-dev+473-g072bb6fd1f`). If `bakery ci matrix` produces an empty matrix after a dispatch, the formats did not align. The shared workflows strip a leading `v` automatically. Check the rest of the version string against bakery's normalization. +- **Multi-arch builds inside the oven need host-level QEMU setup.** Building + or testing `linux/arm64` targets from an amd64 host (or vice versa) needs + QEMU emulation registered in the host kernel's `binfmt_misc` table. This is + outside the oven image's control — it only relays build instructions to the + host's own Docker daemon over the DooD socket. `binfmt_misc` is global to + the host kernel, not namespaced per-container, so installing it on the host + also covers builds run from inside the oven. On Ubuntu: + + ```bash + sudo apt-get install qemu-user-binfmt + ``` + + `qemu-user-binfmt` integrates with `systemd-binfmt.service`, so + registration survives a reboot without further setup. + ## Change-aware builds `bakery ci matrix` supports `--base-ref ` and `--changed-files-from ` to emit From d354de8d5ece9845e304b8fa691bb0a5d5c6e222 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Fri, 7 Aug 2026 14:38:10 -0500 Subject: [PATCH 23/23] Fix oven recipe fragility and drop duplicated QEMU note The oven recipe's git-common-dir guard used an apostrophe inside a ${var:?message} expansion, which parses fine under just's default shell (dash, or bash-as-sh on macOS) but fails with a cryptic "unexpected EOF" if just is ever configured to use native bash. Reworded the message to avoid the apostrophe entirely. The mkdir for the persistent state directory had no failure guard, unlike the two checks right above it. just's default shell doesn't enable errexit, so a failed mkdir would silently fall through to `docker run`, which auto-creates the missing bind-mount source as root - breaking the --user host-UID matching the whole design depends on. Added an explicit guard to match the existing pattern. Also removed the QEMU/binfmt_misc explanation from the justfile comment, since it duplicated CONTRIBUTING.md verbatim; left a one-line pointer instead. --- justfile | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/justfile b/justfile index ca8f4bb5..2317af3f 100755 --- a/justfile +++ b/justfile @@ -43,17 +43,8 @@ _setup-pre-commit: oven-build: docker build -t bakery-oven -f "{{ CWD }}/oven/Containerfile" "{{ CWD }}/oven" -# Building/testing linux/arm64 targets from an amd64 host (or vice versa) -# needs QEMU emulation registered in the host kernel's binfmt_misc table. -# binfmt_misc is global to the host kernel, not namespaced per-container, -# so this is one-time host setup, outside this repo's control, and it -# also covers builds run from inside the oven once installed. On Ubuntu: -# -# sudo apt-get install qemu-user-binfmt -# -# qemu-user-binfmt recommends systemd and integrates with -# systemd-binfmt.service, so registration survives a reboot without any -# further setup. +# Multi-arch (linux/arm64 from amd64, or vice versa) needs one-time host +# QEMU setup — see CONTRIBUTING.md. # Build (if stale) and drop into the oven with sibling repos mounted # @@ -67,11 +58,11 @@ oven-build: # *host* daemon (on the other end of the socket) ever resolves. oven *ARGS: oven-build git_common_dir="$(git -C "{{ CWD }}" rev-parse --path-format=absolute --git-common-dir)"; \ - : "${git_common_dir:?failed to resolve this checkout's git-common-dir}"; \ + : "${git_common_dir:?failed to resolve git-common-dir for this checkout}"; \ mount_root="$(dirname "$(dirname "$git_common_dir")")"; \ docker_gid="$(stat -c '%g' /var/run/docker.sock 2>/dev/null || stat -f '%g' /var/run/docker.sock)"; \ : "${docker_gid:?failed to read the group ID of /var/run/docker.sock}"; \ - mkdir -p "$HOME/.cache/bakery-oven/home"; \ + mkdir -p "$HOME/.cache/bakery-oven/home" || exit 1; \ docker run --rm -i $(test -t 0 && echo -t) \ --user "$(id -u):$(id -g)" \ --group-add "$docker_gid" \