Files
status-indicators/tools/standalone.py
T
christianandClaude Opus 5.5 2f8e3d47a3 Apollo Beach on its own: a standalone panel to send them
- tools/standalone.py builds dist/<Name> Status Panel.zip: program, install.command, READ ME FIRST.txt
- setup --standalone: campuses.json standalone, tells nobody, no hub, no NA key
- Standalone backs itself up nightly to ~/Documents/Status Panel Backups/<id>
- upgrade-companion --from <.dmg/.zip from bitfocus.io> (no NA needed)
- Only the hub lists campuses or shows other campuses' install lines

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:12:30 -04:00

150 lines
7.4 KiB
Python

#!/usr/bin/env python3
"""A status panel a campus installs and runs on its own: no North Attleboro.
python3 tools/standalone.py ab "Apollo Beach" -> dist/Apollo Beach Status Panel.zip
Apollo Beach, 2026-10-21 (the user): "they want to take care of it on their own, so make a sorta
standalone version that i can send them. i dont need to monitor AB from NA like i do for SC and WS".
The zip holds the program (the same files a campus downloads from NA, never NA's settings, plug keys
or login keys), an installer and a read-me. Installed, the campus sets itself up from what it finds,
exactly as Southcoast and Woonsocket did, but it reports to nobody (campuses.json "standalone"), lets
no other Mac in, and backs itself up every night into ~/Documents/Status Panel Backups.
"""
import os
import re
import sys
import zipfile
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT)
from settings.campus import KIT_FILES, KIT_DIRS # noqa: E402
INSTALL = r"""#!/bin/zsh
# Status panel for %(name)s: installs on the Mac that runs Companion and sets itself up from what it finds.
# Safe to run again: it then updates the program and looks for new gear.
set -e
HERE=${0:A:h}
if ! /usr/bin/xcode-select -p >/dev/null 2>&1; then
echo "First the Command Line Tools: a window opens - click Install, wait for it, then run this again."
/usr/bin/xcode-select --install 2>/dev/null || true
exit 1
fi
DIR="$HOME/status-panel-install"
rm -rf "$DIR" && mkdir -p "$DIR"
/usr/bin/ditto "$HERE/program" "$DIR"
/usr/bin/xattr -dr com.apple.quarantine "$DIR" 2>/dev/null || true
[[ -f "$HOME/.ssh/id_status_panel" ]] || { mkdir -p "$HOME/.ssh" && chmod 700 "$HOME/.ssh" && /usr/bin/ssh-keygen -q -t ed25519 -N "" -C "status-panel@$(scutil --get LocalHostName)" -f "$HOME/.ssh/id_status_panel"; }
cd "$DIR"
zsh install.sh --site %(cid)s
cd "$HOME/Library/Application Support/status-panel"
/usr/bin/python3 -m settings.campus setup --id %(cid)s --name %(qname)s --standalone
# This Mac's wired address (the default route's port).
IF=$(/sbin/route -n get default 2>/dev/null | awk '/interface:/{print $2}')
ME=$(/usr/sbin/ipconfig getifaddr "$IF" 2>/dev/null || hostname)
echo
echo "Settings page: http://$ME:8020 (the editing code is printed above)"
echo
echo "Next, on each other computer here (ProPresenter, Tracks, Lighting, FOH...):"
echo " Mac: open Terminal and paste curl -fsSL http://$ME:8020/join | sh"
echo " (or send it to all of them at once in Apple Remote Desktop: Send UNIX Command, as root)"
echo " PC: open PowerShell as administrator and paste irm http://$ME:8020/join.ps1 | iex"
echo "Then on the settings page: Auto-setup > Look now."
"""
README = """%(name)s - Status Panel
%(rule)s
What it is
A status page on the Stream Deck (Bitfocus Companion) that watches your gear - ProPresenter, the
ATEM, Resi, consoles, Tracks, Lighting, MIDI links, the network - and fixes what it can. It runs on
the Mac that runs Companion. It is yours: nothing is sent to any other campus.
Before you start
- Companion 5.0.7 on that Mac, with its HTTP API on (Companion > Settings > Protocols).
If yours is older, the installer says so and how to upgrade (download 5.0.7 for Mac from
https://bitfocus.io/companion; your buttons come along).
- Remote Login on for the other Macs (System Settings > General > Sharing), so the panel can check
and restart programs on them.
Install (about 5 minutes)
1. Copy this folder to the Companion Mac (Downloads is fine).
2. Open Terminal, type zsh and a space, drag "install.command" from this folder into the
window, press Return.
3. It finds what you have, makes the Stream Deck pages (19 status, 20 restart, or the first free
pages), and prints the settings page address and its editing code (PIN). Keep the PIN.
4. On each other computer, paste the line it prints (Mac: Terminal; PC: PowerShell as
administrator). Then on the settings page: Auto-setup > Look now. New gear gets keys by itself.
Using it (the Stream Deck)
- Green means fine. Flashing red names the problem, for example "PROP OFFLINE".
- Press a red key to acknowledge it: it stops flashing and stays dark red.
- Hold any key for 2 seconds to fix it: it reconnects, restarts or reopens whatever is failing.
- Purple means the panel itself is not running: restart the Companion Mac, or run the installer again.
- ALARMS (bottom-left) silences alarms and stops automatic fixes until midnight. Use it during
maintenance.
- The page-number key on every page turns red when something is wrong. Hold it for 1 second to
jump to the status page.
- RESTART PAGE opens the restart page. Hold a key there for 2 seconds to restart that program even
if it looks fine.
- Automatic fixes run Tuesday 2-10 pm, Wednesday 9 am-9 pm and Sunday 5 am-2 pm. Change the hours
on the settings page (Auto-fix) to match your services.
- Nothing is ever restarted while the HyperDeck is recording.
The settings page (http://<Companion Mac>:8020)
Everything is changed there: keys, checks, fixes, auto-fix hours, the Restart page. Every Apply is
kept, and History puts back any earlier one. Changing gear? Auto-setup > Look now adds what is new.
Backups
Every night at 4:30 am the settings are copied to ~/Documents/Status Panel Backups/%(cid)s on the
Companion Mac (the newest 30 are kept). Copy that folder somewhere safe now and then, or point it at
a Google Drive folder (ask whoever installed it).
Run it again
Running install.command again updates the program and looks for new gear; your settings stay.
"""
def build(cid, name, out_dir):
cid = re.match(r"[a-z0-9]{1,8}", cid.lower()).group(0)
top = "%s Status Panel" % name
os.makedirs(out_dir, exist_ok=True)
path = os.path.join(out_dir, top + ".zip")
def add_file(z, src, arc, mode=0o644):
info = zipfile.ZipInfo.from_file(src, arc)
info.external_attr = (0o100000 | mode) << 16
info.compress_type = zipfile.ZIP_DEFLATED
with open(src, "rb") as f:
z.writestr(info, f.read())
def add_text(z, text, arc, mode=0o644):
info = zipfile.ZipInfo(arc, date_time=(2026, 10, 21, 0, 0, 0))
info.external_attr = (0o100000 | mode) << 16
info.compress_type = zipfile.ZIP_DEFLATED
z.writestr(info, text)
with zipfile.ZipFile(path, "w") as z:
prog = top + "/program/"
for f in KIT_FILES + ("install.sh", "README.md"):
p = os.path.join(ROOT, f)
if os.path.exists(p):
add_file(z, p, prog + f, 0o755 if f.endswith(".sh") else 0o644)
for d in KIT_DIRS + ("sites/_template",):
for base, dirs, files in os.walk(os.path.join(ROOT, d)):
dirs[:] = [x for x in dirs if x != "__pycache__"]
for f in files:
if f.endswith(".pyc") or f.startswith("._") or f == ".DS_Store":
continue
p = os.path.join(base, f)
add_file(z, p, prog + os.path.relpath(p, ROOT), 0o755 if f.endswith(".sh") else 0o644)
q = "'" + name.replace("'", "'\"'\"'") + "'"
add_text(z, INSTALL % {"name": name, "cid": cid, "qname": q}, top + "/install.command", 0o755)
add_text(z, README % {"name": name, "cid": cid, "rule": "=" * (len(name) + 15)}, top + "/READ ME FIRST.txt")
return path
if __name__ == "__main__":
if len(sys.argv) < 3:
sys.exit(__doc__)
print(build(sys.argv[1], sys.argv[2], os.path.join(ROOT, "dist")))