#!/usr/bin/env bash # build-standalone-image.sh — the committed, repeatable build path for the # PULLABLE, MULTI-ARCH standalone dev image. # # ./scripts/build-standalone-image.sh [tag] # default tag: m12 (+ also tags :latest) # # WHY THIS IS SEPARATE FROM build-release.sh: # build-release.sh produces the PRODUCTION images (server/dr/stress), amd64-ONLY, # defaulting to CLUSTER mode — they will not run natively on an arm64 dev Mac. # This publishes a manifest list with BOTH linux/amd64 (Linux devs / CI) and # linux/arm64 (Apple-silicon Macs), defaulting to STANDALONE mode, so a consumer # app (e.g. thepeach) can pull it as a docker-compose dependency like postgres — # no source checkout required. # # WHY THE HYBRID (not a single `buildx --platform amd64,arm64`): # `rustc` SIGSEGVs under QEMU emulation, so an in-container amd64 cross-build on # an arm64 host fails. We therefore: # - amd64: package the HOST cross-compiled x86_64 binary (the proven build-release # path) via docker/standalone/amd64.Dockerfile — apt + COPY only, no compile. # - arm64: build natively in-container from docker/standalone/Dockerfile. # - stitch both into a manifest list with `imagetools create`. # # Registry creds come from the Docker daemon's existing login (zot accepts anonymous # push for this registry); no secret is embedded. set -euo pipefail TAG="${1:-m12}" REGISTRY="${TIDAL_REGISTRY:-registry.threesix.ai/tidal}" IMAGE="${REGISTRY}/standalone" BUILDER="${TIDAL_BUILDX_BUILDER:-amd64builder}" TARGET="x86_64-unknown-linux-gnu" die() { echo "build-standalone-image: $*" >&2; exit 1; } command -v docker >/dev/null || die "docker not on PATH" docker buildx inspect "$BUILDER" >/dev/null 2>&1 \ || die "buildx builder '$BUILDER' not found (docker buildx create --name $BUILDER --driver docker-container --use)" REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$REPO_ROOT" echo "==> multi-arch standalone image ${IMAGE}:${TAG} (+ :latest), source $(git rev-parse --short HEAD 2>/dev/null || echo unknown)" # 1. amd64 binary: HOST cross-compile if absent (QEMU rustc segfaults, so never in-container). BIN="target/${TARGET}/release/tidal-server" if [ ! -x "$BIN" ]; then command -v "${TARGET}-gcc" >/dev/null || die "missing ${TARGET}-gcc (brew install ${TARGET}); needed to cross-compile the amd64 binary" rustup target list --installed 2>/dev/null | grep -qx "$TARGET" || die "rust target $TARGET not installed (rustup target add $TARGET)" echo "==> amd64 binary absent — host cross-compiling -p tidal-server for $TARGET" export CC_x86_64_unknown_linux_gnu="${TARGET}-gcc" \ CXX_x86_64_unknown_linux_gnu="${TARGET}-g++" \ AR_x86_64_unknown_linux_gnu="${TARGET}-ar" \ CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER="${TARGET}-gcc" \ PROTOC="${PROTOC:-$(command -v protoc || true)}" [ -n "$PROTOC" ] || die "protoc not found (brew install protobuf), or set PROTOC" cargo build -p tidal-server --release --target "$TARGET" --locked fi # 2. amd64 image: package the prebuilt binary (no compile -> reliable under QEMU). STAGE="tmp/standalone-amd64"; rm -rf "$STAGE"; mkdir -p "$STAGE" cp "$BIN" "$STAGE/tidal-server" cp -R tidal-server/config "$STAGE/config" cp docker/standalone/amd64.Dockerfile "$STAGE/Dockerfile" echo "==> building+pushing ${IMAGE}:${TAG}-amd64 (packaging)" docker buildx build --builder "$BUILDER" --platform linux/amd64 -t "${IMAGE}:${TAG}-amd64" --push "$STAGE" # 3. arm64 image: native in-container build. echo "==> building+pushing ${IMAGE}:${TAG}-arm64 (native in-container)" docker buildx build --builder "$BUILDER" --platform linux/arm64 -f docker/standalone/Dockerfile -t "${IMAGE}:${TAG}-arm64" --push . # 4. stitch the two single-arch tags into one manifest list. echo "==> assembling manifest list ${IMAGE}:${TAG} + :latest" docker buildx imagetools create -t "${IMAGE}:${TAG}" -t "${IMAGE}:latest" "${IMAGE}:${TAG}-amd64" "${IMAGE}:${TAG}-arm64" echo "==> published. Manifest list arches:" docker buildx imagetools inspect "${IMAGE}:${TAG}" 2>/dev/null | grep -E 'Name:|Platform:' || true