2476667510
The owner copies infra/pi/ to the Kvitt Pi and runs it there (D-100); nothing
on the Mac connects to the Pis. compose stack of SPEC §10.2 with images
${KVITT_REGISTRY}/kvitt-{server,cert-sync,backup}, Caddyfile §10.5.2 verbatim,
.env.example with the OQ-3 values as CHANGE_ME placeholders, install.sh,
deploy.sh (backup before every deploy), bootstrap.sh, backup-now.sh,
install-apk.sh. cert-sync image and sync.sh (§10.5.1, tar stream for both
authorized_keys variants); backup.sh uploads with rclone copy --immutable and
deletes local files once both are confirmed on Drive (D-002); both sidecars
schedule with supercronic because busybox crond runs no jobs as non-root;
backup runs as uid 10001. .gitea/workflows/build-images.yaml from the owner's
example (not yet run); infra/build-images.sh and infra/publish-apk.sh (R8
mapping kept, version per §10.6) on the Mac; README checklist and RESTORE.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
100 lines
4.5 KiB
Bash
Executable File
100 lines
4.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build the three 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): ${KVITT_REGISTRY}/kvitt-server (context server/), kvitt-cert-sync (infra/cert-sync),
|
|
# 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=<gitea user> optional
|
|
# KVITT_REGISTRY_TOKEN=<token, write:package> 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"
|
|
|
|
[[ -z "$(git status --porcelain)" ]] || { echo "build-images.sh: the working tree is dirty; commit or stash first." >&2; exit 1; }
|
|
lock=/tmp/kvitt-gradle.lock
|
|
if [[ -d "$lock" ]] && pid="$(cat "$lock/pid" 2>/dev/null)" && [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; 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"
|
|
registry="gitea.lan/jacobh2"; 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:-gitea.lan/jacobh2}"
|
|
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-cert-sync|infra/cert-sync|infra/cert-sync/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)"
|
|
printf '[registry."%s"]\n http = true\n' "$registry_host" > "$cfg"
|
|
docker buildx create --name kvitt-http-builder --config "$cfg" >/dev/null
|
|
rm -f "$cfg"
|
|
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
|
|
docker buildx build --builder kvitt-http-builder --platform linux/arm64 -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: /opt/kvitt/deploy.sh $tag"
|
|
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
|