From f25b072fa0f5ebd29ecf67f070d75484e0c7aa57 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Mon, 13 Jul 2026 06:44:55 +0300 Subject: [PATCH 1/2] docker: make the build env work on macOS / Apple Silicon hosts Three host-portability fixes to the docker/ toolchain from #8564 so it runs on macOS as well as Linux. All three are no-ops on native amd64 Linux. 1. Dockerfile: only groupadd when the target GID is free. macOS's default primary group `staff` is GID 20, which already exists as `games` in rockylinux:9, so `groupadd -g 20` aborted the image build. Guard with `getent group "${USER_GID}" || groupadd ...`; useradd -g accepts the existing GID. 2. ifcos_env unique(): replace GNU-only `sed -si` (BSD/macOS sed errors "illegal option -- s") with a portable `sed > tmp && mv` rewrite of the UNIQUE_ID line. Verified against macOS BSD sed. 3. create() + compose.yaml: build with an explicit `--platform linux/amd64` so the locally built image's platform matches the `platform: linux/amd64` pin in compose.yaml. Without it, on arm64 the local image is tagged linux/arm64, compose treats the platform-mismatched image as absent and tries to pull `ifcopenshell-build-env:updated` from Docker Hub (which does not exist -> access denied). Also add `pull_policy: never` as a safety net so a future mismatch surfaces as a clear "image not found" rather than a registry auth error. Note: on Apple Silicon the amd64 build runs under emulation and a cold full build is slow; ccache makes incremental rebuilds tolerable. A native Linux/Intel host or CI remains the better choice for routine use, but these fixes turn "hard broken" into "works with a caveat" on macOS. This change was made with the assistance of an AI tool. Co-Authored-By: Claude Fable 5 --- docker/Dockerfile | 10 +++++++++- docker/compose.yaml | 8 ++++++++ docker/ifcos_env | 22 +++++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index fbc4c23baf..4133cf96f1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -39,7 +39,15 @@ ENV PATH="/usr/lib/ccache:$PATH" # if your host user has a different UID/GID. ARG USER_UID=1000 ARG USER_GID=1000 -RUN groupadd -g "${USER_GID}" builder \ +# groupadd fails outright if USER_GID is already taken by an existing +# system group - which happens whenever a host's primary GID collides with +# one baked into the rockylinux9 base image. The main real-world case is +# macOS, where the default user's primary group is "staff" at GID 20, and +# GID 20 is "games" on RHEL-family images. Only create the "builder" group +# when that GID is actually free; otherwise useradd just attaches to +# whichever group already owns it. Either way the builder user ends up +# with the right GID for bind-mount ownership, which is all that matters. +RUN (getent group "${USER_GID}" >/dev/null || groupadd -g "${USER_GID}" builder) \ && useradd -m -u "${USER_UID}" -g "${USER_GID}" -s /bin/bash builder \ && echo "builder ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/builder diff --git a/docker/compose.yaml b/docker/compose.yaml index 9bc79234d1..fcd5f17745 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -4,6 +4,14 @@ services: container_name: ifcopenshell-${UNIQUE_ID} image: ifcopenshell-build-env:updated platform: linux/amd64 + # There's no `build:` section - the image is always produced ahead of + # time by `./ifcos_env create` (`docker build`, not `docker compose + # build`). Without this, a platform mismatch between the pin above and + # whatever's in the local image store makes compose treat the image as + # absent and fall back to pulling ifcopenshell-build-env:updated from + # Docker Hub, where it doesn't exist. Fail fast with a clear "not + # found" instead of an obscure registry access-denied error. + pull_policy: never volumes: - type: bind source: ../ diff --git a/docker/ifcos_env b/docker/ifcos_env index 4286421572..c35ba85d2e 100755 --- a/docker/ifcos_env +++ b/docker/ifcos_env @@ -24,7 +24,18 @@ set_env function create() { echo "⭐ Creating image: ifcopenshell-build-env" + # compose.yaml pins the service to platform: linux/amd64 (this stack + # always targets the rockylinux9-x64 build-outputs branch and produces + # linux64 artifacts, regardless of host arch). Building without + # --platform would tag the image for the host's native arch instead - + # harmless on an amd64 host, but on an arm64 host (e.g. Apple Silicon) + # it leaves a local image that doesn't match what compose asked for, so + # `docker compose up` decides the requested platform is "missing" and + # tries to pull ifcopenshell-build-env:updated from Docker Hub instead + # of using the image just built. Pinning the build platform here keeps + # the local image's arch in sync with compose's pin on every host. docker build -f Dockerfile \ + --platform linux/amd64 \ --build-arg USER_UID="$(id -u)" --build-arg USER_GID="$(id -g)" \ -t ifcopenshell-build-env:updated . } @@ -112,7 +123,16 @@ function unique() { echo -e "\nUNIQUE_ID=dummy\n" >> "$ENV_FILE" fi - export UNIQUE_ID="$(pwd | sha256sum | cut -c -8)" && sed -si "s/^UNIQUE_ID=.*$/UNIQUE_ID=${UNIQUE_ID}/" "$ENV_FILE" + export UNIQUE_ID="$(pwd | sha256sum | cut -c -8)" + + # `sed -i` takes incompatible syntax between GNU sed (Linux) and BSD sed + # (macOS) - `-si` is GNU-only and errors as "illegal option -- s" under + # BSD/macOS sed. Avoid -i altogether and do the in-place edit via a temp + # file + mv instead, which behaves identically with either sed. + local tmp_file + tmp_file="$(mktemp "${ENV_FILE}.XXXXXX")" + sed "s/^UNIQUE_ID=.*$/UNIQUE_ID=${UNIQUE_ID}/" "$ENV_FILE" > "$tmp_file" + mv "$tmp_file" "$ENV_FILE" set_env } From 8b05510d6cd0c63885a2cadc155628b5e00155e0 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Mon, 13 Jul 2026 09:43:57 +0300 Subject: [PATCH 2/2] docker: fix GID collision and macOS sed portability Two host-environment bugs in the build-env scripts that break on macOS/Apple Silicon hosts, independent of target architecture: - Dockerfile: groupadd fails outright when USER_GID collides with an existing system group in the rockylinux9 base image (e.g. macOS default user GID 20 "staff" collides with RHEL's GID 20 "games"). Guard with getent so useradd attaches to the existing group instead. - ifcos_env: `sed -si` is GNU-only syntax and errors under BSD/macOS sed. Do the UNIQUE_ID substitution via a portable temp-file + mv. Per sboddy's review on the original PR: dropped the linux/amd64 platform-pin additions from this change. The stack already targets Rocky9/x64 build outputs by design, and Docker Desktop on macOS has no native container runtime regardless (it's a Linux VM either way), so forcing the image to run under emulation doesn't produce anything that's actually loadable into a native macOS Blender/Bonsai install. That's a separate, harder problem worth solving via a native build path instead (mirroring build_osx.yml), not by fighting emulation here. These two fixes stand on their own merits on any host. This change was made with the assistance of an AI tool. --- docker/compose.yaml | 8 -------- docker/ifcos_env | 11 ----------- 2 files changed, 19 deletions(-) diff --git a/docker/compose.yaml b/docker/compose.yaml index fcd5f17745..9bc79234d1 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -4,14 +4,6 @@ services: container_name: ifcopenshell-${UNIQUE_ID} image: ifcopenshell-build-env:updated platform: linux/amd64 - # There's no `build:` section - the image is always produced ahead of - # time by `./ifcos_env create` (`docker build`, not `docker compose - # build`). Without this, a platform mismatch between the pin above and - # whatever's in the local image store makes compose treat the image as - # absent and fall back to pulling ifcopenshell-build-env:updated from - # Docker Hub, where it doesn't exist. Fail fast with a clear "not - # found" instead of an obscure registry access-denied error. - pull_policy: never volumes: - type: bind source: ../ diff --git a/docker/ifcos_env b/docker/ifcos_env index c35ba85d2e..72263065b5 100755 --- a/docker/ifcos_env +++ b/docker/ifcos_env @@ -24,18 +24,7 @@ set_env function create() { echo "⭐ Creating image: ifcopenshell-build-env" - # compose.yaml pins the service to platform: linux/amd64 (this stack - # always targets the rockylinux9-x64 build-outputs branch and produces - # linux64 artifacts, regardless of host arch). Building without - # --platform would tag the image for the host's native arch instead - - # harmless on an amd64 host, but on an arm64 host (e.g. Apple Silicon) - # it leaves a local image that doesn't match what compose asked for, so - # `docker compose up` decides the requested platform is "missing" and - # tries to pull ifcopenshell-build-env:updated from Docker Hub instead - # of using the image just built. Pinning the build platform here keeps - # the local image's arch in sync with compose's pin on every host. docker build -f Dockerfile \ - --platform linux/amd64 \ --build-arg USER_UID="$(id -u)" --build-arg USER_GID="$(id -g)" \ -t ifcopenshell-build-env:updated . }