From 98739bd0418fa1392cdc39aa50d84e9131effaf8 Mon Sep 17 00:00:00 2001 From: christian Date: Thu, 15 Oct 2026 15:02:17 -0400 Subject: [PATCH] Never undo settings applied on the page: sync from NA Ingest, rebase stale drafts, guard the installer - config.json is NA Ingest's live settings: the READY hours (Tue 14-22, Wed 9-19, Sun 5-14) and the auto-fix hours (Tue 14-22, Wed 9-21, Sun 6:30-14) set on the settings page. - Settings page: a draft carries what its page started from. A tab that loaded the settings before they changed (an Apply elsewhere, a program update) now has only its own edits put on top of the live settings; before, it saved its whole older copy and Apply quietly undid the newer values (it took ready.prop_host out twice today). - install.sh refuses (exit 4) to replace live settings that came from an Apply (their fingerprint is a history entry's sha_after) unless --replace-config; --keep-config still updates code only. - Tests pin their own auto-fix and READY hours, so the campus can change its own; the exit-timeout test expects 310 s (the Tascam outlet power-cycle). Co-Authored-By: Claude Opus 5.5 --- config.json | 3230 ++++++++++++++++++------------------ install.sh | 27 + settings/server.py | 12 +- settings/ui/app.js | 14 +- tests/test_readiness.py | 2 + tests/test_schema.py | 4 +- tests/test_settings.py | 21 + tests/test_status_panel.py | 5 +- 8 files changed, 1702 insertions(+), 1613 deletions(-) diff --git a/config.json b/config.json index 4d5743c..06f74a7 100644 --- a/config.json +++ b/config.json @@ -1,81 +1,560 @@ { - "site": { - "id": "na", - "name": "North Attleboro", - "ingest_name": "NA Ingest", - "ingest_hostname": "na-ingest.local", - "ingest_ips": [ - "10.1.1.1", - "10.6.3.222" + "alert_webhook": "", + "atem": { + "host": "10.1.10.2", + "library": "~/Library/Application Support/companion/modules/bmd-atem-*/node_modules/atem-connection", + "node": "/Applications/Companion [0-9]*.app/Contents/Resources/node-runtimes/node22/bin/node", + "reference": "atem-reference.xml", + "source_folder": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/Shared drives/Waters Creative/Production Team/Backup/NA/ATEM" + }, + "auto_fix": { + "after_seconds": 20, + "fixes": [ + "hyperdeck", + "tascam_reconnect", + "midi_reconnect", + "rtpmidi_start", + "vista", + "arena", + "obs", + "ableton", + "translate", + "dvs_prop", + "dvs_tracks", + "dvs_translation", + "solo_afl", + "atem_audio", + "atem_camera", + "atem_midi", + "spotify_play" ], - "live_services": [], - "editing_code": "7381" + "max_tries": 2, + "pause_after_network_seconds": 60, + "restart_connections": true, + "retry_seconds": 90, + "while_muted": [ + "spotify_play" + ], + "windows": [ + { + "days": [ + "tue" + ], + "end": "22:00", + "start": "14:00" + }, + { + "days": [ + "wed" + ], + "end": "21:00", + "start": "09:00" + }, + { + "days": [ + "sun" + ], + "end": "14:00", + "start": "06:30" + } + ] }, "companion": { "host": "127.0.0.1", "port": 8000 }, - "page": 19, - "page_name": "19\\nStatus", - "nav_keys": [ - "pagenum" - ], - "nav_hold_ms": 1000, - "nav_tap_page": 1, - "variables": { - "state": "sp_state", - "heartbeat": "sp_hb", - "command": "sp_cmd" - }, - "service_windows": [ - { - "days": [ - "mon", - "tue", - "wed", - "thu", - "fri", - "sat", - "sun" - ], - "start": "00:00", - "end": "23:59" - } - ], - "debounce_seconds": 3, - "flap": { - "count": 3, - "window_seconds": 600 - }, - "network": { - "interface": "en0", - "gateway": "10.1.0.1", - "burst_connections": 4, - "burst_groups": 3, - "burst_window_seconds": 10, - "remember_seconds": 300 - }, - "alert_webhook": "", - "timing": { - "fix_cooldown_seconds": 60, - "alert_after_seconds": 30 - }, - "page_hold_ms": 2000, - "spotify": { - "playlist": "spotify:playlist:2uTpJVYQLFnKF1WnSOuco8", - "name": "Christian EDM 2026", - "open_wait": 8, - "play_wait": 3 - }, - "ssh_key": "~/.ssh/id_status_panel", "dante_switches": [ "10.66.1.100", "10.66.1.102" ], + "debounce_seconds": 3, + "failover": { + "after_seconds": 2, + "note_seconds": 900, + "windows": [ + { + "days": [ + "sun" + ], + "end": "13:30", + "start": "06:30" + } + ] + }, + "fixes": { + "ableton": { + "host": "tracks", + "kind": "ssh", + "label": "start Ableton if it is not running, with the set it last had open", + "params": { + "app": "Ableton Live 11 Suite", + "host": "tracks", + "restart": false + }, + "run": "pgrep -xq Live || { L=$(ls -td ~/Library/Preferences/Ableton/Live*/ | head -1); S=$(grep -a 'info: Loading document \"' \"${L}Log.txt\" | grep -v '/Templates/' | tail -1 | sed -E 's/^.*Loading document \"(.*)\"[^\"]*$/\\1/'); if [ -n \"$S\" ] && [ -f \"$S\" ]; then open -a 'Ableton Live 11 Suite' \"$S\"; else open -a 'Ableton Live 11 Suite'; fi; }", + "short": "ABLETON", + "template": "ableton" + }, + "ableton_restart": { + "conn": "ableton", + "host": "tracks", + "kind": "ssh", + "label": "restart Ableton on the Tracks Mac and reopen the set it had open (unsaved changes are lost; Live may ask to recover on the Tracks Mac)", + "params": { + "app": "Ableton Live 11 Suite", + "host": "tracks", + "restart": true, + "wait_seconds": 15 + }, + "reconnect": true, + "run": "L=$(ls -td ~/Library/Preferences/Ableton/Live*/ | head -1); S=$(grep -a 'info: Loading document \"' \"${L}Log.txt\" | grep -v '/Templates/' | tail -1 | sed -E 's/^.*Loading document \"(.*)\"[^\"]*$/\\1/'); pkill -TERM -x Live; i=0; while pgrep -xq Live && [ $i -lt 30 ]; do sleep 0.5; i=$((i+1)); done; pgrep -xq Live && pkill -KILL -x Live; sleep 1; if [ -n \"$S\" ] && [ -f \"$S\" ]; then open -a 'Ableton Live 11 Suite' \"$S\"; else open -a 'Ableton Live 11 Suite'; fi", + "short": "ABLETON", + "template": "ableton", + "timeout": 60 + }, + "arena": { + "host": "lighting", + "kind": "ssh", + "label": "start Resolume Arena on the Lighting PC if it is not running, then reconnect Companion", + "params": { + "exe": "Arena.exe", + "host": "lighting", + "task": "Start Arena" + }, + "reconnect": true, + "run": "tasklist /NH /FI \"IMAGENAME eq Arena.exe\" | find /I \"Arena.exe\" >nul || (schtasks /Run /TN \"\\StatusPanel\\Start Arena\" >nul && ping -n 9 127.0.0.1 >nul && tasklist /NH /FI \"IMAGENAME eq Arena.exe\" | find /I \"Arena.exe\" >nul)", + "short": "ARENA", + "template": "win_start" + }, + "atem_audio": { + "kind": "atem", + "label": "set the switcher's audio inputs back to the restore file (Resi on input 16 on, the rest as saved)", + "params": {}, + "part": "audio", + "short": "ATEM AUDIO", + "template": "atem" + }, + "atem_camera": { + "kind": "atem", + "label": "set the cameras' settings back to the restore file (only what differs)", + "params": {}, + "part": "camera", + "short": "ATEM CAMERA", + "template": "atem" + }, + "atem_midi": { + "kind": "local", + "label": "turn off MIDI control in ATEM Software Control (quits and reopens the app; the switcher is not affected)", + "params": {}, + "restart_ok": true, + "run": "was=$(pgrep -f \"ATEM Software Control.app/Contents/MacOS\"); pkill -TERM -f \"ATEM Software Control.app/Contents/MacOS\"; i=0; while pgrep -fq \"ATEM Software Control.app/Contents/MacOS\" && [ $i -lt 20 ]; do sleep 0.5; i=$((i+1)); done; defaults write \"com.blackmagic-design.ATEM Software Control\" midiEnabled -bool false; [ -z \"$was\" ] || open -g -a \"ATEM Software Control\"; [ \"$(defaults read \"com.blackmagic-design.ATEM Software Control\" midiEnabled)\" = 0 ]", + "short": "ATEM MIDI", + "template": "atem_midi_off" + }, + "bibleapp": { + "host": "bibletv", + "kind": "ssh", + "label": "restart the Bible app on the Sanctuary TV PC (Windows: cmd.exe)", + "params": { + "exe": "Bible App.exe", + "host": "bibletv", + "task": "Start BibleApp" + }, + "run": "taskkill /F /T /IM \"Bible App.exe\" >nul 2>&1 & ping -n 3 127.0.0.1 >nul & schtasks /Run /TN \"\\StatusPanel\\Start BibleApp\" >nul && ping -n 9 127.0.0.1 >nul && tasklist /NH /FI \"IMAGENAME eq Bible App.exe\" | find /I \"Bible App.exe\" >nul", + "short": "BIBLE APP", + "template": "win_restart" + }, + "bridge": { + "kind": "local", + "label": "restart the Resi bridge", + "params": { + "labels": [ + "org.waterschurch.resi-propresenter" + ] + }, + "run": "launchctl kickstart -k gui/$(id -u)/org.waterschurch.resi-propresenter || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.resi-propresenter.plist", + "short": "BRIDGE", + "template": "helpers" + }, + "caption_screen": { + "kind": "local", + "label": "restart the lobby caption screen (display app only)", + "params": { + "labels": [ + "org.waterschurch.lobby-captions-display" + ] + }, + "run": "launchctl kickstart -k gui/$(id -u)/org.waterschurch.lobby-captions-display || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.lobby-captions-display.plist", + "short": "CAPTION SCREEN", + "template": "helpers" + }, + "captions": { + "kind": "local", + "label": "restart lobby captions (server and display)", + "params": { + "labels": [ + "org.waterschurch.lobby-captions", + "org.waterschurch.lobby-captions-display" + ] + }, + "run": "s=0; { launchctl kickstart -k gui/$(id -u)/org.waterschurch.lobby-captions || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.lobby-captions.plist; } || s=1; { launchctl kickstart -k gui/$(id -u)/org.waterschurch.lobby-captions-display || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.lobby-captions-display.plist; } || s=1; exit $s", + "short": "CAPTIONS", + "template": "helpers" + }, + "digico_relay": { + "kind": "local", + "label": "restart the DiGiCo relay (Companion and the tablets to the FOH console)", + "params": { + "labels": [ + "org.waterschurch.digico-relay" + ] + }, + "run": "launchctl kickstart -k gui/$(id -u)/org.waterschurch.digico-relay || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.digico-relay.plist", + "short": "DIGICO", + "template": "helpers" + }, + "dvs_ingest": { + "kind": "local", + "label": "restart Dante Virtual Soundcard on NA Ingest, then ProdCom (it keeps a dead DVS input otherwise)", + "params": { + "host": "ingest", + "then_app": "/Applications/ProdCom.app", + "then_process": "ProdCom" + }, + "run": "sudo -n /usr/local/sbin/status-panel-dvs-start && open -g -a 'Dante Virtual Soundcard' || exit 1; sleep 5; pkill -TERM -x ProdCom; i=0; while pgrep -xq ProdCom && [ $i -lt 20 ]; do sleep 0.5; i=$((i+1)); done; pgrep -xq ProdCom && pkill -KILL -x ProdCom; sleep 1; open -g /Applications/ProdCom.app", + "short": "DVS", + "template": "dvs" + }, + "dvs_prop": { + "host": "prop", + "kind": "ssh", + "label": "restart Dante Virtual Soundcard on the ProPresenter Mac (its engine restarts; audio to and from that Mac drops for 5-10 s)", + "params": { + "host": "prop" + }, + "run": "sudo -n /usr/local/sbin/status-panel-dvs-start && open -g -a 'Dante Virtual Soundcard'", + "short": "DVS", + "template": "dvs" + }, + "dvs_tracks": { + "host": "tracks", + "kind": "ssh", + "label": "restart Dante Virtual Soundcard on the Tracks Mac (its engine restarts; audio to and from that Mac drops for 5-10 s)", + "params": { + "host": "tracks" + }, + "run": "sudo -n /usr/local/sbin/status-panel-dvs-start && open -g -a 'Dante Virtual Soundcard'", + "short": "DVS", + "template": "dvs" + }, + "dvs_translation": { + "host": "translation", + "kind": "ssh", + "label": "restart Dante Virtual Soundcard on NA Translation (its engine restarts; audio to and from that Mac drops for 5-10 s)", + "params": { + "host": "translation" + }, + "run": "sudo -n /usr/local/sbin/status-panel-dvs-start && open -g -a 'Dante Virtual Soundcard'", + "short": "DVS", + "template": "dvs" + }, + "hyperdeck": { + "hung_note": "STILL OFFLINE\nHOLD AGAIN", + "kind": "hyperdeck", + "label": "turn Companion's connection to that HyperDeck off and on (what gets a restarted deck green); never reboots the deck", + "params": {}, + "short": "HYPERDECK", + "template": "hyperdeck", + "verify_seconds": 15 + }, + "midi_reconnect": { + "kind": "midi_connect", + "label": "reconnect the peer to the network MIDI session it dropped out of (like Connect in Audio MIDI Setup); the address is where it was last seen, else the check's \"address\"", + "params": {}, + "short": "RECONNECT", + "template": "midi_connect" + }, + "obs": { + "host": "livestream", + "kind": "ssh", + "label": "start OBS on the LiveStream PC if it is not running (wakes the PC if it is off), then reconnect Companion", + "params": { + "exe": "obs64.exe", + "host": "livestream", + "task": "Start OBS" + }, + "reconnect": true, + "run": "tasklist /NH /FI \"IMAGENAME eq obs64.exe\" | find /I \"obs64.exe\" >nul || (schtasks /Run /TN \"\\StatusPanel\\Start OBS\" >nul && ping -n 9 127.0.0.1 >nul && tasklist /NH /FI \"IMAGENAME eq obs64.exe\" | find /I \"obs64.exe\" >nul)", + "short": "OBS", + "template": "win_start" + }, + "prodcom": { + "kind": "local", + "label": "restart ProdCom (transcription; lobby captions blank for 5-15 s)", + "params": { + "app": "/Applications/ProdCom.app", + "background": true, + "force": true, + "host": "ingest", + "process": "ProdCom", + "wait_seconds": 10 + }, + "run": "pkill -TERM -x ProdCom; i=0; while pgrep -xq ProdCom && [ $i -lt 20 ]; do sleep 0.5; i=$((i+1)); done; pgrep -xq ProdCom && pkill -KILL -x ProdCom; sleep 1; open -g /Applications/ProdCom.app", + "short": "PRODCOM", + "template": "mac_restart" + }, + "proddash": { + "kind": "local", + "label": "restart ProdDash (its launcher stops its server cleanly)", + "params": { + "app": "/Applications/ProdDash.app", + "background": true, + "force": false, + "host": "ingest", + "process": "ProdDash", + "wait_seconds": 10 + }, + "run": "pkill -TERM -x ProdDash; i=0; while pgrep -xq ProdDash && [ $i -lt 20 ]; do sleep 0.5; i=$((i+1)); done; open -g /Applications/ProdDash.app", + "short": "PRODDASH", + "template": "mac_restart" + }, + "propresenter": { + "host": "prop", + "kind": "ssh", + "label": "restart ProPresenter (newest installed)", + "params": { + "app": "$APP", + "force": true, + "host": "prop", + "newest_of": "/Applications/ProPresenter*.app", + "process": "ProPresenter", + "wait_seconds": 10 + }, + "run": "APP=$(ls -d /Applications/ProPresenter*.app | sort -V | tail -1); pkill -TERM -x ProPresenter; i=0; while pgrep -xq ProPresenter && [ $i -lt 20 ]; do sleep 0.5; i=$((i+1)); done; pgrep -xq ProPresenter && pkill -KILL -x ProPresenter; sleep 1; open \"$APP\"", + "short": "PROPRES", + "template": "mac_restart" + }, + "ready_copy_tracks": { + "action": "copy_tracks", + "kind": "ready", + "label": "copy this weekend's Ableton project to the locations that do not have it yet", + "never_auto": true, + "short": "COPY" + }, + "ready_led_cross": { + "action": "led_cross", + "kind": "ready", + "label": "put Resolume back on the Service deck, then look at the columns again", + "short": "CROSS" + }, + "ready_lineup": { + "action": "lineup_sync", + "kind": "ready", + "label": "re-sync the Band Lineup board from Planning Center", + "short": "LINEUP" + }, + "ready_ltc": { + "action": "ltc_to_pco", + "kind": "ready", + "label": "write each song's timecode from the tracks into its LTC note in Planning Center", + "short": "LTC" + }, + "ready_recheck": { + "action": "recheck", + "kind": "ready", + "label": "look at this weekend's checklist again now", + "short": "CHECK" + }, + "rtpmidi_start": { + "host": "lighting", + "kind": "ssh", + "label": "start rtpMIDI on the Lighting PC if it is not running (service, then tray app). A running rtpMIDI is never restarted: that drops every session.", + "params": { + "exe": "rtpMIDI.exe", + "host": "lighting", + "service": "rtpMIDIService", + "task": "Start rtpMIDI" + }, + "run": "(sc query rtpMIDIService | find \"RUNNING\" >nul || net start rtpMIDIService) && (tasklist /NH /FI \"IMAGENAME eq rtpMIDI.exe\" | find /I \"rtpMIDI.exe\" >nul || schtasks /Run /TN \"\\StatusPanel\\Start rtpMIDI\")", + "short": "RTPMIDI", + "template": "win_service" + }, + "solo_afl": { + "host": "studiob", + "kind": "ssh", + "label": "set Pro Tools on the Studio B Mac Studio back to Solo Mode AFL + Latch (clicks Options > Solo Mode)", + "params": { + "host": "studiob" + }, + "run": "touch \"$HOME/Library/Application Support/status-panel/solo-mode.fix\"; sleep 3; ! grep -q '\"bad\":true' \"$HOME/Library/Application Support/status-panel/solo-mode.json\"", + "short": "PT SOLO", + "template": "solo_afl" + }, + "spotify_play": { + "kind": "spotify", + "label": "play the Christian EDM 2026 playlist in Spotify on NA Ingest (opens Spotify if it is closed)", + "params": {}, + "short": "SPOTIFY", + "template": "spotify" + }, + "stream_foh_backup": { + "col": 5, + "failover": true, + "kind": "companion_press", + "label": "press the FOH Backup button in Companion (page 4, row 0, column 5): the stream audio comes from the FOH DiGiCo (DMI-2 63/64) and the X32 takes snippet 8", + "page": 4, + "params": { + "button": "FOH Backup", + "col": 5, + "page": 4, + "row": 0 + }, + "row": 0, + "short": "FOH BACKUP", + "template": "companion_press", + "undo": "stream_studio_b" + }, + "stream_studio_b": { + "col": 6, + "kind": "companion_press", + "label": "press the Studio B button in Companion (page 4, row 0, column 6): the stream audio comes from Studio B (RedNet TNX 63/64) and the X32 takes snippet 1", + "page": 4, + "params": { + "button": "Studio B", + "col": 6, + "page": 4, + "row": 0 + }, + "row": 0, + "short": "STUDIO B", + "template": "companion_press" + }, + "tascam_power": { + "conn": "tascam", + "kind": "local", + "label": "power-cycle the Tascam through its smart plug when its control port does not answer (off 45 s, on, wait for it to come back), then reconnect Companion; its Dante card only comes back from the unit's own power button; never while a HyperDeck is recording", + "params": { + "address": "10.1.11.74", + "dante": "STUDIO-B-Tascam", + "outlet": "tascam", + "wait_seconds": 45 + }, + "reconnect": true, + "run": "/usr/bin/python3 \"$HOME/Library/Application Support/status-panel/settings/outlet.py\" cycle tascam --ping 10.1.11.74 --dante STUDIO-B-Tascam --wait 45", + "short": "TASCAM PWR", + "template": "outlet_cycle", + "timeout": 300 + }, + "tascam_reconnect": { + "conn": "tascam", + "hung_note": "HUNG - POWER\nCYCLE IT", + "kind": "reconnect", + "label": "reconnect Companion to the Tascam; if it still does not answer but pings, its control port has hung: power-cycle it at the rack", + "params": {}, + "short": "TASCAM", + "template": "reconnect", + "verify_seconds": 20 + }, + "translate": { + "host": "translation", + "kind": "ssh", + "label": "start Church Translate if it is not running", + "params": { + "app": "Church Translate", + "host": "translation", + "process": "Church Translate" + }, + "run": "pgrep -xq 'Church Translate' || open -a 'Church Translate'", + "short": "TRANSLATE", + "template": "mac_start" + }, + "translate_restart": { + "host": "translation", + "kind": "ssh", + "label": "restart Church Translate on NA Translation (and its translation server)", + "params": { + "app": "Church Translate", + "force": true, + "host": "translation", + "process": "Church Translate", + "stop_children": true, + "wait_seconds": 15 + }, + "run": "pg=$(ps -o pgid= -p \"$(pgrep -x 'Church Translate' | head -1)\" 2>/dev/null | tr -d ' '); pkill -TERM -x 'Church Translate'; i=0; while pgrep -xq 'Church Translate' && [ $i -lt 30 ]; do sleep 0.5; i=$((i+1)); done; pgrep -xq 'Church Translate' && pkill -KILL -x 'Church Translate'; [ -n \"$pg\" ] && [ \"$pg\" != \"$(ps -o pgid= -p $$ | tr -d ' ')\" ] && kill -KILL -- -\"$pg\" 2>/dev/null; sleep 1; open -a 'Church Translate'", + "short": "TRANSLATE", + "template": "mac_restart", + "timeout": 60 + }, + "vista": { + "host": "lighting", + "kind": "ssh", + "label": "start Vista on the Lighting PC if it is not running (a running Vista is never closed)", + "params": { + "exe": "Vista.exe", + "host": "lighting", + "task": "Start Vista" + }, + "run": "tasklist /NH /FI \"IMAGENAME eq Vista.exe\" | find /I \"Vista.exe\" >nul || (schtasks /Run /TN \"\\StatusPanel\\Start Vista\" >nul && ping -n 9 127.0.0.1 >nul && tasklist /NH /FI \"IMAGENAME eq Vista.exe\" | find /I \"Vista.exe\" >nul)", + "short": "VISTA", + "template": "win_start" + } + }, + "flap": { + "count": 3, + "window_seconds": 600 + }, + "gear": {}, "hosts": { + "bibletv": { + "manual_done": { + "wifi_power": { + "by": "Chris", + "on": "2026-10-11" + } + }, + "name": "Bible TV PC", + "notes": "Wi-Fi only; Ethernet recommended. Wi-Fi power saving is off.", + "os": "windows", + "pingable": false, + "role": "the Bible app on the sanctuary TV", + "runs": [ + { + "kind": "sshd" + }, + { + "at_logon": true, + "exe": "Bible App.exe", + "kind": "task", + "name": "Start BibleApp" + }, + { + "kind": "web", + "name": "the Bible app's web page", + "url": "http://10.6.7.13:8080/" + } + ], + "ssh": "media@10.6.7.13" + }, "ingest": { - "self": true, "name": "NA Ingest", + "networks": [ + { + "iface": "en0", + "use": "LAN" + }, + { + "iface": "en1", + "use": "LAN" + }, + { + "iface": "en17", + "use": "Dante" + }, + { + "iface": "en20", + "use": "Netgear management" + } + ], "os": "mac", "role": "the watchdog, Companion, the settings page, lobby captions, transcription", "runs": [ @@ -90,15 +569,15 @@ "name": "the settings page" }, { + "http": "http://127.0.0.1:8000/api/connections", "kind": "app", - "process": "Companion", "name": "Companion 5.0.7", - "http": "http://127.0.0.1:8000/api/connections" + "process": "Companion" }, { "kind": "app", - "process": "Companion Satellite", "name": "Companion Satellite", + "process": "Companion Satellite", "watched": false }, { @@ -117,32 +596,32 @@ "name": "the caption screen" }, { + "http": "http://127.0.0.1:24480/api/v1/ping", "kind": "app", - "process": "ProdCom", "name": "ProdCom (transcription)", - "http": "http://127.0.0.1:24480/api/v1/ping" + "process": "ProdCom" }, { + "http": "http://127.0.0.1:24500/", "kind": "app", - "process": "ProdDash", "name": "ProdDash", - "http": "http://127.0.0.1:24500/" + "process": "ProdDash" }, { "kind": "app", - "process": "Spotify", - "name": "Spotify" + "name": "Spotify", + "process": "Spotify" }, { "kind": "app", - "process": "ATEM Software Control", - "name": "ATEM Software Control" + "name": "ATEM Software Control", + "process": "ATEM Software Control" }, { + "dante_name": "STUDIO-A-Ingest", "kind": "daemon", - "process": "DanteVirtualSoundcard", "name": "Dante Virtual Soundcard", - "dante_name": "STUDIO-A-Ingest" + "process": "DanteVirtualSoundcard" }, { "kind": "helper_dvs", @@ -150,8 +629,8 @@ }, { "kind": "folder", - "path": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/Shared drives/Waters Creative/Production Team/Backup/NA/ATEM", - "name": "Google Drive (the saved switcher files)" + "name": "Google Drive (the saved switcher files)", + "path": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/Shared drives/Waters Creative/Production Team/Backup/NA/ATEM" }, { "kind": "launchagent", @@ -177,169 +656,42 @@ "name": "Church Translate (must NOT run here)" } ], - "networks": [ - { - "iface": "en0", - "use": "LAN" - }, - { - "iface": "en1", - "use": "LAN" - }, - { - "iface": "en17", - "use": "Dante" - }, - { - "iface": "en20", - "use": "Netgear management" - } - ] - }, - "prop": { - "ssh": "napropresenter@10.1.14.225", - "name": "ProPresenter Mac", - "os": "mac", - "role": "ProPresenter, its stage and Prop TV pages, Dante sound card", - "runs": [ - { - "kind": "sshd" - }, - { - "kind": "app", - "process": "ProPresenter", - "name": "ProPresenter (newest installed)", - "http": "http://10.1.14.225:1025/" - }, - { - "kind": "daemon", - "process": "DanteVirtualSoundcard", - "name": "Dante Virtual Soundcard", - "dante_name": "NA-ProPresenter-Mac" - }, - { - "kind": "helper_dvs", - "name": "the DVS helper" - }, - { - "kind": "midi_session", - "name": "NA ProP" - } - ] - }, - "tracks": { - "ssh": "waterschurchmedia@10.1.3.227", - "name": "Tracks Mac", - "os": "mac", - "role": "Ableton (tracks and FX lighting cues), Dante sound card", - "runs": [ - { - "kind": "sshd" - }, - { - "kind": "app", - "process": "Live", - "name": "Ableton Live 11 Suite" - }, - { - "kind": "daemon", - "process": "DanteVirtualSoundcard", - "name": "Dante Virtual Soundcard", - "dante_name": "NA-Tracks" - }, - { - "kind": "helper_dvs", - "name": "the DVS helper" - }, - { - "kind": "midi_session", - "name": "NA Tracks" - } - ] - }, - "translation": { - "ssh": "studio@10.1.7.39", - "name": "NA Translation", - "os": "mac", - "role": "Church Translate, Dante sound card", - "runs": [ - { - "kind": "sshd" - }, - { - "kind": "app", - "process": "Church Translate", - "name": "Church Translate" - }, - { - "kind": "launchagent", - "label": "org.waterschurch.translate.engine", - "name": "the translation engine", - "watched": false - }, - { - "kind": "daemon", - "process": "DanteVirtualSoundcard", - "name": "Dante Virtual Soundcard", - "dante_name": "NA-Translation" - }, - { - "kind": "helper_dvs", - "name": "the DVS helper" - } - ] - }, - "studiob": { - "ssh": "studiob@10.1.13.175", - "name": "Studio B Mac Studio", - "os": "mac", - "role": "Pro Tools", - "runs": [ - { - "kind": "sshd" - }, - { - "kind": "app", - "process": "Pro Tools", - "name": "Pro Tools" - }, - { - "kind": "helper_solo", - "label": "org.waterschurch.status-panel.solo-mode", - "name": "the Pro Tools solo-mode helper" - }, - { - "kind": "midi_session", - "name": "Studio B Mac Studio", - "peer_of": "NA Tracks" - } - ] + "self": true }, "lighting": { - "ssh": "media@10.1.10.206", + "manual_done": { + "public_profile": { + "by": "Chris", + "on": "2026-10-08" + }, + "vista_autoload": { + "by": "Chris", + "on": "2026-10-08" + } + }, "name": "Lighting PC", - "os": "windows", - "role": "Vista, Resolume Arena, rtpMIDI", - "pingable": false, "notes": "Keep the network profile on Public: rtpMIDI's Bonjour rules only allow Public.", + "os": "windows", + "pingable": false, + "role": "Vista, Resolume Arena, rtpMIDI", "runs": [ { "kind": "sshd" }, { + "exe": "Vista.exe", "kind": "task", - "name": "Start Vista", - "exe": "Vista.exe" + "name": "Start Vista" }, { + "exe": "Arena.exe", "kind": "task", - "name": "Start Arena", - "exe": "Arena.exe" + "name": "Start Arena" }, { + "exe": "rtpMIDI.exe", "kind": "task", - "name": "Start rtpMIDI", - "exe": "rtpMIDI.exe" + "name": "Start rtpMIDI" }, { "kind": "service", @@ -356,1025 +708,674 @@ "source": "10.1.10.206" } ], + "ssh": "media@10.1.10.206" + }, + "livestream": { + "mac": "6c:24:08:d7:fb:57", "manual_done": { - "public_profile": { - "by": "Chris", - "on": "2026-10-08" - }, - "vista_autoload": { + "bios_wol": { "by": "Chris", "on": "2026-10-08" } - } - }, - "livestream": { - "ssh": "media@10.1.9.126", - "os": "windows", - "mac": "6c:24:08:d7:fb:57", - "wake_broadcast": "10.1.255.255", + }, "name": "LiveStream PC", + "os": "windows", "role": "OBS (YouTube)", "runs": [ { "kind": "sshd" }, { - "kind": "task", - "name": "Start OBS", - "exe": "obs64.exe", + "args": "--disable-shutdown-check", "elevated": true, - "args": "--disable-shutdown-check" + "exe": "obs64.exe", + "kind": "task", + "name": "Start OBS" } ], - "manual_done": { - "bios_wol": { - "by": "Chris", - "on": "2026-10-08" - } - } + "ssh": "media@10.1.9.126", + "wake_broadcast": "10.1.255.255" }, - "bibletv": { - "ssh": "media@10.6.7.13", - "os": "windows", - "name": "Bible TV PC", - "role": "the Bible app on the sanctuary TV", - "pingable": false, - "notes": "Wi-Fi only; Ethernet recommended. Wi-Fi power saving is off.", + "prop": { + "name": "ProPresenter Mac", + "os": "mac", + "role": "ProPresenter, its stage and Prop TV pages, Dante sound card", "runs": [ { "kind": "sshd" }, { - "kind": "task", - "name": "Start BibleApp", - "exe": "Bible App.exe", - "at_logon": true + "http": "http://10.1.14.225:1025/", + "kind": "app", + "name": "ProPresenter (newest installed)", + "process": "ProPresenter" }, { - "kind": "web", - "url": "http://10.6.7.13:8080/", - "name": "the Bible app's web page" + "dante_name": "NA-ProPresenter-Mac", + "kind": "daemon", + "name": "Dante Virtual Soundcard", + "process": "DanteVirtualSoundcard" + }, + { + "kind": "helper_dvs", + "name": "the DVS helper" + }, + { + "kind": "midi_session", + "name": "NA ProP" } ], - "manual_done": { - "wifi_power": { - "by": "Chris", - "on": "2026-10-11" + "ssh": "napropresenter@10.1.14.225" + }, + "studiob": { + "name": "Studio B Mac Studio", + "os": "mac", + "role": "Pro Tools", + "runs": [ + { + "kind": "sshd" + }, + { + "kind": "app", + "name": "Pro Tools", + "process": "Pro Tools" + }, + { + "kind": "helper_solo", + "label": "org.waterschurch.status-panel.solo-mode", + "name": "the Pro Tools solo-mode helper" + }, + { + "kind": "midi_session", + "name": "Studio B Mac Studio", + "peer_of": "NA Tracks" } - } + ], + "ssh": "studiob@10.1.13.175" + }, + "tracks": { + "name": "Tracks Mac", + "os": "mac", + "role": "Ableton (tracks and FX lighting cues), Dante sound card", + "runs": [ + { + "kind": "sshd" + }, + { + "kind": "app", + "name": "Ableton Live 11 Suite", + "process": "Live" + }, + { + "dante_name": "NA-Tracks", + "kind": "daemon", + "name": "Dante Virtual Soundcard", + "process": "DanteVirtualSoundcard" + }, + { + "kind": "helper_dvs", + "name": "the DVS helper" + }, + { + "kind": "midi_session", + "name": "NA Tracks" + } + ], + "ssh": "waterschurchmedia@10.1.3.227" + }, + "translation": { + "name": "NA Translation", + "os": "mac", + "role": "Church Translate, Dante sound card", + "runs": [ + { + "kind": "sshd" + }, + { + "kind": "app", + "name": "Church Translate", + "process": "Church Translate" + }, + { + "kind": "launchagent", + "label": "org.waterschurch.translate.engine", + "name": "the translation engine", + "watched": false + }, + { + "dante_name": "NA-Translation", + "kind": "daemon", + "name": "Dante Virtual Soundcard", + "process": "DanteVirtualSoundcard" + }, + { + "kind": "helper_dvs", + "name": "the DVS helper" + } + ], + "ssh": "studio@10.1.7.39" } }, - "gear": {}, - "fixes": { - "propresenter": { - "host": "prop", - "short": "PROPRES", - "label": "restart ProPresenter (newest installed)", - "run": "APP=$(ls -d /Applications/ProPresenter*.app | sort -V | tail -1); pkill -TERM -x ProPresenter; i=0; while pgrep -xq ProPresenter && [ $i -lt 20 ]; do sleep 0.5; i=$((i+1)); done; pgrep -xq ProPresenter && pkill -KILL -x ProPresenter; sleep 1; open \"$APP\"", - "template": "mac_restart", - "params": { - "host": "prop", - "process": "ProPresenter", - "app": "$APP", - "newest_of": "/Applications/ProPresenter*.app", - "wait_seconds": 10, - "force": true - }, - "kind": "ssh" - }, - "ableton": { - "host": "tracks", - "short": "ABLETON", - "label": "start Ableton if it is not running, with the set it last had open", - "run": "pgrep -xq Live || { L=$(ls -td ~/Library/Preferences/Ableton/Live*/ | head -1); S=$(grep -a 'info: Loading document \"' \"${L}Log.txt\" | grep -v '/Templates/' | tail -1 | sed -E 's/^.*Loading document \"(.*)\"[^\"]*$/\\1/'); if [ -n \"$S\" ] && [ -f \"$S\" ]; then open -a 'Ableton Live 11 Suite' \"$S\"; else open -a 'Ableton Live 11 Suite'; fi; }", - "template": "ableton", - "params": { - "host": "tracks", - "app": "Ableton Live 11 Suite", - "restart": false - }, - "kind": "ssh" - }, - "translate": { - "host": "translation", - "short": "TRANSLATE", - "label": "start Church Translate if it is not running", - "run": "pgrep -xq 'Church Translate' || open -a 'Church Translate'", - "template": "mac_start", - "params": { - "host": "translation", - "process": "Church Translate", - "app": "Church Translate" - }, - "kind": "ssh" - }, - "dvs_prop": { - "host": "prop", - "short": "DVS", - "label": "restart Dante Virtual Soundcard on the ProPresenter Mac (its engine restarts; audio to and from that Mac drops for 5-10 s)", - "run": "sudo -n /usr/local/sbin/status-panel-dvs-start && open -g -a 'Dante Virtual Soundcard'", - "template": "dvs", - "params": { - "host": "prop" - }, - "kind": "ssh" - }, - "dvs_tracks": { - "host": "tracks", - "short": "DVS", - "label": "restart Dante Virtual Soundcard on the Tracks Mac (its engine restarts; audio to and from that Mac drops for 5-10 s)", - "run": "sudo -n /usr/local/sbin/status-panel-dvs-start && open -g -a 'Dante Virtual Soundcard'", - "template": "dvs", - "params": { - "host": "tracks" - }, - "kind": "ssh" - }, - "dvs_translation": { - "host": "translation", - "short": "DVS", - "label": "restart Dante Virtual Soundcard on NA Translation (its engine restarts; audio to and from that Mac drops for 5-10 s)", - "run": "sudo -n /usr/local/sbin/status-panel-dvs-start && open -g -a 'Dante Virtual Soundcard'", - "template": "dvs", - "params": { - "host": "translation" - }, - "kind": "ssh" - }, - "dvs_ingest": { - "kind": "local", - "short": "DVS", - "label": "restart Dante Virtual Soundcard on NA Ingest, then ProdCom (it keeps a dead DVS input otherwise)", - "run": "sudo -n /usr/local/sbin/status-panel-dvs-start && open -g -a 'Dante Virtual Soundcard' || exit 1; sleep 5; pkill -TERM -x ProdCom; i=0; while pgrep -xq ProdCom && [ $i -lt 20 ]; do sleep 0.5; i=$((i+1)); done; pgrep -xq ProdCom && pkill -KILL -x ProdCom; sleep 1; open -g /Applications/ProdCom.app", - "template": "dvs", - "params": { - "host": "ingest", - "then_process": "ProdCom", - "then_app": "/Applications/ProdCom.app" - } - }, - "bridge": { - "kind": "local", - "short": "BRIDGE", - "label": "restart the Resi bridge", - "run": "launchctl kickstart -k gui/$(id -u)/org.waterschurch.resi-propresenter || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.resi-propresenter.plist", - "template": "helpers", - "params": { - "labels": [ - "org.waterschurch.resi-propresenter" - ] - } - }, - "captions": { - "kind": "local", - "short": "CAPTIONS", - "label": "restart lobby captions (server and display)", - "run": "s=0; { launchctl kickstart -k gui/$(id -u)/org.waterschurch.lobby-captions || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.lobby-captions.plist; } || s=1; { launchctl kickstart -k gui/$(id -u)/org.waterschurch.lobby-captions-display || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.lobby-captions-display.plist; } || s=1; exit $s", - "template": "helpers", - "params": { - "labels": [ - "org.waterschurch.lobby-captions", - "org.waterschurch.lobby-captions-display" - ] - } - }, - "bibleapp": { - "host": "bibletv", - "short": "BIBLE APP", - "label": "restart the Bible app on the Sanctuary TV PC (Windows: cmd.exe)", - "run": "taskkill /F /T /IM \"Bible App.exe\" >nul 2>&1 & ping -n 3 127.0.0.1 >nul & schtasks /Run /TN \"\\StatusPanel\\Start BibleApp\" >nul && ping -n 9 127.0.0.1 >nul && tasklist /NH /FI \"IMAGENAME eq Bible App.exe\" | find /I \"Bible App.exe\" >nul", - "template": "win_restart", - "params": { - "host": "bibletv", - "task": "Start BibleApp", - "exe": "Bible App.exe" - }, - "kind": "ssh" - }, - "obs": { - "host": "livestream", - "short": "OBS", - "label": "start OBS on the LiveStream PC if it is not running (wakes the PC if it is off), then reconnect Companion", - "reconnect": true, - "run": "tasklist /NH /FI \"IMAGENAME eq obs64.exe\" | find /I \"obs64.exe\" >nul || (schtasks /Run /TN \"\\StatusPanel\\Start OBS\" >nul && ping -n 9 127.0.0.1 >nul && tasklist /NH /FI \"IMAGENAME eq obs64.exe\" | find /I \"obs64.exe\" >nul)", - "template": "win_start", - "params": { - "host": "livestream", - "task": "Start OBS", - "exe": "obs64.exe" - }, - "kind": "ssh" - }, - "arena": { - "host": "lighting", - "short": "ARENA", - "label": "start Resolume Arena on the Lighting PC if it is not running, then reconnect Companion", - "reconnect": true, - "run": "tasklist /NH /FI \"IMAGENAME eq Arena.exe\" | find /I \"Arena.exe\" >nul || (schtasks /Run /TN \"\\StatusPanel\\Start Arena\" >nul && ping -n 9 127.0.0.1 >nul && tasklist /NH /FI \"IMAGENAME eq Arena.exe\" | find /I \"Arena.exe\" >nul)", - "template": "win_start", - "params": { - "host": "lighting", - "task": "Start Arena", - "exe": "Arena.exe" - }, - "kind": "ssh" - }, - "vista": { - "host": "lighting", - "short": "VISTA", - "label": "start Vista on the Lighting PC if it is not running (a running Vista is never closed)", - "run": "tasklist /NH /FI \"IMAGENAME eq Vista.exe\" | find /I \"Vista.exe\" >nul || (schtasks /Run /TN \"\\StatusPanel\\Start Vista\" >nul && ping -n 9 127.0.0.1 >nul && tasklist /NH /FI \"IMAGENAME eq Vista.exe\" | find /I \"Vista.exe\" >nul)", - "template": "win_start", - "params": { - "host": "lighting", - "task": "Start Vista", - "exe": "Vista.exe" - }, - "kind": "ssh" - }, - "rtpmidi_start": { - "host": "lighting", - "short": "RTPMIDI", - "label": "start rtpMIDI on the Lighting PC if it is not running (service, then tray app). A running rtpMIDI is never restarted: that drops every session.", - "run": "(sc query rtpMIDIService | find \"RUNNING\" >nul || net start rtpMIDIService) && (tasklist /NH /FI \"IMAGENAME eq rtpMIDI.exe\" | find /I \"rtpMIDI.exe\" >nul || schtasks /Run /TN \"\\StatusPanel\\Start rtpMIDI\")", - "template": "win_service", - "params": { - "host": "lighting", - "service": "rtpMIDIService", - "task": "Start rtpMIDI", - "exe": "rtpMIDI.exe" - }, - "kind": "ssh" - }, - "midi_reconnect": { - "kind": "midi_connect", - "short": "RECONNECT", - "label": "reconnect the peer to the network MIDI session it dropped out of (like Connect in Audio MIDI Setup); the address is where it was last seen, else the check's \"address\"", - "template": "midi_connect", - "params": {} - }, - "caption_screen": { - "kind": "local", - "short": "CAPTION SCREEN", - "label": "restart the lobby caption screen (display app only)", - "run": "launchctl kickstart -k gui/$(id -u)/org.waterschurch.lobby-captions-display || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.lobby-captions-display.plist", - "template": "helpers", - "params": { - "labels": [ - "org.waterschurch.lobby-captions-display" - ] - } - }, - "solo_afl": { - "host": "studiob", - "short": "PT SOLO", - "label": "set Pro Tools on the Studio B Mac Studio back to Solo Mode AFL + Latch (clicks Options > Solo Mode)", - "run": "touch \"$HOME/Library/Application Support/status-panel/solo-mode.fix\"; sleep 3; ! grep -q '\"bad\":true' \"$HOME/Library/Application Support/status-panel/solo-mode.json\"", - "template": "solo_afl", - "params": { - "host": "studiob" - }, - "kind": "ssh" - }, - "atem_audio": { - "kind": "atem", - "part": "audio", - "short": "ATEM AUDIO", - "label": "set the switcher's audio inputs back to the restore file (Resi on input 16 on, the rest as saved)", - "template": "atem", - "params": {} - }, - "atem_camera": { - "kind": "atem", - "part": "camera", - "short": "ATEM CAMERA", - "label": "set the cameras' settings back to the restore file (only what differs)", - "template": "atem", - "params": {} - }, - "atem_midi": { - "kind": "local", - "short": "ATEM MIDI", - "restart_ok": true, - "label": "turn off MIDI control in ATEM Software Control (quits and reopens the app; the switcher is not affected)", - "run": "was=$(pgrep -f \"ATEM Software Control.app/Contents/MacOS\"); pkill -TERM -f \"ATEM Software Control.app/Contents/MacOS\"; i=0; while pgrep -fq \"ATEM Software Control.app/Contents/MacOS\" && [ $i -lt 20 ]; do sleep 0.5; i=$((i+1)); done; defaults write \"com.blackmagic-design.ATEM Software Control\" midiEnabled -bool false; [ -z \"$was\" ] || open -g -a \"ATEM Software Control\"; [ \"$(defaults read \"com.blackmagic-design.ATEM Software Control\" midiEnabled)\" = 0 ]", - "template": "atem_midi_off", - "params": {} - }, - "spotify_play": { - "kind": "spotify", - "short": "SPOTIFY", - "label": "play the Christian EDM 2026 playlist in Spotify on NA Ingest (opens Spotify if it is closed)", - "template": "spotify", - "params": {} - }, - "proddash": { - "kind": "local", - "short": "PRODDASH", - "label": "restart ProdDash (its launcher stops its server cleanly)", - "run": "pkill -TERM -x ProdDash; i=0; while pgrep -xq ProdDash && [ $i -lt 20 ]; do sleep 0.5; i=$((i+1)); done; open -g /Applications/ProdDash.app", - "template": "mac_restart", - "params": { - "host": "ingest", - "process": "ProdDash", - "app": "/Applications/ProdDash.app", - "wait_seconds": 10, - "force": false, - "background": true - } - }, - "prodcom": { - "kind": "local", - "short": "PRODCOM", - "label": "restart ProdCom (transcription; lobby captions blank for 5-15 s)", - "run": "pkill -TERM -x ProdCom; i=0; while pgrep -xq ProdCom && [ $i -lt 20 ]; do sleep 0.5; i=$((i+1)); done; pgrep -xq ProdCom && pkill -KILL -x ProdCom; sleep 1; open -g /Applications/ProdCom.app", - "template": "mac_restart", - "params": { - "host": "ingest", - "process": "ProdCom", - "app": "/Applications/ProdCom.app", - "wait_seconds": 10, - "force": true, - "background": true - } - }, - "ableton_restart": { - "host": "tracks", - "short": "ABLETON", - "label": "restart Ableton on the Tracks Mac and reopen the set it had open (unsaved changes are lost; Live may ask to recover on the Tracks Mac)", - "conn": "ableton", - "reconnect": true, - "timeout": 60, - "run": "L=$(ls -td ~/Library/Preferences/Ableton/Live*/ | head -1); S=$(grep -a 'info: Loading document \"' \"${L}Log.txt\" | grep -v '/Templates/' | tail -1 | sed -E 's/^.*Loading document \"(.*)\"[^\"]*$/\\1/'); pkill -TERM -x Live; i=0; while pgrep -xq Live && [ $i -lt 30 ]; do sleep 0.5; i=$((i+1)); done; pgrep -xq Live && pkill -KILL -x Live; sleep 1; if [ -n \"$S\" ] && [ -f \"$S\" ]; then open -a 'Ableton Live 11 Suite' \"$S\"; else open -a 'Ableton Live 11 Suite'; fi", - "template": "ableton", - "params": { - "host": "tracks", - "app": "Ableton Live 11 Suite", - "restart": true, - "wait_seconds": 15 - }, - "kind": "ssh" - }, - "translate_restart": { - "host": "translation", - "short": "TRANSLATE", - "label": "restart Church Translate on NA Translation (and its translation server)", - "timeout": 60, - "run": "pg=$(ps -o pgid= -p \"$(pgrep -x 'Church Translate' | head -1)\" 2>/dev/null | tr -d ' '); pkill -TERM -x 'Church Translate'; i=0; while pgrep -xq 'Church Translate' && [ $i -lt 30 ]; do sleep 0.5; i=$((i+1)); done; pgrep -xq 'Church Translate' && pkill -KILL -x 'Church Translate'; [ -n \"$pg\" ] && [ \"$pg\" != \"$(ps -o pgid= -p $$ | tr -d ' ')\" ] && kill -KILL -- -\"$pg\" 2>/dev/null; sleep 1; open -a 'Church Translate'", - "template": "mac_restart", - "params": { - "host": "translation", - "process": "Church Translate", - "app": "Church Translate", - "wait_seconds": 15, - "force": true, - "stop_children": true - }, - "kind": "ssh" - }, - "tascam_reconnect": { - "kind": "reconnect", - "conn": "tascam", - "short": "TASCAM", - "verify_seconds": 20, - "hung_note": "HUNG - POWER\nCYCLE IT", - "label": "reconnect Companion to the Tascam; if it still does not answer but pings, its control port has hung: power-cycle it at the rack", - "template": "reconnect", - "params": {} - }, - "hyperdeck": { - "kind": "hyperdeck", - "short": "HYPERDECK", - "verify_seconds": 15, - "hung_note": "STILL OFFLINE\nHOLD AGAIN", - "label": "turn Companion's connection to that HyperDeck off and on (what gets a restarted deck green); never reboots the deck", - "template": "hyperdeck", - "params": {} - }, - "ready_recheck": { - "kind": "ready", - "action": "recheck", - "short": "CHECK", - "label": "look at this weekend's checklist again now" - }, - "ready_lineup": { - "kind": "ready", - "action": "lineup_sync", - "short": "LINEUP", - "label": "re-sync the Band Lineup board from Planning Center" - }, - "ready_led_cross": { - "kind": "ready", - "action": "led_cross", - "short": "CROSS", - "label": "put Resolume back on the Service deck, then look at the columns again" - }, - "ready_copy_tracks": { - "kind": "ready", - "action": "copy_tracks", - "short": "COPY", - "never_auto": true, - "label": "copy this weekend's Ableton project to the locations that do not have it yet" - }, - "ready_ltc": { - "kind": "ready", - "action": "ltc_to_pco", - "short": "LTC", - "label": "write each song's timecode from the tracks into its LTC note in Planning Center" - }, - "tascam_power": { - "kind": "local", - "template": "outlet_cycle", - "short": "TASCAM PWR", - "timeout": 300, - "reconnect": true, - "conn": "tascam", - "label": "power-cycle the Tascam through its smart plug when its control port does not answer (off 45 s, on, wait for it to come back), then reconnect Companion; its Dante card only comes back from the unit's own power button; never while a HyperDeck is recording", - "run": "/usr/bin/python3 \"$HOME/Library/Application Support/status-panel/settings/outlet.py\" cycle tascam --ping 10.1.11.74 --dante STUDIO-B-Tascam --wait 45", - "params": { - "outlet": "tascam", - "address": "10.1.11.74", - "wait_seconds": 45, - "dante": "STUDIO-B-Tascam" - } - }, - "digico_relay": { - "kind": "local", - "short": "DIGICO", - "label": "restart the DiGiCo relay (Companion and the tablets to the FOH console)", - "run": "launchctl kickstart -k gui/$(id -u)/org.waterschurch.digico-relay || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.digico-relay.plist", - "template": "helpers", - "params": { - "labels": [ - "org.waterschurch.digico-relay" - ] - } - }, - "stream_foh_backup": { - "kind": "companion_press", - "page": 4, - "row": 0, - "col": 5, - "short": "FOH BACKUP", - "label": "press the FOH Backup button in Companion (page 4, row 0, column 5): the stream audio comes from the FOH DiGiCo (DMI-2 63/64) and the X32 takes snippet 8", - "template": "companion_press", - "params": { - "page": 4, - "row": 0, - "col": 5, - "button": "FOH Backup" - }, - "failover": true, - "undo": "stream_studio_b" - }, - "stream_studio_b": { - "kind": "companion_press", - "page": 4, - "row": 0, - "col": 6, - "short": "STUDIO B", - "label": "press the Studio B button in Companion (page 4, row 0, column 6): the stream audio comes from Studio B (RedNet TNX 63/64) and the X32 takes snippet 1", - "template": "companion_press", - "params": { - "page": 4, - "row": 0, - "col": 6, - "button": "Studio B" - } - } + "nav_hold_ms": 1000, + "nav_keys": [ + "pagenum" + ], + "nav_tap_page": 1, + "network": { + "burst_connections": 4, + "burst_groups": 3, + "burst_window_seconds": 10, + "gateway": "10.1.0.1", + "interface": "en0", + "remember_seconds": 300 }, - "atem": { - "host": "10.1.10.2", - "node": "/Applications/Companion [0-9]*.app/Contents/Resources/node-runtimes/node22/bin/node", - "library": "~/Library/Application Support/companion/modules/bmd-atem-*/node_modules/atem-connection", - "reference": "atem-reference.xml", - "source_folder": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/Shared drives/Waters Creative/Production Team/Backup/NA/ATEM" - }, - "auto_fix": { - "windows": [ + "page": 19, + "page_hold_ms": 2000, + "page_name": "19\\nStatus", + "ready": { + "due": [ + { + "days": [ + "tue" + ], + "end": "22:00", + "start": "14:00" + }, { "days": [ "wed" ], - "end": "21:00", - "start": "16:00" + "end": "19:00", + "start": "09:00" }, { "days": [ "sun" ], - "end": "13:30", - "start": "06:30" + "end": "14:00", + "start": "05:00" } ], - "after_seconds": 20, - "max_tries": 2, - "retry_seconds": 90, - "pause_after_network_seconds": 60, - "restart_connections": true, - "fixes": [ - "hyperdeck", - "tascam_reconnect", - "midi_reconnect", - "rtpmidi_start", - "vista", - "arena", - "obs", - "ableton", - "translate", - "dvs_prop", - "dvs_tracks", - "dvs_translation", - "solo_afl", - "atem_audio", - "atem_camera", - "atem_midi", - "spotify_play" + "folder_format": "{date} {loc} Project", + "interval_seconds": 120, + "lighting_track": "LIGHTING CUES", + "lineup_skip_positions": [], + "lineup_teams": [ + "Band", + "Communication" ], - "while_muted": [ - "spotify_play" - ] - }, - "failover": { - "windows": [ + "lineup_url": "http://127.0.0.1:24500/api/modules/band-lineup", + "locations": [ + "AB", + "SC" + ], + "ltc_note": "LTC", + "ltc_track": "LTC", + "mic_positions": { + "MC": "MC", + "Speaker": "TIM" + }, + "pco_credentials": "~/Library/Application Support/ProdDash/modules.json", + "pco_module": "pco-plan", + "pco_service_type": "275974", + "projects": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/Shared drives/Waters Creative/Ableton/Projects", + "prop_bundle_folder": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/Shared drives/Location Assets/ProPresenter", + "prop_bundle_host": "prop", + "prop_bundle_pattern": "*.probundle", + "prop_lists": [ { - "days": [ - "sun" - ], - "start": "06:30", - "end": "13:30" + "kind": "count", + "match": "announcement", + "short": "MC" + }, + { + "kind": "names", + "match": "baptism", + "short": "BAPTISM" + }, + { + "kind": "names", + "match": "dedication", + "short": "DEDICATION" } ], - "after_seconds": 2, - "note_seconds": 900 - }, - "restart_page": { - "page": 20, - "page_name": "20\\nRestart", - "jump_pos": [ - 2, - 6 - ], - "back_pos": [ - 2, - 6 - ], - "keys": [ - { - "id": "rs_dvs_prop", - "label": "DVS PROP", - "pos": [ - 0, - 1 - ], - "fix": "dvs_prop" - }, - { - "id": "rs_dvs_tracks", - "label": "DVS TRACKS", - "pos": [ - 0, - 2 - ], - "fix": "dvs_tracks" - }, - { - "id": "rs_dvs_transl", - "label": "DVS TRANSL", - "pos": [ - 0, - 3 - ], - "fix": "dvs_translation" - }, - { - "id": "rs_dvs_ingest", - "label": "DVS INGEST", - "pos": [ - 0, - 4 - ], - "fix": "dvs_ingest" - }, - { - "id": "rs_propres", - "label": "PROPRESENTER", - "pos": [ - 1, - 1 - ], - "fix": "propresenter" - }, - { - "id": "rs_ableton", - "label": "ABLETON", - "pos": [ - 1, - 2 - ], - "fix": "ableton_restart", - "watch": [ - "ableton", - "conn:ableton" - ] - }, - { - "id": "rs_translate", - "label": "TRANSLATE", - "pos": [ - 1, - 3 - ], - "fix": "translate_restart", - "watch": [ - "translate" - ] - }, - { - "id": "rs_spotify", - "label": "SPOTIFY", - "pos": [ - 1, - 4 - ], - "fix": "spotify_play" - }, - { - "id": "rs_prodcom", - "label": "PRODCOM", - "pos": [ - 2, - 1 - ], - "fix": "prodcom" - }, - { - "id": "rs_proddash", - "label": "PRODDASH", - "pos": [ - 2, - 2 - ], - "fix": "proddash" - }, - { - "id": "rs_captions", - "label": "CAPTIONS", - "pos": [ - 2, - 3 - ], - "fix": "captions", - "watch": [ - "caption_screen" - ] - }, - { - "id": "rs_bridge", - "label": "BRIDGE", - "pos": [ - 2, - 4 - ], - "fix": "bridge" - }, - { - "id": "rs_tascam", - "label": "TASCAM", - "pos": [ - 3, - 1 - ], - "fix": "tascam_reconnect", - "watch": [ - "conn:tascam" - ] - } + "prop_url": "http://10.1.14.225:1025", + "remote_timeout": 90, + "reset_day": "mon", + "resolume_deck": "Service", + "prop_host": "prop", + "resolume_url": "http://10.1.10.206:8080", + "site_folder": "NA", + "skip_songs": [], + "tracks_host": "tracks", + "vocal_positions": [ + "Vocalist", + "[Leader]" ] }, "ready_page": { - "page": 24, - "page_name": "24\\nReady", "back_pos": [ 3, 0 ], "keys": [ { + "checks": [ + { + "fix": "ready_recheck", + "item": "tracks", + "type": "ready" + } + ], "id": "rd_tracks", "label": "TRACKS", "pos": [ 0, 1 - ], - "checks": [ - { - "type": "ready", - "item": "tracks", - "fix": "ready_recheck" - } ] }, { + "checks": [ + { + "fix": "ready_recheck", + "item": "mix_foh", + "type": "ready" + } + ], "id": "rd_foh", "label": "FOH MIX", "pos": [ 0, 2 - ], - "checks": [ - { - "type": "ready", - "item": "mix_foh", - "fix": "ready_recheck" - } ] }, { + "checks": [ + { + "item": "mix_boh", + "type": "ready_manual" + } + ], "id": "rd_boh", "label": "BOH MIX", "pos": [ 0, 3 - ], - "checks": [ - { - "type": "ready_manual", - "item": "mix_boh" - } ] }, { + "checks": [ + { + "fix": "ready_recheck", + "item": "lights", + "type": "ready" + } + ], "id": "rd_lights", "label": "LIGHTS", "pos": [ 0, 4 - ], - "checks": [ - { - "type": "ready", - "item": "lights", - "fix": "ready_recheck" - } ] }, { + "checks": [ + { + "address": "10.1.10.206:5004", + "fix": "midi_reconnect", + "host": "tracks", + "peer": "Lighting PC", + "session": "NA Tracks", + "tag": "FX LIGHTS", + "type": "midi_peer" + }, + { + "address": "10.1.13.140:5004", + "fix": "midi_reconnect", + "host": "tracks", + "peer": "SuperRack-LiveBox", + "session": "NA Tracks", + "tag": "TRK LIVEBOX", + "type": "midi_peer" + }, + { + "address": "\tStudio B Mac Studio\tlocal.", + "fix": "midi_reconnect", + "host": "tracks", + "peer": "Studio B Mac Studio", + "session": "NA Tracks", + "tag": "TRK STUDIO B", + "type": "midi_peer" + }, + { + "cmd": "pgrep -xq Live", + "fix": "ableton", + "host": "tracks", + "msg": "ABLETON NOT RUNNING", + "params": { + "host": "tracks", + "process": "Live" + }, + "template": "mac_program", + "type": "remote" + } + ], "id": "rd_midi", "label": "MIDI", "pos": [ 0, 5 - ], - "checks": [ - { - "type": "midi_peer", - "host": "tracks", - "session": "NA Tracks", - "peer": "Lighting PC", - "tag": "FX LIGHTS", - "fix": "midi_reconnect", - "address": "10.1.10.206:5004" - }, - { - "type": "midi_peer", - "host": "tracks", - "session": "NA Tracks", - "peer": "SuperRack-LiveBox", - "tag": "TRK LIVEBOX", - "fix": "midi_reconnect", - "address": "10.1.13.140:5004" - }, - { - "type": "midi_peer", - "host": "tracks", - "session": "NA Tracks", - "peer": "Studio B Mac Studio", - "tag": "TRK STUDIO B", - "fix": "midi_reconnect", - "address": "\tStudio B Mac Studio\tlocal." - }, - { - "type": "remote", - "host": "tracks", - "cmd": "pgrep -xq Live", - "msg": "ABLETON NOT RUNNING", - "fix": "ableton", - "template": "mac_program", - "params": { - "host": "tracks", - "process": "Live" - } - } ] }, { + "checks": [ + { + "fix": "ready_recheck", + "item": "prop", + "type": "ready" + } + ], "id": "rd_prop", "label": "PROP", "pos": [ 1, 1 - ], - "checks": [ - { - "type": "ready", - "item": "prop", - "fix": "ready_recheck" - } ] }, { + "checks": [ + { + "fix": "ready_ltc", + "item": "timecode", + "type": "ready" + } + ], "id": "rd_timecode", "label": "TIMECODE", "pos": [ 1, 2 - ], - "checks": [ - { - "type": "ready", - "item": "timecode", - "fix": "ready_ltc" - } ] }, { + "checks": [ + { + "fix": "ready_recheck", + "item": "backgrounds", + "type": "ready" + } + ], "id": "rd_bg", "label": "BKGDS", "pos": [ 1, 3 - ], - "checks": [ - { - "type": "ready", - "item": "backgrounds", - "fix": "ready_recheck" - } ] }, { + "checks": [ + { + "fix": "ready_led_cross", + "item": "led_cross", + "type": "ready" + } + ], "id": "rd_cross", "label": "LED CROSS", "pos": [ 1, 4 - ], - "checks": [ - { - "type": "ready", - "item": "led_cross", - "fix": "ready_led_cross" - } ] }, { + "checks": [ + { + "fix": "ready_lineup", + "item": "lineup", + "type": "ready" + } + ], "id": "rd_lineup", "label": "LINEUP TV", "pos": [ 2, 1 - ], - "checks": [ - { - "type": "ready", - "item": "lineup", - "fix": "ready_lineup" - } ] }, { + "checks": [ + { + "fix": "ready_copy_tracks", + "item": "track_bundle", + "type": "ready" + } + ], "id": "rd_trk_out", "label": "TRACKS OUT", "pos": [ 3, 1 - ], - "checks": [ - { - "type": "ready", - "item": "track_bundle", - "fix": "ready_copy_tracks" - } ] }, { + "checks": [ + { + "fix": "ready_recheck", + "item": "prop_bundle", + "type": "ready" + } + ], "id": "rd_prop_out", "label": "PROP OUT", "pos": [ 3, 2 - ], - "checks": [ - { - "type": "ready", - "item": "prop_bundle", - "fix": "ready_recheck" - } ] }, { "id": "rd_all", + "kind": "ready", "label": "READY", "pos": [ 3, 7 - ], - "kind": "ready" + ] } - ] + ], + "page": 24, + "page_name": "24\\nReady" }, - "ready": { - "interval_seconds": 120, - "due": [ + "restart_page": { + "back_pos": [ + 2, + 6 + ], + "jump_pos": [ + 2, + 6 + ], + "keys": [ { - "days": [ - "sat" - ], - "start": "12:00", - "end": "23:59" + "fix": "dvs_prop", + "id": "rs_dvs_prop", + "label": "DVS PROP", + "pos": [ + 0, + 1 + ] }, { - "days": [ - "sun" + "fix": "dvs_tracks", + "id": "rs_dvs_tracks", + "label": "DVS TRACKS", + "pos": [ + 0, + 2 + ] + }, + { + "fix": "dvs_translation", + "id": "rs_dvs_transl", + "label": "DVS TRANSL", + "pos": [ + 0, + 3 + ] + }, + { + "fix": "dvs_ingest", + "id": "rs_dvs_ingest", + "label": "DVS INGEST", + "pos": [ + 0, + 4 + ] + }, + { + "fix": "propresenter", + "id": "rs_propres", + "label": "PROPRESENTER", + "pos": [ + 1, + 1 + ] + }, + { + "fix": "ableton_restart", + "id": "rs_ableton", + "label": "ABLETON", + "pos": [ + 1, + 2 ], - "start": "00:00", - "end": "13:30" + "watch": [ + "ableton", + "conn:ableton" + ] + }, + { + "fix": "translate_restart", + "id": "rs_translate", + "label": "TRANSLATE", + "pos": [ + 1, + 3 + ], + "watch": [ + "translate" + ] + }, + { + "fix": "spotify_play", + "id": "rs_spotify", + "label": "SPOTIFY", + "pos": [ + 1, + 4 + ] + }, + { + "fix": "prodcom", + "id": "rs_prodcom", + "label": "PRODCOM", + "pos": [ + 2, + 1 + ] + }, + { + "fix": "proddash", + "id": "rs_proddash", + "label": "PRODDASH", + "pos": [ + 2, + 2 + ] + }, + { + "fix": "captions", + "id": "rs_captions", + "label": "CAPTIONS", + "pos": [ + 2, + 3 + ], + "watch": [ + "caption_screen" + ] + }, + { + "fix": "bridge", + "id": "rs_bridge", + "label": "BRIDGE", + "pos": [ + 2, + 4 + ] + }, + { + "fix": "tascam_reconnect", + "id": "rs_tascam", + "label": "TASCAM", + "pos": [ + 3, + 1 + ], + "watch": [ + "conn:tascam" + ] } ], - "reset_day": "mon", - "pco_credentials": "~/Library/Application Support/ProdDash/modules.json", - "pco_module": "pco-plan", - "pco_service_type": "275974", - "skip_songs": [], - "ltc_note": "LTC", - "prop_url": "http://10.1.14.225:1025", - "prop_lists": [ - { - "match": "announcement", - "kind": "count", - "short": "MC" - }, - { - "match": "baptism", - "kind": "names", - "short": "BAPTISM" - }, - { - "match": "dedication", - "kind": "names", - "short": "DEDICATION" - } - ], - "tracks_host": "tracks", - "projects": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/Shared drives/Waters Creative/Ableton/Projects", - "site_folder": "NA", - "folder_format": "{date} {loc} Project", - "ltc_track": "LTC", - "lighting_track": "LIGHTING CUES", - "locations": [ - "AB", - "SC" - ], - "remote_timeout": 90, - "lineup_url": "http://127.0.0.1:24500/api/modules/band-lineup", - "lineup_teams": [ - "Band", - "Communication" - ], - "lineup_skip_positions": [], - "resolume_url": "http://10.1.10.206:8080", - "resolume_deck": "Service", - "prop_host": "prop", - "prop_bundle_host": "prop", - "prop_bundle_folder": "~/Library/CloudStorage/GoogleDrive-media@waterschurch.org/Shared drives/Location Assets/ProPresenter", - "prop_bundle_pattern": "*.probundle", - "vocal_positions": [ - "Vocalist", - "[Leader]" - ], - "mic_positions": { - "MC": "MC", - "Speaker": "TIM" + "page": 20, + "page_name": "20\\nRestart" + }, + "service_windows": [ + { + "days": [ + "mon", + "tue", + "wed", + "thu", + "fri", + "sat", + "sun" + ], + "end": "23:59", + "start": "00:00" } + ], + "site": { + "editing_code": "7381", + "id": "na", + "ingest_hostname": "na-ingest.local", + "ingest_ips": [ + "10.1.1.1", + "10.6.3.222" + ], + "ingest_name": "NA Ingest", + "live_services": [], + "name": "North Attleboro" }, + "spotify": { + "name": "Christian EDM 2026", + "open_wait": 8, + "play_wait": 3, + "playlist": "spotify:playlist:2uTpJVYQLFnKF1WnSOuco8" + }, + "ssh_key": "~/.ssh/id_status_panel", "tiles": [ { "id": "all", + "kind": "master", "label": "ALL", "pos": [ 0, 1 - ], - "kind": "master" + ] }, { "id": "rec", "label": "REC", - "pos": [ - 0, - 2 - ], "members": [ "hd1", "hd2", @@ -1383,29 +1384,29 @@ "hd5", "hd6", "tascam" + ], + "pos": [ + 0, + 2 ] }, { "id": "audio", "label": "AUDIO", - "pos": [ - 0, - 3 - ], "members": [ "x32", "digico", "dante", "stb_audio" + ], + "pos": [ + 0, + 3 ] }, { "id": "rf", "label": "RF", - "pos": [ - 0, - 4 - ], "members": [ "axient1", "axient2", @@ -1413,827 +1414,840 @@ "psm1", "psm2", "psm3" + ], + "pos": [ + 0, + 4 ] }, { "id": "show", "label": "SHOW", - "pos": [ - 0, - 5 - ], "members": [ "midi", "apps", "vista", "spotify" + ], + "pos": [ + 0, + 5 ] }, { "id": "video", "label": "VIDEO", - "pos": [ - 0, - 6 - ], "members": [ "atem", "led", "arena" + ], + "pos": [ + 0, + 6 ] }, { "id": "stream", "label": "STREAM", - "pos": [ - 0, - 7 - ], "members": [ "obs", "resi" + ], + "pos": [ + 0, + 7 ] }, { + "checks": [ + { + "conn": "HyperDeck_1", + "fix": { + "conn": "hyperdeck" + }, + "type": "hyperdeck" + } + ], + "hold_always": "hyperdeck", "id": "hd1", "label": "HD1", "pos": [ 1, 1 - ], - "hold_always": "hyperdeck", - "checks": [ - { - "type": "hyperdeck", - "conn": "HyperDeck_1", - "fix": { - "conn": "hyperdeck" - } - } ] }, { + "checks": [ + { + "conn": "HyperDeck_2", + "fix": { + "conn": "hyperdeck" + }, + "optional": true, + "type": "hyperdeck" + } + ], + "hold_always": "hyperdeck", "id": "hd2", "label": "HD2", "pos": [ 1, 2 - ], - "hold_always": "hyperdeck", - "checks": [ - { - "type": "hyperdeck", - "conn": "HyperDeck_2", - "optional": true, - "fix": { - "conn": "hyperdeck" - } - } ] }, { + "checks": [ + { + "conn": "HyperDeck_3", + "fix": { + "conn": "hyperdeck" + }, + "type": "hyperdeck" + } + ], + "hold_always": "hyperdeck", "id": "hd3", "label": "HD3", "pos": [ 1, 3 - ], - "hold_always": "hyperdeck", - "checks": [ - { - "type": "hyperdeck", - "conn": "HyperDeck_3", - "fix": { - "conn": "hyperdeck" - } - } ] }, { + "checks": [ + { + "conn": "HyperDeck_4", + "fix": { + "conn": "hyperdeck" + }, + "optional": true, + "type": "hyperdeck" + } + ], + "hold_always": "hyperdeck", "id": "hd4", "label": "HD4", "pos": [ 1, 4 - ], - "hold_always": "hyperdeck", - "checks": [ - { - "type": "hyperdeck", - "conn": "HyperDeck_4", - "optional": true, - "fix": { - "conn": "hyperdeck" - } - } ] }, { + "checks": [ + { + "conn": "HyperDeck_5", + "fix": { + "conn": "hyperdeck" + }, + "type": "hyperdeck" + } + ], + "hold_always": "hyperdeck", "id": "hd5", "label": "HD5", "pos": [ 1, 5 - ], - "hold_always": "hyperdeck", - "checks": [ - { - "type": "hyperdeck", - "conn": "HyperDeck_5", - "fix": { - "conn": "hyperdeck" - } - } ] }, { + "checks": [ + { + "conn": "HyperDeck_6", + "fix": { + "conn": "hyperdeck" + }, + "type": "hyperdeck" + } + ], + "hold_always": "hyperdeck", "id": "hd6", "label": "HD6", "pos": [ 1, 6 - ], - "hold_always": "hyperdeck", - "checks": [ - { - "type": "hyperdeck", - "conn": "HyperDeck_6", - "fix": { - "conn": "hyperdeck" - } - } ] }, { + "checks": [ + { + "conn": "tascam", + "fix": "tascam_reconnect", + "type": "tascam" + }, + { + "conn": "tascam", + "fix": "tascam_power", + "host": "10.1.11.74", + "name": "TASCAM", + "type": "ping" + } + ], + "hold_always": "tascam_reconnect", "id": "tascam", "label": "TASCAM", "pos": [ 1, 7 - ], - "hold_always": "tascam_reconnect", - "checks": [ - { - "type": "tascam", - "conn": "tascam", - "fix": "tascam_reconnect" - }, - { - "type": "ping", - "conn": "tascam", - "host": "10.1.11.74", - "name": "TASCAM", - "fix": "tascam_power" - } ] }, { + "checks": [ + { + "conn": "x32", + "type": "conn" + } + ], "id": "x32", "label": "X32", "pos": [ 2, 1 - ], - "checks": [ - { - "type": "conn", - "conn": "x32" - } ] }, { - "id": "digico", - "label": "DIGICO", "checks": [ { - "type": "conn", "conn": "DiGiCo_FOH", - "tag": "DIGICO" + "tag": "DIGICO", + "type": "conn" }, { - "type": "launchd", + "fix": "digico_relay", "label": "org.waterschurch.digico-relay", "name": "RELAY", - "fix": "digico_relay" + "type": "launchd" }, { - "type": "ping", "host": "10.1.60.100", - "name": "DIGICO" + "name": "DIGICO", + "type": "ping" } - ] + ], + "id": "digico", + "label": "DIGICO" }, { - "id": "stb_audio", - "label": "STB AUDIO", "checks": [ { - "type": "ping", - "host": "10.1.13.175", - "name": "STUDIO B", - "interval_seconds": 1, - "failures": 2, "debounce_seconds": 1, - "fix": "stream_foh_backup" + "failures": 2, + "fix": "stream_foh_backup", + "host": "10.1.13.175", + "interval_seconds": 1, + "name": "STUDIO B", + "type": "ping" }, { - "type": "remote", - "host": "studiob", "cmd": "pgrep -xq 'Pro Tools'", - "msg": "PRO TOOLS NOT RUNNING", - "interval_seconds": 2, "debounce_seconds": 1, "fix": "stream_foh_backup", - "template": "mac_program", + "host": "studiob", + "interval_seconds": 2, + "msg": "PRO TOOLS NOT RUNNING", "params": { "host": "studiob", "process": "Pro Tools" - } + }, + "template": "mac_program", + "type": "remote" }, { - "type": "dante", "conn": "audinate-dantecontroller", "devices": { "STUDIO-B-RedNet-TNX": "STB TNX" }, "fix": { "STUDIO-B-RedNet-TNX": "stream_foh_backup" - } + }, + "type": "dante" } - ] + ], + "id": "stb_audio", + "label": "STB AUDIO" }, { + "checks": [ + { + "conn": "audinate-dantecontroller", + "devices": { + "BOH-AD4Q-A": "AD4Q-A", + "BOH-AD4Q-B": "AD4Q-B", + "BOH-DQ-Rack": "DQ RACK", + "BOH-WING": "WING", + "FOH-DiGiCo-DMI-1": "DMI-1", + "FOH-DiGiCo-DMI-2": "DMI-2", + "FOH-SuperRack-LiveBox": "SUPERRACK", + "NA-ProPresenter-Mac": "PROP MAC", + "NA-Tracks": "TRACKS", + "NA-Translation": "TRANSL", + "STUDIO-A-Ingest": "INGEST", + "STUDIO-A-Resi-AJA-SDI": "RESI AJA", + "STUDIO-A-X32": "STA X32", + "STUDIO-B-MON": "STB MON", + "STUDIO-B-RedNet-TNX": "STB TNX", + "STUDIO-B-SOLO-MON": "STB SOLO", + "STUDIO-B-TB": "STB TB", + "STUDIO-B-TB-MON": "STB TB MON", + "STUDIO-B-Tascam": "STB TASCAM" + }, + "fix": { + "*": "dante-auto", + "NA-ProPresenter-Mac": "dvs_prop", + "NA-Tracks": "dvs_tracks", + "NA-Translation": "dvs_translation", + "STUDIO-A-Ingest": "dvs_ingest" + }, + "type": "dante" + } + ], "id": "dante", "label": "DANTE", "pos": [ 2, 2 - ], - "checks": [ - { - "type": "dante", - "conn": "audinate-dantecontroller", - "fix": { - "NA-ProPresenter-Mac": "dvs_prop", - "NA-Tracks": "dvs_tracks", - "NA-Translation": "dvs_translation", - "STUDIO-A-Ingest": "dvs_ingest", - "*": "dante-auto" - }, - "devices": { - "FOH-DiGiCo-DMI-1": "DMI-1", - "FOH-DiGiCo-DMI-2": "DMI-2", - "BOH-WING": "WING", - "BOH-DQ-Rack": "DQ RACK", - "BOH-AD4Q-A": "AD4Q-A", - "BOH-AD4Q-B": "AD4Q-B", - "STUDIO-A-X32": "STA X32", - "STUDIO-A-Resi-AJA-SDI": "RESI AJA", - "STUDIO-A-Ingest": "INGEST", - "NA-Translation": "TRANSL", - "STUDIO-B-RedNet-TNX": "STB TNX", - "STUDIO-B-TB": "STB TB", - "STUDIO-B-TB-MON": "STB TB MON", - "STUDIO-B-MON": "STB MON", - "STUDIO-B-SOLO-MON": "STB SOLO", - "STUDIO-B-Tascam": "STB TASCAM", - "NA-Tracks": "TRACKS", - "FOH-SuperRack-LiveBox": "SUPERRACK", - "NA-ProPresenter-Mac": "PROP MAC" - } - } ] }, { + "checks": [ + { + "address": "10.1.10.206:5004", + "fix": "midi_reconnect", + "peer": "Lighting PC", + "session": "NA Ingest", + "tag": "LIGHT PC", + "type": "midi_peer" + }, + { + "address": "10.1.13.140:5004", + "fix": "midi_reconnect", + "peer": "SuperRack-LiveBox", + "session": "NA Ingest", + "tag": "SUPERRACK", + "type": "midi_peer" + }, + { + "address": "10.1.10.206:5004", + "fix": "midi_reconnect", + "host": "tracks", + "peer": "Lighting PC", + "session": "NA Tracks", + "tag": "FX LIGHTS", + "type": "midi_peer" + }, + { + "address": "10.1.13.140:5004", + "fix": "midi_reconnect", + "host": "tracks", + "peer": "SuperRack-LiveBox", + "session": "NA Tracks", + "tag": "TRK LIVEBOX", + "type": "midi_peer" + }, + { + "address": "\tStudio B Mac Studio\tlocal.", + "fix": "midi_reconnect", + "host": "tracks", + "peer": "Studio B Mac Studio", + "session": "NA Tracks", + "tag": "TRK STUDIO B", + "type": "midi_peer" + }, + { + "address": "10.1.10.206:5004", + "fix": "midi_reconnect", + "host": "prop", + "peer": "Lighting PC", + "session": "NA ProP", + "tag": "PROP LIGHTS", + "type": "midi_peer" + }, + { + "conn": "lighting-midi", + "tag": "COMPANION MIDI", + "type": "conn" + }, + { + "fix": "rtpmidi_start", + "instance": "Lighting PC", + "name": "LIGHT PC MIDI", + "type": "mdns" + }, + { + "instance": "NA Tracks", + "name": "TRACKS MIDI", + "type": "mdns" + }, + { + "name": "TRACKS IN", + "source": "Network NA Ingest Listen", + "type": "midi_rx" + } + ], "id": "midi", "label": "MIDI", "pos": [ 2, 3 - ], - "checks": [ - { - "type": "midi_peer", - "session": "NA Ingest", - "peer": "Lighting PC", - "tag": "LIGHT PC", - "fix": "midi_reconnect", - "address": "10.1.10.206:5004" - }, - { - "type": "midi_peer", - "session": "NA Ingest", - "peer": "SuperRack-LiveBox", - "tag": "SUPERRACK", - "fix": "midi_reconnect", - "address": "10.1.13.140:5004" - }, - { - "type": "midi_peer", - "host": "tracks", - "session": "NA Tracks", - "peer": "Lighting PC", - "tag": "FX LIGHTS", - "fix": "midi_reconnect", - "address": "10.1.10.206:5004" - }, - { - "type": "midi_peer", - "host": "tracks", - "session": "NA Tracks", - "peer": "SuperRack-LiveBox", - "tag": "TRK LIVEBOX", - "fix": "midi_reconnect", - "address": "10.1.13.140:5004" - }, - { - "type": "midi_peer", - "host": "tracks", - "session": "NA Tracks", - "peer": "Studio B Mac Studio", - "tag": "TRK STUDIO B", - "fix": "midi_reconnect", - "address": "\tStudio B Mac Studio\tlocal." - }, - { - "type": "midi_peer", - "host": "prop", - "session": "NA ProP", - "peer": "Lighting PC", - "tag": "PROP LIGHTS", - "fix": "midi_reconnect", - "address": "10.1.10.206:5004" - }, - { - "type": "conn", - "conn": "lighting-midi", - "tag": "COMPANION MIDI" - }, - { - "type": "mdns", - "instance": "Lighting PC", - "name": "LIGHT PC MIDI", - "fix": "rtpmidi_start" - }, - { - "type": "mdns", - "instance": "NA Tracks", - "name": "TRACKS MIDI" - }, - { - "type": "midi_rx", - "source": "Network NA Ingest Listen", - "name": "TRACKS IN" - } ] }, { - "id": "spotify", - "label": "SPOTIFY", - "hold_always": "spotify_play", "checks": [ { - "type": "spotify", - "fix": "spotify_play" + "fix": "spotify_play", + "type": "spotify" } - ] + ], + "hold_always": "spotify_play", + "id": "spotify", + "label": "SPOTIFY" }, { "id": "ready", + "kind": "ready", "label": "READY", "pos": [ 2, 7 - ], - "kind": "ready" + ] }, { + "checks": [ + { + "fix": "vista", + "max_silence_seconds": 5, + "source": "10.1.10.206", + "type": "artnet" + } + ], "id": "vista", "label": "VISTA", "pos": [ 2, 5 - ], - "checks": [ - { - "type": "artnet", - "source": "10.1.10.206", - "max_silence_seconds": 5, - "fix": "vista" - } ] }, { + "checks": [ + { + "conn": "Propresenter-API", + "fix": "propresenter", + "ignore_warning": true, + "tag": "PROPRES", + "type": "conn" + }, + { + "cmd": "pgrep -xq ProPresenter", + "fix": "propresenter", + "host": "prop", + "msg": "PROPRES NOT RUNNING", + "params": { + "host": "prop", + "process": "ProPresenter" + }, + "template": "mac_program", + "type": "remote" + }, + { + "conn": "ableton", + "tag": "ABLETON", + "type": "conn" + }, + { + "cmd": "pgrep -xq Live", + "fix": "ableton", + "host": "tracks", + "msg": "ABLETON NOT RUNNING", + "params": { + "host": "tracks", + "process": "Live" + }, + "template": "mac_program", + "type": "remote" + }, + { + "cmd": "pgrep -xq 'Church Translate'", + "fix": "translate", + "host": "translation", + "msg": "TRANSLATE NOT RUNNING", + "params": { + "host": "translation", + "process": "Church Translate" + }, + "template": "mac_program", + "type": "remote" + }, + { + "host": "10.1.3.227", + "name": "TRACKS MAC", + "type": "ping" + }, + { + "fix": "propresenter", + "interval_seconds": 5, + "msg": "PROP TV DOWN", + "type": "http", + "url": "http://10.1.14.225:1025/" + }, + { + "fix": "bibleapp", + "interval_seconds": 5, + "msg": "BIBLE APP DOWN", + "type": "http", + "url": "http://10.6.7.13:8080/" + }, + { + "conn": "BibleTV", + "tag": "BIBLE OSC", + "type": "conn" + }, + { + "cmd": "! grep -q '\"bad\":true' \"$HOME/Library/Application Support/status-panel/solo-mode.json\"", + "fix": "solo_afl", + "host": "studiob", + "msg": "PT SOLO NOT AFL", + "params": { + "host": "studiob" + }, + "template": "helper_flag", + "type": "remote" + }, + { + "cmd": "f=\"$HOME/Library/Application Support/status-panel/solo-mode.json\"; [ $(( $(date +%s) - $(stat -f %m \"$f\") )) -lt 20 ] && grep -q '\"trusted\":true' \"$f\"", + "host": "studiob", + "msg": "PT SOLO CHECK OFF", + "params": { + "host": "studiob" + }, + "template": "helper_fresh", + "type": "remote" + } + ], "id": "apps", "label": "APPS", "pos": [ 2, 4 - ], - "checks": [ - { - "type": "conn", - "conn": "Propresenter-API", - "tag": "PROPRES", - "fix": "propresenter", - "ignore_warning": true - }, - { - "type": "remote", - "host": "prop", - "cmd": "pgrep -xq ProPresenter", - "msg": "PROPRES NOT RUNNING", - "fix": "propresenter", - "template": "mac_program", - "params": { - "host": "prop", - "process": "ProPresenter" - } - }, - { - "type": "conn", - "conn": "ableton", - "tag": "ABLETON" - }, - { - "type": "remote", - "host": "tracks", - "cmd": "pgrep -xq Live", - "msg": "ABLETON NOT RUNNING", - "fix": "ableton", - "template": "mac_program", - "params": { - "host": "tracks", - "process": "Live" - } - }, - { - "type": "remote", - "host": "translation", - "cmd": "pgrep -xq 'Church Translate'", - "msg": "TRANSLATE NOT RUNNING", - "fix": "translate", - "template": "mac_program", - "params": { - "host": "translation", - "process": "Church Translate" - } - }, - { - "type": "ping", - "host": "10.1.3.227", - "name": "TRACKS MAC" - }, - { - "type": "http", - "url": "http://10.1.14.225:1025/", - "msg": "PROP TV DOWN", - "interval_seconds": 5, - "fix": "propresenter" - }, - { - "type": "http", - "url": "http://10.6.7.13:8080/", - "msg": "BIBLE APP DOWN", - "interval_seconds": 5, - "fix": "bibleapp" - }, - { - "type": "conn", - "conn": "BibleTV", - "tag": "BIBLE OSC" - }, - { - "type": "remote", - "host": "studiob", - "cmd": "! grep -q '\"bad\":true' \"$HOME/Library/Application Support/status-panel/solo-mode.json\"", - "msg": "PT SOLO NOT AFL", - "fix": "solo_afl", - "template": "helper_flag", - "params": { - "host": "studiob" - } - }, - { - "type": "remote", - "host": "studiob", - "cmd": "f=\"$HOME/Library/Application Support/status-panel/solo-mode.json\"; [ $(( $(date +%s) - $(stat -f %m \"$f\") )) -lt 20 ] && grep -q '\"trusted\":true' \"$f\"", - "msg": "PT SOLO CHECK OFF", - "template": "helper_fresh", - "params": { - "host": "studiob" - } - } ] }, { "id": "service", + "kind": "service", "label": "ALARMS", "pos": [ 3, 0 - ], - "kind": "service" + ] }, { + "checks": [ + { + "conn": "ATEM", + "type": "conn" + }, + { + "fix": "atem_audio", + "msg": "ATEM AUDIO WRONG", + "part": "audio", + "type": "atem" + }, + { + "fix": "atem_camera", + "msg": "ATEM CAMERA WRONG", + "part": "camera", + "type": "atem" + }, + { + "domain": "com.blackmagic-design.ATEM Software Control", + "fix": "atem_midi", + "key": "midiEnabled", + "msg": "ATEM SW MIDI ON", + "type": "pref", + "want": "0" + } + ], "id": "atem", "label": "ATEM", "pos": [ 3, 1 - ], - "checks": [ - { - "type": "conn", - "conn": "ATEM" - }, - { - "type": "atem", - "part": "audio", - "msg": "ATEM AUDIO WRONG", - "fix": "atem_audio" - }, - { - "type": "atem", - "part": "camera", - "msg": "ATEM CAMERA WRONG", - "fix": "atem_camera" - }, - { - "type": "pref", - "domain": "com.blackmagic-design.ATEM Software Control", - "key": "midiEnabled", - "want": "0", - "msg": "ATEM SW MIDI ON", - "fix": "atem_midi" - } ] }, { + "checks": [ + { + "conn": "OBS-Youtube", + "fix": "obs", + "service_only": true, + "type": "conn" + } + ], "id": "obs", "label": "OBS", "pos": [ 3, 2 - ], - "checks": [ - { - "type": "conn", - "conn": "OBS-Youtube", - "service_only": true, - "fix": "obs" - } ] }, { + "checks": [ + { + "conn": "RESI_Decoder", + "tag": "DECODER", + "type": "conn" + }, + { + "host": "10.1.10.203", + "name": "DECODER", + "type": "ping" + }, + { + "host": "10.1.10.202", + "name": "ENCODER", + "type": "ping" + }, + { + "interval_seconds": 10, + "msg": "ENCODER WEB DOWN", + "type": "http", + "url": "http://10.1.10.202/" + }, + { + "conn": "resi-studio-cues", + "type": "conn" + }, + { + "name": "CLOUD", + "type": "http", + "url": "https://central.resi.io/" + } + ], "id": "resi", "label": "RESI", "pos": [ 3, 3 - ], - "checks": [ - { - "type": "conn", - "conn": "RESI_Decoder", - "tag": "DECODER" - }, - { - "type": "ping", - "host": "10.1.10.203", - "name": "DECODER" - }, - { - "type": "ping", - "host": "10.1.10.202", - "name": "ENCODER" - }, - { - "type": "http", - "url": "http://10.1.10.202/", - "msg": "ENCODER WEB DOWN", - "interval_seconds": 10 - }, - { - "type": "conn", - "conn": "resi-studio-cues" - }, - { - "type": "http", - "url": "https://central.resi.io/", - "name": "CLOUD" - } ] }, { "id": "net", + "kind": "network", "label": "NET", "pos": [ 3, 4 - ], - "kind": "network" + ] }, { + "checks": [ + { + "fix": "bridge", + "label": "org.waterschurch.resi-propresenter", + "name": "BRIDGE", + "type": "launchd" + }, + { + "fix": "captions", + "label": "org.waterschurch.lobby-captions", + "name": "CAPTIONS", + "type": "launchd" + }, + { + "fix": "caption_screen", + "label": "org.waterschurch.lobby-captions-display", + "name": "CAPTION SCREEN", + "type": "launchd" + }, + { + "fix": "prodcom", + "interval_seconds": 5, + "msg": "PRODCOM DOWN", + "type": "http", + "url": "http://127.0.0.1:24480/api/v1/ping" + }, + { + "fix": "proddash", + "match": "/Applications/ProdDash.app/Contents/MacOS/ProdDash", + "name": "PRODDASH", + "type": "process" + }, + { + "fix": "proddash", + "interval_seconds": 10, + "msg": "PRODDASH DOWN", + "type": "http", + "url": "http://127.0.0.1:24500/" + }, + { + "err_gb": 8, + "path": "/", + "type": "disk", + "warn_gb": 25 + } + ], "id": "ingest", "label": "INGEST", "pos": [ 3, 5 - ], - "checks": [ - { - "type": "launchd", - "label": "org.waterschurch.resi-propresenter", - "name": "BRIDGE", - "fix": "bridge" - }, - { - "type": "launchd", - "label": "org.waterschurch.lobby-captions", - "name": "CAPTIONS", - "fix": "captions" - }, - { - "type": "launchd", - "label": "org.waterschurch.lobby-captions-display", - "name": "CAPTION SCREEN", - "fix": "caption_screen" - }, - { - "type": "http", - "url": "http://127.0.0.1:24480/api/v1/ping", - "msg": "PRODCOM DOWN", - "interval_seconds": 5, - "fix": "prodcom" - }, - { - "type": "process", - "match": "/Applications/ProdDash.app/Contents/MacOS/ProdDash", - "name": "PRODDASH", - "fix": "proddash" - }, - { - "type": "http", - "url": "http://127.0.0.1:24500/", - "msg": "PRODDASH DOWN", - "interval_seconds": 10, - "fix": "proddash" - }, - { - "type": "disk", - "path": "/", - "warn_gb": 25, - "err_gb": 8 - } ] }, { + "checks": [ + { + "conn": "VX1000", + "tag": "VX1000", + "type": "conn" + }, + { + "conn": "UHDJr", + "tag": "UHDJR", + "type": "conn" + }, + { + "host": "10.1.14.116", + "name": "VX1000", + "type": "ping" + }, + { + "host": "10.1.14.115", + "name": "UHDJR", + "type": "ping" + } + ], "id": "led", "label": "LED", "pos": [ 3, 6 - ], - "checks": [ - { - "type": "conn", - "conn": "VX1000", - "tag": "VX1000" - }, - { - "type": "conn", - "conn": "UHDJr", - "tag": "UHDJR" - }, - { - "type": "ping", - "host": "10.1.14.116", - "name": "VX1000" - }, - { - "type": "ping", - "host": "10.1.14.115", - "name": "UHDJR" - } ] }, { + "checks": [ + { + "conn": "arena", + "fix": "arena", + "type": "conn" + } + ], "id": "arena", "label": "ARENA", "pos": [ 3, 7 - ], - "checks": [ - { - "type": "conn", - "conn": "arena", - "fix": "arena" - } ] }, { - "id": "axient1", - "label": "AXIENT 1", "checks": [ { - "type": "shure", + "channels": 4, "conn": "Axient_1", - "channels": 4 + "type": "shure" }, { - "type": "ping", "conn": "Axient_1", "host": "10.1.1.101", - "name": "AXIENT 1" + "name": "AXIENT 1", + "type": "ping" } - ] + ], + "id": "axient1", + "label": "AXIENT 1" }, { - "id": "axient2", - "label": "AXIENT 2", "checks": [ { - "type": "shure", + "channels": 4, "conn": "Axient_2", - "channels": 4 + "type": "shure" }, { - "type": "ping", "conn": "Axient_2", "host": "10.1.1.102", - "name": "AXIENT 2" + "name": "AXIENT 2", + "type": "ping" } - ] + ], + "id": "axient2", + "label": "AXIENT 2" }, { - "id": "ulxd", - "label": "ULXD", "checks": [ { - "type": "shure", + "channels": 1, "conn": "ULXD-4", - "channels": 1 + "type": "shure" }, { - "type": "ping", "conn": "ULXD-4", "host": "10.1.1.106", - "name": "ULXD" + "name": "ULXD", + "type": "ping" } - ] + ], + "id": "ulxd", + "label": "ULXD" }, { - "id": "psm1", - "label": "PSM 1", "checks": [ { - "type": "psm", + "channels": 2, "conn": "PSM_1", - "channels": 2 + "type": "psm" }, { - "type": "ping", "conn": "PSM_1", "host": "10.1.1.103", - "name": "PSM 1" + "name": "PSM 1", + "type": "ping" } - ] + ], + "id": "psm1", + "label": "PSM 1" }, { - "id": "psm2", - "label": "PSM 2", "checks": [ { - "type": "psm", + "channels": 2, "conn": "PSM_2", - "channels": 2 + "type": "psm" }, { - "type": "ping", "conn": "PSM_2", "host": "10.1.1.104", - "name": "PSM 2" + "name": "PSM 2", + "type": "ping" } - ] + ], + "id": "psm2", + "label": "PSM 2" }, { - "id": "psm3", - "label": "PSM 3", "checks": [ { - "type": "psm", + "channels": 2, "conn": "PSM_3", - "channels": 2 + "type": "psm" }, { - "type": "ping", "conn": "PSM_3", "host": "10.1.1.105", - "name": "PSM 3" + "name": "PSM 3", + "type": "ping" } - ] + ], + "id": "psm3", + "label": "PSM 3" } - ] + ], + "timing": { + "alert_after_seconds": 30, + "fix_cooldown_seconds": 60 + }, + "variables": { + "command": "sp_cmd", + "heartbeat": "sp_hb", + "state": "sp_state" + } } diff --git a/install.sh b/install.sh index 5c70d24..933c2aa 100644 --- a/install.sh +++ b/install.sh @@ -4,6 +4,7 @@ # # zsh install.sh install / update and (re)start # zsh install.sh --keep-config update the code but leave the live config.json as it is +# zsh install.sh --replace-config replace the live config even when it came from an Apply on the settings page # zsh install.sh --site first install of a campus: its config from sites//, else sites/_template/ # zsh install.sh --rollback put the previous code back (kept in prev/) and restart # zsh install.sh --remove stop it and remove the LaunchAgent (files stay) @@ -26,6 +27,7 @@ for arg in "$@"; do if [[ $SITE_NEXT == 1 ]]; then SITE="$arg"; SITE_NEXT=0; continue; fi case "$arg" in --keep-config) KEEP_CONFIG=1 ;; + --replace-config) REPLACE_CONFIG=1 ;; --site) SITE_NEXT=1 ;; --remove|--rollback|--now) ;; *) echo "unknown option $arg" >&2; exit 2 ;; @@ -151,6 +153,31 @@ then exit 3 fi +# 0b. Never undo an Apply. Live settings that came from the settings page (their fingerprint is the +# "sha_after" of an entry in history/) and differ from the config this would install were changed on +# the page since this folder was made: replacing them would quietly undo that (2026-10-15: READY hours +# set on the page went back to the folder's defaults). Copy them into the folder first, or say so. +if [[ $KEEP_CONFIG == 0 && ${REPLACE_CONFIG:-0} == 0 && -f "$DEST/config.json" && -d "$DEST/history" ]] \ + && ! cmp -s "${SRC_CONFIG:-$SRC/config.json}" "$DEST/config.json" \ + && /usr/bin/python3 -c ' +import glob, hashlib, json, os, sys +d = sys.argv[1] +live = hashlib.sha256(open(os.path.join(d, "config.json"), "rb").read()).hexdigest() +for m in glob.glob(os.path.join(d, "history", "*.meta.json")): + try: + meta = json.load(open(m)) + except ValueError: + continue + if meta.get("sha_after") == live: + print("the live settings come from the settings page (%s, %s): not replacing them." % (meta.get("by", "?"), os.path.basename(m)[:15])) + sys.exit(0) +sys.exit(1)' "$DEST" +then + echo "Copy them into this folder first (scp checklist-ingest:'Library/Application Support/status-panel/config.json' config.json)," >&2 + echo "or run with --keep-config (code only) or --replace-config (overwrite them)." >&2 + exit 4 +fi + # 1. The config that will run, checked with the code that will run it. mkdir -p "$DEST" if [[ $KEEP_CONFIG == 1 && -f "$DEST/config.json" ]]; then diff --git a/settings/server.py b/settings/server.py index d726de2..6b76f9f 100644 --- a/settings/server.py +++ b/settings/server.py @@ -863,12 +863,22 @@ class Handler(http.server.BaseHTTPRequestHandler): return if path == "/api/draft" and self.command == "PUT": cfg = schema.unmask(body.get("config") or {}, site.load()) + # A page that loaded the settings before they changed here (an Apply from another tab, a + # program update) sends what it started from: only its own changes are put on top of the + # live settings, so newer values are never quietly undone (2026-10-15). + rebased = False + if body.get("base") and body["base"] != site.cfg_sha and isinstance(body.get("base_config"), dict): + base_cfg = schema.unmask(body["base_config"], site.load()) + cfg, _ = applying.rebase({"config": cfg}, site.load(), base_cfg) + rebased = True + log.info("draft from %s rebased: the page started from %s, the settings are now %s", + who, str(body["base"])[:10], (site.cfg_sha or "")[:10]) draft, conflict = site.draft.update(cfg, who, site.cfg_sha, body.get("expect")) if conflict: changed = applying.changed_paths(cfg, conflict["config"]) return self.send(409, {"error": "%s changed this a moment ago" % conflict.get("by", "someone"), "draft": self.masked_draft(conflict), "changed": [applying.words_for(p, a, b, cfg, conflict["config"])["where"] for p, a, b in changed[:5]]}) - return self.send(200, {"draft": self.masked_draft(draft)}) + return self.send(200, {"draft": self.masked_draft(draft), "rebased": rebased, "live_sha": site.cfg_sha}) if path == "/api/draft" and self.command == "DELETE": site.draft.clear() self.discard_atem() diff --git a/settings/ui/app.js b/settings/ui/app.js index c871f5c..c77bb73 100644 --- a/settings/ui/app.js +++ b/settings/ui/app.js @@ -77,12 +77,15 @@ async function loadAll() { const [c, s, d, b] = await Promise.all([api("/api/config"), api("/api/schema"), api("/api/draft"), api("/api/builders")]); state.live = c.config; state.liveSha = c.sha; state.schema = s; state.draft = d.draft; state.draftStale = !!d.stale; state.atemPending = !!d.atem_pending; state.builders = b; + // What this page's draft was made from: a save carries it, so the Mac can put the change on top of + // settings that moved on meanwhile (an Apply elsewhere, a program update) instead of undoing them. + if (state.draft && !state.draft.base && !d.stale) { state.draft.base = c.sha; state.draft.baseConfig = deep(c.config); } document.getElementById("sitename").textContent = (state.live.site || {}).name || ""; badge(); } function ensureDraft() { if (!needEditing()) return false; - if (!state.draft) state.draft = { config: deep(state.live), version: null, by: state.name }; + if (!state.draft) state.draft = { config: deep(state.live), version: null, by: state.name, base: state.liveSha, baseConfig: deep(state.live) }; return true; } function changed() { @@ -110,8 +113,15 @@ state.saving = new Promise((done) => { state.saveTimer = setTimeout(async () => { try { - const r = await post("/api/draft", { config: state.draft.config, expect: state.draft.version }, "PUT"); + const r = await post("/api/draft", { config: state.draft.config, expect: state.draft.version, base: state.draft.base, base_config: state.draft.base ? state.draft.baseConfig : undefined }, "PUT"); state.draft.version = r.draft.version; state.draft.by = r.draft.by; state.draft.started = r.draft.started; state.draft.started_by = r.draft.started_by; + if (r.rebased) { // the settings moved on since this page loaded: keep both + const c = await api("/api/config"); + state.live = c.config; state.liveSha = c.sha; + state.draft.config = r.draft.config; state.draft.base = c.sha; state.draft.baseConfig = deep(c.config); + badge(); route(); + alertBox("The settings changed on the Mac since this page loaded (an Apply elsewhere, or a program update). Your changes were put on top of the new settings; nothing of either was lost."); + } else if (r.live_sha) { state.draft.base = r.live_sha; } } catch (e) { if (e.status === 409) { alertBox(`${e.body.error}: ${(e.body.changed || []).join(", ") || "the draft"}. The page now shows their version.`); state.draft = e.body.draft; route(); } else if (e.status === 401 || e.status === 403) { alertBox(e.message); state.editing = null; state.name = null; store.set("sp-code", null); showEditing(); } diff --git a/tests/test_readiness.py b/tests/test_readiness.py index 96dc50b..1a4bb35 100644 --- a/tests/test_readiness.py +++ b/tests/test_readiness.py @@ -19,6 +19,8 @@ sp.log.addHandler(logging.NullHandler()) sp.log.propagate = False CONFIG = sp.load_config(os.path.join(ROOT, "config.json")) +# The tests pin their own READY hours (the campus sets its own on the settings page). +CONFIG["ready"]["due"] = [{"days": ["sat"], "start": "12:00", "end": "23:59"}, {"days": ["sun"], "start": "00:00", "end": "13:30"}] RC = CONFIG["ready"] WEDNESDAY = datetime(2026, 10, 14, 15, 0).timestamp() # the week before the plan: gaps are amber SATURDAY = datetime(2026, 10, 17, 16, 0).timestamp() # inside "due": gaps are red diff --git a/tests/test_schema.py b/tests/test_schema.py index f8439ae..e08d2df 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -295,7 +295,9 @@ class TestSchemaMatchesCode(unittest.TestCase): def test_phrases(self): cfg = sp.load_config(os.path.join(ROOT, "config.json")) p = schema.phrases(cfg) - self.assertEqual(p["auto_fix"], "may run by itself on Sundays 06:30-13:30") + self.assertEqual(p["auto_fix"], "may run by itself " + sp.windows_phrase(cfg["auto_fix"]["windows"])) # the campus's own hours + self.assertEqual(schema.phrases(dict(cfg, auto_fix={"windows": [{"days": ["sun"], "start": "06:30", "end": "13:30"}]}))["auto_fix"], + "may run by itself on Sundays 06:30-13:30") self.assertEqual(p["live"], "changes wait while a HyperDeck is recording") self.assertEqual(p["hold"], "HOLD 2s") self.assertEqual(sp.windows_phrase([{"days": ["sat", "sun"], "start": "18:00", "end": "00:30"}]), diff --git a/tests/test_settings.py b/tests/test_settings.py index ce7ad30..829f02d 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -291,6 +291,27 @@ class TestServer(unittest.TestCase): self.assertEqual(len(v["changes"]), 1); self.assertTrue(v["changes"][0]["text"].startswith("computer Lighting PC › notes")) self.put("/api/draft", {}, method="DELETE") + def test_a_page_that_loaded_before_a_change_never_undoes_it(self): + """2026-10-15: a tab loaded the settings, a program update then added ready.prop_host, the tab + changed one window and saved its whole (older) copy - Apply took prop_host out again. Now the + page says what it started from and only its own change goes on top of the live settings.""" + import hashlib + before = self.site.load(); base_sha = self.site.cfg_sha + newer = json.loads(json.dumps(before)); newer["debounce_seconds"] = (before.get("debounce_seconds") or 3) + 2 + sp.write_json(self.site.config_path, newer) # changed under the page + self.site.load() + page = json.loads(json.dumps(before)); page["flap"] = dict(before.get("flap") or {}, count=7) # the page's own edit + st, r = self.put("/api/draft", {"config": page, "base": base_sha, "base_config": before}, who="christian") + self.assertTrue(r["rebased"]) + d = self.site.draft.read()["config"] + self.assertEqual(d["flap"]["count"], 7) # the page's change + self.assertEqual(d["debounce_seconds"], newer["debounce_seconds"]) # the newer value kept + st, r = self.put("/api/draft", {"config": d, "base": self.site.cfg_sha, "base_config": d}, who="christian") + self.assertFalse(r["rebased"]) # up to date: nothing to do + self.put("/api/draft", {}, method="DELETE") + sp.write_json(self.site.config_path, before) + self.site.load() + def test_probe_is_refused_while_a_fix_runs(self): json.dump({"fix-x": {"key": "hd2"}}, open(os.path.join(self.data, "fixes-running.json"), "w")) try: diff --git a/tests/test_status_panel.py b/tests/test_status_panel.py index 842c8a7..e261adf 100644 --- a/tests/test_status_panel.py +++ b/tests/test_status_panel.py @@ -30,6 +30,8 @@ CONFIG["tiles"] = [dict(t, label="SERVICE") if t["id"] == "service" else t for t # A Sunday 08:00 (inside the service window) and a Wednesday 15:00 (outside it). SUNDAY = datetime(2026, 10, 11, 8, 0).timestamp() CONFIG["auto_fix"]["log_dir"] = tempfile.mkdtemp(prefix="sp-autolog-") # never the real Desktop +# ...and their own auto-fix hours, so the campus can change its own on the settings page. +CONFIG["auto_fix"]["windows"] = [{"days": ["wed"], "start": "16:00", "end": "21:00"}, {"days": ["sun"], "start": "06:30", "end": "13:30"}] WEDNESDAY = datetime(2026, 10, 7, 15, 0).timestamp() @@ -1447,7 +1449,8 @@ class TestFixes(unittest.TestCase): for t in self.engine.fix_threads: t.join(2) self.assertEqual(self.engine.running, {}) - self.assertEqual(sp.exit_timeout(CONFIG), 90) # the longest fix + its verify + 10 + # the longest fix + its verify + 10: the Tascam outlet power-cycle (timeout 300) + self.assertEqual(sp.exit_timeout(CONFIG), 310) def test_a_hyperdeck_switched_off_is_always_switched_back_on(self): calls = []