Files
Classroom_Desktop/tools/lab/dirty-upgrade
T
ekklesiaandClaude Opus 5.5 6c04f743ed test: every rehearsal installs with apt and proves which version landed
The dirty-upgrade lab installed the new package with dpkg -i. Since
0.2.6.2 the package conflicts with khelpcenter, which dpkg refuses and apt
resolves, so the 0.2.2 rehearsal left the machine on 0.2.2 and asked every
question of the old release. Old package, new package and the reinstall
now all go through apt, as ekklesia-classroom-update does, and each stops
the run unless dpkg reports exactly that version, fully installed. The
container gate asserts the same of its previous release and its reinstall.

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

267 lines
9.5 KiB
Bash
Executable File

#!/bin/bash
# The upgrade that failed, reproduced on purpose.
#
# A real classroom does not arrive new, and the clean-install test was
# passing on a machine that had never been anything else. This builds a
# machine that has: the Hyprland experiment package actually installed,
# with sway, waybar and mako behind it, a Waybar activated through
# graphical-session, an autostart entry, a device.conf still asking for a
# compositor that is gone, and a Plasma profile belonging to a different
# desktop. Then it installs the classroom package over the top and asks
# whether the room converged.
#
# tools/lab/dirty-upgrade [NEW.deb] [OLD.deb]
# CHECK_ARGS=--restart tools/lab/dirty-upgrade ask twice, either side
# of a logout
# tools/lab/dirty-upgrade NEW.deb dist/ekklesia-classroom_0.2.2_amd64.deb
# the day the desktops move from 0.2.2
#
# The container is restarted between the install and the session, because
# a room takes its upgrade and reboots.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
LAB="$ROOT/tools/lab/lab"
NAME="${LAB_NAME:-classroom-v2-lab}"
NEW="${1:-$(ls -t "$ROOT"/dist/ekklesia-classroom_*_amd64.deb | grep -v '~' | head -1)}"
OLD="${2:-$ROOT/dist/ekklesia-classroom_0.1.45~hypr3_amd64.deb}"
step() { printf '\n\033[1m== %s\033[0m\n' "$1"; }
step "a fresh machine"
"$LAB" up >/dev/null
docker exec "$NAME" sh -c '
printf "#!/bin/sh\nexit 101\n" > /usr/sbin/policy-rc.d
chmod +x /usr/sbin/policy-rc.d
apt-get update -qq >/dev/null 2>&1
# The old package schedules a provisioning run that would go to
# backports for Hyprland. The lab is not testing that.
systemctl mask ekklesia-classroom-provision.service >/dev/null 2>&1'
# Which version is installed, fully configured, or the rehearsal stops.
# Every step below is a question about one particular release, and a
# step that quietly left the machine on another one answers for the
# wrong release — which is how the 0.2.2 rehearsal once passed on 0.2.2.
require_installed() {
local want got
want=$(dpkg-deb --field "$1" Version)
got=$(docker exec "$NAME" dpkg-query -W -f='${Version} ${db:Status-Abbrev}' ekklesia-classroom 2>/dev/null || true)
echo " installed: $got"
if [[ "$got" != "$want ii " ]]; then
echo " $(basename "$1") DID NOT INSTALL: [$got], wanted [$want ii]" >&2
echo " Nothing after this would be a question about $want." >&2
exit 1
fi
}
step "the old classroom: $(basename "$OLD")"
# With apt, the way a room installs anything, so an old package's own
# dependencies are resolved rather than forced.
docker exec "$NAME" sh -c "
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq '/srv/classroom/${OLD#$ROOT/}' >/dev/null 2>&1
true" || true
require_installed "$OLD"
step "and everything a used machine accumulates"
docker exec -i "$NAME" bash <<'DIRT'
set -e
# Waybar and mako, activated the way the real room had them. Neither
# package ships a unit in trixie, so the units themselves are part of what
# somebody added — and one of the three activation directories is the one
# the broken room actually used.
for unit in waybar mako; do
cat > "/etc/systemd/user/$unit.service" <<UNIT
[Unit]
Description=$unit (left over from the old classroom shell)
PartOf=graphical-session.target
After=graphical-session.target
[Service]
ExecStart=/usr/bin/$unit
Restart=on-failure
[Install]
WantedBy=graphical-session.target
UNIT
done
for dir in /etc/systemd/user /etc/xdg/systemd/user \
/home/classroom/.config/systemd/user; do
mkdir -p "$dir/graphical-session.target.wants"
ln -sf /etc/systemd/user/waybar.service \
"$dir/graphical-session.target.wants/waybar.service"
ln -sf /etc/systemd/user/mako.service \
"$dir/graphical-session.target.wants/mako.service"
done
# An autostart entry as well, for a bar that is started two ways.
mkdir -p /home/classroom/.config/autostart
cat > /home/classroom/.config/autostart/waybar.desktop <<'ENTRY'
[Desktop Entry]
Type=Application
Name=Waybar
Exec=waybar
ENTRY
# The room still asking for a compositor that is gone, with a broker
# password in the same file to prove the migration does not rewrite it.
sed -i 's/^CHANNEL=.*/CHANNEL=hyprland/' /etc/ekklesia-classroom/device.conf
{
echo "COMPOSITOR=hyprland"
echo "HYPR_APPEARANCE=visual"
} >> /etc/ekklesia-classroom/device.conf
sed -i 's/^MQTT_HOST=.*/MQTT_HOST=broker.example.invalid/' /etc/ekklesia-classroom/device.conf
sed -i 's/^MQTT_PASSWORD=.*/MQTT_PASSWORD=keep-me-exactly/' /etc/ekklesia-classroom/device.conf
# A Plasma profile from a different desktop: someone else's palette, in
# both of the places that outrank the package.
mkdir -p /home/classroom/.config/kdedefaults
cat > /home/classroom/.config/kdedefaults/kdeglobals <<'KDE'
[General]
ColorScheme=BreezeLight
[Icons]
Theme=breeze
[KDE]
LookAndFeelPackage=org.kde.breeze.desktop
KDE
cat > /home/classroom/.config/kdeglobals <<'KDE'
[General]
ColorScheme=BreezeLight
[Colors:Window]
BackgroundNormal=239,240,241
ForegroundNormal=35,38,41
[KDE]
LookAndFeelPackage=org.kde.breeze.desktop
KDE
# And a stock panel, already built, of the kind the broken upgrade made.
cat > /home/classroom/.config/plasma-org.kde.plasma.desktop-appletsrc <<'PANEL'
[Containments][1]
plugin=org.kde.plasma.folder
[Containments][2]
plugin=org.kde.panel
[Containments][2][Applets][3]
plugin=org.kde.plasma.kickoff
[Containments][2][Applets][4]
plugin=org.kde.plasma.pager
[Containments][2][Applets][5]
plugin=org.kde.plasma.icontasks
[Containments][2][Applets][5][Configuration][General]
launchers=applications:systemsettings.desktop,applications:org.kde.discover.desktop,preferred://browser
[Containments][2][Applets][6]
plugin=org.kde.plasma.systemtray
PANEL
# A user-level power profile that would suspend the room.
cat > /home/classroom/.config/powermanagementprofilesrc <<'POWER'
[AC][SuspendSession]
idleTime=1800000
suspendType=1
[AC][DPMSControl]
idleTime=600
POWER
chown -R classroom:classroom /home/classroom/.config
echo " dirt in place"
DIRT
step "the new classroom: $(basename "$NEW")"
docker exec "$NAME" sh -c '
systemctl unmask ekklesia-classroom-provision.service >/dev/null 2>&1 || true'
# The way ekklesia-classroom-update installs it: apt, not dpkg. The
# package conflicts with khelpcenter since 0.2.6.2, and dpkg -i refuses a
# conflict where apt resolves it — so from 0.2.2 this step used to leave
# the machine on 0.2.2 and every check below was asked of the old release.
docker exec "$NAME" sh -c "
DEBIAN_FRONTEND=noninteractive apt-get install -y '/srv/classroom/${NEW#$ROOT/}' 2>&1 \
| grep -E '^\[classroom-migrate\]' || true
rm -f /usr/sbin/policy-rc.d
true" || true
require_installed "$NEW"
step "and the room reboots"
"$LAB" reboot
step "the session"
"$LAB" session >/dev/null
sleep 6
step "did it converge?"
converged=0
"$ROOT/tools/lab/converged-check" ${CHECK_ARGS:-} || converged=1
step "and again, installed a second time over itself"
# An update that changes nothing must change nothing. The migration runs
# again here, over a machine that is already converged.
docker exec "$NAME" sh -c "
DEBIAN_FRONTEND=noninteractive apt-get install --reinstall -y -qq '/srv/classroom/${NEW#$ROOT/}' >/dev/null 2>&1
true" || true
require_installed "$NEW"
before=$(docker exec "$NAME" sh -c 'cat /etc/ekklesia-classroom/device.conf | sha256sum')
"$LAB" plasma-down >/dev/null
"$LAB" plasma-up >/dev/null
sleep 4
after=$(docker exec "$NAME" sh -c 'cat /etc/ekklesia-classroom/device.conf | sha256sum')
[[ "$before" == "$after" ]] \
&& echo " device.conf unchanged by the second install" \
|| { echo " DEVICE.CONF CHANGED ON REINSTALL"; converged=1; }
"$ROOT/tools/lab/converged-check" || converged=1
step "and the room's own theme choice survives a logout"
# Deleting the look and feel defaults every login was measured to make
# Plasma re-apply the look and feel and throw the room's chosen palette
# away with it. This is the check that stops that coming back.
# With the session's own cascade. plasma-apply-colorscheme writes the
# chosen name into ~/.config/kdedefaults, which is in XDG_CONFIG_DIRS for
# a session and not for a bare command, so asking without it reports
# nothing and means nothing.
theme() {
local dirs
dirs=$(docker exec "$NAME" bash -c 'p=$(pgrep -x plasmashell | head -1); tr "\0" "\n" < /proc/$p/environ | grep "^XDG_CONFIG_DIRS=" | cut -d= -f2-')
docker exec -u classroom -e XDG_RUNTIME_DIR=/run/user/1000 \
-e DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
-e WAYLAND_DISPLAY=wayland-0 -e "XDG_CONFIG_DIRS=$dirs" "$NAME" \
/usr/local/bin/classroom-theme "$1" 2>/dev/null | tail -1
}
theme light >/dev/null
"$LAB" plasma-down >/dev/null
"$LAB" plasma-up >/dev/null
sleep 4
chosen=$(theme show)
if [[ "$chosen" == EkklesiaLight ]]; then
echo " light survived the logout"
else
echo " THE ROOM'S THEME CHOICE WAS LOST: $chosen"
converged=1
fi
theme dark >/dev/null
echo " back to $(theme show)"
step "and every desktop has the classroom's wallpaper, however it was made"
# Tiny Pebbles, 0.2.2 -> 0.2.6.4: a desktop Plasma made after the layout
# had run came up with Plasma's own wallpaper, and nothing here noticed,
# because every session this rehearsal started had its screen from the
# beginning. This makes Plasma build one by itself, and puts Tiny
# Pebbles' own file in front of a shell restart.
"$ROOT/tools/lab/wallpaper-check" || converged=1
step "and it still recovers"
"$ROOT/tools/lab/recovery-check" || converged=1
exit "$converged"