Files
Classroom_Desktop/tools/validate-trixie-release.sh
T
ekklesiaandClaude Opus 5.5 690ef95aff feat: four-part versions, and a published version is never used again
0.2.6.1 is a maintenance release of 0.2.6. The build refused four
parts, update_canary.sh's automatic bump could not add one to "6.1", and
neither publish script would have noticed a version being published
twice — which is what happened to 0.2.6 this morning: it was rebuilt
with the Presentations hardening and uploaded over the first 0.2.6, and
a room that had installed the first can never be offered the second.

One release library now holds the version pattern (three or four parts,
no leading zeros, no .0 maintenance), orders by dpkg --compare-versions,
refuses a version that has ever been published or is not newer than all
of them, uploads with --ignore-existing and checks the server's
checksum, writes a manifest only for a package the server holds, and
reads the channel back over HTTPS afterwards. update_canary.sh takes the
version explicitly; update_stable.sh refuses to go backwards.

The classroom updater refuses a manifest whose version, package name or
checksum is malformed, and refuses a download whose own control file is
not the version the manifest promised, so a room can never loop on
reinstalling and rebooting.

tests/test-release-versions.sh: 87 checks against a stand-in server and
real dpkg. The Trixie gate now also rehearses a stable room: 0.2.2 to an
interim 0.2.6.1 upgraded with settings, channel and Presentations=false
intact, and the installed updater answering correctly.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-23 18:19:37 -05:00

529 lines
23 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if [[ $# -lt 1 || $# -gt 2 ]]; then
echo "Usage: $0 CANDIDATE.deb [PREVIOUS.deb]" >&2
exit 2
fi
candidate="$(realpath "$1")"
previous=""
[[ -f "$candidate" ]] || { echo "Package not found: $candidate" >&2; exit 1; }
if [[ $# -eq 2 ]]; then
previous="$(realpath "$2")"
[[ -f "$previous" ]] || { echo "Package not found: $previous" >&2; exit 1; }
fi
# The channel the upgraded room is on. canary by default, which is the
# machine a new release goes to first; stable to rehearse the day the
# desktops are moved, from the release they are actually on.
channel="${VALIDATION_CHANNEL:-canary}"
case "$channel" in
stable|canary) ;;
*) echo "VALIDATION_CHANNEL must be stable or canary" >&2; exit 2 ;;
esac
echo "Validating $(basename "$candidate") on Debian Trixie"
docker_args=(
run --rm -i
-v "$candidate:/packages/candidate.deb:ro"
)
if [[ -n "$previous" ]]; then
docker_args+=( -v "$previous:/packages/previous.deb:ro" )
fi
# 720s was enough while this package's heaviest dependency was Plasma.
# 0.2.6 adds LibreOffice and a dozen font packages for Presentations, and
# the run installs the PREVIOUS package as well as the candidate, so the
# container now does two full desktop installs. Measured: the previous
# install alone ran past 360s on the development VM and the whole run was
# killed before it reached a single assertion.
#
# Generous rather than tight, on purpose. This is the gate that has to
# say "the package is wrong", and a gate that says "the machine was busy"
# instead teaches everybody to re-run it until it passes.
timeout --signal=TERM --kill-after=15s 2700s \
docker "${docker_args[@]}" debian:trixie-slim \
bash -s -- "${previous:+/packages/previous.deb}" "$channel" <<'CONTAINER'
set -Eeuo pipefail
validation_failure() {
status=$?
echo "Validation failed at line ${BASH_LINENO[0]}: ${BASH_COMMAND}" >&2
if command -v lpstat >/dev/null 2>&1; then
lpstat -t >&2 || true
lpoptions -p Elmer -l >&2 || true
fi
[[ ! -f /tmp/provision-first.log ]] || cat /tmp/provision-first.log >&2
[[ ! -f /tmp/provision-second.log ]] || cat /tmp/provision-second.log >&2
exit "$status"
}
trap validation_failure ERR
export DEBIAN_FRONTEND=noninteractive
# Package validation runs in a container with no init system. Do not let
# maintainer scripts try to start CUPS, Avahi or other irrelevant daemons;
# services are inspected and CUPS is started explicitly when the test needs it.
cat > /usr/sbin/policy-rc.d <<'POLICY'
#!/bin/sh
exit 101
POLICY
chmod 0755 /usr/sbin/policy-rc.d
run_logged() {
label="$1"
seconds="$2"
shift 2
if ! timeout --signal=TERM --kill-after=10s "${seconds}s" "$@" \
>"/tmp/${label}.log" 2>&1; then
cat "/tmp/${label}.log" >&2
echo "${label} exceeded ${seconds}s or failed" >&2
exit 1
fi
}
run_logged apt-update 300 apt-get update -qq
previous="${1:-}"
channel="${2:-canary}"
if [[ -n "$previous" ]]; then
run_logged previous-install 1200 apt-get install -y -qq "$previous"
cat > /etc/ekklesia-classroom/device.conf <<'CONFIG'
# Configuration carried forward from 0.1.36
UPDATE_URL=https://updates.riverofglorymn.org
CHANNEL=@CHANNEL@
DVD_ENABLED=false
UI_SCALE=1.25
MQTT_ENABLED=true
MQTT_HOST=broker.example.test
MQTT_PORT=1884
MQTT_USERNAME=classroom
MQTT_PASSWORD=preserve-me
CUSTOM_SETTING=preserve-me-too
CONFIG
sed -i "s/@CHANNEL@/${channel}/" /etc/ekklesia-classroom/device.conf
previous_version="$(dpkg-query -W -f='${Version}' ekklesia-classroom)"
fi
run_logged candidate-install 1200 apt-get install -y -qq /packages/candidate.deb
# THE VERSION, AS THE MACHINE REPORTS IT.
#
# Three places say which release a classroom is running: dpkg's database,
# which the updater compares against the manifest; the VERSION file the
# package carries; and the file name the release was published under.
# They have to be one number, or a room either never updates or updates
# for ever.
candidate_version="$(dpkg-deb --field /packages/candidate.deb Version)"
[[ "$(dpkg-query -W -f='${Version}' ekklesia-classroom)" == "$candidate_version" ]]
[[ "$(cat /usr/local/lib/ekklesia-classroom/VERSION)" == "$candidate_version" ]]
echo "installed ekklesia-classroom $candidate_version${previous_version:+ over $previous_version}"
if [[ -n "${previous_version:-}" ]]; then
dpkg --compare-versions "$candidate_version" gt "$previous_version"
fi
command -v nmtui >/dev/null
command -v lpadmin >/dev/null
command -v lpinfo >/dev/null
systemctl is-enabled NetworkManager.service >/dev/null
systemctl is-enabled cups.service >/dev/null
! systemctl is-enabled NetworkManager-wait-online.service >/dev/null 2>&1
# The audio policy reaches a session, and is ordered after the server it
# actually talks to. Ordering it only after pipewire and wireplumber is
# what let it race PipeWire's PulseAudio server at boot, fail its one
# startup attempt and then sit idle — the rooms were silent on 0.2.2 for
# exactly this reason, so the ordering is asserted rather than assumed.
audio_unit=/etc/systemd/user/ekklesia-classroom-audio.service
[[ -f "$audio_unit" ]]
grep -q 'pipewire-pulse' "$audio_unit"
[[ -L /etc/systemd/user/plasma-workspace.target.wants/ekklesia-classroom-audio.service ]]
command -v classroom-audio >/dev/null
command -v pactl >/dev/null
# The classroom account cannot be shown a saved Wi-Fi password. The rule
# has to be installed, owned by root — polkit silently ignores a rules
# file that is not — and it has to refuse the action Debian ships as
# "yes", which is the one a teacher would actually reach.
network_rules=/usr/share/polkit-1/rules.d/49-ekklesia-classroom-network.rules
[[ -f "$network_rules" ]]
[[ "$(stat -c %U:%G "$network_rules")" == "root:root" ]]
grep -q "settings.modify.own" "$network_rules"
grep -q "wifi.share.protected" "$network_rules"
# And it must NOT refuse the actions that keep the room on the network.
! grep -qE '"org\.freedesktop\.NetworkManager\.network-control"' "$network_rules"
grep -q "kcm_networkmanagement=false" /etc/ekklesia-classroom/xdg/kdeglobals
command -v classroom-wifi >/dev/null
# Adopting an installer-configured Wi-Fi is an administrator's command,
# never something an upgrade does on its own: it moves a machine's only
# link between managers, and the machine it would strand is a laptop in a
# locked room. The migration may only LOOK.
classroom-wifi --check 2>&1 | grep -q "no wireless interface"
classroom-wifi --adopt 2>&1 | grep -q "nothing to adopt"
! grep -qE '^[^#]*classroom-wifi --adopt' /usr/local/bin/classroom-migrate
grep -q "classroom-wifi --check" /usr/local/bin/classroom-migrate
PYTHONPATH=/usr/local/lib/ekklesia-classroom python3 -c \
'import classroom_network; classroom_network.parse("")'
# THE 0.2.5 WI-FI REGRESSION, ASSERTED ON A REAL INSTALL.
#
# The prayer-room laptop's network is hidden and its name has a space in
# it. The migration dropped the hidden flag, so the connection it built
# could never associate. Checked against the module the package actually
# installed, not the one in a checkout.
PYTHONPATH=/usr/local/lib/ekklesia-classroom python3 <<'WIFI_CHECK'
import classroom_network as n
stanza = n.wireless(n.parse(
'allow-hotplug wlp2s0\n'
'iface wlp2s0 inet dhcp\n'
'\twpa-ssid "ROGC Classroom"\n'
'\twpa-psk se#cret\n'
'\twpa-scan-ssid 1\n'
), lambda device: True)[0]
found = n.profile(stanza)
assert found["ssid"] == "ROGC Classroom", found
assert found["hidden"] is True, "the hidden flag was dropped again"
assert found["psk"] == "se#cret", "a # in the password was truncated again"
assert found["key_mgmt"] == "wpa-psk", found
# A rollback puts back exactly what it commented out, and nothing else.
before = ("auto enp1s0\niface enp1s0 inet static\n\taddress 10.40.0.9/24\n\n"
"allow-hotplug wlp2s0\niface wlp2s0 inet dhcp\n\twpa-ssid Sanctuary\n")
after = n.comment_out(before, n.wireless(n.parse(before),
lambda d: d == "wlp2s0")[0])
restored, count = n.uncomment(after)
assert count, "nothing was marked, so a rollback would have nothing to undo"
assert restored == before, "the rollback did not restore the file exactly"
WIFI_CHECK
# And the command itself carries both halves of the fix.
grep -q '802-11-wireless.hidden' /usr/local/bin/classroom-wifi
grep -q 'managed", "yes"' /usr/local/bin/classroom-wifi
# PRESENTATIONS.
#
# Off by default, with everything it needs present on every machine, so
# enabling it on a device is one line in device.conf and a reboot.
command -v classroom-presentations >/dev/null
command -v classroom-presentation-import >/dev/null
command -v soffice >/dev/null
command -v pdftoppm >/dev/null
command -v pdfinfo >/dev/null
[[ -f /etc/systemd/user/ekklesia-classroom-presentations.service ]]
# Enabled the same way the audio policy is, so it actually reaches a
# session rather than merely being installed. A unit file nobody starts
# is a teacher whose download never appears.
[[ -L /etc/systemd/user/plasma-workspace.target.wants/ekklesia-classroom-presentations.service ]]
[[ -f /usr/share/applications/ekklesia-presentations.desktop ]]
[[ -f /usr/share/icons/hicolor/scalable/apps/ekklesia-presentations.svg ]]
# A classroom that has never heard of the setting comes up without it.
PYTHONPATH=/usr/local/lib/ekklesia-classroom python3 -c \
'from classroom_config import feature; assert feature({}, "PRESENTATIONS_ENABLED") is False'
# Disabled is the default, so both commands decline to run and say why.
classroom-presentations 2>&1 | grep -q 'PRESENTATIONS_ENABLED is false'
classroom-presentation-import --once 2>&1 | grep -q 'PRESENTATIONS_ENABLED is false'
# THE CONVERSION THIS WHOLE FEATURE RESTS ON.
#
# On a real Debian, with the fonts this package installs. No window, no
# splash, no first-run dialog, no editor — measured rather than assumed,
# because "LibreOffice is installed" is not the same claim as "a PPTX
# becomes slides a teacher can be shown".
mkdir -p /tmp/deck
python3 <<'MAKE_DECK'
page = ('<draw:page draw:name="p%d"><draw:frame svg:width="20cm" '
'svg:height="3cm" svg:x="1cm" svg:y="2cm"><draw:text-box>'
'<text:p>%s</text:p></draw:text-box></draw:frame></draw:page>')
open("/tmp/deck/deck.fodp", "w").write(
'<?xml version="1.0" encoding="UTF-8"?>'
'<office:document '
'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '
'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" '
'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" '
'xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" '
'xmlns:presentation='
'"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" '
'office:version="1.3" '
'office:mimetype="application/vnd.oasis.opendocument.presentation">'
'<office:body><office:presentation>'
+ (page % (1, "Wednesday Bible Study"))
+ (page % (2, "Session 7"))
+ '</office:presentation></office:body></office:document>')
MAKE_DECK
HOME=/tmp/deck timeout 240 soffice --headless --norestore --nologo \
--convert-to pptx --outdir /tmp/deck /tmp/deck/deck.fodp >/dev/null 2>&1
[[ -f /tmp/deck/deck.pptx ]]
PYTHONPATH=/usr/local/lib/ekklesia-classroom HOME=/tmp/deck python3 <<'CONVERT_CHECK'
import pathlib
import classroom_presentations as library
ok, error = library.convert_to_pdf("/tmp/deck/deck.pptx", "/tmp/deck/slides.pdf")
assert ok, f"PPTX could not be converted: {error}"
pages = library.page_count("/tmp/deck/slides.pdf")
assert pages == 2, f"expected 2 slides, got {pages}"
made, error = library.render_pages("/tmp/deck/slides.pdf", "/tmp/deck/pages")
assert made == 2, f"expected 2 rendered pages, got {made}: {error}"
# The library asks for page-001.png whatever the page count's width is.
for number in (1, 2):
path = pathlib.Path(f"/tmp/deck/pages/page-{number:03d}.png")
assert path.exists(), f"{path} was not rendered"
assert path.stat().st_size > 0, f"{path} is empty"
# Slide 2 is the one a teacher picks as the card picture, so its text has
# to have survived the conversion.
text = library.first_text("/tmp/deck/slides.pdf", pages=2)
assert "Session 7" in text, f"text did not survive the conversion: {text!r}"
CONVERT_CHECK
# The fonts a Google Slides export asks for, as files on disk.
#
# Asserted by path rather than through fc-list: fc-list belongs to the
# fontconfig package, which nothing here pulls in, so the check passed
# vacuously until it didn't and then failed for a reason that had nothing
# to do with the fonts. What LibreOffice actually needs is the file.
[[ -f /usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf ]]
[[ -f /usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf ]]
[[ -f /usr/share/fonts/truetype/liberation/LiberationSansNarrow-Regular.ttf ]]
[[ -f /usr/share/fonts/truetype/crosextra/Carlito-Regular.ttf ]]
[[ -f /usr/share/fonts/truetype/crosextra/Caladea-Regular.ttf ]]
[[ -f /usr/share/fonts/truetype/croscore/Arimo-Regular.ttf ]]
# And the one that must NOT be here, because it prompts for a licence.
! dpkg -l ttf-mscorefonts-installer 2>/dev/null | grep -q '^ii'
# THE MANUAL.
#
# Installed on every machine, works with no network at all, and shows
# only the features this device has.
[[ -f /usr/local/share/ekklesia-classroom/manual/classroom-manual.html ]]
[[ -f /usr/local/share/ekklesia-classroom/manual/classroom-manual.pdf ]]
command -v classroom-manual >/dev/null
[[ -f /usr/share/applications/ekklesia-manual.desktop ]]
head -c 5 /usr/local/share/ekklesia-classroom/manual/classroom-manual.pdf \
| grep -q '%PDF-'
grep -aq 'Revision' /usr/local/share/ekklesia-classroom/manual/classroom-manual.pdf
! grep -qE 'https?://' \
/usr/local/share/ekklesia-classroom/manual/classroom-manual.html
# The manifest the manual reads carries feature NAMES and nothing else:
# no broker password, no addresses, nothing else out of device.conf.
printf 'PRESENTATIONS_ENABLED=true\nWONDERINK_ENABLED=false\nMQTT_PASSWORD=never-a-teacher\n' \
>> /etc/ekklesia-classroom/device.conf
classroom-desktop-layout >/dev/null 2>&1 || true
[[ -f /run/ekklesia-classroom/features.json ]]
grep -q 'PRESENTATIONS_ENABLED' /run/ekklesia-classroom/features.json
! grep -q 'WONDERINK_ENABLED' /run/ekklesia-classroom/features.json
! grep -q 'never-a-teacher' /run/ekklesia-classroom/features.json
! grep -q '10.40.0.2' /run/ekklesia-classroom/features.json
classroom-manual --features | grep -qE 'PRESENTATIONS_ENABLED +shown'
classroom-manual --features | grep -qE 'WONDERINK_ENABLED +hidden'
# The panel offers Presentations only where it is enabled, keeps the classroom
# applications in order, and does not expose the separately installed manual
# as a teacher-facing Help launcher.
layout=/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/layouts/org.kde.plasma.desktop-layout.js
grep -q 'var PRESENTATIONS = true;' "$layout"
grep -q 'ekklesia-presentations.desktop' "$layout"
! grep -q 'ekklesia-manual.desktop' "$layout"
python3 - "$layout" <<'ORDER_CHECK'
import re
import sys
text = open(sys.argv[1]).read()
order = re.findall(r'launchers\.push\("applications:([^"]+)"\)', text)
expected = ["ekklesia-wonderink.desktop", "google-chrome.desktop",
"ekklesia-dvd.desktop", "ekklesia-presentations.desktop"]
assert order == expected, f"the dock order changed: {order}"
ORDER_CHECK
sed -i '/^PRESENTATIONS_ENABLED=true$/d;/^WONDERINK_ENABLED=false$/d;/^MQTT_PASSWORD=never-a-teacher$/d' \
/etc/ekklesia-classroom/device.conf
# The policy module and the daemon agree on what they import, on a machine
# rather than in a checkout.
PYTHONPATH=/usr/local/lib/ekklesia-classroom python3 -c \
'import classroom_audio; classroom_audio.settled([], "auto", None)'
grep -q '^managed=false$' /etc/NetworkManager/NetworkManager.conf
# A normal Docker container has Ethernet and no Wi-Fi adapter. Reaching this
# point proves package configuration did not require Wi-Fi hardware.
! find /sys/class/net -mindepth 2 -maxdepth 2 -type d -name wireless | grep -q .
/usr/sbin/cupsd
sleep 1
driver='foomatic-db-compressed-ppds:0/ppd/foomatic-ppd/Generic-PCL_6_PCL_XL_Printer-pxlcolor.ppd'
lpinfo -m | grep -q "^${driver} "
# Simulate the queue an older release may have left behind, then run the real
# boot provisioning path. Chrome is stubbed as already installed and DVD is
# disabled so the test remains entirely local and deterministic.
lpadmin -p Classroom_Color -v socket://192.0.2.10:9100 -m "$driver"
install -m755 /bin/true /usr/local/bin/google-chrome
timeout --signal=TERM --kill-after=5s 45s classroom-provision \
>/tmp/provision-first.log
! lpstat -p Classroom_Color >/dev/null 2>&1
lpstat -p Elmer -d -v | grep -q 'socket://10.10.0.155:9100'
lpstat -l -p Elmer | grep -q 'Description: Elmer'
lpoptions -p Elmer -l | grep -Eq '^PrintoutMode/.*:.*\*Normal([[:space:]]|$)'
lpoptions -p Elmer -l | grep -Eq '^ColorModel/.*:.*\*Color([[:space:]]|$)'
[[ "$(lpstat -e)" == "Elmer" ]]
[[ "$(systemctl is-enabled avahi-daemon.service 2>/dev/null || true)" == "masked" ]]
[[ "$(systemctl is-enabled avahi-daemon.socket 2>/dev/null || true)" == "masked" ]]
timeout --signal=TERM --kill-after=5s 45s classroom-provision \
>/tmp/provision-second.log
grep -q 'Elmer is already correctly configured' /tmp/provision-second.log
# THE UPDATER THIS PACKAGE INSTALLED, AGAINST A MANIFEST.
#
# Served from this container, so nothing reaches the real update server.
# The installed version has to be "up to date" against a manifest naming
# itself — or the room reinstalls and reboots at every check — and has to
# see the next feature release as newer.
mkdir -p /tmp/updates/packages "/tmp/updates/$channel"
cp /packages/candidate.deb "/tmp/updates/packages/ekklesia-classroom_${candidate_version}_amd64.deb"
serve_manifest() {
python3 - "$1" "$channel" <<'MANIFEST'
import hashlib, json, sys
version, channel = sys.argv[1], sys.argv[2]
name = f"ekklesia-classroom_{version}_amd64.deb"
try:
digest = hashlib.sha256(open(f"/tmp/updates/packages/{name}", "rb").read()).hexdigest()
except FileNotFoundError:
digest = "0" * 64
json.dump({"version": version, "package": f"/packages/{name}",
"sha256": digest, "requires_reboot": True},
open(f"/tmp/updates/{channel}/manifest.json", "w"))
MANIFEST
}
python3 -m http.server --bind 127.0.0.1 --directory /tmp/updates 8765 \
>/tmp/updates.log 2>&1 &
updates_server=$!
sleep 1
cp /etc/ekklesia-classroom/device.conf /tmp/device.conf.before-updater
sed -i 's|^UPDATE_URL=.*|UPDATE_URL=http://127.0.0.1:8765|' \
/etc/ekklesia-classroom/device.conf
serve_manifest "$candidate_version"
ekklesia-classroom-update --check >/tmp/updater-same.log 2>&1
grep -q "Already up to date" /tmp/updater-same.log
# The next milestone after whatever this is, and a release every
# classroom has long since passed.
IFS=. read -r major minor _ <<< "$candidate_version"
next_version="${major}.$((minor + 1)).0"
serve_manifest "$next_version"
ekklesia-classroom-update --check >/tmp/updater-next.log 2>&1
grep -q "Update available: $candidate_version -> $next_version" /tmp/updater-next.log
serve_manifest 0.2.2
ekklesia-classroom-update --check >/tmp/updater-older.log 2>&1
grep -q "Already up to date" /tmp/updater-older.log
echo '{"version": "0.2.06", "package": "/packages/x.deb", "sha256": "x"}' \
> "/tmp/updates/$channel/manifest.json"
! ekklesia-classroom-update --check >/tmp/updater-bad.log 2>&1
grep -q "invalid version" /tmp/updater-bad.log
kill "$updates_server"
cp /tmp/device.conf.before-updater /etc/ekklesia-classroom/device.conf
if [[ -n "$previous" ]]; then
# The room's own values, untouched by the upgrade.
grep -q '^DVD_ENABLED=false$' /etc/ekklesia-classroom/device.conf
grep -q '^UI_SCALE=1.25$' /etc/ekklesia-classroom/device.conf
grep -q '^MQTT_PASSWORD=preserve-me$' /etc/ekklesia-classroom/device.conf
grep -q '^CUSTOM_SETTING=preserve-me-too$' /etc/ekklesia-classroom/device.conf
# The channel a room was put on deliberately survives the upgrade.
# canary is where the next development release goes, and the Plasma
# classroom is the stable line now, so nothing here has to move.
grep -q "^CHANNEL=${channel}\$" /etc/ekklesia-classroom/device.conf
# An optional feature an existing room never had stays off. A
# Presentations launcher appearing on the desktops' panels by itself
# would be an upgrade changing a classroom, which upgrades do not do.
[[ "$(grep -c '^PRESENTATIONS_ENABLED=' /etc/ekklesia-classroom/device.conf)" -eq 1 ]]
grep -q '^PRESENTATIONS_ENABLED=false$' /etc/ekklesia-classroom/device.conf
classroom-presentations 2>&1 | grep -q 'PRESENTATIONS_ENABLED is false'
classroom-desktop-layout >/dev/null 2>&1 || true
grep -q 'var PRESENTATIONS = false;' \
/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/layouts/org.kde.plasma.desktop-layout.js
# And a room that was carried through the experiment on its own
# channel comes home, because that channel is the obsolete one now.
sed -i 's/^CHANNEL=.*/CHANNEL=classroom-v2/' /etc/ekklesia-classroom/device.conf
classroom-migrate >/tmp/migrate-from-v2.log 2>&1
grep -q '^CHANNEL=stable$' /etc/ekklesia-classroom/device.conf
grep -q '^MQTT_PASSWORD=preserve-me$' /etc/ekklesia-classroom/device.conf
grep -q '^CUSTOM_SETTING=preserve-me-too$' /etc/ekklesia-classroom/device.conf
sed -i "s/^CHANNEL=.*/CHANNEL=${channel}/" /etc/ekklesia-classroom/device.conf
# Every setting this release has that the room predates, appended
# exactly once. classroom-migrate hands device.conf to the shared
# helper after its own work for this, so that a machine converges
# whether it got here through this postinst or through somebody
# running the command by hand.
for setting in LIVE_ENABLED AUDIO_OUTPUT UI_SCALE; do
[[ "$(grep -c "^${setting}=" /etc/ekklesia-classroom/device.conf)" -eq 1 ]]
done
run_logged candidate-reinstall 900 \
apt-get install --reinstall -y -qq /packages/candidate.deb
# An install that changes nothing must change nothing.
for setting in LIVE_ENABLED AUDIO_OUTPUT UI_SCALE; do
[[ "$(grep -c "^${setting}=" /etc/ekklesia-classroom/device.conf)" -eq 1 ]]
done
grep -q '^MQTT_PASSWORD=preserve-me$' /etc/ekklesia-classroom/device.conf
grep -q '^CUSTOM_SETTING=preserve-me-too$' /etc/ekklesia-classroom/device.conf
grep -q "^CHANNEL=${channel}\$" /etc/ekklesia-classroom/device.conf
# And by hand, on a machine that is already converged: the command a
# technician runs must be safe to run twice.
classroom-migrate >/tmp/migrate-by-hand.log 2>&1
for setting in LIVE_ENABLED AUDIO_OUTPUT UI_SCALE; do
[[ "$(grep -c "^${setting}=" /etc/ekklesia-classroom/device.conf)" -eq 1 ]]
done
grep -q '^MQTT_PASSWORD=preserve-me$' /etc/ekklesia-classroom/device.conf
! grep -q 'preserve-me' /tmp/migrate-by-hand.log
fi
echo "Trixie package, upgrade, bounded install, networking, Elmer printing and reinstall checks passed."
CONTAINER