Documentation becomes part of the release rather than a job afterwards.
docs/manual is now the only place teacher instructions are written, and
tools/build-manual produces both versions from it:
classroom-manual.html installed on every machine, showing only the
features THAT device has
classroom-manual.pdf the universal printed manual, every feature,
with a conditional sentence above each
optional page so it stays true in a room that
does not have them all
They cannot drift apart because neither is written by hand, and
build-release.sh builds and checks them on every release: a page that
declares a feature with no printed-if fails the build, because that is
exactly how the printed manual comes to state an optional feature as
though every room had it. So does a page naming a feature device.conf
does not have, and an optional feature with no page at all.
WHAT REACHES THE CLASSROOM ACCOUNT. Only a list of setting NAMES, and
only ones the package already defines as optional applications.
classroom-desktop-layout — already the one place that decides what a
device offers — writes them to /run/ekklesia-classroom/features.json,
and classroom-manual filters that against FEATURES before using it. No
values from device.conf, no broker password, no addresses. With no
manifest at all every page is shown, which is the printed manual and is
never wrong, only less specific.
Help is a Quick Launch button beside Parent Call, deliberately not in
the row with the applications: putting it there would move buttons
teachers reach for without looking. Every room gets it, because a
teacher who needs the manual is by definition one who does not know
where to look.
The PDF is written directly rather than converted. A manual is
headings, paragraphs, bullets, steps and one table; writing that with
the standard fonts is a couple of hundred lines and no dependency, so a
release can be built on any machine with Python. Pictures are embedded
in the digital manual as data URIs so it stays one file that works
wherever it is copied and with the network unplugged; the printed
manual names them instead, which is the one thing this builder does not
do and is noted where somebody would look for it.
Two defects found building it, both of which hung or corrupted the
output rather than failing: a paragraph opening with a bold word
matched the bullet guard but not the bullet rule, so nothing consumed
it and the build spun; and a list item wrapped across two source lines
ended the list, so a three-step procedure printed as three steps all
numbered 1.
Also adds the 0.2.6 physical checklist to docs/testing.md and the
release validator's checks for the Wi-Fi regression, the conversion
pipeline on a real Debian, the fonts, the manual and the dock order.
Co-Authored-By: Claude Opus 5 <[email protected]>
413 lines
17 KiB
Bash
Executable File
413 lines
17 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
|
|
|
|
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
|
|
|
|
timeout --signal=TERM --kill-after=15s 720s \
|
|
docker "${docker_args[@]}" debian:trixie-slim \
|
|
bash -s -- "${previous:+/packages/previous.deb}" <<'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 120 apt-get update -qq
|
|
|
|
previous="${1:-}"
|
|
|
|
if [[ -n "$previous" ]]; then
|
|
run_logged previous-install 360 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=canary
|
|
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
|
|
fi
|
|
|
|
run_logged candidate-install 360 apt-get install -y -qq /packages/candidate.deb
|
|
|
|
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, by their real names.
|
|
fc-list 2>/dev/null | grep -qi 'Liberation Sans'
|
|
fc-list 2>/dev/null | grep -qi 'Carlito'
|
|
fc-list 2>/dev/null | grep -qi 'Caladea'
|
|
# 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, and the three
|
|
# applications that were already on the bar keep their order.
|
|
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
|
|
|
|
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=canary$' /etc/ekklesia-classroom/device.conf
|
|
|
|
# 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=canary/' /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 360 \
|
|
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=canary$' /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
|