feat: give every room the broker, one scale, and its own panel back

Little Waders took the Plasma build and reported what a container
cannot. A disc player on a machine with no optical drive, a clock nobody
at the back of the room could read, a browser at half the size of the
panel around it, and Parent Call with no broker to call.

The broker is the same for every classroom, so it is a default now and
not something to fill in room by room. classroom-migrate brings an
existing room to the same place without touching its name, its username
or its password, and Parent Call distinguishes a broker it cannot reach
from one that was never set up: those send somebody to look at
completely different things.

The disc player's detection was right and ran too late — alongside the
session rather than before it, and the panel is built from the layout the
moment a session starts. It runs before the display manager now, and
again from udev when a drive is plugged in.

UI_SCALE had a ceiling of 1.5, which cannot express a 4K television; a
clock pinned at 15 points, which is the same size at every scale; and a
browser that was never told the number at all. All three are fixed and
the room doubles together at 200%.

This also brings 0.1.47's work into the repository, where it had only
ever existed in a build directory under /tmp: the hidden display dialog,
its authenticated writer, the polkit action, and the church's own
wallpaper. One piece of it is deliberately left out. The custom volume
display needed a shell package of our own, and with one selected
plasmashell builds no panel at all — measured twice, with that build's
own files, on a classroom that deletes its panel configuration at every
login by design.

The lab connected to the real church broker the moment the package
defaulted to it. Nothing was left behind, which was checked, but the
guard that should have stopped it had never worked: it used iptables,
which the lab image does not have, and probed with /dev/tcp under dash,
where that always fails. Both reported success.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
ekklesia
2026-09-12 19:05:09 -05:00
co-authored by Claude Opus 5
parent 926668dc9b
commit 34fd02e08f
59 changed files with 1335 additions and 198 deletions
+60 -1
View File
@@ -207,7 +207,8 @@ install -Dm644 \
# Not /etc/xdg: plasma-workspace already owns files by these names there.
# The classroom's directory goes in front of it in XDG_CONFIG_DIRS instead.
for setting in kwinrc kwinrulesrc kscreenlockerrc ksmserverrc \
krunnerrc plasmanotifyrc powermanagementprofilesrc; do
krunnerrc plasmanotifyrc powermanagementprofilesrc plasmarc \
plasmaparc; do
install -Dm644 \
"$ROOT/config/plasma/xdg/${setting}" \
"$STAGE/etc/ekklesia-classroom/xdg/${setting}"
@@ -258,6 +259,32 @@ install -Dm644 \
"$ROOT/config/plasma/lookandfeel/contents/layouts/org.kde.plasma.desktop-layout.js" \
"$LOOK/contents/layouts/org.kde.plasma.desktop-layout.js"
# NO SHELL PACKAGE OF OUR OWN.
#
# Plasma 6.3 loads the volume display from the shell package and from
# nowhere else — the look and feel is not consulted, which was measured —
# so a shell package is the only supported way to replace it, and a
# classroom-sized one was built and tried. With it selected, plasmashell
# builds no panel at all: not the stock one, not ours. Measured twice, and
# the room's panel is deleted at every login on purpose, so every login
# would have come up with nothing on the screen.
#
# The volume display is Plasma's own instead. UI_SCALE makes it the right
# size on a television, which is what the room actually needed.
# The room's Plasma style: the card a notification and the volume display
# are drawn on. Only the pieces the classroom changes; everything else
# comes from Breeze, so this can never leave a popup undrawn.
STYLE="$STAGE/usr/share/plasma/desktoptheme/ekklesia"
install -Dm644 \
"$ROOT/config/plasma/desktoptheme/metadata.json" \
"$STYLE/metadata.json"
install -Dm644 \
"$ROOT/config/plasma/desktoptheme/dialogs/background.svg" \
"$STYLE/dialogs/background.svg"
for entry in "$ROOT"/config/plasma/applications/*.desktop; do
install -Dm644 "$entry" \
"$STAGE/usr/share/applications/$(basename "$entry")"
@@ -339,6 +366,38 @@ install -Dm755 \
"$ROOT/scripts/classroom-migrate" \
"$STAGE/usr/local/bin/classroom-migrate"
# One number, one place to ask for it.
install -Dm755 \
"$ROOT/scripts/classroom-ui-scale" \
"$STAGE/usr/local/bin/classroom-ui-scale"
# The room's display size: a hidden dialog behind a chord, a writer that
# asks for an administrator password, and the one thing that applies the
# answer at every login.
for command in classroom-display-settings classroom-set-scale; do
install -Dm755 \
"$ROOT/scripts/${command}" \
"$STAGE/usr/local/bin/${command}"
done
install -Dm644 \
"$ROOT/config/polkit/org.ekklesia.classroom.display.policy" \
"$STAGE/usr/share/polkit-1/actions/org.ekklesia.classroom.display.policy"
install -Dm644 \
"$ROOT/config/plasma/autostart/ekklesia-classroom-scale.desktop" \
"$STAGE/etc/xdg/autostart/ekklesia-classroom-scale.desktop"
# The panel's disc player follows this machine's hardware, and the
# detection has to have happened before a session starts.
install -Dm644 \
"$ROOT/systemd/layout/ekklesia-classroom-layout.service" \
"$STAGE/etc/systemd/system/ekklesia-classroom-layout.service"
install -Dm644 \
"$ROOT/config/udev/60-ekklesia-classroom-optical.rules" \
"$STAGE/etc/udev/rules.d/60-ekklesia-classroom-optical.rules"
install -Dm644 \
"$ROOT/lib/classroom_audio.py" \
"$STAGE/usr/local/lib/ekklesia-classroom/classroom_audio.py"
+16 -1
View File
@@ -13,7 +13,8 @@ SRC="${1:-/srv/classroom}"
# XDG_CONFIG_DIRS. plasma-workspace owns files by these names in /etc/xdg
# and two packages cannot own one path.
for setting in kwinrc kwinrulesrc kscreenlockerrc ksmserverrc \
krunnerrc plasmanotifyrc powermanagementprofilesrc; do
krunnerrc plasmanotifyrc powermanagementprofilesrc plasmarc \
plasmaparc; do
install -Dm644 "$SRC/config/plasma/xdg/$setting" \
"/etc/ekklesia-classroom/xdg/$setting"
rm -f "/etc/xdg/$setting.ekklesia-old"
@@ -48,6 +49,20 @@ install -Dm644 "$SRC/config/plasma/lookandfeel/contents/defaults" \
install -Dm644 \
"$SRC/config/plasma/lookandfeel/contents/layouts/org.kde.plasma.desktop-layout.js" \
"$LNF/contents/layouts/org.kde.plasma.desktop-layout.js"
install -Dm644 "$SRC/config/polkit/org.ekklesia.classroom.display.policy" \
/usr/share/polkit-1/actions/org.ekklesia.classroom.display.policy
install -Dm644 "$SRC/config/plasma/autostart/ekklesia-classroom-scale.desktop" \
/etc/xdg/autostart/ekklesia-classroom-scale.desktop
install -Dm644 "$SRC/config/plasma/desktoptheme/metadata.json" \
/usr/share/plasma/desktoptheme/ekklesia/metadata.json
install -Dm644 "$SRC/config/plasma/desktoptheme/dialogs/background.svg" \
/usr/share/plasma/desktoptheme/ekklesia/dialogs/background.svg
for script in classroom-ui-scale classroom-scale classroom-desktop-layout \
classroom-display-settings classroom-set-scale; do
install -Dm755 "$SRC/scripts/$script" "/usr/local/bin/$script"
done
for entry in "$SRC"/config/plasma/applications/*.desktop; do
install -Dm644 "$entry" "/usr/share/applications/$(basename "$entry")"
+32
View File
@@ -77,6 +77,38 @@ up)
done
# startplasma looks for `qdbus`; Debian ships it as qdbus6.
docker exec "$NAME" ln -sf /usr/bin/qdbus6 /usr/local/bin/qdbus
# The church's own networks, refused from inside the container.
#
# This is not theoretical. The moment the package began defaulting
# MQTT_HOST to the church broker, the lab connected to it: a test
# machine appearing on the real broker under a container id. Nothing
# was left behind — the broker held no retained status afterwards,
# which was checked — but the lab had no business being there at all,
# and the Parent Call coordinator is on the same networks.
#
# Container-local: this netns belongs to the container alone and
# nothing on the host or the church network is changed by it. Debian's
# own mirrors are untouched, so installs still work.
# A blackhole route rather than a firewall rule: the lab image has no
# iptables, and the rig's guard had been quietly doing nothing because
# of it. Routes need no netfilter modules and cannot silently fail.
docker exec "$NAME" sh -c '
for net in 10.30.0.0/16 10.40.0.0/16; do
ip route replace blackhole "$net" || exit 1
done' || echo " WARNING: could not fence the church networks"
# And checked with bash, not sh. The probe uses /dev/tcp, which is a
# bash feature: under dash it fails whatever the network is doing, so
# the check passed on a lab that could reach the broker perfectly
# well. Measured.
for target in 10.40.0.2/1883 10.30.0.2/1025; do
if docker exec "$NAME" timeout 4 bash -c "exec 3<>/dev/tcp/${target}" 2>/dev/null; then
echo "REFUSING TO CONTINUE: ${target} is reachable from the lab" >&2
exit 1
fi
done
echo " church networks fenced out of the lab"
echo "lab $NAME is up ($(docker exec "$NAME" systemctl is-system-running 2>/dev/null))"
;;
+10 -5
View File
@@ -24,14 +24,19 @@ inlab() { docker exec "$NAME" "$@"; }
guard() {
# Container-local. This netns belongs to the container alone, so
# nothing on the host or the church network is changed by it.
# Blackhole routes, not iptables: the lab image has no iptables at
# all, so these two lines had been failing silently since they were
# written. `tools/lab/lab up` now does the same thing at start-up, so
# this is a second statement of it rather than the only one.
inlab sh -c '
iptables -C OUTPUT -d 10.30.0.0/16 -j REJECT 2>/dev/null ||
iptables -I OUTPUT -d 10.30.0.0/16 -j REJECT
iptables -C OUTPUT -d 10.40.0.0/16 -j REJECT 2>/dev/null ||
iptables -I OUTPUT -d 10.40.0.0/16 -j REJECT'
for net in 10.30.0.0/16 10.40.0.0/16; do
ip route replace blackhole "$net" || exit 1
done'
# bash, not sh. /dev/tcp is a bash feature and this probe reported
# every target unreachable under dash, whatever the network was doing.
for target in 10.40.0.2/1883 10.30.0.2/1025; do
if inlab timeout 4 sh -c "exec 3<>/dev/tcp/${target}" 2>/dev/null; then
if inlab timeout 4 bash -c "exec 3<>/dev/tcp/${target}" 2>/dev/null; then
echo "REFUSING TO CONTINUE: ${target} is still reachable" >&2
exit 1
fi