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
44 lines
2.4 KiB
Bash
Executable File
44 lines
2.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Put the APKs that infra/publish-apk.sh made on the Mac into the server's data volume (SPEC §10.6).
|
|
# The owner copies infra/pi/out/apk/ from the Mac to /opt/kvitt/apk/ on the Pi, then runs ON the Pi:
|
|
# /opt/kvitt/install-apk.sh
|
|
# Copies every kvitt-<version_code>.apk from /opt/kvitt/apk into /data/apk, then latest.json last and atomically
|
|
# (temp file, rename), so the server never announces a file it does not have; keeps the newest 5 APKs in /data/apk.
|
|
# R8 mapping files (kvitt-<version_code>.mapping.txt) stay in /opt/kvitt/apk; the server never needs them.
|
|
set -euo pipefail
|
|
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=lib.sh
|
|
. "$here/lib.sh"
|
|
|
|
src="$KVITT_DIR/apk"
|
|
[[ -f "$src/latest.json" ]] || die "no $src/latest.json: copy infra/pi/out/apk/ from the Mac first (README, Publishing an APK)"
|
|
file_name=$(sed -n 's/.*"file_name": *"\([^"]*\)".*/\1/p' "$src/latest.json")
|
|
[[ "$file_name" =~ ^kvitt-[0-9]+\.apk$ ]] || die "latest.json has no valid file_name"
|
|
[[ -f "$src/$file_name" ]] || die "latest.json names $file_name, which is not in $src"
|
|
sha=$(sed -n 's/.*"sha256": *"\([0-9a-f]\{64\}\)".*/\1/p' "$src/latest.json")
|
|
[[ "$(sha256sum "$src/$file_name" | cut -d' ' -f1)" == "$sha" ]] || die "$file_name does not match the sha256 in latest.json"
|
|
|
|
# backup runs as uid 10001, the owner of /data, and has a shell; the server image has none.
|
|
# shellcheck disable=SC2016 # the script runs inside the backup container
|
|
compose run --rm --no-deps -T -v "$src:/incoming:ro" --entrypoint /bin/sh backup -c '
|
|
set -eu
|
|
mkdir -p /data/apk
|
|
for f in /incoming/kvitt-*.apk; do
|
|
[ -f "$f" ] || continue
|
|
n=$(basename "$f")
|
|
[ -f "/data/apk/$n" ] && cmp -s "$f" "/data/apk/$n" && continue
|
|
cp "$f" "/data/apk/.$n.tmp" && mv -f "/data/apk/.$n.tmp" "/data/apk/$n"
|
|
echo "copied $n"
|
|
done
|
|
cp /incoming/latest.json /data/apk/.latest.json.tmp
|
|
mv -f /data/apk/.latest.json.tmp /data/apk/latest.json
|
|
# keep the newest 5 APKs by version code
|
|
ls /data/apk | sed -n "s/^kvitt-\([0-9]*\)\.apk$/\1/p" | sort -n -r | tail -n +6 | while read -r v; do
|
|
rm -f "/data/apk/kvitt-$v.apk"; echo "removed kvitt-$v.apk"
|
|
done
|
|
echo "latest.json installed"'
|
|
say "GET https://$PUBLIC_HOST:$PUBLIC_PORT/apk/latest (via this Pi):"
|
|
curl -fsS --max-time 15 --resolve "$PUBLIC_HOST:$PUBLIC_PORT:127.0.0.1" "https://$PUBLIC_HOST:$PUBLIC_PORT/apk/latest" \
|
|
|| die "GET /apk/latest failed"
|
|
echo
|