#!/usr/bin/env bash # Build the two Kvitt images for linux/arm64 on the Mac and push them to the Gitea registry (SPEC §10.3). # The Mac fallback for the Gitea Actions workflow .gitea/workflows/build-images.yaml (which is the normal path). # Usage: infra/build-images.sh [tag] tag defaults to the short git SHA of HEAD; `latest` is always tagged too. # # Images (D-104, D-120): ${KVITT_REGISTRY}/kvitt-server (context server/) and kvitt-backup (infra/backup). # KVITT_REGISTRY and the optional credentials come from infra/secrets/registry.env (git-ignored): # KVITT_REGISTRY=gitea.lan/jacobh2 # KVITT_REGISTRY_USER= optional # KVITT_REGISTRY_TOKEN= optional # Without that file the images are built locally and the exact push commands are printed instead. # Refuses a dirty working tree, and refuses to run while a Gradle build holds the gradle-serial lock (CLAUDE.md). # Works with Docker (buildx) and with Podman (this Mac's `docker`); the registry is plain HTTP on the home Wi-Fi. set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$repo_root" # shellcheck source=../tools/gradle-lock.sh . "$repo_root/tools/gradle-lock.sh" [[ -z "$(git status --porcelain)" ]] || { echo "build-images.sh: the working tree is dirty; commit or stash first." >&2; exit 1; } if pid="$(gradle_lock_holder)"; then echo "build-images.sh: a Gradle build is running (pid $pid); try again when it is done." >&2 exit 1 fi tag="${1:-$(git rev-parse --short HEAD)}" [[ "$tag" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]] || { echo "build-images.sh: not a valid tag: $tag" >&2; exit 2; } reg_file="$repo_root/infra/secrets/registry.env" default_registry="gitea.lan/jacobh2" # SPEC §10.3, D-104 registry="$default_registry"; user=""; token=""; push=0 if [[ -f "$reg_file" ]]; then value() { sed -n "s/^$1=//p" "$reg_file" | tail -n 1; } registry="$(value KVITT_REGISTRY)"; registry="${registry:-$default_registry}" user="$(value KVITT_REGISTRY_USER)"; token="$(value KVITT_REGISTRY_TOKEN)" push=1 fi registry_host="${registry%%/*}" # name|context|dockerfile images=( "kvitt-server|server|server/Dockerfile" "kvitt-backup|infra/backup|infra/backup/Dockerfile" ) if docker version 2>/dev/null | grep -qi podman; then engine=podman; else engine=docker; fi echo "Engine: $engine; registry: $registry; tag: $tag (and latest); push: $([[ $push == 1 ]] && echo yes || echo no)" if [[ $push == 1 && -n "$user" && -n "$token" ]]; then if [[ $engine == podman ]]; then printf '%s' "$token" | podman login --tls-verify=false "$registry_host" -u "$user" --password-stdin else printf '%s' "$token" | docker login "$registry_host" -u "$user" --password-stdin fi fi if [[ $engine == docker && $push == 1 ]]; then # A buildx builder that may push to the plain-HTTP registry (same rule as the Gitea workflow). if ! docker buildx inspect kvitt-http-builder >/dev/null 2>&1; then cfg="$(mktemp)" trap 'rm -f "$cfg"' EXIT printf '[registry."%s"]\n http = true\n' "$registry_host" > "$cfg" docker buildx create --name kvitt-http-builder --config "$cfg" >/dev/null rm -f "$cfg" trap - EXIT fi fi for spec in "${images[@]}"; do IFS='|' read -r name context dockerfile <<< "$spec" ref="$registry/$name" echo "== $ref:$tag" if [[ $engine == podman ]]; then podman build --platform linux/arm64 -f "$dockerfile" -t "$ref:$tag" -t "$ref:latest" "$context" if [[ $push == 1 ]]; then podman push --tls-verify=false "$ref:$tag" podman push --tls-verify=false "$ref:latest" fi elif [[ $push == 1 ]]; then # --provenance=false: push a plain image manifest, as Podman does, rather than an index with an attestation # entry (same choice as the Gitea workflow). docker buildx build --builder kvitt-http-builder --platform linux/arm64 --provenance=false -f "$dockerfile" \ -t "$ref:$tag" -t "$ref:latest" --push "$context" else docker buildx build --platform linux/arm64 -f "$dockerfile" -t "$ref:$tag" -t "$ref:latest" --load "$context" fi done if [[ $push == 1 ]]; then echo "Pushed. On the Pi: cd ~/kvitt, set KVITT_TAG=$tag in .env, then docker compose pull && docker compose up -d" else echo echo "Built locally, not pushed: $reg_file does not exist. To push, create it (KVITT_REGISTRY=$registry, plus" echo "KVITT_REGISTRY_USER and KVITT_REGISTRY_TOKEN if Gitea needs them) and run this script again, or push by hand:" for spec in "${images[@]}"; do IFS='|' read -r name _ _ <<< "$spec" if [[ $engine == podman ]]; then echo " podman push --tls-verify=false $registry/$name:$tag && podman push --tls-verify=false $registry/$name:latest" else echo " docker push $registry/$name:$tag && docker push $registry/$name:latest # needs $registry_host in insecure-registries" fi done fi