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]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6f2d169355
commit
690ef95aff
@@ -18,6 +18,16 @@ if [[ $# -eq 2 ]]; then
|
||||
[[ -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=(
|
||||
@@ -41,7 +51,7 @@ fi
|
||||
# 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}" <<'CONTAINER'
|
||||
bash -s -- "${previous:+/packages/previous.deb}" "$channel" <<'CONTAINER'
|
||||
set -Eeuo pipefail
|
||||
|
||||
validation_failure() {
|
||||
@@ -87,6 +97,7 @@ run_logged() {
|
||||
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"
|
||||
@@ -94,7 +105,7 @@ if [[ -n "$previous" ]]; then
|
||||
cat > /etc/ekklesia-classroom/device.conf <<'CONFIG'
|
||||
# Configuration carried forward from 0.1.36
|
||||
UPDATE_URL=https://updates.riverofglorymn.org
|
||||
CHANNEL=canary
|
||||
CHANNEL=@CHANNEL@
|
||||
DVD_ENABLED=false
|
||||
UI_SCALE=1.25
|
||||
MQTT_ENABLED=true
|
||||
@@ -104,10 +115,28 @@ 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
|
||||
@@ -376,6 +405,64 @@ 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
|
||||
@@ -386,7 +473,17 @@ if [[ -n "$previous" ]]; then
|
||||
# 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
|
||||
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.
|
||||
@@ -395,7 +492,7 @@ if [[ -n "$previous" ]]; then
|
||||
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
|
||||
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
|
||||
@@ -415,7 +512,7 @@ if [[ -n "$previous" ]]; then
|
||||
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
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user