feat: the power button does nothing, and one chord opens a terminal
Two layers for the button, because one is not enough: while a session is running PowerDevil holds logind's handle-power-key inhibitor and acts on the button itself, so a logind drop-in alone would be overruled. logind covers the machine before the session starts, after it ends, and if PowerDevil is not running. Verified: after the drop-in is installed and logind reloaded, it reports ignore for the power, suspend, hibernate and lid keys, and the running session was not disturbed by the reload. Holding the button still cuts the power. That is firmware, below any operating system, and it is deliberately left alone as the only recovery a person standing at a wedged machine has. The maintenance terminal is Meta+Ctrl+Alt+Shift+K, declared in the package rather than seeded into a teacher's configuration, where an update could not reach it. KGlobalAccel read it and registered the component; invoking it opened a working shell. It needed a wrapper. The classroom's Kiosk policy sets shell_access=false and Konsole honours it — measured: with the classroom configuration in its cascade Konsole starts and says "Program to run not set." The restriction is worth keeping everywhere else, so it is lifted for this one process by leaving the classroom's configuration directory out of its cascade. It grants no privilege: the classroom user has no sudo rule, and that restriction was always a barrier in the interface rather than a privilege boundary. Konsole's own entry is shadowed and hidden from /usr/local/share, so nothing is patched and Konsole's package stays maintainable. Nothing is pinned to the panel; a task button appears only while a terminal is open, and closing it returns the bar to three launchers. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
699858a71e
commit
30a924d945
@@ -0,0 +1,20 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
# Konsole, hidden.
|
||||||
|
#
|
||||||
|
# Shadows the entry Konsole's own package installs, from
|
||||||
|
# /usr/local/share/applications, which XDG_DATA_DIRS searches first. The
|
||||||
|
# real entry is left exactly where Konsole put it, so the package stays
|
||||||
|
# maintainable and an upgrade does not fight this.
|
||||||
|
#
|
||||||
|
# The terminal is still reachable, by one chord, through
|
||||||
|
# ekklesia-admin-terminal.desktop. It is simply not something a teacher
|
||||||
|
# can come across.
|
||||||
|
Type=Application
|
||||||
|
Name=Konsole
|
||||||
|
Comment=Terminal
|
||||||
|
Exec=/usr/bin/konsole
|
||||||
|
Icon=utilities-terminal
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;TerminalEmulator;
|
||||||
|
NoDisplay=true
|
||||||
|
StartupWMClass=org.kde.konsole
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Ekklesia maintenance terminal
|
||||||
|
Comment=For the technical administrator, standing at the machine
|
||||||
|
Exec=/usr/local/bin/classroom-admin-terminal
|
||||||
|
Icon=utilities-terminal
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;
|
||||||
|
StartupNotify=true
|
||||||
|
StartupWMClass=org.kde.konsole
|
||||||
|
# The chord is deliberately awkward. KGlobalAccel reads this key from the
|
||||||
|
# entry itself, so the shortcut ships in the package rather than having to
|
||||||
|
# be seeded into a teacher's configuration where an update could not
|
||||||
|
# reach it.
|
||||||
|
X-KDE-Shortcuts=Meta+Ctrl+Alt+Shift+K
|
||||||
|
# Not in any application list. There is no menu in this classroom anyway;
|
||||||
|
# this says so out loud.
|
||||||
|
NoDisplay=true
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# The desktop half of "the power button does nothing".
|
||||||
|
#
|
||||||
|
# PowerDevil takes logind's handle-power-key inhibitor while a session is
|
||||||
|
# running, so the logind drop-in on its own would be overruled by
|
||||||
|
# whatever this says. 0 is no action.
|
||||||
|
#
|
||||||
|
# Immutable, so nothing in the session can put an action back.
|
||||||
|
#
|
||||||
|
# Every profile gets the same answer. A classroom machine has no battery,
|
||||||
|
# but a profile that exists and disagrees is worse than one that does not
|
||||||
|
# exist at all.
|
||||||
|
|
||||||
|
[AC][HandleButtonEvents][$i]
|
||||||
|
powerButtonAction=0
|
||||||
|
lidAction=0
|
||||||
|
triggerLidActionWhenExternalMonitorPresent=false
|
||||||
|
|
||||||
|
[Battery][HandleButtonEvents][$i]
|
||||||
|
powerButtonAction=0
|
||||||
|
lidAction=0
|
||||||
|
triggerLidActionWhenExternalMonitorPresent=false
|
||||||
|
|
||||||
|
[LowBattery][HandleButtonEvents][$i]
|
||||||
|
powerButtonAction=0
|
||||||
|
lidAction=0
|
||||||
|
triggerLidActionWhenExternalMonitorPresent=false
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# The classroom's power button does nothing.
|
||||||
|
#
|
||||||
|
# A teacher reaching behind a television for a cable, a child leaning on
|
||||||
|
# the tower, a volunteer tidying the room: none of those should be able to
|
||||||
|
# take the computer down in the middle of a Sunday morning. Recovering a
|
||||||
|
# room that has been switched off needs somebody who knows where the
|
||||||
|
# button is, which is exactly the person who is not there.
|
||||||
|
#
|
||||||
|
# This is one of two layers, and both are needed. While a graphical
|
||||||
|
# session is running, PowerDevil takes logind's handle-power-key
|
||||||
|
# inhibitor and acts on the button itself, so logind alone would be
|
||||||
|
# ignored — see the classroom's powermanagementprofilesrc for the other
|
||||||
|
# half. This layer is what covers the machine before the session starts,
|
||||||
|
# after it ends, and if PowerDevil is not running at all.
|
||||||
|
#
|
||||||
|
# Holding the button for several seconds still cuts the power. That is
|
||||||
|
# the firmware doing it, below any operating system, and it is left alone
|
||||||
|
# on purpose: it is the only recovery a person standing at a wedged
|
||||||
|
# machine has.
|
||||||
|
|
||||||
|
[Login]
|
||||||
|
HandlePowerKey=ignore
|
||||||
|
HandlePowerKeyLongPress=ignore
|
||||||
|
HandleSuspendKey=ignore
|
||||||
|
HandleSuspendKeyLongPress=ignore
|
||||||
|
HandleHibernateKey=ignore
|
||||||
|
HandleHibernateKeyLongPress=ignore
|
||||||
|
HandleRebootKey=ignore
|
||||||
|
HandleRebootKeyLongPress=ignore
|
||||||
|
# Classroom machines are desktops, but a laptop pressed into service in a
|
||||||
|
# room should not sleep when its lid is closed either.
|
||||||
|
HandleLidSwitch=ignore
|
||||||
|
HandleLidSwitchExternalPower=ignore
|
||||||
|
HandleLidSwitchDocked=ignore
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 165 KiB |
@@ -4,7 +4,7 @@ Section: misc
|
|||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: amd64
|
Architecture: amd64
|
||||||
Maintainer: Ekklesia
|
Maintainer: Ekklesia
|
||||||
Depends: plasma-desktop, plasma-workspace, kwin-wayland, plasma-nm, plasma-pa, plasma-widgets-addons, sddm, dbus-user-session, xwayland, fonts-dejavu, fonts-noto, network-manager, pipewire, pipewire-audio, wireplumber, pulseaudio-utils, cups, cups-client, cups-filters, foomatic-db-compressed-ppds, python3-gi, python3-gi-cairo, gir1.2-gtk-3.0, gir1.2-gtklayershell-0.1, libnotify-bin, curl, gnupg, ca-certificates, python3-paho-mqtt
|
Depends: plasma-desktop, plasma-workspace, kwin-wayland, plasma-nm, plasma-pa, plasma-widgets-addons, konsole, sddm, dbus-user-session, xwayland, fonts-dejavu, fonts-noto, network-manager, pipewire, pipewire-audio, wireplumber, pulseaudio-utils, cups, cups-client, cups-filters, foomatic-db-compressed-ppds, python3-gi, python3-gi-cairo, gir1.2-gtk-3.0, gir1.2-gtklayershell-0.1, libnotify-bin, curl, gnupg, ca-certificates, python3-paho-mqtt
|
||||||
Recommends: vlc
|
Recommends: vlc
|
||||||
Conflicts: plasma-welcome, plasma-discover, kdeconnect, kup-backup
|
Conflicts: plasma-welcome, plasma-discover, kdeconnect, kup-backup
|
||||||
Description: Ekklesia managed classroom environment
|
Description: Ekklesia managed classroom environment
|
||||||
@@ -14,6 +14,11 @@ Description: Ekklesia managed classroom environment
|
|||||||
left here is the church's own subject matter — Wonder Ink, the browser,
|
left here is the church's own subject matter — Wonder Ink, the browser,
|
||||||
the DVD player, the printer, the audio policy and the updater.
|
the DVD player, the printer, the audio policy and the updater.
|
||||||
.
|
.
|
||||||
|
konsole is here for one chord. It is the local recovery route for a
|
||||||
|
technical administrator standing at a machine when SSH is not
|
||||||
|
available; it is hidden from every application list and reachable only
|
||||||
|
by Meta+Ctrl+Alt+Shift+K.
|
||||||
|
.
|
||||||
plasma-widgets-addons is here for one widget: Quick Launch, which is
|
plasma-widgets-addons is here for one widget: Quick Launch, which is
|
||||||
the only stock way to put a single launcher on the panel that reads
|
the only stock way to put a single launcher on the panel that reads
|
||||||
/usr/share/applications live. The Icon widget copies the entry into the
|
/usr/share/applications live. The Icon widget copies the entry into the
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ chmod 0640 /etc/ekklesia-classroom/device.conf
|
|||||||
|
|
||||||
systemctl daemon-reload || true
|
systemctl daemon-reload || true
|
||||||
|
|
||||||
|
# The power button stops working immediately rather than at the next
|
||||||
|
# boot. Verified in the lab: logind re-reads its drop-ins on reload and
|
||||||
|
# reports ignore afterwards, and the running graphical session is not
|
||||||
|
# disturbed by it.
|
||||||
|
systemctl reload systemd-logind >/dev/null 2>&1 || true
|
||||||
|
|
||||||
# Autologin into the classroom, and back into it if the session ever ends.
|
# Autologin into the classroom, and back into it if the session ever ends.
|
||||||
systemctl enable sddm.service >/dev/null 2>&1 || true
|
systemctl enable sddm.service >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
|||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# The maintenance terminal, for the one person who is allowed one.
|
||||||
|
#
|
||||||
|
# Normal administration is over SSH. This exists for the case where
|
||||||
|
# somebody is standing at a classroom machine and SSH is not available,
|
||||||
|
# and it is reached only by a deliberately awkward chord that no teacher
|
||||||
|
# will find by accident.
|
||||||
|
#
|
||||||
|
# Why it is a wrapper rather than plain Konsole:
|
||||||
|
#
|
||||||
|
# the classroom's Kiosk policy sets shell_access=false, and Konsole
|
||||||
|
# honours it — measured: with the classroom configuration in its
|
||||||
|
# cascade it starts and prints "Program to run not set." rather than
|
||||||
|
# opening a shell. That restriction is worth keeping for everything
|
||||||
|
# else, so it is lifted for this one process by leaving the classroom's
|
||||||
|
# configuration directory out of its cascade. Nothing is patched and no
|
||||||
|
# binary is replaced; XDG_CONFIG_DIRS is the documented way to say
|
||||||
|
# which configuration an application sees.
|
||||||
|
#
|
||||||
|
# The terminal therefore looks like stock KDE rather than like the
|
||||||
|
# classroom, which is fair: it is not part of the classroom.
|
||||||
|
#
|
||||||
|
# This grants no privilege. The classroom user has no sudo rule, and
|
||||||
|
# anything that needs root still needs the administrator's own account
|
||||||
|
# and password. The Kiosk restriction was always a barrier in the
|
||||||
|
# interface, never a privilege boundary.
|
||||||
|
|
||||||
|
exec env XDG_CONFIG_DIRS=/etc/xdg konsole "$@"
|
||||||
@@ -119,6 +119,51 @@ for entry in sorted((root / "config/plasma/applications").glob("*.desktop")):
|
|||||||
f'"$ROOT/config/icons/${{icon}}.svg"' in build or icon in build)
|
f'"$ROOT/config/icons/${{icon}}.svg"' in build or icon in build)
|
||||||
check(f"{icon}.svg exists", (root / f"config/icons/{icon}.svg").is_file())
|
check(f"{icon}.svg exists", (root / f"config/icons/{icon}.svg").is_file())
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("4b4. the power button does nothing, at both layers")
|
||||||
|
logind = (root / "config/systemd/logind-ekklesia-classroom.conf").read_text()
|
||||||
|
power = (root / "config/plasma/xdg/powermanagementprofilesrc").read_text()
|
||||||
|
# PowerDevil holds logind's handle-power-key inhibitor while a session is
|
||||||
|
# running, so one layer without the other is a button that still works.
|
||||||
|
for key in ("HandlePowerKey=ignore", "HandleSuspendKey=ignore",
|
||||||
|
"HandleHibernateKey=ignore", "HandleLidSwitch=ignore"):
|
||||||
|
check(f"logind: {key}", key in logind)
|
||||||
|
check("PowerDevil: no action on the power button",
|
||||||
|
power.count("powerButtonAction=0") == 3)
|
||||||
|
check("PowerDevil: no action on a lid either",
|
||||||
|
power.count("lidAction=0") == 3)
|
||||||
|
check("and the session cannot put an action back",
|
||||||
|
power.count("[$i]") == 3)
|
||||||
|
check("the logind drop-in is packaged",
|
||||||
|
"/etc/systemd/logind.conf.d/ekklesia-classroom.conf" in build)
|
||||||
|
check("the PowerDevil profile is packaged",
|
||||||
|
"powermanagementprofilesrc" in build)
|
||||||
|
check("the long hold is left alone on purpose",
|
||||||
|
"firmware" in logind)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("4b5. the maintenance terminal is hidden and reachable")
|
||||||
|
admin = (root / "config/plasma/applications/ekklesia-admin-terminal.desktop").read_text()
|
||||||
|
konsole = (root / "config/plasma/applications-local/org.kde.konsole.desktop").read_text()
|
||||||
|
wrapper = (root / "scripts/classroom-admin-terminal").read_text()
|
||||||
|
check("the chord is the obscure one",
|
||||||
|
"X-KDE-Shortcuts=Meta+Ctrl+Alt+Shift+K" in admin)
|
||||||
|
check("its entry is in no application list", "NoDisplay=true" in admin)
|
||||||
|
check("Konsole's own entry is shadowed and hidden",
|
||||||
|
"NoDisplay=true" in konsole)
|
||||||
|
check("the shadow is in /usr/local/share, not on top of Konsole's file",
|
||||||
|
"$STAGE/usr/local/share/applications/" in build)
|
||||||
|
check("the terminal is launched outside the classroom's restrictions",
|
||||||
|
"XDG_CONFIG_DIRS=/etc/xdg" in wrapper)
|
||||||
|
check("and grants no privilege of its own",
|
||||||
|
"sudo" not in wrapper.replace("no sudo rule", ""))
|
||||||
|
check("the wrapper is packaged", "classroom-admin-terminal" in build)
|
||||||
|
check("Konsole is a dependency, so the route exists on every machine",
|
||||||
|
"konsole" in (root / "packaging/debian/control.template").read_text())
|
||||||
|
check("no terminal is pinned to the panel",
|
||||||
|
"konsole" not in (root / "config/plasma/lookandfeel/contents/layouts"
|
||||||
|
/ "org.kde.plasma.desktop-layout.js").read_text().lower())
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print("4c. the teacher cannot reach a command line")
|
print("4c. the teacher cannot reach a command line")
|
||||||
for restriction in ("run_command=false", "shell_access=false",
|
for restriction in ("run_command=false", "shell_access=false",
|
||||||
|
|||||||
+11
-1
@@ -207,7 +207,7 @@ install -Dm644 \
|
|||||||
# Not /etc/xdg: plasma-workspace already owns files by these names there.
|
# Not /etc/xdg: plasma-workspace already owns files by these names there.
|
||||||
# The classroom's directory goes in front of it in XDG_CONFIG_DIRS instead.
|
# The classroom's directory goes in front of it in XDG_CONFIG_DIRS instead.
|
||||||
for setting in kdeglobals kwinrc kwinrulesrc kscreenlockerrc ksmserverrc \
|
for setting in kdeglobals kwinrc kwinrulesrc kscreenlockerrc ksmserverrc \
|
||||||
krunnerrc plasmanotifyrc; do
|
krunnerrc plasmanotifyrc powermanagementprofilesrc; do
|
||||||
install -Dm644 \
|
install -Dm644 \
|
||||||
"$ROOT/config/plasma/xdg/${setting}" \
|
"$ROOT/config/plasma/xdg/${setting}" \
|
||||||
"$STAGE/etc/ekklesia-classroom/xdg/${setting}"
|
"$STAGE/etc/ekklesia-classroom/xdg/${setting}"
|
||||||
@@ -217,6 +217,12 @@ install -Dm644 \
|
|||||||
"$ROOT/config/environment/90-ekklesia-classroom.conf" \
|
"$ROOT/config/environment/90-ekklesia-classroom.conf" \
|
||||||
"$STAGE/etc/environment.d/90-ekklesia-classroom.conf"
|
"$STAGE/etc/environment.d/90-ekklesia-classroom.conf"
|
||||||
|
|
||||||
|
# The power button does nothing. Two layers, because while a session is
|
||||||
|
# running PowerDevil holds logind's inhibitor and would overrule it.
|
||||||
|
install -Dm644 \
|
||||||
|
"$ROOT/config/systemd/logind-ekklesia-classroom.conf" \
|
||||||
|
"$STAGE/etc/systemd/logind.conf.d/ekklesia-classroom.conf"
|
||||||
|
|
||||||
for scheme in EkklesiaDark EkklesiaLight; do
|
for scheme in EkklesiaDark EkklesiaLight; do
|
||||||
install -Dm644 \
|
install -Dm644 \
|
||||||
"$ROOT/config/plasma/colors/${scheme}.colors" \
|
"$ROOT/config/plasma/colors/${scheme}.colors" \
|
||||||
@@ -292,6 +298,10 @@ install -Dm755 \
|
|||||||
"$ROOT/scripts/classroom-desktop-layout" \
|
"$ROOT/scripts/classroom-desktop-layout" \
|
||||||
"$STAGE/usr/local/bin/classroom-desktop-layout"
|
"$STAGE/usr/local/bin/classroom-desktop-layout"
|
||||||
|
|
||||||
|
install -Dm755 \
|
||||||
|
"$ROOT/scripts/classroom-admin-terminal" \
|
||||||
|
"$STAGE/usr/local/bin/classroom-admin-terminal"
|
||||||
|
|
||||||
install -Dm755 \
|
install -Dm755 \
|
||||||
"$ROOT/scripts/classroom-desktop-reset" \
|
"$ROOT/scripts/classroom-desktop-reset" \
|
||||||
"$STAGE/usr/local/bin/classroom-desktop-reset"
|
"$STAGE/usr/local/bin/classroom-desktop-reset"
|
||||||
|
|||||||
@@ -92,3 +92,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get purge -y \
|
|||||||
# Quick Launch, for the one auxiliary button on the panel.
|
# Quick Launch, for the one auxiliary button on the panel.
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends plasma-widgets-addons \
|
RUN apt-get update && apt-get install -y --no-install-recommends plasma-widgets-addons \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Konsole, for the maintenance chord.
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends konsole \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ SRC="${1:-/srv/classroom}"
|
|||||||
# XDG_CONFIG_DIRS. plasma-workspace owns files by these names in /etc/xdg
|
# XDG_CONFIG_DIRS. plasma-workspace owns files by these names in /etc/xdg
|
||||||
# and two packages cannot own one path.
|
# and two packages cannot own one path.
|
||||||
for setting in kdeglobals kwinrc kwinrulesrc kscreenlockerrc ksmserverrc \
|
for setting in kdeglobals kwinrc kwinrulesrc kscreenlockerrc ksmserverrc \
|
||||||
krunnerrc plasmanotifyrc; do
|
krunnerrc plasmanotifyrc powermanagementprofilesrc; do
|
||||||
install -Dm644 "$SRC/config/plasma/xdg/$setting" \
|
install -Dm644 "$SRC/config/plasma/xdg/$setting" \
|
||||||
"/etc/ekklesia-classroom/xdg/$setting"
|
"/etc/ekklesia-classroom/xdg/$setting"
|
||||||
rm -f "/etc/xdg/$setting.ekklesia-old"
|
rm -f "/etc/xdg/$setting.ekklesia-old"
|
||||||
@@ -22,6 +22,9 @@ done
|
|||||||
install -Dm644 "$SRC/config/environment/90-ekklesia-classroom.conf" \
|
install -Dm644 "$SRC/config/environment/90-ekklesia-classroom.conf" \
|
||||||
/etc/environment.d/90-ekklesia-classroom.conf
|
/etc/environment.d/90-ekklesia-classroom.conf
|
||||||
|
|
||||||
|
install -Dm644 "$SRC/config/systemd/logind-ekklesia-classroom.conf" \
|
||||||
|
/etc/systemd/logind.conf.d/ekklesia-classroom.conf
|
||||||
|
|
||||||
install -Dm644 "$SRC/config/plasma/colors/EkklesiaDark.colors" \
|
install -Dm644 "$SRC/config/plasma/colors/EkklesiaDark.colors" \
|
||||||
/usr/share/color-schemes/EkklesiaDark.colors
|
/usr/share/color-schemes/EkklesiaDark.colors
|
||||||
install -Dm644 "$SRC/config/plasma/colors/EkklesiaLight.colors" \
|
install -Dm644 "$SRC/config/plasma/colors/EkklesiaLight.colors" \
|
||||||
|
|||||||
Reference in New Issue
Block a user