#!/bin/bash # # install-latest.sh — quit, replace, relaunch. One step, repeatable. # # Doing this by hand meant three builds in a row were staged and never # installed, so the app under test was two versions behind what the log said # was fixed. That is worse than no build at all. # set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" REL="$ROOT/Releases" DIST="$ROOT/app/dist" APPS="/Applications" NAME="Church Translate.app" # Both places, newest first. `npm run dist` writes to app/dist and only a # release copy lands in Releases/ — looking in one of them installed a build # that was two versions behind the one just compiled, and said "3.8.0 -> 3.8.0" # while doing it. That is the exact failure this script was written to stop. DMG="$(ls -t "$REL"/*.dmg "$DIST"/*.dmg 2>/dev/null | head -1)" [ -n "$DMG" ] || { echo "No .dmg in $REL or $DIST"; exit 1; } echo "installing: $(basename "$DMG")" # Stale volumes from earlier installs keep the old version's name around and # make it genuinely hard to tell what is mounted. for v in /Volumes/*Church\ Translate*; do [ -d "$v" ] && { echo " ejecting stale $v"; hdiutil detach "$v" -quiet -force || true; } done # NEVER install over a live service. main.js deliberately blocks its own window # close with a confirmation dialog while translation is live, so that nobody # takes the captions down mid-sermon — and this script's force-terminate would # walk straight through that protection. Ask the server instead. if pgrep -f "$NAME/Contents/MacOS" >/dev/null; then CFG="$HOME/Library/Application Support/Church Translate/data/config.json" KEY="$(/usr/bin/python3 -c "import json,sys;print(json.load(open(sys.argv[1])).get('controlToken',''))" "$CFG" 2>/dev/null || true)" if [ -n "$KEY" ]; then ST="$(curl -s --max-time 4 "http://127.0.0.1:3100/api/status.txt?key=$KEY" 2>/dev/null || true)" if [ "$ST" = "live" ]; then echo "REFUSING TO INSTALL: translation is LIVE right now." echo "Stop the service first — installing would cut the captions for everyone listening." exit 2 fi [ -n "$ST" ] && echo " service is '$ST' (not live) — safe to replace" fi echo " quitting the running app" osascript -e 'tell application "Church Translate" to quit' 2>/dev/null || true for _ in $(seq 1 50); do # 25s: it shuts three model children down first pgrep -f "$NAME/Contents/MacOS" >/dev/null || break sleep 0.5 done # The forked children (server, mt, tts) exit with the parent; if the parent # is wedged, take it down rather than installing under a live process. pgrep -f "$NAME/Contents/MacOS" >/dev/null && { echo " did not quit in 25s — terminating" pkill -f "$NAME/Contents/MacOS" || true sleep 2 } fi MNT="$(mktemp -d /tmp/ct-mnt.XXXXXX)" hdiutil attach "$DMG" -nobrowse -quiet -mountpoint "$MNT" trap 'hdiutil detach "$MNT" -quiet -force >/dev/null 2>&1 || true; rmdir "$MNT" 2>/dev/null || true' EXIT # Prove the replacement exists BEFORE removing what works. [ -d "$MNT/$NAME" ] || { echo "no $NAME inside the dmg"; exit 1; } NEW="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$MNT/$NAME/Contents/Info.plist")" NEWB="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$MNT/$NAME/Contents/Info.plist" 2>/dev/null || echo '?')" OLD="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$APPS/$NAME/Contents/Info.plist" 2>/dev/null || echo 'none')" OLDB="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$APPS/$NAME/Contents/Info.plist" 2>/dev/null || echo '0')" echo " $OLD (build $OLDB) -> $NEW (build $NEWB)" # Installing the same build over itself looks identical to a successful # install and is how a fix gets reported as shipped when it is not on disk. if [ "$OLD" = "$NEW" ] && [ "$OLDB" = "$NEWB" ]; then echo " NOTE: that is the build already installed — nothing changed." echo " Did you forget to bump version/buildNumber, or to run npm run dist?" fi rm -rf "$APPS/$NAME" ditto "$MNT/$NAME" "$APPS/$NAME" # The build is deliberately unsigned, so a fresh copy off a disk image carries # a quarantine flag and Gatekeeper refuses to open it without a right-click. # This is the user's own locally built app being installed at their request. xattr -dr com.apple.quarantine "$APPS/$NAME" 2>/dev/null || true open -a "$APPS/$NAME" echo " launched"