Every campus matches NA, tailored: Restart page, Companion-Mac programs, NA's auto-fix hours, nightly backups, restart from NA
- Auto-setup: programs on the Companion Mac itself (ProPresenter at Woonsocket) get a local process check and local start/restart fixes; every campus gets a Restart page (keys arrive as computers join); NA's auto-fix hours (Tue, Wed, Sun); a program found after its Companion connection joins that key instead of making a second one. settings.campus rebuild remakes a campus from what is found now, keeping its name, PIN and pages; upgrade-companion brings Companion to 5.0.7 from NA's copy (/campus/companion.zip), as Woonsocket was; setup stops with that advice on an older one. - NA's CAMPUSES page has a RESTART key per campus; the CAMPUS page shows that campus's Restart page and a hold there restarts it at that campus. - Backups: backup/backup-campuses.sh, nightly 04:30 on NA (installed everywhere, acts on the hub), into Drive > Waters Creative > Apps > Status Panel Backups/<campus>, 30 kept, alert email via the checklist's alert.js. Campuses report the account to back up with. sites/ws/config.json in the repo. - Woonsocket rebuilt: 9 keys (PROP with ProPresenter on its Mac), Restart page 20 (SUPERRACK, VISTA, ABLETON, PROP), auto-fix on NA's hours; all OK. Both campuses backed up to Drive by the job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
# backup-campuses.sh - every campus's status panel settings, off-site in Google Drive, nightly.
|
||||
#
|
||||
# The same pattern as the checklist's backup-to-drive.sh on this Mac: a LaunchAgent runs it at 04:30,
|
||||
# it copies into the shared Drive (Waters Creative > Apps > Status Panel Backups > <campus>), keeps
|
||||
# the newest 30 per campus, and emails an alert (through the checklist's alert.js) when something
|
||||
# fails, because a backup nobody hears about failing is no backup.
|
||||
#
|
||||
# Only the hub (North Attleboro: no "home" in campuses.json) does anything; elsewhere it exits.
|
||||
# Each campus is reached over SSH with the panel's own key, as the account it reported. What is
|
||||
# copied: config.json (the settings), history/ (every Apply), the page-key record and the campus
|
||||
# lists. Never outlets.json (the smart-plug keys stay on their Mac).
|
||||
|
||||
set -uo pipefail
|
||||
DEST="$HOME/Library/Application Support/status-panel"
|
||||
LOG="$HOME/Library/Logs/status-panel-backup.log"
|
||||
KEEP=30
|
||||
KEY="$HOME/.ssh/id_status_panel"
|
||||
FILES="config.json history nav_locations.json campuses.json computers.json discover-seen.json"
|
||||
log() { printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG"; echo "$*"; }
|
||||
alert() {
|
||||
log "ALERT: $1"
|
||||
local js="$HOME/checklist-app/scripts/alert.js"
|
||||
[ -f "$js" ] && node "$js" "Status panel backup: $1" "${2:-}$(printf '\n\nLog: %s' "$LOG")" >> "$LOG" 2>&1 || true
|
||||
}
|
||||
|
||||
/usr/bin/python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); sys.exit(0 if d.get("list") and not d.get("home") else 1)' \
|
||||
"$DEST/campuses.json" 2>/dev/null || { log "not the hub: nothing to back up from here"; exit 0; }
|
||||
|
||||
DRIVE=""
|
||||
for acct in "$HOME/Library/CloudStorage"/GoogleDrive-*; do
|
||||
[ -d "$acct/Shared drives/Waters Creative/Apps" ] && { DRIVE="$acct/Shared drives/Waters Creative/Apps/Status Panel Backups"; break; }
|
||||
done
|
||||
if [ -z "$DRIVE" ]; then
|
||||
alert "tonight's backup did not run - the Google Drive folder is missing" \
|
||||
"Shared drives > Waters Creative > Apps could not be found on this Mac. Usually Google Drive is not running or signed out."
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$DRIVE" || { alert "could not create $DRIVE"; exit 1; }
|
||||
log "── run start ── into $DRIVE"
|
||||
|
||||
STAMP="$(date '+%Y-%m-%d')"
|
||||
FAILED=""
|
||||
while IFS=$'\t' read -r id user host; do
|
||||
[ -z "$id" ] && continue
|
||||
mkdir -p "$DRIVE/$id"
|
||||
OUT="$DRIVE/$id/$id-$STAMP.tgz"
|
||||
TMP="$(mktemp -t status-backup)"
|
||||
if [ "$host" = "self" ]; then
|
||||
(cd "$DEST" && tar -czf "$TMP" $(ls -d $FILES 2>/dev/null)) 2>>"$LOG"
|
||||
else
|
||||
ssh -n -i "$KEY" -o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new "$user@$host" \
|
||||
"cd 'Library/Application Support/status-panel' && tar -czf - \$(ls -d $FILES 2>/dev/null)" > "$TMP" 2>>"$LOG"
|
||||
fi
|
||||
# Listing it proves it whole (not gzip -t: macOS tar pads a piped archive, which gzip calls "trailing garbage").
|
||||
if [ -s "$TMP" ] && tar -tzf "$TMP" 2>/dev/null | grep -qx 'config.json'; then
|
||||
cp "$TMP" "$OUT.part" && mv "$OUT.part" "$OUT" && log "✓ $id -> $(basename "$OUT") ($(du -k "$OUT" | cut -f1) KB)"
|
||||
ls -1t "$DRIVE/$id"/"$id"-*.tgz 2>/dev/null | tail -n +$((KEEP + 1)) | while read -r old; do rm -f "$old"; done
|
||||
else
|
||||
FAILED="$FAILED $id"
|
||||
log "✗ $id: nothing usable came back from $user@$host"
|
||||
fi
|
||||
rm -f "$TMP"
|
||||
done < <(/usr/bin/python3 - "$DEST/campuses.json" "$DEST/config.json" <<'PY'
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1])); me = (json.load(open(sys.argv[2])).get("site") or {}).get("id")
|
||||
for cid, e in sorted((d.get("list") or {}).items()):
|
||||
if cid == me:
|
||||
print("%s\t-\tself" % cid)
|
||||
elif e.get("user") and e.get("url"):
|
||||
print("%s\t%s\t%s" % (cid, e["user"], e["url"].split("//")[1].split(":")[0]))
|
||||
else:
|
||||
print("%s\t?\t?" % cid)
|
||||
PY
|
||||
)
|
||||
if [ -n "$FAILED" ]; then
|
||||
alert "no backup tonight for:$FAILED" "The other campuses were backed up. A campus missing here is usually off, off the network, or no longer letting this Mac log in."
|
||||
exit 1
|
||||
fi
|
||||
log "── done ──"
|
||||
Reference in New Issue
Block a user