diff options
| -rw-r--r-- | README.md | 4 | ||||
| -rwxr-xr-x | bin/publish-apk | 13 | ||||
| -rwxr-xr-x | test/run-tests.sh | 26 |
3 files changed, 41 insertions, 2 deletions
@@ -137,7 +137,9 @@ Publishes a built APK to the static site, checking each thing that has gone wrong before: that this is the deploy host and not a box resembling it, that the file is genuinely an APK rather than an HTML error page, that what landed is byte-identical to what was tested, and that the web server serves those same -bytes. Backs up any existing file first. +bytes. Backs up any existing file first, keeping the two most recent (`--keep N`) — each backup is +a full copy of a large APK in a public directory, and four publishes in one evening left +448MB of them. ``` publish-apk app/build/outputs/apk/debug/app-debug.apk listen-debug.apk diff --git a/bin/publish-apk b/bin/publish-apk index faf6db8..520eeae 100755 --- a/bin/publish-apk +++ b/bin/publish-apk @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# publish-apk <apk> <published-name> [--host tungsten] [--dir /site/static.terst.org/public/files] +# publish-apk <apk> <published-name> [--host H] [--dir D] [--keep N] # # Publishes a built APK to the static site, verifying every step that has gone wrong before: # the host, that the file is really an APK, that what landed is byte-identical to what was @@ -15,6 +15,9 @@ die() { echo "publish-apk: $1" >&2; exit 2; } EXPECT_HOST="tungsten" DEST_DIR="/site/static.terst.org/public/files" +# Each backup is a full copy of a large APK, in a public directory. Four publishes in one +# evening left 448MB of them. Keep enough to roll back, not enough to become the problem. +KEEP_BACKUPS=2 [ $# -ge 2 ] || die "usage: publish-apk <apk> <published-name> [--host H] [--dir D]" APK="$1"; NAME="$2"; shift 2 @@ -22,6 +25,7 @@ while [ $# -gt 0 ]; do case "$1" in --host) EXPECT_HOST="$2"; shift 2 ;; --dir) DEST_DIR="$2"; shift 2 ;; + --keep) KEEP_BACKUPS="$2"; shift 2 ;; *) die "unknown option '$1'" ;; esac done @@ -71,6 +75,13 @@ else echo "backup : none needed (new file)" fi +# Prune before copying, so a failed publish doesn't leave the pruning undone too. +PRUNED=$(ls -t "$DEST".*.bak 2>/dev/null | tail -n +$((KEEP_BACKUPS + 1))) +if [ -n "$PRUNED" ]; then + echo "$PRUNED" | while read -r old; do rm -f "$old"; done + echo "pruned : $(echo "$PRUNED" | wc -l) old backup(s), keeping the newest $KEEP_BACKUPS" +fi + cp "$APK" "$DEST" || die "copy failed" chmod 644 "$DEST" || die "chmod failed" chown root:root "$DEST" 2>/dev/null || true diff --git a/test/run-tests.sh b/test/run-tests.sh index 415ecc3..0e655d0 100755 --- a/test/run-tests.sh +++ b/test/run-tests.sh @@ -292,5 +292,31 @@ else ok "accepts a real multi-entry APK (no SIGPIPE from the zip check)" fi +# 24. Backups are full copies of a large file in a public directory; without a cap they +# become the problem they were guarding against. +pubdir="$tmproot/pubdir"; mkdir -p "$pubdir" +cp "$realapk" "$pubdir/keep.apk" +# Distinct mtimes matter: an earlier version of this test gave all four the same time, so +# `ls -t` ordered them arbitrarily and the assertion proved nothing. +hour=1 +for stamp in 20260101-0100 20260101-0200 20260101-0300 20260101-0400; do + printf 'old' > "$pubdir/keep.apk.$stamp.bak" + touch -d "2026-01-01 0$hour:00:00" "$pubdir/keep.apk.$stamp.bak" + hour=$((hour + 1)) +done +out=$("$BIN/publish-apk" "$realapk" keep.apk --host "$(hostname)" --dir "$pubdir" --keep 2 2>&1) +remaining=$(ls "$pubdir"/keep.apk.*.bak 2>/dev/null | wc -l) +# 2 kept from before, plus the one this run made. +if [ "$remaining" -le 3 ]; then + ok "prunes old backups instead of accumulating them" +else + bad "backup pruning" "$remaining backups remain" +fi +if [ -f "$pubdir/keep.apk.20260101-0400.bak" ]; then + ok "keeps the most recent backups" +else + bad "pruning kept the wrong ones" "newest backup was deleted" +fi + printf '\n=== summary: %d passed, %d failed ===\n' "$pass" "$fail" [ "$fail" -eq 0 ] |
