lazyBoy/scripts/build-image.sh

137 lines
4.9 KiB
Bash
Raw Normal View History

2026-09-08 01:14:53 +00:00
#!/usr/bin/env bash
# Build one LazyBoy image. Single entry point for everything in image/.
#
# scripts/build-image.sh # desktop image, host arch, into docker
# scripts/build-image.sh --file image/api/Dockerfile # any image, host arch
# scripts/build-image.sh --platforms linux/arm64 # one foreign arch, needs QEMU
# scripts/build-image.sh --multi # amd64 + arm64 -> OCI archive
# scripts/build-image.sh --multi --push # amd64 + arm64 -> registry
#
# Supported arches: linux/amd64 and linux/arm64 -- see the ceiling note below.
# --multi/--platforms cannot use the default "docker" builder: that driver
# cannot export a manifest list. A docker-container builder plus QEMU binfmt is
# bootstrapped here.
set -euo pipefail
root="$(cd "$(dirname "$0")/.." && pwd)"
cd "$root"
file="image/computer/Dockerfile"
platforms=""
multi=0
push=0
target=""
oci_name=""
builder="${BUILDX_BUILDER:-lazyboy}"
tag=""
while [ $# -gt 0 ]; do
case "$1" in
--file) file="$2"; shift 2 ;;
--tag) tag="$2"; shift 2 ;;
--target) target="$2"; shift 2 ;;
--platforms) platforms="$2"; shift 2 ;;
--oci) oci_name="$2"; shift 2 ;;
--builder) builder="$2"; shift 2 ;;
--multi) multi=1; shift ;;
--push) push=1; shift ;;
-h|--help) sed -n '2,13p' "$0"; exit 0 ;;
*) echo "unknown option: $1" >&2; exit 2 ;;
esac
done
# Defaults follow docker-compose.yml: the desktop image is published under a
# lazyboy/ namespace, the services under the compose project name.
if [ -z "$tag" ]; then
case "$file" in
image/computer/Dockerfile) tag="lazyboy/computer:local" ;;
image/supervisor/Dockerfile) tag="lazyboy-supervisor:latest" ;;
image/api/Dockerfile) tag="lazyboy-api:latest" ;;
*) echo "--tag is required for $file" >&2; exit 2 ;;
esac
fi
if [ "$multi" = 1 ] && [ -z "$platforms" ]; then
platforms="linux/amd64,linux/arm64"
fi
if [ -z "$oci_name" ]; then
case "$file" in
image/computer/Dockerfile) oci_name="dist/lazyboy-computer-multi.oci.tar" ;;
*) oci_name="dist/$(basename "$(dirname "$file")")-multi.oci.tar" ;;
esac
fi
if [ "$push" = 1 ] && [ -z "$platforms" ]; then
echo "--push only makes sense with --multi or --platforms" >&2
exit 2
fi
# Architecture ceiling, set by two upstream binary distributions: the Cua Driver
# (cua-driver-rs-v0.23.2) ships Linux builds for x86_64 and arm64 only, and
# ONNX Runtime (v1.24.1) for x64 and aarch64 only. image/computer/Dockerfile and
# scripts/fetch-onnxruntime.sh fail fast on anything else; checking here rejects
# a bad --platforms before binfmt + GBs of cross-compilation are spent.
supported_arches="amd64 arm64"
for platform in ${platforms//,/ }; do
os="${platform%%/*}"
arch="${platform#*/}"
arch="${arch%%/*}"
if [ "$os" != "linux" ]; then
echo "--platforms: only Linux images are built (got $platform)" >&2
exit 2
fi
case " $supported_arches " in
*" $arch "*) ;;
*)
echo "--platforms: unsupported architecture '$arch' in '$platform'." >&2
echo " LazyBoy images exist for linux/amd64 and linux/arm64: the Cua" >&2
echo " Driver ships only linux-x86_64/linux-arm64 and ONNX Runtime only" >&2
echo " linux-x64/linux-aarch64, so no other Linux arch can run them." >&2
exit 2
;;
esac
done
if [ -z "$platforms" ]; then
# Native build: the docker driver, so the result lands in the local store and
# `docker compose up` picks it up without a registry.
args=(docker build -f "$file" -t "$tag")
[ -n "$target" ] && args+=(--target "$target")
"${args[@]}" .
echo "built $tag"
exit 0
fi
docker buildx inspect "$builder" >/dev/null 2>&1 \
|| docker buildx create --name "$builder" --driver docker-container --bootstrap
# QEMU is only needed for architectures the host cannot run natively; asking
# binfmt to register the host's own handler fails inside a privileged container
# and would look like a real failure, so the native arch is skipped.
native="$(dpkg --print-architecture 2>/dev/null || true)"
if [ -z "$native" ]; then
case "$(uname -m)" in
x86_64) native=amd64 ;;
aarch64) native=arm64 ;;
*) native=unknown ;;
esac
fi
for platform in ${platforms//,/ }; do
arch="${platform#*/}"
if [ "$arch" != "$native" ]; then
docker run --privileged --rm tonistiigi/binfmt --install "$arch" >/dev/null 2>&1 \
|| echo "WARNING: no QEMU handler for $arch; that build fails with 'exec format error'" >&2
fi
done
args=(docker buildx build --builder "$builder" -f "$file" -t "$tag" --platform "$platforms")
[ -n "$target" ] && args+=(--target "$target")
if [ "$push" = 1 ]; then
args+=(--push)
else
mkdir -p "$(dirname "$oci_name")"
args+=(--output "type=oci,dest=$oci_name")
fi
"${args[@]}" .
if [ "$push" = 1 ]; then
echo "published $tag for ${platforms}"
else
echo "wrote $oci_name for ${platforms} (load with: nerdctl load -i $oci_name)"
fi