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>
This commit is contained in:
2026-10-21 13:12:30 -04:00
co-authored by Claude Opus 5.5
parent bea587cfad
commit 2f8e3d47a3
9 changed files with 314 additions and 21 deletions
+72
View File
@@ -459,3 +459,75 @@ class TestCampusHello(TestRoutes):
get(self.port, "/api/campus/hello", {"id": "new", "url": "http://10.5.1.9:8020"})
get(self.port, "/api/campus/hello", {"id": "sc", "name": "Southcoast", "url": "http://10.5.1.9:8020"})
self.assertNotIn("new", cv.load(self.data)["list"]) # same Mac, now named: the old entry goes
class TestStandalone(unittest.TestCase):
"""Apollo Beach runs its own panel (2026-10-21): it tells nobody, keeps no campus list, backs itself up."""
def test_a_standalone_campus_tells_nobody_and_is_no_hub(self):
from settings import campuses as cv
data_dir = tempfile.mkdtemp(prefix="sp-sa-")
cv.save(data_dir, {"standalone": True})
self.assertFalse(cv.Mirror.is_hub(cv.load(data_dir)))
self.assertTrue(cv.Mirror.is_hub({})) # NA: no home
self.assertFalse(cv.Mirror.is_hub({"home": "http://10.1.1.1:8020"}))
class FakeSite:
def __init__(self):
self.data_dir = data_dir
def load(self):
return {"site": {"id": "ab", "name": "Apollo Beach"}}
sent = []
real, real_url = cv.http_json, cv.my_url
cv.http_json = lambda url, body=None, timeout=3: sent.append(url) or {"ok": True}
cv.my_url = lambda cfg: "http://10.4.1.1:8020"
try:
m = cv.Mirror(FakeSite())
data = m.sync_registry()
m.poll()
m.refresh_pages()
finally:
cv.http_json, cv.my_url = real, real_url
self.assertEqual(sent, [])
self.assertEqual(list(data["list"]), ["ab"])
def test_setup_marks_it_standalone_and_drops_any_home(self):
from settings import campuses as cv
data_dir = tempfile.mkdtemp(prefix="sp-sa-")
cv.save(data_dir, {"home": "http://10.1.1.1:8020"})
real = campus.is_fresh
campus.is_fresh = lambda cfg: False # set up already: only the look runs
real_look = campus.look_and_add
campus.look_and_add = lambda site, say=print, force=False: None
real_site = server.Site
server.Site = lambda d: type("S", (), {"load": lambda self: {}})()
try:
campus.setup(data_dir, "ab", "Apollo Beach", say=lambda *a: None, standalone=True)
finally:
campus.is_fresh, campus.look_and_add, server.Site = real, real_look, real_site
data = cv.load(data_dir)
self.assertTrue(data.get("standalone"))
self.assertNotIn("home", data)
def test_the_standalone_zip_has_the_program_an_installer_and_no_settings(self):
import zipfile
sys.path.insert(0, os.path.join(ROOT, "tools"))
import standalone
out = tempfile.mkdtemp(prefix="sp-zip-")
path = standalone.build("ab", "Apollo Beach", out)
with zipfile.ZipFile(path) as z:
names = z.namelist()
inst = z.read("Apollo Beach Status Panel/install.command").decode()
mode = z.getinfo("Apollo Beach Status Panel/install.command").external_attr >> 16
top = "Apollo Beach Status Panel/program/"
for f in ("status_panel.py", "install.sh", "settings/campus.py", "sites/_template/config.json", "backup/backup-campuses.sh"):
self.assertIn(top + f, names)
for never in ("config.json", "outlets.json", "campuses.json", "sites/ws/config.json", "sites/sc/config.json"):
self.assertNotIn(top + never, names)
self.assertFalse([n for n in names if "__pycache__" in n or "/._" in n])
self.assertIn("Apollo Beach Status Panel/READ ME FIRST.txt", names)
self.assertTrue(mode & 0o100)
self.assertIn("--standalone", inst)
self.assertNotIn("10.1.1.1", inst)
self.assertNotIn("authorized_keys", inst) # no one else's key is let in