Little Waders took the Plasma build and reported what a container cannot. A disc player on a machine with no optical drive, a clock nobody at the back of the room could read, a browser at half the size of the panel around it, and Parent Call with no broker to call. The broker is the same for every classroom, so it is a default now and not something to fill in room by room. classroom-migrate brings an existing room to the same place without touching its name, its username or its password, and Parent Call distinguishes a broker it cannot reach from one that was never set up: those send somebody to look at completely different things. The disc player's detection was right and ran too late — alongside the session rather than before it, and the panel is built from the layout the moment a session starts. It runs before the display manager now, and again from udev when a drive is plugged in. UI_SCALE had a ceiling of 1.5, which cannot express a 4K television; a clock pinned at 15 points, which is the same size at every scale; and a browser that was never told the number at all. All three are fixed and the room doubles together at 200%. This also brings 0.1.47's work into the repository, where it had only ever existed in a build directory under /tmp: the hidden display dialog, its authenticated writer, the polkit action, and the church's own wallpaper. One piece of it is deliberately left out. The custom volume display needed a shell package of our own, and with one selected plasmashell builds no panel at all — measured twice, with that build's own files, on a classroom that deletes its panel configuration at every login by design. The lab connected to the real church broker the moment the package defaulted to it. Nothing was left behind, which was checked, but the guard that should have stopped it had never worked: it used iptables, which the lab image does not have, and probed with /dev/tcp under dash, where that always fails. Both reported success. Co-Authored-By: Claude Opus 5 <[email protected]>
209 lines
6.8 KiB
Bash
Executable File
209 lines
6.8 KiB
Bash
Executable File
#!/bin/bash
|
|
set -uo pipefail
|
|
|
|
# The upgrade path, as far as source can be checked without a machine.
|
|
#
|
|
# A real room upgraded to the Plasma classroom and came up as stock Debian
|
|
# Plasma with the old Waybar still across the top and a lock screen waiting
|
|
# for a password the account does not have. Every check here is one of the
|
|
# reasons that happened. The lab scenario in tools/lab/dirty-upgrade is the
|
|
# other half: this one guards the package, that one guards the machine.
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
python3 - "$ROOT" <<'PY'
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
root = Path(sys.argv[1])
|
|
|
|
env_script = (root / "config/plasma/env/zz-ekklesia-classroom.sh").read_text()
|
|
build = (root / "tools/build-release.sh").read_text()
|
|
postinst = (root / "packaging/debian/postinst").read_text()
|
|
migrate = (root / "scripts/classroom-migrate").read_text()
|
|
reset = (root / "scripts/classroom-desktop-reset").read_text()
|
|
kdeglobals = (root / "config/plasma/xdg/kdeglobals").read_text()
|
|
power = (root / "config/plasma/xdg/powermanagementprofilesrc").read_text()
|
|
locker = (root / "config/plasma/xdg/kscreenlockerrc").read_text()
|
|
logind = (root / "config/systemd/logind-ekklesia-classroom.conf").read_text()
|
|
sddm = (root / "config/sddm/ekklesia-classroom.conf").read_text()
|
|
lab = (root / "tools/lab/lab").read_text()
|
|
|
|
passed = failed = 0
|
|
|
|
|
|
def check(label, condition):
|
|
global passed, failed
|
|
if condition:
|
|
print(f" ok {label}")
|
|
passed += 1
|
|
else:
|
|
print(f" FAIL {label}")
|
|
failed += 1
|
|
|
|
|
|
print("the session environment")
|
|
|
|
# The root cause. /etc/environment.d is read by systemd's user manager;
|
|
# SDDM starts the session through PAM, which is not systemd, so the
|
|
# classroom's settings directory never reached Plasma. startplasma sources
|
|
# /etc/xdg/plasma-workspace/env, which is why the file lives there.
|
|
check(
|
|
"the classroom directory is set where startplasma looks",
|
|
"/etc/xdg/plasma-workspace/env/zz-ekklesia-classroom.sh" in build,
|
|
)
|
|
check(
|
|
"and it is in the package, not only in the lab",
|
|
'config/plasma/env/zz-ekklesia-classroom.sh' in build,
|
|
)
|
|
check(
|
|
"it prepends rather than replaces",
|
|
"XDG_CONFIG_DIRS=\"/etc/ekklesia-classroom/xdg:${XDG_CONFIG_DIRS:-/etc/xdg}\""
|
|
in env_script,
|
|
)
|
|
check(
|
|
"and does nothing when the directory is already there",
|
|
"*:/etc/ekklesia-classroom/xdg:*" in env_script,
|
|
)
|
|
check(
|
|
"the name sorts after Debian's own env.sh",
|
|
Path("config/plasma/env/zz-ekklesia-classroom.sh").name > "env.sh",
|
|
)
|
|
check(
|
|
"environment.d is still shipped for services outside a session",
|
|
"/etc/environment.d/90-ekklesia-classroom.conf" in build,
|
|
)
|
|
check(
|
|
"the lab no longer sets the variable for the session under test",
|
|
"-e XDG_CONFIG_DIRS=" not in lab,
|
|
)
|
|
|
|
print("\nthe desktop cannot be answered for by a stale profile")
|
|
|
|
check(
|
|
"the look and feel package is immutable on its own key",
|
|
"LookAndFeelPackage[$i]=org.ekklesia.classroom.desktop" in kdeglobals,
|
|
)
|
|
check(
|
|
"the palette is composed into the shipped kdeglobals",
|
|
"tools/build-kdeglobals" in build,
|
|
)
|
|
check(
|
|
"another desktop's look and feel defaults are removed at login",
|
|
"removed another desktop's look and feel defaults" in reset,
|
|
)
|
|
check(
|
|
"and the classroom's own are kept, so a chosen theme survives",
|
|
"defaults_are_ours=yes" in reset,
|
|
)
|
|
check(
|
|
"a foreign palette is removed and an Ekklesia one kept",
|
|
"Ekklesia*)" in reset and 'rm -f "$CONFIG_HOME/kdeglobals"' in reset,
|
|
)
|
|
check(
|
|
"the panel is still rebuilt from the packaged layout every login",
|
|
"plasma-org.kde.plasma.desktop-appletsrc" in reset,
|
|
)
|
|
check(
|
|
"and the reset says so out loud if the cascade is wrong",
|
|
"XDG_CONFIG_DIRS did not contain" in reset,
|
|
)
|
|
|
|
print("\nthe old shell converges away")
|
|
|
|
check("the migration runs on every install", "classroom-migrate" in postinst)
|
|
check("it is in the package", "scripts/classroom-migrate" in build)
|
|
|
|
for unit in ("waybar.service", "mako.service"):
|
|
check(f"{unit} is deactivated", unit in migrate)
|
|
|
|
check(
|
|
"activation is removed from all three places it can live",
|
|
"/etc/systemd/user" in migrate
|
|
and "/etc/xdg/systemd/user" in migrate
|
|
and ".config/systemd/user" in migrate,
|
|
)
|
|
check("and then masked, so a stray unit file cannot start it", "--global mask" in migrate)
|
|
check(
|
|
"autostart entries are matched on what they run",
|
|
"^Exec=" in migrate and "autostart" in migrate,
|
|
)
|
|
check(
|
|
"the login shell that started sway is removed",
|
|
".bash_profile" in migrate,
|
|
)
|
|
check(
|
|
"and the tty1 autologin drop-in with it",
|
|
"[email protected]/autologin.conf" in migrate,
|
|
)
|
|
check(
|
|
"the old notification daemon cannot be D-Bus activated",
|
|
"/usr/local/share/dbus-1/services/fr.emersion.mako.service" in build,
|
|
)
|
|
check(
|
|
"the shadow makes activation fail rather than hang",
|
|
"Exec=/bin/false"
|
|
in (root / "config/dbus/fr.emersion.mako.service").read_text(),
|
|
)
|
|
check(
|
|
"and a running session bus is told to re-read its services",
|
|
"ReloadConfig" in migrate,
|
|
)
|
|
check(
|
|
"nothing removes a Debian package",
|
|
"apt-get remove" not in migrate and "apt remove" not in migrate,
|
|
)
|
|
check(
|
|
"device.conf loses the compositor it can no longer choose",
|
|
'OBSOLETE_KEYS = ("COMPOSITOR", "HYPR_APPEARANCE")' in migrate,
|
|
)
|
|
check(
|
|
"and comes off any channel that would undo the upgrade",
|
|
'OBSOLETE_CHANNELS = {"stable", "canary", "hyprland"}' in migrate,
|
|
)
|
|
check(
|
|
"the rest of device.conf is rewritten line by line, not regenerated",
|
|
"handle.writelines(out)" in migrate,
|
|
)
|
|
|
|
print("\nnothing puts a password prompt in front of a teacher")
|
|
|
|
check("no automatic lock", "Autolock=false" in locker)
|
|
check("no lock on resume", "LockOnResume=false" in locker)
|
|
check("the lock settings are immutable", "[Daemon][$i]" in locker)
|
|
check(
|
|
"no idle suspend, dim or blank in any profile",
|
|
not any(
|
|
f"[{profile}][{action}]" in power
|
|
for profile in ("AC", "Battery", "LowBattery")
|
|
for action in ("SuspendSession", "DimDisplay", "DPMSControl")
|
|
),
|
|
)
|
|
check(
|
|
"the profiles themselves are immutable, so none can be added back",
|
|
all(f"[{profile}][$i]" in power for profile in ("AC", "Battery", "LowBattery")),
|
|
)
|
|
check(
|
|
"a leftover user power profile is cleared at login",
|
|
"powermanagementprofilesrc" in reset,
|
|
)
|
|
check("logind takes no idle action", "IdleAction=ignore" in logind)
|
|
check("the power button is still ignored", "HandlePowerKey=ignore" in logind)
|
|
check(
|
|
"and a long press is still the firmware's business",
|
|
"HandlePowerKeyLongPress=ignore" in logind,
|
|
)
|
|
check(
|
|
"the package holds no account credentials",
|
|
"passwd" not in postinst and "chpasswd" not in postinst,
|
|
)
|
|
check(
|
|
"a classroom account created after another user still appears",
|
|
"MaximumUid=1999" in sddm,
|
|
)
|
|
|
|
print()
|
|
print(f"{passed} passed, {failed} failed")
|
|
sys.exit(1 if failed else 0)
|
|
PY
|