feat: give the rooms a line to the church broker

The first piece of turning these machines into connected endpoints.
classroom-mqtt is one small service per room that subscribes, validates
what arrives, and hands the work to commands that already exist.

Nothing in the room depends on it. A broker that is down, a network that
is missing, a machine with no broker configured at all — each is a
service sitting quietly, not a classroom that fails. Nothing waits for
it at boot and the shipped default is off, so every existing classroom
upgrades into this release with messaging disabled until a broker
exists.

The room is the hostname, which is already how these machines are named.
Topics carry no product name because the broker is the church's and
these are just rooms: classroom/notify/all, classroom/notify/<room>, and
a retained classroom/status/<room> so anyone asking later can be told
which rooms are up without waiting for them to say so again.
Notifications are events and are never retained; status is state and is.

Stale messages cannot come back, which is the property that matters most
and has two halves. The session is clean, so a broker never hands over a
backlog from while the room was off. And retained notifications are
refused on arrival, because otherwise one would replay into every room
at every reboot forever. The honest limit is written down: MQTT only
flags a retained message when it is replayed to a new subscription, so a
retained publish delivered live is indistinguishable from an ordinary
one and is shown once — which is right, since it is live at that moment,
and the replay is the actual hazard.

Delivery reuses the session-aware sender rather than a second copy of
it. classroom-notify-test grew into the real thing, so it is now
classroom-notify with the sample sender a wrapper over it, and its 36
tests came along.

The broker password lives in device.conf, so that file stops being world
readable: 0640 root:classroom, which everything that reads it already
satisfies.

Transport logic is kept free of any broker library so it can be tested
without one — 40 checks on room identity, the topic contract and which
payloads become notifications. Separately verified against a real
mosquitto: connect, subscribe, retained status, per-room and all-room
delivery, urgency, duplicate and expired and malformed messages ignored,
the broker killed with the daemon surviving, reconnect, and a stale
retained notification left on the broker not reaching the screen when a
room boots.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
ekklesia
2026-09-06 14:41:49 -05:00
co-authored by Claude Opus 5
parent c3106edd30
commit dfd42f6fdf
12 changed files with 992 additions and 195 deletions
+33 -2
View File
@@ -22,7 +22,9 @@ chmod 0755 /usr/local/bin/classroom-window
chmod 0755 /usr/local/bin/classroom-dvd
chmod 0755 /usr/local/bin/classroom-dvd-session
chmod 0755 /usr/local/bin/classroom-dvd-css-setup
chmod 0755 /usr/local/bin/classroom-notify
chmod 0755 /usr/local/bin/classroom-notify-test
chmod 0755 /usr/local/bin/classroom-mqtt
chmod 0755 /usr/local/bin/classroom-notifications
chmod 0755 /usr/local/bin/classroom-scale
chmod 0755 /usr/local/bin/classroom-shutdown
@@ -48,9 +50,15 @@ DVD_ENABLED=false
# Size of the dock, clock and notifications, 0.8 to 1.5.
# Larger rooms and larger TVs want a larger number.
UI_SCALE=1.0
CONFIG
chmod 0644 /etc/ekklesia-classroom/device.conf
# Church message broker. The room is this machine's hostname unless ROOM
# is set. Leave MQTT_ENABLED=false until a broker exists.
MQTT_ENABLED=false
MQTT_HOST=
MQTT_PORT=1883
MQTT_USERNAME=
MQTT_PASSWORD=
CONFIG
fi
# Make the settings discoverable on classrooms installed before they
@@ -73,6 +81,24 @@ UI_SCALE=1.0
CONFIG
fi
if ! grep -q '^MQTT_ENABLED=' /etc/ekklesia-classroom/device.conf; then
cat >> /etc/ekklesia-classroom/device.conf <<'CONFIG'
# Church message broker. The room is this machine's hostname unless ROOM
# is set. Leave MQTT_ENABLED=false until a broker exists.
MQTT_ENABLED=false
MQTT_HOST=
MQTT_PORT=1883
MQTT_USERNAME=
MQTT_PASSWORD=
CONFIG
fi
# The broker password lives here, so the file stops being world readable.
# Everything that reads it runs as root or as the classroom user.
chown root:classroom /etc/ekklesia-classroom/device.conf 2>/dev/null || true
chmod 0640 /etc/ekklesia-classroom/device.conf
systemctl daemon-reload || true
systemctl enable ekklesia-classroom-update.timer || true
@@ -94,6 +120,11 @@ systemctl enable ekklesia-classroom-provision.service || true
# Gives Chrome a chance to close properly on reboot, so its profile is
# not marked as crashed. Best effort: it must never hold a shutdown up.
systemctl enable ekklesia-classroom-shutdown.service || true
# The room's link to the church broker. Enabled always; it does nothing
# at all until a broker is configured in device.conf.
systemctl enable ekklesia-classroom-mqtt.service || true
systemctl restart ekklesia-classroom-mqtt.service || true
systemctl start ekklesia-classroom-shutdown.service || true
exit 0