ec1e0d1cd7d2dffc400ca31770b812f097c56933
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
32ee0b544a |
fix: check the fonts by the files LibreOffice opens, not by fc-list
The validator asked fc-list whether Liberation Sans, Carlito and Caladea were installed. fc-list belongs to the fontconfig package, which nothing in this classroom pulls in, so the check could only ever fail — and it did, twenty-odd minutes into a release run, for a reason that had nothing to do with the fonts. They were all present the whole time. Asserted by path now, which is what LibreOffice actually looks for. Also depends on fonts-liberation rather than fonts-liberation2. The latter is a transitional package in Trixie that exists only to depend on the former, and a release should not name a package whose whole purpose is to go away. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
89b24acff0 |
fix: the release gate has time to install what the package now depends on
The validator installs the previous package and then the candidate, two full desktop installs in one container, on a 720s cap with 360s per install. 0.2.6 adds LibreOffice and a dozen font packages — 216MB and 186 packages on top of Plasma — and the run was killed during the PREVIOUS install, before it reached a single assertion. Raised generously rather than to just-enough. This is the gate that has to say "the package is wrong"; one that says "the machine was busy" instead teaches everybody to re-run it until it passes, which is how a gate stops being one. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
f2942a2946 |
docs: one manual, the pages a device has and a printable copy of all of them
Documentation becomes part of the release rather than a job afterwards.
docs/manual is now the only place teacher instructions are written, and
tools/build-manual produces both versions from it:
classroom-manual.html installed on every machine, showing only the
features THAT device has
classroom-manual.pdf the universal printed manual, every feature,
with a conditional sentence above each
optional page so it stays true in a room that
does not have them all
They cannot drift apart because neither is written by hand, and
build-release.sh builds and checks them on every release: a page that
declares a feature with no printed-if fails the build, because that is
exactly how the printed manual comes to state an optional feature as
though every room had it. So does a page naming a feature device.conf
does not have, and an optional feature with no page at all.
WHAT REACHES THE CLASSROOM ACCOUNT. Only a list of setting NAMES, and
only ones the package already defines as optional applications.
classroom-desktop-layout — already the one place that decides what a
device offers — writes them to /run/ekklesia-classroom/features.json,
and classroom-manual filters that against FEATURES before using it. No
values from device.conf, no broker password, no addresses. With no
manifest at all every page is shown, which is the printed manual and is
never wrong, only less specific.
Help is a Quick Launch button beside Parent Call, deliberately not in
the row with the applications: putting it there would move buttons
teachers reach for without looking. Every room gets it, because a
teacher who needs the manual is by definition one who does not know
where to look.
The PDF is written directly rather than converted. A manual is
headings, paragraphs, bullets, steps and one table; writing that with
the standard fonts is a couple of hundred lines and no dependency, so a
release can be built on any machine with Python. Pictures are embedded
in the digital manual as data URIs so it stays one file that works
wherever it is copied and with the network unplugged; the printed
manual names them instead, which is the one thing this builder does not
do and is noted where somebody would look for it.
Two defects found building it, both of which hung or corrupted the
output rather than failing: a paragraph opening with a bold word
matched the bullet guard but not the bullet rule, so nothing consumed
it and the build spun; and a list item wrapped across two source lines
ended the list, so a three-step procedure printed as three steps all
numbered 1.
Also adds the 0.2.6 physical checklist to docs/testing.md and the
release validator's checks for the Wi-Fi regression, the conversion
pipeline on a real Debian, the fonts, the manual and the dock order.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
35e3bdcbbd |
fix: give a Wi-Fi the installer set up to NetworkManager, by hand
The Bible study laptop had working internet and a panel that said there
was no connection. Both were true, and one thing caused both.
Measured on the machine:
nmcli device status wlp2s0 wifi unmanaged
nmcli networking connectivity none while curl fetched fine
nmcli device wifi list nothing, with the radio enabled
/run/network/ifstate wlp2s0=wlp2s0
a second wpa_supplicant, -i wlp2s0, with no -c config file
dhcpcd holding the lease
/etc/network/interfaces 0600 root:root
Debian's installer had been given the Wi-Fi, so it wrote it into
/etc/network/interfaces and ifupdown brings it up. NetworkManager ships
with plugins=ifupdown and [ifupdown] managed=false, so an interface named
in that file is UNMANAGED — which is why the applet had nothing to report
and why scanning found nothing. An unmanaged device is not a radio
NetworkManager will scan with. rfkill was ruled out separately: the radio
is enabled.
managed=false stays; it is what keeps a tower's static Ethernet where an
administrator put it. Only a wireless interface is moved.
classroom-wifi grows --check, --adopt (with --dry-run) and --rollback.
--adopt builds a NetworkManager SYSTEM connection from the SSID and key
already in that root-only file, so the password never leaves the machine
and never reaches a human or a log; it lands in a 0600 root file with
psk-flags=0, the same shape 0.2.4 established, and the polkit rules
protecting it are untouched.
Deliberately NOT run by the upgrade. It moves a machine's only link
between managers and the machine it could strand is a laptop in a locked
room, so classroom-migrate only looks and prints what it found. An
administrator runs it over SSH while watching.
Two things this learned from testing the rollback rather than from
reading the code, and both are why the rollback is worth testing:
* ifdown was missing in the lab, subprocess raised FileNotFoundError,
and the exception escaped AFTER the stanza was commented out. No
rollback ran. Every hand-over command now goes through a runner that
tolerates a missing binary, and the whole hand-over is inside a guard
that restores the file on any failure at all.
* it now refuses to start when ifdown or ifup are absent, because
without them the hand-over would be a one-way door.
Success also requires the interface itself to be carrying the connection,
not merely that the machine is online — a machine with a second link
would otherwise report a failed migration as a success.
Verified in the lab against real NetworkManager with a real ifupdown
file: --check and --dry-run leave it byte-identical, a failed --adopt
rolls back byte-identically, and --rollback restores a half-migrated
machine byte-identically and exits 0. The one thing no lab can do is the
hand-over succeeding, which needs the laptop's radio.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
bf7b959893 |
feat: the room may use the church Wi-Fi and may not read its password
These are shared computers in rooms full of people, and in a stock Plasma
desktop "use the network" and "read the network's password" are much
closer together than they look.
Measured on Debian 13 with NetworkManager 1.52 and Plasma 6.3:
* org.freedesktop.NetworkManager.settings.modify.own ships as "yes" for
an active session — no prompt, no administrator. It covers any
connection whose connection.permissions names that user, and a Wi-Fi
network added from the desktop is created exactly that way. So a
password a teacher types in is readable by the next person to sit
down.
* the applet offers "Show Network's QR Code" on every saved wireless
connection, and the QR code contains the pre-shared key.
* that action, and the "Configure…" beside it, are contextual actions in
ConnectionItem.qml and do NOT consult
authorizeControlModule("kcm_networkmanagement"). The Kiosk key gates
the toolbar button only. Hiding buttons was never going to be the
answer.
Every one of those paths ends at NetworkManager's GetSecrets, so that is
where this is answered. A polkit rule refuses settings.modify.own,
settings.modify.system, both wifi.share actions and the machine-wide
settings, for the classroom user by name.
Deliberately NOT refused: network-control, wifi.scan and the
enable/disable actions. None returns a secret, and they are what
connecting and seeing a network are made of. Automatic reconnection does
not go through polkit at all — NetworkManager does it as root — so nothing
here can stop the room coming back on the network after a reboot, which is
the property that matters most on a Sunday.
An administrator's account and root are untouched, and the override route
is the same one the sleep rule has.
classroom-wifi is the other half, and it is a requirement rather than a
convenience: it provisions the church Wi-Fi as a SYSTEM connection, with
no connection.permissions, psk-flags=0 and a 0600 root:root file. A
connection the machine owns is one nobody in the room can read. Verified
against real NetworkManager in the lab — the classroom account gets
"Permission denied" on the file, "--" from nmcli and "Insufficient
privileges" from GetSecrets, while autoconnect stays on.
There is deliberately no teacher-facing join dialog. Anything that lets
the room add a connection lets the room own it, and an owned connection is
a readable one; docs/networking.md says so rather than leaving it to be
rediscovered.
The rule is plain JavaScript, so the test evaluates it with polkit's
objects stubbed and asserts both halves: which actions come back NO, and
which are left alone. What it cannot test is the last mile — polkit
resolves a subject through logind and a container has no graphical seat
session, so every check there is denied for reasons unrelated to this
rule. That one is a physical check, and it is written down as one.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
27cd64a668 |
fix: the room asks for its output again, not only once at boot
River Kids came up silent on stable 0.2.2. The decision this policy makes
was right and was never asked for.
classroom-audio applied its policy once at startup and then only when a
PipeWire event arrived. pactl talks to PipeWire's PulseAudio server, and
the unit was ordered after pipewire and wireplumber but not after that.
At boot it therefore raced the server and lost: the single startup apply
failed with "Connection refused", the subscription attached cleanly a few
seconds later, and from that moment the daemon waited for an event that
had already happened. systemd said active, the subscription was live, and
the policy had never run at all.
That is why the card was found on output:analog-stereo+input:analog-stereo
with its HDMI port available. That profile is WirePlumber's own pick, not
one this policy would ever produce, which is the evidence it never ran.
Reproduced in the lab against real PipeWire 1.4.2 and WirePlumber 0.5.8 by
starting the daemon with pipewire-pulse stopped and then starting it: the
old daemon sits idle forever, the new one converges.
The shape is now: wait for the server, apply, subscribe, apply again the
moment the subscription attaches, and coalesce each burst of events into
one decision. Applying on attach is the fix for the race. While the room
has not got the output it would prefer it also looks again on a backing
off timer capped at a minute, so a television switched on hours later is
picked up whether or not ALSA's jack change ever reaches us as an event.
Once the room has what it wants it stops looking entirely, and a machine
with no digital output never looks at all.
Also here, all of it found by measuring the real server rather than
reading the code:
* sink lookup matched on fields a sink does not have. Two of its three
conditions could never be true; the one that worked returned whichever
sink of the card came first rather than the one for the chosen port.
* volume and mute are no longer touched. The old code unmuted and forced
70% on every output change, so every television power cycle reset a
volume the teacher had set and could unmute an output muted on
purpose. WirePlumber already remembers volume per device. A machine
this policy has never configured still gets 70% once.
* the profile chosen keeps the card's input, which is the difference
between output:hdmi-stereo and the output:hdmi-stereo+input:analog-stereo
an administrator reaches for by hand.
* a card profile is only set when it is not already in place, and only
so many times, so a sink that never appears cannot become a switching
loop.
* a machine with no sound server at all says so once and backs off,
rather than filling the journal. pactl subscribe's own stderr is
dropped because saying it is this daemon's job, and a subscription is
only treated as healthy once it has lasted, since pactl starts
successfully and then fails to connect.
* the fixtures described a machine nobody has. Real pactl puts no
"direction" on a port at all and says "availability unknown" rather
than nothing, so a test could not have caught this.
Existing streams are moved explicitly. Measured both ways on the real
stack: changing the default sink does move a stream that never asked for
a particular sink, but a stream pinned to a device stays where it is with
no policy running, and this moves it back without the application being
restarted.
tests/test-classroom-audio-daemon.sh drives the real daemon through the
boot race, a television switched on with and without an event announcing
it, a power cycle, a set that never comes on, and a room already correct.
It uses a pactl mock because no loopback or dummy sound driver has an HDMI
port, so the one thing a lab cannot prove is that the real jack change
arrives; docs/testing.md carries the physical procedure for that and says
not to take a passing build as evidence of it.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
8db5e8dd9f |
release: the Plasma classroom becomes stable 0.2.0
0.1.50 was taken to the church and the things a container cannot answer were answered on the hardware: boot, autologin, the panel after a reboot, panel and browser scaling, Wonder Ink, Parent Call opening in the corner and reaching the sanctuary with a typed name, readable and silent notifications, HDMI audio and the volume keys. The promotion is a fast-forward. The experiment was cut from the published stable source and never diverged from it, so master reaches this work without a merge and there is one history. One thing changed for the release. The channel rules turn around: classroom-v2 and hyprland are the obsolete channels now and a room on either comes home to stable, while canary — where the next development release goes — and any channel somebody named by hand stay where they were put. A fresh installation has always defaulted to stable. Nothing else moved. This is the 0.1.50 that was verified, with a version number and a channel rule. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
bd55c5abd9 |
fix: let the migration hand device.conf to the one place defaults live
classroom-migrate knew how to remove a setting, move a channel and point a room at the broker, and knew nothing about which settings the classroom has gained since a room was installed. That list is in classroom_config.py and it stays there: the migration now hands the file to ensure_device_config when its own work is done, rather than growing a second copy of it. It matters for the command rather than the package. postinst already called the shared helper before running the migration, so an install converged; a technician running `sudo classroom-migrate` by hand on an old machine got a file that was still short of LIVE_ENABLED, AUDIO_OUTPUT and UI_SCALE. Both paths end in the same place now, and the release gate runs the command by hand on an already-converged machine to prove it. The validator's own expectation was the release-blocking failure, and it was not what it looked like: it stops at CHANNEL=canary, before it ever reaches the LIVE_ENABLED count. canary is the sway line, and moving a migrated room off it is deliberate — a Plasma room left there is handed the old desktop back by the next release published to it. The fixture now expects the move, checks that every setting this release has arrived exactly once, and checks it again after a reinstall. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
f65c814eff |
fix: put the popup close button in the dock, and clear the printer location
Three things, none of them redesigns. The close affordance for a secondary Chrome window was a separate layer-shell surface anchored to that window's corner, repositioned by a five-per-second poll. Any position change tore the surface down and built another, so on real hardware it chased the window and flickered. Removed entirely: the GTK button process, the geometry maths that placed it, the --close-button mode that spawned it, and the poll loop that drove it. The window helper no longer imports GTK at all. The behaviour underneath it was right and is kept. The first Chrome top-level is the classroom browser, tiled and borderless; later ones float centred at about three quarters of the output. Creation order is the only thing that can tell them apart — confirmed on a real session, where every Chrome top-level shares app_id, shell, PID and a null transient_for, and sway exposes no window properties for Wayland toplevels at all. Titles are still not used. It now follows sway's own window events instead of polling, so dragging a popup costs nothing. Close moved into the dock, which was already on screen and does not move, so it cannot chase anything. It appears only while a secondary window is focused and runs kill on that one container. The helper publishes which window that is; the dock watches the file rather than polling. A stock sway title bar has no close button, so restoring the border would not have solved this either. Two bugs found by looking rather than by testing state. Chrome renegotiates the size of a popup opened with explicit dimensions after the resize returns, which left windows centred for a width they no longer had; placement now settles and repeats once, at creation only, so dragging is still free. And the dock's Close button came up empty because show_all does not recurse into a no-show-all widget. The printer's Location is now blank. CUPS prints it under the queue name in every dialog, so "Ekklesia Classroom" was a second line the teacher had to read past; the identity is one word. The already-configured check includes Location, so a machine set up before this is corrected rather than left as close enough. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
7f66228b9e | Release 0.1.37 deployment prep |