#!/bin/bash # Is this machine the classroom? # # One list of properties, run against whatever the lab is currently # holding. A fresh install and a machine upgraded from the old sway or # Hyprland shell have to answer it identically — that is the whole point # of the migration work, and the reason this is a file rather than a # paragraph in a report. # # tools/lab/converged-check the properties # tools/lab/converged-check --restart and again after a session restart # # Every check reads the running machine. Nothing here consults the # repository, so a check cannot pass by agreeing with the source it came # from. set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" LAB="$ROOT/tools/lab/lab" NAME="${LAB_NAME:-classroom-v2-lab}" PASS=0 FAIL=0 in_lab() { docker exec "$NAME" bash -c "$1" 2>/dev/null; } # As the session itself reads configuration: the cascade of the running # shell, not one this script made up. session_config() { local dirs dirs=$(in_lab '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 HOME=/home/classroom \ -e LANG=C.UTF-8 -e "XDG_CONFIG_DIRS=$dirs" "$NAME" \ kreadconfig6 "$@" 2>/dev/null | tr -d '\r' } running() { in_lab "pgrep -x $1 | wc -l"; } # Which process is actually answering org.freedesktop.Notifications. On a # machine that still has mako installed this is the question that matters: # the name is taken by whoever gets there first. notification_owner() { local pid pid=$(in_lab 'XDG_RUNTIME_DIR=/run/user/1000 runuser -u classroom -- \ busctl --user --no-pager call org.freedesktop.DBus /org/freedesktop/DBus \ org.freedesktop.DBus GetConnectionUnixProcessID s org.freedesktop.Notifications' \ | awk '{print $2}') [[ -n "$pid" ]] && in_lab "cat /proc/$pid/comm" } # logind answers for the running configuration, not for a file on disk. logind_property() { in_lab "busctl get-property org.freedesktop.login1 /org/freedesktop/login1 \ org.freedesktop.login1.Manager $1" | cut -d'"' -f2 } APPLETS=/home/classroom/.config/plasma-org.kde.plasma.desktop-appletsrc check() { # check DESCRIPTION EXPECTED ACTUAL if [[ "$2" == "$3" ]]; then printf ' \033[32mPASS\033[0m %-52s %s\n' "$1" "$3" PASS=$((PASS + 1)) else printf ' \033[31mFAIL\033[0m %-52s got [%s] want [%s]\n' "$1" "$3" "$2" FAIL=$((FAIL + 1)) fi } section() { printf '\n%s\n' "$1"; } run_checks() { section "the session" check "kwin_wayland running" 1 "$(running kwin_wayland)" check "plasmashell running" 1 "$(running plasmashell)" section "the old shell is gone" check "waybar not running" 0 "$(running waybar)" check "mako not running" 0 "$(running mako)" # The bracket keeps the pattern from matching the command that carries it. check "classroom dock not running" 0 "$(in_lab 'pgrep -f "[c]lassroom-dock" | wc -l')" # The lab's own outer compositor is sway, so the question is whether any # sway is running that is not the harness's. check "no sway but the lab harness's" 0 \ "$(in_lab 'pgrep -x sway | while read -r p; do tr "\0" " " < /proc/$p/cmdline | grep -q outer-sway.conf || echo x; done | grep -c x || true')" check "Hyprland not running" 0 "$(running Hyprland)" check "waybar activation removed" 0 \ "$(in_lab 'find /etc/systemd/user /etc/xdg/systemd/user /home/classroom/.config/systemd/user -name "waybar.service" -path "*.wants/*" 2>/dev/null | wc -l')" check "mako activation removed" 0 \ "$(in_lab 'find /etc/systemd/user /etc/xdg/systemd/user /home/classroom/.config/systemd/user -name "mako.service" -path "*.wants/*" 2>/dev/null | wc -l')" check "waybar masked" masked "$(in_lab 'XDG_RUNTIME_DIR=/run/user/1000 runuser -u classroom -- systemctl --user is-enabled waybar.service || true')" check "mako masked" masked "$(in_lab 'XDG_RUNTIME_DIR=/run/user/1000 runuser -u classroom -- systemctl --user is-enabled mako.service || true')" check "no login shell starting a compositor" 0 \ "$(in_lab 'grep -lE "exec (sway|Hyprland|start-hyprland|classroom-session)" /home/classroom/.bash_profile 2>/dev/null | wc -l')" check "no tty1 autologin drop-in" 0 \ "$(in_lab 'ls /etc/systemd/system/getty@tty1.service.d/autologin.conf 2>/dev/null | wc -l')" check "no autostart entry for the old shell" 0 \ "$(in_lab 'grep -lE "^Exec=.*(waybar|mako|classroom-dock)" /home/classroom/.config/autostart/*.desktop 2>/dev/null | wc -l')" section "device.conf" check "COMPOSITOR removed" 0 "$(in_lab 'grep -c "^COMPOSITOR=" /etc/ekklesia-classroom/device.conf || true')" check "not on the hyprland channel" 0 "$(in_lab 'grep -c "^CHANNEL=hyprland" /etc/ekklesia-classroom/device.conf || true')" section "the configuration cascade" check "classroom settings in the session's cascade" 1 \ "$(in_lab 'p=$(pgrep -x plasmashell | head -1); tr "\0" "\n" < /proc/$p/environ | grep -c "^XDG_CONFIG_DIRS=.*ekklesia-classroom/xdg"')" check "look and feel authoritative" org.ekklesia.classroom.desktop \ "$(session_config --file kdeglobals --group KDE --key LookAndFeelPackage)" check "and immutable" 1 \ "$(in_lab 'grep -c "^LookAndFeelPackage\[\$i\]=" /etc/ekklesia-classroom/xdg/kdeglobals')" check "the palette is the packaged one" 20,28,32 \ "$(session_config --file kdeglobals --group 'Colors:Window' --key BackgroundNormal)" section "the panel" check "applet count" 10 "$(in_lab "grep -c '^plugin=' $APPLETS")" check "Parent Call launcher" 1 \ "$(in_lab "grep -c 'ekklesia-parent-call.desktop' $APPLETS")" check "quick launch, one widget" 1 \ "$(in_lab "grep -c '^plugin=org.kde.plasma.quicklaunch' $APPLETS")" check "the applications are the room's" 1 \ "$(in_lab "grep -c 'launchers=applications:ekklesia-wonderink.desktop,applications:google-chrome.desktop' $APPLETS")" check "network, volume, notifications, clock" 4 \ "$(in_lab "grep -cE '^plugin=org.kde.plasma.(networkmanagement|volume|notifications|digitalclock)$' $APPLETS")" check "no kickoff" 0 "$(in_lab "grep -c 'plugin=org.kde.plasma.kickoff' $APPLETS")" check "no pager" 0 "$(in_lab "grep -c 'plugin=org.kde.plasma.pager' $APPLETS")" check "no system tray" 0 "$(in_lab "grep -c 'systemtray' $APPLETS")" check "no discover or system settings" 0 \ "$(in_lab "grep -cE 'discover.desktop|systemsettings.desktop' $APPLETS")" check "the packaged wallpaper" 1 \ "$(in_lab "grep -c 'Image=file:///usr/share/backgrounds/ekklesia/classroom-calm.png' $APPLETS")" section "it will not strand a teacher" check "no automatic lock" false "$(session_config --file kscreenlockerrc --group Daemon --key Autolock)" check "no lock on resume" false "$(session_config --file kscreenlockerrc --group Daemon --key LockOnResume)" check "the lock action is forbidden" false \ "$(session_config --file kdeglobals --group 'KDE Action Restrictions' --key action/lock_screen)" check "power button does nothing" 0 \ "$(session_config --file powermanagementprofilesrc --group AC --group HandleButtonEvents --key powerButtonAction)" check "no idle suspend, dim or blank configured" 0 \ "$(in_lab 'grep -cE "^\[(AC|Battery|LowBattery)\]\[(SuspendSession|DimDisplay|DPMSControl)\]" /etc/ekklesia-classroom/xdg/powermanagementprofilesrc || true')" check "and the profiles are immutable" 3 \ "$(in_lab 'grep -cE "^\[(AC|Battery|LowBattery)\]\[\\\$i\]" /etc/ekklesia-classroom/xdg/powermanagementprofilesrc || true')" check "logind ignores the power key" ignore "$(logind_property HandlePowerKey)" check "logind ignores a long press" ignore "$(logind_property HandlePowerKeyLongPress)" check "logind has no idle action" ignore "$(logind_property IdleAction)" check "logind ignores the lid" ignore "$(logind_property HandleLidSwitch)" section "lockdown still in force" for key in run_command shell_access logout; do check "$key" false "$(session_config --file kdeglobals --group 'KDE Action Restrictions' --key "$key")" done check "the panel cannot be edited" false \ "$(session_config --file kdeglobals --group 'KDE Action Restrictions' --key plasma/plasmashell/unlockedDesktop)" section "autologin" check "sddm enabled" enabled "$(in_lab 'systemctl is-enabled sddm.service || true')" # Not a reading of the configuration: what SDDM actually did at the last # boot of this machine. It authenticates the classroom through PAM and # launches the session; the lab has no display hardware for the session to # land on, so everything past this point is a hardware test. check "sddm autologged the classroom in at boot" yes \ "$(in_lab 'journalctl -u sddm --no-pager 2>/dev/null | grep -q "Authentication for user .classroom. successful" && echo yes || echo no')" check "and chose the Plasma session" yes \ "$(in_lab 'journalctl -u sddm --no-pager 2>/dev/null | grep -q "wayland-sessions/plasma.desktop. selected" && echo yes || echo no')" check "autologin user" classroom \ "$(in_lab 'grep -A3 "^\[Autologin\]" /etc/sddm.conf.d/ekklesia-classroom.conf | sed -n "s/^User=//p"')" check "autologin session" plasma \ "$(in_lab 'grep -A3 "^\[Autologin\]" /etc/sddm.conf.d/ekklesia-classroom.conf | sed -n "s/^Session=//p"')" check "the session it names exists" 1 \ "$(in_lab 'ls /usr/share/wayland-sessions/plasma.desktop 2>/dev/null | wc -l')" check "the classroom uid is inside the greeter's range" 1 \ "$(in_lab 'uid=$(id -u classroom); min=$(sed -n "s/^MinimumUid=//p" /etc/sddm.conf.d/ekklesia-classroom.conf); max=$(sed -n "s/^MaximumUid=//p" /etc/sddm.conf.d/ekklesia-classroom.conf); [ "$uid" -ge "$min" ] && [ "$uid" -le "$max" ] && echo 1 || echo 0')" section "notifications belong to Plasma" check "the old daemon cannot be activated" 1 \ "$(in_lab 'grep -c "^Exec=/bin/false" /usr/local/share/dbus-1/services/fr.emersion.mako.service 2>/dev/null || true')" check "and Plasma owns the notification service" plasmashell "$(notification_owner)" section "the room's own parts survived" check "parent call installed" 1 "$(in_lab 'ls /usr/local/bin/classroom-parent-call 2>/dev/null | wc -l')" check "parent call icon installed" 1 \ "$(in_lab 'ls /usr/share/icons/hicolor/scalable/apps/ekklesia-parent-call.svg 2>/dev/null | wc -l')" check "wonderink installed" 1 "$(in_lab 'ls /usr/local/bin/classroom-wonderink 2>/dev/null | wc -l')" check "the maintenance chord is registered" 1 \ "$(in_lab 'grep -c "^X-KDE-Shortcuts=Meta+Ctrl+Alt+Shift+K" /usr/share/applications/ekklesia-admin-terminal.desktop || true')" check "no sudo rule for the classroom" 0 \ "$(in_lab 'grep -rl classroom /etc/sudoers /etc/sudoers.d 2>/dev/null | wc -l')" check "the panel is supervised" always \ "$(in_lab 'XDG_RUNTIME_DIR=/run/user/1000 runuser -u classroom -- systemctl --user show plasma-plasmashell.service -p Restart --value')" check "audio policy active" active \ "$(in_lab 'XDG_RUNTIME_DIR=/run/user/1000 runuser -u classroom -- systemctl --user is-active ekklesia-classroom-audio.service || true')" } run_checks if [[ "${1:-}" == "--restart" ]]; then section "and again after logging out and back in" "$LAB" plasma-down >/dev/null "$LAB" plasma-up >/dev/null sleep 3 run_checks fi printf '\n%s passed, %s failed\n' "$PASS" "$FAIL" [[ "$FAIL" -eq 0 ]]