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]>
161 lines
5.5 KiB
Bash
Executable File
161 lines
5.5 KiB
Bash
Executable File
#!/bin/bash
|
|
# A Parent Call sanctuary that cannot reach the sanctuary.
|
|
#
|
|
# The real coordinator, from the control computer, running in the lab
|
|
# against a broker on loopback and a ProPresenter that is a mock. The
|
|
# church's own networks are rejected from inside the container first, so
|
|
# a wrong host in a config file cannot put a child's name on the real
|
|
# screen. Coordinator development has done that once already.
|
|
#
|
|
# tools/lab/parent-call-rig up [MODE] # off | approval | auto
|
|
# tools/lab/parent-call-rig mode MODE
|
|
# tools/lab/parent-call-rig approve|reject
|
|
# tools/lab/parent-call-rig watch
|
|
# tools/lab/parent-call-rig triggers
|
|
# tools/lab/parent-call-rig down
|
|
set -uo pipefail
|
|
|
|
NAME="${LAB_NAME:-classroom-v2-lab}"
|
|
SRC="${PARENT_CALL_SRC:-classroom-control:/home/media/docker/parent-call}"
|
|
ROOT=classroom/parent-call
|
|
|
|
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 '
|
|
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 bash -c "exec 3<>/dev/tcp/${target}" 2>/dev/null; then
|
|
echo "REFUSING TO CONTINUE: ${target} is still reachable" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
echo " church networks unreachable from the lab"
|
|
}
|
|
|
|
case "${1:-up}" in
|
|
up)
|
|
mode="${2:-approval}"
|
|
|
|
inlab sh -c 'apt-get install -y -qq --no-install-recommends \
|
|
mosquitto mosquitto-clients python3-paho-mqtt iptables >/dev/null 2>&1'
|
|
guard
|
|
|
|
inlab sh -c 'mkdir -p /opt/pc-test'
|
|
ssh -o BatchMode=yes "${SRC%%:*}" "cd ${SRC#*:} && tar cf - src" \
|
|
| docker exec -i "$NAME" sh -c 'cd /opt/pc-test && tar xf -'
|
|
echo " coordinator source copied from the control computer"
|
|
|
|
docker exec -i "$NAME" sh -c 'cat > /opt/pc-test/mosquitto.conf' <<CONF
|
|
# Loopback only. Nothing published here can leave the container.
|
|
listener 1883 127.0.0.1
|
|
allow_anonymous true
|
|
persistence false
|
|
CONF
|
|
|
|
docker exec -i "$NAME" sh -c 'cat > /opt/pc-test/coordinator.env' <<ENV
|
|
MQTT_HOST=127.0.0.1
|
|
MQTT_PORT=1883
|
|
MQTT_CLIENT_ID=parent-call-lab
|
|
TOPIC_ROOT=$ROOT
|
|
PROPRESENTER_BASE_URL=http://127.0.0.1:1025
|
|
PROPRESENTER_MESSAGE='Parent Caller'
|
|
PROPRESENTER_CHILD_TOKEN=Name
|
|
PROPRESENTER_ROOM_TOKEN=Room
|
|
PROPRESENTER_TIMEOUT=4
|
|
PROPRESENTER_CLEAR_AFTER=20
|
|
ROOM_NAMES='nursery=Nursery,k2=K-2,older=Older Kids'
|
|
REQUEST_TTL=180
|
|
MAX_CHILD_LENGTH=40
|
|
MAX_ROOM_LENGTH=32
|
|
MAX_CLOCK_SKEW=120
|
|
DEFAULT_MODE=$mode
|
|
HEALTH_PORT=8099
|
|
ENV
|
|
|
|
docker exec -i "$NAME" sh -c 'cat > /opt/propresenter-mock.py' \
|
|
< "$(dirname "$0")/propresenter-mock.py"
|
|
|
|
# Debian's own mosquitto unit would listen where the lab does not want
|
|
# it to. The rig runs its own, on loopback, and nothing else.
|
|
inlab sh -c 'systemctl disable --now mosquitto >/dev/null 2>&1;
|
|
pkill -f propresenter-mock; pkill -f parent_call.main; pkill mosquitto;
|
|
fuser -k 1025/tcp 1883/tcp 2>/dev/null; true'
|
|
sleep 3
|
|
inlab sh -c 'setsid mosquitto -c /opt/pc-test/mosquitto.conf > /tmp/mosquitto.log 2>&1 &'
|
|
inlab sh -c 'setsid python3 /opt/propresenter-mock.py 1025 > /tmp/ppmock.log 2>&1 &'
|
|
sleep 2
|
|
inlab sh -c 'cd /opt/pc-test && set -a && . ./coordinator.env && set +a &&
|
|
setsid env PYTHONPATH=/opt/pc-test/src python3 -m parent_call.main \
|
|
> /tmp/coordinator.log 2>&1 &'
|
|
sleep 4
|
|
|
|
# The room points at the loopback broker, never at the church's.
|
|
# docker exec needs -i or the heredoc reaches nothing at all, which is
|
|
# how this silently left the room pointing nowhere.
|
|
docker exec -i "$NAME" python3 - <<'PY'
|
|
import pathlib
|
|
p = pathlib.Path("/etc/ekklesia-classroom/device.conf")
|
|
t = p.read_text()
|
|
t = t.replace("MQTT_ENABLED=false", "MQTT_ENABLED=true")
|
|
if "MQTT_HOST=127.0.0.1" not in t:
|
|
t = t.replace("MQTT_HOST=", "MQTT_HOST=127.0.0.1", 1)
|
|
if "\nROOM=" not in t:
|
|
t += "\n# Lab only.\nROOM=nursery\n"
|
|
p.write_text(t)
|
|
PY
|
|
|
|
"$0" mode "$mode"
|
|
inlab tail -3 /tmp/coordinator.log
|
|
echo " rig up in mode $mode"
|
|
;;
|
|
|
|
mode)
|
|
inlab mosquitto_pub -h 127.0.0.1 -t "$ROOT/mode/set" -m "${2:?mode}"
|
|
sleep 1
|
|
echo -n " mode is now: "
|
|
inlab timeout 3 mosquitto_sub -h 127.0.0.1 -t "$ROOT/mode" -C 1 || true
|
|
;;
|
|
|
|
approve|reject)
|
|
id=$(inlab timeout 3 mosquitto_sub -h 127.0.0.1 -t "$ROOT/pending" -C 1 |
|
|
python3 -c 'import json,sys; print(json.load(sys.stdin).get("id") or "")')
|
|
[ -n "$id" ] || { echo " nothing is waiting"; exit 0; }
|
|
inlab mosquitto_pub -h 127.0.0.1 -t "$ROOT/$1" -m "{\"id\": \"$id\"}"
|
|
echo " $1ed $id"
|
|
;;
|
|
|
|
watch)
|
|
inlab mosquitto_sub -h 127.0.0.1 -t "$ROOT/#" -v
|
|
;;
|
|
|
|
triggers)
|
|
inlab sh -c 'cat /tmp/propresenter-mock.log 2>/dev/null || echo "(none)"'
|
|
;;
|
|
|
|
log)
|
|
inlab tail -n "${2:-25}" /tmp/coordinator.log
|
|
;;
|
|
|
|
down)
|
|
inlab sh -c 'pkill -f parent_call.main; pkill -f propresenter-mock; pkill mosquitto; true'
|
|
echo " rig down"
|
|
;;
|
|
|
|
*)
|
|
sed -n '2,14p' "$0"
|
|
exit 1
|
|
;;
|
|
esac
|