Church Translate, as it runs at the campuses
Live sermon translation into 100+ languages, running entirely on one Mac per campus: audio in, voice detection, Whisper on the GPU, NLLB-200 in its own process, and a built-in web server the congregation reads from on their phones. No audio leaves the building. This is the source only. What used to sit beside it — 14 GB of speech and translation models, 8 GB of voice bundles, 6 GB of natural voices, the Electron caches, and the app data holding the operator PIN and every service transcript — is machine state and downloaded assets, and stays in the NAS backup where it belongs. The vendored node, cloudflared and Windows ffmpeg binaries are out too (533 MB, re-fetched for packaging); the browser-side runtime beside them is kept, so a checkout still serves the congregation page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user