No developer-tools pop-ups on the computers; apps found where they really are; SuperRack and X32-Edit
On a Mac without the Command Line Tools, /usr/bin/python3 is a stub that opens Apple's installer on that Mac's screen: Woonsocket's Tracks and FOH Macs showed it after joining (and would every ten minutes). Python now runs on another Mac only when xcode-select says it is there (MIDI reads, MIDI reconnects, the Computers probe); without it those Macs' own MIDI sessions are simply not read. The app list used a glob that zsh (the Macs' login shell) turns into an error when one pattern matches nothing, so no app was ever found there: find instead, and a running program's app path is taken from the program itself. New recipes: SuperRack SoundGrid and X32-Edit (AUDIO). Woonsocket now has ABLETON, SUPERRACK and X32 EDIT keys, all OK, and NA's page 99 shows them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,8 @@ MAC_APPS = [
|
||||
{"app": "obs", "process": "obs", "label": "OBS", "open": "/Applications/OBS.app", "restart": True},
|
||||
{"app": "translate", "process": "Church Translate", "label": "TRANSLATE", "open": "/Applications/Church Translate.app", "restart": True},
|
||||
{"app": "protools", "process": "Pro Tools", "label": "PRO TOOLS", "open": "/Applications/Pro Tools.app", "restart": True},
|
||||
{"app": "superrack", "process": "SuperRack SoundGrid", "label": "SUPERRACK", "open": "/Applications/*SuperRack*.app", "restart": True, "group": "AUDIO"},
|
||||
{"app": "x32edit", "process": "X32-Edit", "label": "X32 EDIT", "open": "/Applications/X32-Edit.app", "restart": False, "group": "AUDIO"},
|
||||
]
|
||||
WIN_APPS = [
|
||||
{"app": "vista", "exe": "Vista.exe", "label": "VISTA", "task": "Start Vista", "restart": True},
|
||||
@@ -172,7 +174,7 @@ def items(inv, nets):
|
||||
if c.get("os", "mac") == "mac":
|
||||
if a["process"] not in procs:
|
||||
continue
|
||||
app_path = newest_app(c.get("apps") or [], a["open"])
|
||||
app_path = (c.get("paths") or {}).get(a["process"]) or newest_app(c.get("apps") or [], a["open"])
|
||||
if not app_path:
|
||||
continue
|
||||
check = {"type": "remote", "template": "mac_program", "params": {"host": alias, "process": a["process"]},
|
||||
@@ -195,7 +197,7 @@ def items(inv, nets):
|
||||
ident = "app:%s|%s" % (alias, a["exe"])
|
||||
fid = slug("%s_%s" % (a["app"], alias))
|
||||
pretty = {"propresenter": "ProPresenter", "ableton": "Ableton", "arena": "Resolume Arena", "obs": "OBS", "translate": "Church Translate",
|
||||
"protools": "Pro Tools", "vista": "Vista", "rtpmidi": "rtpMIDI"}.get(a["app"], a["label"].title())
|
||||
"protools": "Pro Tools", "vista": "Vista", "rtpmidi": "rtpMIDI", "superrack": "SuperRack", "x32edit": "X32-Edit"}.get(a["app"], a["label"].title())
|
||||
where = c.get("name") or alias
|
||||
start = dict(start, label="start %s on %s if it is not running" % (pretty, where), short=a["label"])
|
||||
again = dict(again, label="restart %s on %s (closes it first)" % (pretty, where), short=a["label"])
|
||||
@@ -207,6 +209,8 @@ def items(inv, nets):
|
||||
item["restart"].append({"label": a["label"], "fix": rid})
|
||||
if a["app"] == "arena":
|
||||
item["group"] = "VIDEO"
|
||||
if a.get("group"):
|
||||
item["group"] = a["group"]
|
||||
out.append(item)
|
||||
apps_at[(a["app"], c.get("ip"))] = item
|
||||
for ip in c.get("ips") or []:
|
||||
|
||||
@@ -20,7 +20,8 @@ from settings import PARENT # noqa: F401 (puts the project folder on sys.path)
|
||||
import status_panel as sp
|
||||
|
||||
TIMEOUT = 8
|
||||
MAC_LOOK = "echo SP_PROCS; ps -axo comm= 2>/dev/null; echo SP_APPS; ls -d /Applications/*.app /Applications/*/*.app 2>/dev/null; echo SP_END"
|
||||
# find, not a glob: in zsh (the Macs' login shell) a pattern that matches nothing aborts the whole line.
|
||||
MAC_LOOK = "echo SP_PROCS; ps -axo comm= 2>/dev/null; echo SP_APPS; find /Applications -maxdepth 3 -name '*.app' -prune 2>/dev/null; echo SP_END"
|
||||
WIN_LOOK = "echo SP_PROCS & tasklist /FO CSV /NH 2>nul & echo SP_TASKS & schtasks /Query /FO CSV /NH 2>nul & echo SP_END"
|
||||
HOST_FIELDS = ("host", "bonjourHost", "serverHost", "ip", "address")
|
||||
|
||||
@@ -243,10 +244,14 @@ def look_at(cfg, alias, host):
|
||||
out = {"reachable": True}
|
||||
if os_name == "mac":
|
||||
out["procs"] = sorted({os.path.basename(p.strip()) for p in got.get("PROCS", []) if p.strip()})
|
||||
# Where each running program's app is, from its own path (".../SuperRack SoundGrid.app/Contents/MacOS/...").
|
||||
out["paths"] = {os.path.basename(p.strip()): p.strip().split(".app/")[0] + ".app"
|
||||
for p in got.get("PROCS", []) if ".app/Contents/MacOS/" in p}
|
||||
out["apps"] = sorted(a.strip() for a in got.get("APPS", []) if a.strip().endswith(".app"))
|
||||
try:
|
||||
m = subprocess.run(sp.ssh_cmd(cfg, alias) + ["/usr/bin/python3", "-"], input=sp.remote_midi_script(), capture_output=True, text=True, timeout=15)
|
||||
m = subprocess.run(sp.ssh_cmd(cfg, alias) + sp.remote_python("-"), input=sp.remote_midi_script(), capture_output=True, text=True, timeout=15)
|
||||
out["midi"] = sp.parse_remote_midi(m.stdout) or {}
|
||||
out["python"] = m.returncode != 97
|
||||
except subprocess.TimeoutExpired:
|
||||
out["midi"] = {}
|
||||
else:
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ TIMEOUT = 8
|
||||
|
||||
MAC_PROBE = (
|
||||
'echo "SP_NAME=$(uname -n)"; echo "SP_OSV=$(sw_vers -productVersion 2>/dev/null)"; '
|
||||
'echo "SP_PY=$(/usr/bin/python3 --version 2>&1)"; '
|
||||
'echo "SP_PY=$(xcode-select -p >/dev/null 2>&1 && /usr/bin/python3 --version 2>&1 || echo none)"; '
|
||||
'echo "SP_KEY=$(grep -c status-panel ~/.ssh/authorized_keys 2>/dev/null)"; '
|
||||
'echo SP_AGENTS; launchctl list 2>/dev/null; echo SP_PROCS; ps -axo comm= 2>/dev/null; '
|
||||
'echo SP_APPS; ls /Applications 2>/dev/null; echo SP_HELPER; ls -l {helper} 2>&1; sudo -n -l {helper} 2>&1 | tail -1; '
|
||||
|
||||
+13
-3
@@ -2445,7 +2445,7 @@ def stream_remote_midi(snap, cfg, host, stop):
|
||||
proc = None
|
||||
try:
|
||||
proc = subprocess.Popen(ssh_cmd(cfg, host)[:1] + ["-o", "ServerAliveInterval=2", "-o", "ServerAliveCountMax=3"]
|
||||
+ ssh_cmd(cfg, host)[1:] + ["/usr/bin/python3", "-u", "-"],
|
||||
+ ssh_cmd(cfg, host)[1:] + remote_python("-u", "-"),
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
|
||||
proc.stdin.write(script)
|
||||
proc.stdin.close()
|
||||
@@ -2488,13 +2488,23 @@ def poll_remote_midi(snap, cfg):
|
||||
script = remote_midi_script()
|
||||
for host in remote_midi_hosts(cfg):
|
||||
try:
|
||||
r = subprocess.run(ssh_cmd(cfg, host) + ["/usr/bin/python3", "-"], input=script,
|
||||
r = subprocess.run(ssh_cmd(cfg, host) + remote_python("-"), input=script,
|
||||
capture_output=True, text=True, timeout=20)
|
||||
snap.remote_midi[host] = parse_remote_midi(r.stdout) if r.returncode == 0 else None
|
||||
remember_peers(snap, host, snap.remote_midi[host])
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
snap.remote_midi[host] = None
|
||||
|
||||
# Python on another Mac, only where it is really there: on a Mac without the Command Line Tools,
|
||||
# /usr/bin/python3 is a stub that pops up Apple's "install the developer tools" window on that Mac's
|
||||
# screen (2026-10-20: Woonsocket's Tracks and FOH Macs, every ten minutes). Exit 97 = no python there.
|
||||
REMOTE_PY = "xcode-select -p >/dev/null 2>&1 || exit 97; exec /usr/bin/python3"
|
||||
|
||||
|
||||
def remote_python(*args):
|
||||
return [" ".join([REMOTE_PY] + list(args))]
|
||||
|
||||
|
||||
def ssh_cmd(cfg, host):
|
||||
"""ssh to a machine in config "hosts", with NA Ingest's status-panel key; one shared connection per host."""
|
||||
return ["/usr/bin/ssh", "-i", os.path.expanduser(cfg.get("ssh_key", "~/.ssh/id_status_panel")),
|
||||
@@ -2767,7 +2777,7 @@ def execute_fix(cfg, fix_id, chk):
|
||||
args = (chk["session"], chk["peer"], chk["address"])
|
||||
repoint = bool(chk.get("repoint"))
|
||||
if chk.get("host"):
|
||||
r = subprocess.run(ssh_cmd(cfg, chk["host"]) + ["/usr/bin/python3", "-"],
|
||||
r = subprocess.run(ssh_cmd(cfg, chk["host"]) + remote_python("-"),
|
||||
input=remote_midi_script("ns['connect_peer'](%r, %r, %r, repoint=%r)" % (args + (repoint,))),
|
||||
capture_output=True, text=True, timeout=30)
|
||||
result = parse_remote_midi(r.stdout) if r.returncode == 0 else None
|
||||
|
||||
@@ -98,6 +98,21 @@ class TestBuild(unittest.TestCase):
|
||||
self.assertIn("conn:x32", seen)
|
||||
self.assertIn("dante:SC-X32", seen)
|
||||
|
||||
def test_a_programs_app_comes_from_its_running_path(self):
|
||||
"""Woonsocket's FOH Mac: SuperRack lives three folders down, and zsh had hidden the app list."""
|
||||
inv = small()
|
||||
inv["computers"]["foh"] = {"name": "WS FOH", "os": "mac", "user": "watersmedia", "ip": "10.4.1.12", "ips": ["10.4.1.12"], "reachable": True,
|
||||
"procs": ["SuperRack SoundGrid", "X32-Edit"], "apps": [],
|
||||
"paths": {"SuperRack SoundGrid": "/Applications/Waves/SuperRack SoundGrid/SuperRack SoundGrid.app",
|
||||
"X32-Edit": "/Applications/X32-Edit.app"}}
|
||||
cfg, _ = ac.build(BASE, inv, {"id": "ws", "name": "Woonsocket"}, "1234")
|
||||
self.assertEqual(schema.validate(cfg)[0], [])
|
||||
sr = next(t for t in cfg["tiles"] if t["label"] == "SUPERRACK")
|
||||
self.assertEqual(sr["checks"][0]["cmd"], "pgrep -xq 'SuperRack SoundGrid'")
|
||||
self.assertIn("/Applications/Waves/SuperRack SoundGrid/SuperRack SoundGrid.app", cfg["fixes"][sr["checks"][0]["fix"]]["run"])
|
||||
self.assertIn("X32 EDIT", [t["label"] for t in cfg["tiles"]])
|
||||
self.assertIn("SUPERRACK", [k["label"] for k in cfg["restart_page"]["keys"]])
|
||||
|
||||
def test_a_big_campus_gets_group_keys_and_the_receivers_fold_first(self):
|
||||
cfg, _ = ac.build(BASE, big(), {"id": "ab", "name": "Apollo Beach"}, "1111")
|
||||
self.assertEqual(schema.validate(cfg)[0], [])
|
||||
|
||||
Reference in New Issue
Block a user