From 0d04aefa0c5e7956cb2dbc80596910cbe53136bf Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 07:36:19 +0000 Subject: publish-apk: allow publishing from another machine over ssh 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 Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B --- bin/publish-apk | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 4 deletions(-) (limited to 'bin') 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 [--host H] [--dir D] [--keep N] # +# From another machine: PUBLISH_SSH=user@tungsten publish-apk +# # 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= publish-apk + 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" <&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" -- cgit v1.2.3