#!/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 # # 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' step "the old classroom: $(basename "$OLD")" docker exec "$NAME" sh -c " DEBIAN_FRONTEND=noninteractive dpkg -i '/srv/classroom/${OLD#$ROOT/}' >/dev/null 2>&1 DEBIAN_FRONTEND=noninteractive apt-get -y -qq -f install >/dev/null 2>&1 DEBIAN_FRONTEND=noninteractive dpkg --configure ekklesia-classroom >/dev/null 2>&1 true" || true docker exec "$NAME" dpkg-query -W -f=' installed: ${Version}\n' ekklesia-classroom 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" < /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' docker exec "$NAME" sh -c " DEBIAN_FRONTEND=noninteractive dpkg -i '/srv/classroom/${NEW#$ROOT/}' 2>&1 \ | grep -E '^\[classroom-migrate\]' || true DEBIAN_FRONTEND=noninteractive apt-get -y -qq -f install >/dev/null 2>&1 DEBIAN_FRONTEND=noninteractive dpkg --configure ekklesia-classroom >/dev/null 2>&1 rm -f /usr/sbin/policy-rc.d true" || true docker exec "$NAME" dpkg-query -W -f=' installed: ${Version} ${db:Status-Abbrev}\n' ekklesia-classroom 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 dpkg -i '/srv/classroom/${NEW#$ROOT/}' >/dev/null 2>&1 true" || true 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 it still recovers" "$ROOT/tools/lab/recovery-check" || converged=1 exit "$converged"