6e5981d62a
The Kvitt Pi runs Kvitt from /home/jacobhagstedt/kvitt: compose.yaml, .env, secrets/ and relative bind mounts ./data, ./backups, ./import. Both containers run as the owner's uid/gid (KVITT_UID, KVITT_GID). No install script, no /opt, no Docker daemon or system changes; operations are plain docker compose commands (README "Pi: what the owner does", RESTORE.md). Removed install.sh, deploy.sh, bootstrap.sh, backup-now.sh, install-apk.sh, lib.sh. .env holds only the keys compose.yaml reads; RCLONE_REMOTE is fixed to gdrive: in compose.yaml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
101 lines
5.7 KiB
Bash
Executable File
101 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build the release APK on the Mac and stage it for the Pi (SPEC §10.6, D-100).
|
|
# Usage, from the main checkout: infra/publish-apk.sh
|
|
# 1. refuses a dirty working tree (the version is derived from HEAD)
|
|
# 2. version (SPEC §10.6): version_code = `git rev-list --count HEAD`, version_name = YYYY.MM.DD-<short sha> of
|
|
# HEAD's commit date (Europe/Stockholm), passed as -Pkvitt.versionCode / -Pkvitt.versionName
|
|
# 3. reads KVITT_PUBLIC_HOST from infra/pi/.env (read only; refuses a missing file or key, or a CHANGE_ME value) and
|
|
# passes it as -Pkvitt.publicHost, the host of the app's release server and invite links (D-121)
|
|
# 4. :app:assembleRelease through tools/gradle-serial.sh (JDK 25 from Homebrew on PATH, JAVA_HOME untouched)
|
|
# 5. writes into infra/pi/out/apk/: kvitt-<version_code>.apk, kvitt-<version_code>.mapping.txt (R8 mapping, for
|
|
# reading stack traces; never served), and latest.json (written to a temp file, then renamed)
|
|
# 6. keeps the newest 5 APKs (and their mappings) in infra/pi/out/apk/
|
|
# Then the owner copies the APK and then latest.json into ~/kvitt/data/apk/ on the Pi with scp (infra/README.md).
|
|
# Refuses an unsigned build: without the kvitt.release.* keys in android/local.properties the APK cannot be
|
|
# installed over an earlier release (D-024, D-101).
|
|
set -euo pipefail
|
|
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$repo_root"
|
|
|
|
[[ -z "$(git status --porcelain)" ]] || { echo "publish-apk.sh: the working tree is dirty; commit or stash first." >&2; exit 1; }
|
|
|
|
version_code="$(git rev-list --count HEAD)"
|
|
short_sha="$(git rev-parse --short=7 HEAD)"
|
|
commit_day="$(TZ=Europe/Stockholm git show -s --date=format-local:%Y.%m.%d --format=%cd HEAD)"
|
|
version_name="$commit_day-$short_sha"
|
|
|
|
# The public host the app is built for (D-121), the value the Pi's compose.yaml reads from the same .env (D-120).
|
|
env_file="$repo_root/infra/pi/.env"
|
|
[[ -f "$env_file" ]] || { echo "publish-apk.sh: no infra/pi/.env; cp infra/pi/.env.example infra/pi/.env and set KVITT_PUBLIC_HOST." >&2; exit 1; }
|
|
KVITT_PUBLIC_HOST="$(sed -n 's/^KVITT_PUBLIC_HOST=//p' "$env_file" | tail -n 1)"
|
|
[[ -n "$KVITT_PUBLIC_HOST" ]] || { echo "publish-apk.sh: infra/pi/.env has no KVITT_PUBLIC_HOST (or it is empty)." >&2; exit 1; }
|
|
[[ "$KVITT_PUBLIC_HOST" != CHANGE_ME* ]] || { echo "publish-apk.sh: KVITT_PUBLIC_HOST in infra/pi/.env is still $KVITT_PUBLIC_HOST." >&2; exit 1; }
|
|
[[ "$KVITT_PUBLIC_HOST" =~ ^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?\.[A-Za-z]{2,}$ ]] \
|
|
|| { echo "publish-apk.sh: KVITT_PUBLIC_HOST in infra/pi/.env must be a host name such as kvitt.example.org (no scheme, no port), not $KVITT_PUBLIC_HOST." >&2; exit 1; }
|
|
[[ "$KVITT_PUBLIC_HOST" != icehack.asuscomm.com ]] \
|
|
|| { echo "publish-apk.sh: KVITT_PUBLIC_HOST cannot be icehack.asuscomm.com, NPM's proxy host for Home Assistant (D-120)." >&2; exit 1; }
|
|
|
|
echo "Release $version_name (version_code $version_code) for https://$KVITT_PUBLIC_HOST"
|
|
|
|
PATH="/opt/homebrew/opt/openjdk@25/bin:$PATH" tools/gradle-serial.sh :app:assembleRelease \
|
|
"-Pkvitt.versionCode=$version_code" "-Pkvitt.versionName=$version_name" "-Pkvitt.publicHost=$KVITT_PUBLIC_HOST"
|
|
|
|
out_dir="$repo_root/android/app/build/outputs/apk/release"
|
|
apk="$out_dir/app-release.apk"
|
|
mapping="$repo_root/android/app/build/outputs/mapping/release/mapping.txt"
|
|
if [[ ! -f "$apk" ]]; then
|
|
if [[ -f "$out_dir/app-release-unsigned.apk" ]]; then
|
|
echo "publish-apk.sh: the build is unsigned (no kvitt.release.* keys in android/local.properties); not published." >&2
|
|
else
|
|
echo "publish-apk.sh: no APK in $out_dir" >&2
|
|
fi
|
|
exit 1
|
|
fi
|
|
# The APK must be the one just built: output-metadata.json carries the version code Gradle used.
|
|
python3 - "$out_dir/output-metadata.json" "$version_code" <<'PY'
|
|
import json, sys
|
|
meta = json.load(open(sys.argv[1]))
|
|
codes = {str(e.get("versionCode")) for e in meta.get("elements", [])}
|
|
if sys.argv[2] not in codes:
|
|
sys.exit(f"publish-apk.sh: output-metadata.json has version codes {sorted(codes)}, expected {sys.argv[2]}")
|
|
PY
|
|
[[ -f "$mapping" ]] || { echo "publish-apk.sh: no R8 mapping at $mapping" >&2; exit 1; }
|
|
|
|
dest="$repo_root/infra/pi/out/apk"
|
|
mkdir -p "$dest"
|
|
file_name="kvitt-$version_code.apk"
|
|
cp "$apk" "$dest/.$file_name.tmp" && mv -f "$dest/.$file_name.tmp" "$dest/$file_name"
|
|
cp "$mapping" "$dest/kvitt-$version_code.mapping.txt"
|
|
size_bytes="$(wc -c < "$dest/$file_name" | tr -d ' ')"
|
|
sha256="$(shasum -a 256 "$dest/$file_name" | cut -d' ' -f1)"
|
|
released_at="$(python3 -c 'import datetime; t=datetime.datetime.now(datetime.timezone.utc); print(t.strftime("%Y-%m-%dT%H:%M:%S.") + f"{t.microsecond // 1000:03d}Z")')"
|
|
|
|
tmp="$(mktemp "$dest/.latest.json.XXXXXX")"
|
|
cat > "$tmp" <<JSON
|
|
{
|
|
"version_code": $version_code,
|
|
"version_name": "$version_name",
|
|
"file_name": "$file_name",
|
|
"size_bytes": $size_bytes,
|
|
"sha256": "$sha256",
|
|
"released_at": "$released_at"
|
|
}
|
|
JSON
|
|
chmod 0644 "$tmp"
|
|
mv -f "$tmp" "$dest/latest.json"
|
|
|
|
# Keep the newest 5 APKs by version code, with their mappings.
|
|
for f in "$dest"/kvitt-*.apk; do
|
|
[[ -f "$f" ]] || continue
|
|
basename "$f" | sed -n 's/^kvitt-\([0-9]*\)\.apk$/\1/p'
|
|
done | sort -n -r | tail -n +6 | while read -r old; do
|
|
rm -f "$dest/kvitt-$old.apk" "$dest/kvitt-$old.mapping.txt"
|
|
echo "removed kvitt-$old.apk and its mapping"
|
|
done
|
|
|
|
echo "Staged in infra/pi/out/apk/: $file_name ($size_bytes bytes, sha256 $sha256) and latest.json."
|
|
echo "Built for the release server https://$KVITT_PUBLIC_HOST (KVITT_PUBLIC_HOST in infra/pi/.env)."
|
|
echo "Copy to the Pi, the APK first and latest.json last (infra/README.md, Publishing an APK):"
|
|
echo " scp infra/pi/out/apk/$file_name jacobhagstedt@<kvitt-pi-address>:kvitt/data/apk/"
|
|
echo " scp infra/pi/out/apk/latest.json jacobhagstedt@<kvitt-pi-address>:kvitt/data/apk/"
|