Files
hermes-agent/scripts/termux/build_builder_image.sh
ethernet 75a13386fb fix: make macOS shell and mode contracts portable
The macOS runner uses Bash 3.2, which rejects parameter case conversion.
Normalize the GHCR owner with portable tr and verify mixed-case input.

The macOS runner can clear setgid from a directory when chmod applies 2770.
Compare scratch permissions with the native chmod result while preserving all
bits the host accepts.

Tests: scripts/run_tests.sh tests/scripts/test_termux_build_driver.py tests/test_scratch_dir.py
Shell: bash -n scripts/termux/build_builder_image.sh
2026-09-22 00:09:09 -04:00

62 lines
2.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Build + push the derived termux builder image (toolchain pre-baked).
#
# The image tag hashes the full pinned base digest and Dockerfile bytes,
# so either a lock bump or a toolchain recipe change produces a new image.
# Pushes to GHCR with the repo's CI identity (GITHUB_TOKEN); idempotent --
# an existing identical tag is left alone.
#
# Usage: build_builder_image.sh (build + push if missing)
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$HERE/../.." && pwd)"
cd "$REPO_ROOT"
DIGEST="$(python3 -c 'import sys; sys.path.insert(0, "."); from pm.lock import termux_docker_digest; print(termux_docker_digest())')"
[ -n "$DIGEST" ] || { echo "termux-docker digest missing" >&2; exit 1; }
BASE="termux/termux-docker@${DIGEST}"
DOCKERFILE="scripts/termux/termux-builder.Dockerfile"
SHORT="$(python3 -c '
import hashlib, pathlib, sys
identity = sys.argv[1].encode() + b"\0" + pathlib.Path(sys.argv[2]).read_bytes()
print(hashlib.sha256(identity).hexdigest()[:12])
' "$BASE" "$DOCKERFILE")"
REGISTRY="ghcr.io"
# macOS still ships Bash 3.2, which predates ${value,,} case conversion.
OWNER="$(printf '%s' "$GITHUB_REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')"
IMAGE="${REGISTRY}/${OWNER}/hermes-termux-builder:${SHORT}"
if docker manifest inspect "$IMAGE" >/dev/null 2>&1; then
echo "builder image already published: $IMAGE"
echo "$IMAGE"
exit 0
fi
echo "building $IMAGE from $BASE"
docker build \
-f "$DOCKERFILE" \
--build-arg "BASE=${BASE}" \
-t "$IMAGE" \
scripts/termux \
|| { echo "builder image build failed" >&2; exit 1; }
# Smoke: the baked image must answer the runtime probes termux_build.sh
# performs (clang + rustc + cargo + make) before we publish it. /bin/sh is
# linked at RUNTIME inside the wheelhouse phase's --tmpfs /bin, so it is
# deliberately absent from the baked image.
docker run --rm --platform linux/arm64 "$IMAGE" \
/data/data/com.termux/files/usr/bin/bash -c '
export PREFIX=/data/data/com.termux/files/usr
for tool in clang rustc cargo make; do
command -v "$tool" >/dev/null 2>&1 || { echo "smoke FAIL: $tool"; exit 1; }
done
echo "builder image smoke OK"
' || { echo "builder image failed its smoke test" >&2; exit 1; }
echo "$GITHUB_TOKEN" | docker login "$REGISTRY" -u "${GITHUB_ACTOR:-x}" --password-stdin
docker push "$IMAGE" || { echo "builder image push failed" >&2; exit 1; }
echo "published $IMAGE"
echo "$IMAGE"