Replace unreliable AppleScript PowerPoint API (run slide show of settings) with System Events keystroke approach, matching proven behavior from the old MasterKey app. Opens the file, waits 3s for load, then sends Cmd+Return to start the slideshow from slide 1.
221 lines
8.2 KiB
Bash
Executable File
221 lines
8.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# deploy.sh — Deploy Aether Native Launcher to onsite Mac laptops
|
|
#
|
|
# USAGE:
|
|
# ./deploy.sh <num> [num ...] Deploy to one or more laptops (e.g. 03 04 05)
|
|
# ./deploy.sh all Deploy to all laptops in devices.conf
|
|
# ./deploy.sh --seed-only <num> Update seed.json only — skip .app copy
|
|
# ./deploy.sh --seed-only all
|
|
# ./deploy.sh --build <num> [num ...] Build first (npm run package:mac), then deploy
|
|
# ./deploy.sh --build all
|
|
#
|
|
# REQUIRES:
|
|
# event.env — copy from event.env.example and fill in AETHER_API_KEY
|
|
# builds/ — pre-built, or use --build to build before deploying
|
|
#
|
|
# SSH keys must already be installed on each target (run ssh-copy-id once per laptop).
|
|
|
|
set -uo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DEVICES_FILE="$SCRIPT_DIR/devices.conf"
|
|
EVENT_ENV="$SCRIPT_DIR/event.env"
|
|
SSH_USER="speaker ready"
|
|
BUILD_DIR="$SCRIPT_DIR/../builds"
|
|
|
|
# ── Argument parsing ──────────────────────────────────────────────────────────
|
|
|
|
SEED_ONLY=false
|
|
BUILD_FIRST=false
|
|
TARGETS=()
|
|
|
|
usage() {
|
|
grep '^#' "$0" | grep -v '^#!/' | sed 's/^# \{0,1\}//'
|
|
exit 1
|
|
}
|
|
|
|
if [[ $# -eq 0 ]]; then usage; fi
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--seed-only) SEED_ONLY=true ;;
|
|
--build) BUILD_FIRST=true ;;
|
|
--help|-h) usage ;;
|
|
all) TARGETS+=("all") ;;
|
|
*) TARGETS+=("$1") ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [[ ${#TARGETS[@]} -eq 0 ]]; then
|
|
echo "ERROR: No targets specified."
|
|
usage
|
|
fi
|
|
|
|
# ── Load event config ─────────────────────────────────────────────────────────
|
|
|
|
if [[ ! -f "$EVENT_ENV" ]]; then
|
|
echo "ERROR: $EVENT_ENV not found."
|
|
echo " Copy event.env.example → event.env and fill in AETHER_API_KEY."
|
|
exit 1
|
|
fi
|
|
|
|
# shellcheck source=/dev/null
|
|
source "$EVENT_ENV"
|
|
|
|
: "${AETHER_API_KEY:?event.env must set AETHER_API_KEY}"
|
|
: "${PRIMARY_API_BASE_URL:?event.env must set PRIMARY_API_BASE_URL}"
|
|
: "${BACKUP_API_BASE_URL:?event.env must set BACKUP_API_BASE_URL}"
|
|
|
|
# Render onsite URL as JSON null or quoted string
|
|
if [[ -n "${ONSITE_API_BASE_URL:-}" ]]; then
|
|
ONSITE_JSON="\"$ONSITE_API_BASE_URL\""
|
|
else
|
|
ONSITE_JSON="null"
|
|
fi
|
|
|
|
# ── Device list helpers ───────────────────────────────────────────────────────
|
|
|
|
# Returns "ip device_id" for a laptop number, or empty if not found
|
|
lookup_device() {
|
|
local num="$1"
|
|
grep -v '^[[:space:]]*#' "$DEVICES_FILE" \
|
|
| grep -E "^[[:space:]]*${num}[[:space:]]" \
|
|
| awk '{print $2, $3}' \
|
|
| head -1
|
|
}
|
|
|
|
# Returns all laptop numbers from devices.conf (first column, non-comment lines)
|
|
all_device_nums() {
|
|
grep -v '^[[:space:]]*#' "$DEVICES_FILE" \
|
|
| grep -v '^[[:space:]]*$' \
|
|
| awk '{print $1}'
|
|
}
|
|
|
|
# ── Deploy one laptop ─────────────────────────────────────────────────────────
|
|
|
|
deploy_laptop() {
|
|
local num="$1"
|
|
|
|
local info
|
|
info=$(lookup_device "$num")
|
|
if [[ -z "$info" ]]; then
|
|
echo " ERROR: Laptop $num not found in devices.conf"
|
|
return 1
|
|
fi
|
|
|
|
local ip device_id
|
|
ip=$(echo "$info" | awk '{print $1}')
|
|
device_id=$(echo "$info" | awk '{print $2}')
|
|
|
|
echo ""
|
|
echo "══════════════════════════════════════════════"
|
|
echo " Laptop $num · $ip · $device_id"
|
|
echo "══════════════════════════════════════════════"
|
|
|
|
# ── Copy .app ──────────────────────────────────────────────────────────
|
|
if [[ "$SEED_ONLY" != "true" ]]; then
|
|
echo " Detecting architecture..."
|
|
local arch
|
|
arch=$(ssh "$SSH_USER@$ip" "uname -m" 2>/dev/null) || {
|
|
echo " ERROR: SSH failed for $ip — is the laptop on the network?"
|
|
return 1
|
|
}
|
|
|
|
local bundle
|
|
case "$arch" in
|
|
x86_64) bundle="$BUILD_DIR/aether_launcher-darwin-x64/aether_launcher.app" ;;
|
|
arm64) bundle="$BUILD_DIR/aether_launcher-darwin-arm64/aether_launcher.app" ;;
|
|
*)
|
|
echo " ERROR: Unknown arch '$arch' on $ip"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
if [[ ! -d "$bundle" ]]; then
|
|
echo " ERROR: Build not found: $bundle"
|
|
echo " Run: npm run package:mac"
|
|
return 1
|
|
fi
|
|
|
|
echo " Arch: $arch → syncing $(basename "$bundle")..."
|
|
# rsync --delete syncs contents in-place without removing the top-level .app dir.
|
|
# This preserves the inode so macOS Aliases and Desktop shortcuts keep working.
|
|
rsync -a --delete -e ssh "$bundle/" "$SSH_USER@$ip:/Applications/aether_launcher.app/" || {
|
|
echo " ERROR: rsync failed."
|
|
return 1
|
|
}
|
|
echo " .app synced."
|
|
else
|
|
echo " (--seed-only: skipping .app copy)"
|
|
fi
|
|
|
|
# ── Write seed.json ────────────────────────────────────────────────────
|
|
echo " Writing seed.json..."
|
|
ssh "$SSH_USER@$ip" "cat > ~/seed.json" <<EOF
|
|
{
|
|
"event_device_id": "$device_id",
|
|
"aether_api_key": "$AETHER_API_KEY",
|
|
"primary_api_base_url": "$PRIMARY_API_BASE_URL",
|
|
"backup_api_base_url": "$BACKUP_API_BASE_URL",
|
|
"onsite_api_base_url": $ONSITE_JSON
|
|
}
|
|
EOF
|
|
|
|
# ── Verify ─────────────────────────────────────────────────────────────
|
|
echo " Verifying..."
|
|
ssh "$SSH_USER@$ip" "cat ~/seed.json"
|
|
if [[ "$SEED_ONLY" != "true" ]]; then
|
|
ssh "$SSH_USER@$ip" \
|
|
"test -d /Applications/aether_launcher.app && echo ' ✓ .app present' || echo ' ✗ .app NOT found'"
|
|
fi
|
|
|
|
echo " ✓ Laptop $num done."
|
|
return 0
|
|
}
|
|
|
|
# ── Build if requested ───────────────────────────────────────────────────────
|
|
|
|
if [[ "$BUILD_FIRST" == "true" ]]; then
|
|
echo "══════════════════════════════════════════════"
|
|
echo " Building: npm run package:mac"
|
|
echo "══════════════════════════════════════════════"
|
|
(cd "$SCRIPT_DIR/.." && npm run package:mac) || {
|
|
echo "ERROR: Build failed. Aborting deploy."
|
|
exit 1
|
|
}
|
|
echo ""
|
|
fi
|
|
|
|
# ── Expand "all" target ───────────────────────────────────────────────────────
|
|
|
|
EXPANDED_TARGETS=()
|
|
for t in "${TARGETS[@]}"; do
|
|
if [[ "$t" == "all" ]]; then
|
|
while IFS= read -r num; do
|
|
EXPANDED_TARGETS+=("$num")
|
|
done < <(all_device_nums)
|
|
else
|
|
EXPANDED_TARGETS+=("$t")
|
|
fi
|
|
done
|
|
|
|
# ── Run deploys ───────────────────────────────────────────────────────────────
|
|
|
|
FAILED=()
|
|
for num in "${EXPANDED_TARGETS[@]}"; do
|
|
if ! deploy_laptop "$num"; then
|
|
FAILED+=("$num")
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
echo "══════════════════════════════════════════════"
|
|
if [[ ${#FAILED[@]} -eq 0 ]]; then
|
|
echo " All done. ✓"
|
|
else
|
|
echo " FAILED: ${FAILED[*]}"
|
|
echo " Re-run with just those numbers to retry."
|
|
exit 1
|
|
fi
|