summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rwxr-xr-xbin/publish-apk13
-rwxr-xr-xtest/run-tests.sh26
3 files changed, 41 insertions, 2 deletions
diff --git a/README.md b/README.md
index eff764a..6e8fc59 100644
--- a/README.md
+++ b/README.md
@@ -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 ]