fix: make an existing classroom converge into the Plasma appliance

A real room upgraded to 0.1.45 and came up as stock Debian Plasma with
the old Waybar across the top, mako still catching notifications, and a
lock screen on an account that has no password.

The cause was one line, and it was in the lab: the harness handed the
session under test XDG_CONFIG_DIRS by hand, so every result it had ever
produced was a result about a machine somebody had already configured.
On a machine SDDM starts the session through PAM, PAM does not read
/etc/environment.d, and Debian's own plasma-workspace env.sh then set the
variable from scratch — so the classroom's settings directory was not in
the cascade at all. No layout, no lock policy, no Kiosk restrictions.

The settings now arrive the way Plasma delivers them, through a file in
/etc/xdg/plasma-workspace/env. Around that: the look and feel is
immutable on its own key so a stale profile cannot answer for it; the
palette is composed into the package, because naming a colour scheme does
not produce one and the dark bar had been coming from a technician's
hand; the desktop reset converges another desktop's appearance state and
keeps the room's own; and classroom-migrate stops, deactivates and masks
the old shell, including the D-Bus activation that brought mako back
while the panel was down — found only by killing the panel on a migrated
machine.

device.conf loses COMPOSITOR and comes off the hyprland channel, which
would otherwise have handed the room the experiment back.

The lab stops flattering the package: it restarts the machine, lets SDDM
actually autologin, and asks a fresh install and a machine upgraded from
the Hyprland package the same sixty questions. Both answer identically,
twice, and the panel still comes back from eight SIGKILLs.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
ekklesia
2026-09-10 23:06:56 -05:00
co-authored by Claude Opus 5
parent 6050144e5d
commit 926668dc9b
29 changed files with 1588 additions and 40 deletions
+199 -11
View File
@@ -937,6 +937,165 @@ ekklesia-classroom-tv.service small, supervised, ours
adapters arrive today; the commands get measured before anything is
built on them.
## The upgrade that failed
The clean-install result was real and the migration was not. A classroom
machine that had been through the sway build and then the Hyprland
experiment took the 0.1.45 package and came up as **stock Debian Plasma**:
kickoff, a pager, a full system tray, the Debian wallpaper, the old Waybar
still across the top, mako still catching notifications — and then a lock
screen, on an account that has no password. Somebody had to give the
appliance account a password over SSH to get back in.
None of that was visible here, because the lab had been answering an
easier question than the one a room asks.
### Reproducing it
One line of the harness:
```
tools/lab/lab plasma-up: -e XDG_CONFIG_DIRS=/etc/ekklesia-classroom/xdg:/etc/xdg
```
The lab had been handing the session under test the very setting whose
delivery was the thing in doubt. Removing it and starting the session the
way a machine does reproduced the failure exactly: 23 applets, kickoff,
pager, system tray, Debian wallpaper — `docs/screenshots/migration-before.png`
is that screen, and it is the teacher's screen.
### Root causes
**1. The classroom's settings never reached the session.** Everything the
room is — the panel layout, the lock screen that must never appear, the
idle behaviour, every Kiosk restriction — lives in
`/etc/ekklesia-classroom/xdg`, and reaches Plasma only if that directory
is in `XDG_CONFIG_DIRS`. It was set in `/etc/environment.d`, which
systemd's **user manager** reads. SDDM starts the session through PAM, and
PAM does not read `/etc/environment.d`. Debian's own
`/etc/xdg/plasma-workspace/env/env.sh` then set the variable from scratch,
because it was empty, and `startplasma` pushed its own environment back
into the user manager — so the value was not merely bypassed, it was
overwritten for every user service too. Measured in the session that came
up:
```
XDG_CONFIG_DIRS=/home/classroom/.config/kdedefaults:/etc/xdg:/usr/share/desktop-base/kf5-settings
LookAndFeelPackage = org.debian.desktop
Autolock = (unset, so on)
shell_access = (unset, so allowed)
```
The stock panel, the lock screen and the absence of every Kiosk
restriction are all that one line.
**2. Deleting the panel configuration made it worse.** The desktop reset
removes `plasma-org.kde.plasma.desktop-appletsrc` before the shell starts
so that the bar always comes from the package. With the look and feel
unresolvable, Plasma rebuilt the **Debian** default layout instead. The
reset was correct and was pointed at the wrong answer.
**3. Naming a colour scheme does not produce one.** `ColorScheme=EkklesiaDark`
is a label; KConfig reads the actual colours from `[Colors:*]` groups in
kdeglobals, and nothing was putting them there except a technician having
run `classroom-theme` once by hand. The clean-install screenshots looked
right for a reason the package did not own. A machine with no user
profile got Breeze light — confirmed here the moment a profile was
cleared.
**4. A stale user profile outranks the package.** `~/.config/kdeglobals`
sits ahead of every system directory, and `~/.config/kdedefaults` ahead of
ours. A migrated machine carried a Breeze-light palette in both.
**5. The old shell was still being started.** `waybar.service` was
activated through `graphical-session.target.wants` — and, found only by
running the crash-recovery suite on a migrated machine, **mako came back
through D-Bus**: the panel was killed, something sent a notification while
nothing owned `org.freedesktop.Notifications`, and D-Bus started mako from
the file `mako-notifier` ships. Deactivating the systemd unit would not
have been enough.
And one more that had not bitten yet: `device.conf` still said
`CHANNEL=hyprland`, so the next release published on that channel would
have handed the room the Hyprland experiment back.
### The fix
| | |
| --- | --- |
| `config/plasma/env/zz-ekklesia-classroom.sh` | new. `/etc/xdg/plasma-workspace/env/` is sourced by `startplasma` for every session; the name sorts after Debian's `env.sh`, so it appends rather than replaces, and it is a no-op if the directory is already there. This is the root fix. |
| `config/plasma/xdg/kdeglobals` | `LookAndFeelPackage[$i]` — per-entry immutability, so no leftover profile and no stray write can answer that question differently. The palette stays writable, because `classroom-theme` may change it. |
| `tools/build-kdeglobals` | new. Composes the shipped kdeglobals from the session policy **and** `EkklesiaDark.colors`, so the room is dark because the package says so. One source of truth: it composes, it does not duplicate. |
| `scripts/classroom-desktop-reset` | asserts the cascade and repairs it for the shell about to start if it is wrong; removes another desktop's look and feel defaults and a foreign palette; keeps an Ekklesia one; clears a user power profile. |
| `scripts/classroom-migrate` | new. Stops, deactivates and masks the old shell's units, removes activation from all three directories it can live in, removes hand-written unit files that no Debian package owns, clears old autostart entries, removes the login shell that used to start sway, and migrates `device.conf`. Run from `postinst` on every install. |
| `config/dbus/fr.emersion.mako.service` | new. Shadows mako's D-Bus activation from `/usr/local/share`, where the session bus looks first. `Exec=/bin/false`, which is what a machine with no notification daemon does anyway. |
| `config/plasma/xdg/powermanagementprofilesrc` | profiles immutable at the profile level, not just on the button subgroups: PowerDevil loads an idle action when it finds a group for it, so a writable profile is one a leftover user file can add a suspend to. |
| `config/systemd/logind-ekklesia-classroom.conf` | `IdleAction=ignore` as well. |
| `config/sddm/ekklesia-classroom.conf` | uid range 1000–1999. The first migrated room's classroom account was uid 1001 and would not have appeared in the greeter at all. |
### What device.conf means now
`COMPOSITOR` and `HYPR_APPEARANCE` are removed on upgrade. There is one
desktop stack and nothing left for them to choose; a stale value is an
instruction this package cannot carry out.
`CHANNEL=hyprland` becomes `CHANNEL=classroom-v2`. Not `stable` and not
`canary`: both are the sway line, and a release published there would
convert a v2 machine back. **That channel is not published yet**, so a
migrated machine takes no updates until it is — which is the honest state
of an unpublished experiment, and better than being quietly reverted.
Everything else in the file, the broker password included, is rewritten
line by line and byte-for-byte.
### The tests that would have caught it
- `tools/lab/lab plasma-up` no longer sets `XDG_CONFIG_DIRS`. The package
has to put it there, the way it has to on a machine.
- `tools/lab/lab reboot` — a real restart of the machine under test: new
PID 1, new user manager, every drop-in and generator read again. **SDDM
autologin genuinely runs here**: it authenticates the classroom through
PAM and launches the Plasma session, and then has no display hardware to
draw on. Both scenarios now go through it.
- `tools/lab/converged-check` — 60 properties of a converged machine,
asked of the running system and never of the repository. Both scenarios
run the same list, because a fresh install and a migrated one have to be
indistinguishable from a teacher's chair.
- `tools/lab/dirty-upgrade` — the Hyprland package actually installed,
with sway, waybar and mako behind it; Waybar activated in all three
directories; an autostart entry; a device.conf asking for a compositor
that is gone; a Breeze-light profile in both places that outrank the
package; a stock panel already built; and a user power profile that
would suspend the room. Then the new package, a reboot, and the
questions.
- `tests/test-migration.sh` — 41 source-level checks, one per reason the
failure happened.
### Results
| | |
| --- | --- |
| dirty upgrade → converged | 60 / 60 |
| installed a second time over itself | 60 / 60, device.conf unchanged |
| clean install → converged | 60 / 60 |
| panel killed eight times | back every time, 2.2–7.4s |
| compositor killed twice | 0.1–0.4s |
| audio policy killed three times | active every time |
| the room's light theme across a logout | survived |
| every test script | 11 / 11 |
`docs/screenshots/migration-after.png` and `v2-from-package.png` are the
migrated machine and the fresh one. They are the same desktop.
### One regression this pass made and caught
The first version of the reset deleted `~/.config/kdedefaults`
unconditionally. Plasma treats that directory as the record of which look
and feel is in force, so deleting it made Plasma re-apply the look and
feel at every login — which throws away the room's chosen palette.
Measured: `classroom-theme light`, log out, and the bar came back dark.
The rule is now whose the defaults are: the classroom's are kept, another
desktop's are removed.
## Unresolved risks
In the order I would worry about them.
@@ -960,10 +1119,13 @@ In the order I would worry about them.
tries disturbs the session enough to close the card. The name was
typed through a lab-only `--child` argument. **Type a name on the
bench before a Sunday.**
4. **SDDM is configured and untested.** It cannot be exercised in a
container, and it is the thing that starts the session. If the config is
wrong the room boots to nothing. This is the first thing to check on a
VM or a bench machine, and it is why the checklist below starts there.
4. **SDDM has never put a picture on a screen.** More is known than
before: in the lab it really does autologin, authenticating the
classroom through PAM and launching the Plasma session — that is now
one of the checks. What it cannot do in a container is find a display
to draw on, so everything from the compositor's first frame onwards is
still a bench test. It remains the first thing to check on a VM or a
bench machine.
5. **No real audio hardware has been through the policy.** The decision is
well tested; applying it is not. A wrong `pactl set-card-profile` on
real hardware is a silent room.
@@ -988,7 +1150,17 @@ In the order I would worry about them.
registered from the packaged entry and its action opens a shell —
verified over D-Bus — but key injection cannot reach the nested
session, so `Meta+Ctrl+Alt+Shift+K` itself is unpressed.
13. **Ctrl+Alt+T is still registered.** Konsole's own entry declares it
13. **Idle behaviour is configured and unobserved.** No suspend, no dim,
no blank and no lock are all now in the package, immutable, and read
back correctly from a migrated session. Nothing in a container goes
idle, so none of it has been watched not happening. **Leave a bench
machine alone for an hour.**
14. **The v2 channel does not exist yet.** A migrated machine is moved to
`CHANNEL=classroom-v2` so that a stable or Hyprland release cannot
take it back, and nothing is published there. Until it is, a v2
machine takes no updates at all. That is deliberate; it is also a
thing to remember before leaving one in a room.
15. **Ctrl+Alt+T is still registered.** Konsole's own entry declares it
and the registration survives being shadowed. It is inert, because
Konsole refuses to open a shell under the classroom's
`shell_access=false` — measured — but it rests on that one
@@ -1078,6 +1250,16 @@ The existing Debian classroom test VM should be kept.
package, hidden from every application list, granting no privilege.
* **Long-Escape investigated and recommended against**, with the two
clean alternatives written down.
* **The migration failure found on a real room and fixed in the package**:
the settings directory now reaches the session the way Plasma delivers
it, the look and feel cannot be answered for by a stale profile, the
palette is in the package rather than in somebody's home directory, and
the old shell is stopped, deactivated, masked and — the one that was
only found by killing the panel — unable to come back through D-Bus.
* **The lab stopped flattering the package**: it no longer sets the
session's configuration path by hand, it restarts the machine, and it
asks a fresh install and a machine upgraded from the Hyprland
experiment the same sixty questions. Both answer them identically.
**Next**, in order:
@@ -1090,11 +1272,17 @@ The existing Debian classroom test VM should be kept.
disc. These three are the only things left that a container cannot
answer, and each of them is a Sunday-morning path. Add the power
button and the maintenance chord to that list.
2. Publish `0.1.45~plasma1` to a channel of its own, the way the Hyprland
milestone did, so a stable or canary room can never be offered it.
3. Bench-test SDDM: autologin, and that ending the session brings the
classroom back rather than a shell.
4. When the CEC adapters have been exercised, replace the computer volume
2. Publish the v2 channel. `classroom-migrate` already moves a migrated
room onto `CHANNEL=classroom-v2`, and nothing serves that manifest
yet, so those machines are taking no updates at all. This is now the
first packaging job rather than a tidy-up.
3. Take the upgraded VM through the same list by hand:
`tools/lab/converged-check` is the shape of it, and the things a
container cannot answer are the lock screen never appearing after an
hour alone, and the machine still being there on Monday.
4. Bench-test SDDM on hardware: autologin all the way to a picture, and
that ending the session brings the classroom back rather than a shell.
5. When the CEC adapters have been exercised, replace the computer volume
applet with a television control, behind the service boundary in
*Television control, and where CEC will fit*.
5. Live View, when the rest is quiet.
6. Live View, when the rest is quiet.