Files
Classroom_Desktop/tools/validate-trixie-release.sh
T
ekklesiaandClaude Opus 5.5 9fc86f9d4c test: the wallpaper repair, on Tiny Pebbles' own file, in the lab and on a real install
Unit: Tiny Pebbles' file (desktop 10, panel 1), a healthy River Kids file
left byte for byte, a late desktop beside a good one, idempotence, the
refusal under a running shell. Lab: Plasma makes a bare desktop itself
through a new activity, and the screen is read, not the file. Container
gate: the installed package's fallback is the package, and its repair
fixes the room's file and leaves the panel alone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-29 18:05:54 -05:00

608 lines
26 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 update check: daily at 03:00, caught up after a night switched off,
# and still so after this install or upgrade. The reboot after an update
# waits until nobody is using the computer, in a unit nothing enables.
systemctl is-enabled ekklesia-classroom-update.timer >/dev/null
timer=/etc/systemd/system/ekklesia-classroom-update.timer
grep -qx 'OnCalendar=\*-\*-\* 03:00:00' "$timer"
grep -qx 'Persistent=true' "$timer"
! grep -qE '^(OnBootSec|OnStartupSec|OnUnitActiveSec|OnUnitInactiveSec|OnActiveSec)=' "$timer"
! ls /etc/systemd/system/ekklesia-classroom-update.timer.d/*.conf >/dev/null 2>&1
test -x /usr/local/bin/classroom-reboot-when-idle
[[ "$(systemctl is-enabled ekklesia-classroom-reboot-when-idle.service 2>/dev/null || true)" == "static" ]]
! grep -q '"systemctl", "reboot"' /usr/local/bin/ekklesia-classroom-update
grep -q 'ekklesia-classroom-reboot-when-idle.service' /usr/local/bin/ekklesia-classroom-update
# 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
# TINY PEBBLES, 0.2.2 -> 0.2.6.4: THE DESKTOP WITH NO WALLPAPER.
#
# The room's desktop was containment 10, made by Plasma after the
# layout had run, with nothing configured on it; it showed Plasma's
# own wallpaper. What a desktop gets when nothing wrote it is the look
# and feel's default, and that has to be a package Plasma can find.
lnf=/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop/contents/defaults
[[ "$(sed -n '/^\[Wallpaper\]/,/^\[/s/^Image=//p' "$lnf")" == EkklesiaClassroom ]]
grep -q '"Id": "EkklesiaClassroom"' /usr/share/wallpapers/EkklesiaClassroom/metadata.json
[[ "$(readlink -f /usr/share/wallpapers/EkklesiaClassroom/contents/images/*.png)" \
== /usr/share/backgrounds/ekklesia/classroom-calm.png ]]
grep -qx 'ExecStartPre=-/usr/local/bin/classroom-desktop-wallpaper' \
/etc/systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf
# And the repair the package installed, on that room's own file: the
# panel built with no screen took 1 and its applets 2 to 9.
wallpaper_home=$(mktemp -d)
cat > "$wallpaper_home/plasma-org.kde.plasma.desktop-appletsrc" <<'APPLETS'
[Containments][1]
formfactor=2
location=4
plugin=org.kde.panel
wallpaperplugin=org.kde.image
[Containments][1][Applets][2]
plugin=org.kde.plasma.quicklaunch
[Containments][1][Applets][9]
plugin=org.kde.plasma.digitalclock
[Containments][1][General]
AppletOrder=2;3;4;5;6;7;8;9
[Containments][10]
activityId=62d3d55d-bd31-4fc8-9a7b-a5b9b2b99b60
formfactor=0
immutability=1
lastScreen=0
location=0
plugin=org.kde.plasma.folder
wallpaperplugin=org.kde.image
[Containments][10][General]
positions={"1920x1080":[]}
APPLETS
applets="$wallpaper_home/plasma-org.kde.plasma.desktop-appletsrc"
panel_before=$(sed -n '/^\[Containments\]\[1\]/,/^$/p' "$applets")
! XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper --check >/dev/null
XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper \
| grep -q 'configured on desktop 10'
sed -n '/^\[Containments\]\[10\]\[Wallpaper\]\[org.kde.image\]\[General\]$/,/^$/p' "$applets" \
| grep -qx 'Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png'
[[ "$(sed -n '/^\[Containments\]\[1\]/,/^$/p' "$applets")" == "$panel_before" ]]
[[ "$(grep -c '^\[Containments\]\[[0-9]*\]$' "$applets")" -eq 2 ]]
# Twice changes nothing, and a room that was already right is not
# written at all.
repaired=$(sha256sum < "$applets")
XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper \
| grep -q 'the classroom wallpaper already'
[[ "$(sha256sum < "$applets")" == "$repaired" ]]
XDG_CONFIG_HOME="$wallpaper_home" classroom-desktop-wallpaper --check >/dev/null
rm -rf "$wallpaper_home"
# 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