summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-09-25 07:36:19 +0000
committerClaude <noreply@anthropic.com>2026-09-25 07:36:19 +0000
commit0d04aefa0c5e7956cb2dbc80596910cbe53136bf (patch)
tree9580972c3b17dc6ed55282f6227999de8007ecc0
parentc89250698116180acfe9a98c24e2e5291e050240 (diff)
publish-apk: allow publishing from another machine over sshHEADmain
The host gate stopped the Fedora laptop publishing at all, which left a build made there with nowhere to go. It now takes PUBLISH_SSH and sends the file to the real host instead. The gate itself is unchanged in spirit: without PUBLISH_SSH a build from elsewhere is still refused, because the failure it guards against is writing to a local path that merely looks like the deploy host's. Two tests cover that -- the error names the way out, and a failed remote publish never falls back to writing locally. Transfers land on a temporary name and move into place only once complete, and the remote copy is hash-compared against the source before the served bytes are checked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
-rw-r--r--README.md12
-rwxr-xr-xbin/publish-apk62
-rwxr-xr-xtest/run-tests.sh18
3 files changed, 88 insertions, 4 deletions
diff --git a/README.md b/README.md
index 6e8fc59..160c5ae 100644
--- a/README.md
+++ b/README.md
@@ -144,3 +144,15 @@ a full copy of a large APK in a public directory, and four publishes in one even
```
publish-apk app/build/outputs/apk/debug/app-debug.apk listen-debug.apk
```
+
+From another machine — the laptop — send it to the real host rather than
+writing locally:
+
+```
+PUBLISH_SSH=user@tungsten publish-apk app/build/outputs/apk/debug/app-debug.apk listen-debug.apk
+```
+
+The host check still applies: without `PUBLISH_SSH` a build from elsewhere is
+refused outright, and with it the file is copied to a temporary name and moved
+into place only once the transfer completes. The remote copy is hash-compared
+against the source and the served bytes are checked, same as a local publish.
diff --git a/bin/publish-apk b/bin/publish-apk
index 520eeae..9faf36a 100755
--- a/bin/publish-apk
+++ b/bin/publish-apk
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
# publish-apk <apk> <published-name> [--host H] [--dir D] [--keep N]
#
+# From another machine: PUBLISH_SSH=user@tungsten publish-apk <apk> <name>
+#
# 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
# tested, and that the web server actually serves it.
@@ -39,8 +41,20 @@ esac
# Publishing from a box that merely resembles the deploy host is how a previous session
# "deployed" to a sandbox and had to unwind it.
ACTUAL_HOST=$(hostname)
-[ "$ACTUAL_HOST" = "$EXPECT_HOST" ] \
- || die "host is '$ACTUAL_HOST', expected '$EXPECT_HOST'. Build and test here, publish there."
+REMOTE=0
+if [ "$ACTUAL_HOST" != "$EXPECT_HOST" ]; then
+ # Publishing from elsewhere is allowed, but only by sending the file to the real host --
+ # never by writing to a local path that merely looks like it. A previous session
+ # "deployed" to a sandbox exactly that way and had to unwind it.
+ if [ -n "${PUBLISH_SSH:-}" ]; then
+ REMOTE=1
+ else
+ die "host is '$ACTUAL_HOST', expected '$EXPECT_HOST'.
+ To publish from here, send it over ssh instead:
+ PUBLISH_SSH=<user@$EXPECT_HOST> publish-apk <apk> <name>
+ That copies to the real host rather than writing to a local path of the same name."
+ fi
+fi
[ -f "$APK" ] || die "no such file: $APK"
@@ -59,14 +73,54 @@ case "$LISTING" in
*) die "$APK has no AndroidManifest.xml — not an APK" ;;
esac
-[ -d "$DEST_DIR" ] || die "no such directory: $DEST_DIR"
+if [ $REMOTE -eq 0 ]; then
+ [ -d "$DEST_DIR" ] || die "no such directory: $DEST_DIR"
+fi
DEST="$DEST_DIR/$NAME"
echo "=== publish-apk ==="
-echo "host : $ACTUAL_HOST"
+echo "host : $ACTUAL_HOST$([ $REMOTE -eq 1 ] && echo " (publishing to $PUBLISH_SSH over ssh)")"
echo "source : $APK ($(stat -c%s "$APK") bytes)"
echo "dest : $DEST"
+if [ $REMOTE -eq 1 ]; then
+ echo "source : $APK ($(stat -c%s "$APK") bytes)"
+ LOCAL_SHA=$(sha256sum "$APK" | awk '{print $1}')
+
+ # Copy to a temp name first, so a half-transferred file is never the published one.
+ scp -q "$APK" "$PUBLISH_SSH:/tmp/$NAME.incoming" || die "scp failed"
+
+ ssh "$PUBLISH_SSH" "bash -s" <<REMOTE_SCRIPT || die "remote publish failed"
+set -euo pipefail
+dest="$DEST_DIR/$NAME"
+[ -d "$DEST_DIR" ] || { echo "no such directory: $DEST_DIR" >&2; exit 1; }
+[ -f "\$dest" ] && cp "\$dest" "\$dest.\$(date +%Y%m%d-%H%M%S).bak"
+ls -t "\$dest".*.bak 2>/dev/null | tail -n +$((KEEP_BACKUPS + 1)) | while read -r old; do rm -f "\$old"; done
+mv "/tmp/$NAME.incoming" "\$dest"
+chmod 644 "\$dest"
+chown root:root "\$dest" 2>/dev/null || true
+sha256sum "\$dest" | awk '{print \$1}'
+REMOTE_SCRIPT
+
+ REMOTE_SHA=$(ssh "$PUBLISH_SSH" "sha256sum '$DEST_DIR/$NAME'" 2>/dev/null | awk '{print $1}')
+ if [ "$LOCAL_SHA" = "$REMOTE_SHA" ]; then
+ echo "identity: published copy is byte-identical to the source"
+ else
+ die "published copy differs from the source (local $LOCAL_SHA, remote $REMOTE_SHA)"
+ fi
+
+ URL="https://static.terst.org/files/$NAME"
+ REMOTE_LEN=$(curl -sI "$URL" 2>/dev/null | tr -d '\r' | awk 'tolower($1)=="content-length:"{print $2}')
+ LOCAL_LEN=$(stat -c%s "$APK")
+ echo "url : $URL"
+ if [ "$REMOTE_LEN" = "$LOCAL_LEN" ]; then
+ echo "result : PASS -- served bytes match the tested build"
+ exit 0
+ fi
+ echo "result : FAIL -- server reports ${REMOTE_LEN:-nothing}, built $LOCAL_LEN"
+ exit 1
+fi
+
if [ -f "$DEST" ]; then
BACKUP="$DEST.$(date +%Y%m%d-%H%M%S).bak"
cp "$DEST" "$BACKUP" || die "could not back up the existing file"
diff --git a/test/run-tests.sh b/test/run-tests.sh
index 1c75cd7..5830224 100755
--- a/test/run-tests.sh
+++ b/test/run-tests.sh
@@ -329,5 +329,23 @@ else
bad "pruning kept the wrong ones" "newest backup was deleted"
fi
+# 25. The wrong-host message has to name the way out, or the laptop is simply stuck.
+out=$("$BIN/publish-apk" /dev/null thing.apk --host definitely-not-this-host 2>&1); rc=$?
+if [ $rc -eq 2 ] && printf '%s' "$out" | grep -q "PUBLISH_SSH"; then
+ ok "wrong-host error explains how to publish remotely"
+else
+ bad "ssh hint" "rc=$rc out=$out"
+fi
+
+# 26. PUBLISH_SSH must not become a way to write locally on the wrong host -- the whole point
+# is that it sends the file to the real one.
+out=$(PUBLISH_SSH="nobody@invalid.invalid" "$BIN/publish-apk" "$realapk" thing.apk \
+ --host definitely-not-this-host --dir "$tmproot/should-not-be-touched" 2>&1); rc=$?
+if [ $rc -ne 0 ] && [ ! -e "$tmproot/should-not-be-touched/thing.apk" ]; then
+ ok "remote publish never falls back to writing locally"
+else
+ bad "remote fallback" "rc=$rc; local file created?"
+fi
+
printf '\n=== summary: %d passed, %d failed ===\n' "$pass" "$fail"
[ "$fail" -eq 0 ]