Files
Classroom_Desktop/tools/lab/lab
T
ekklesiaandClaude Opus 5 f7d5aecd6c build: make the classroom desktop something a .deb installs
Building the package found two things reading the tree did not.

dpkg refused it outright: plasma-workspace already owns
/etc/xdg/plasmanotifyrc and friends, and two packages cannot own one
path. The room's settings move to a directory of their own with
/etc/environment.d putting it in front of /etc/xdg, which is KConfig's
own cascade rather than a file fight.

The postinst then failed on a chmod for a command that no longer
exists. It now walks what the package installed instead of naming
files, removes the login shell that used to start sway on tty1, and
enables SDDM and the audio service.

Verified by installing the built package on a fresh machine and
starting the session from it: restrictions in force, palette applied,
audio service enabled and running, panel supervision unconditional.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 09:06:53 -05:00

221 lines
7.2 KiB
Bash
Executable File

#!/bin/bash
# Classroom v2 test lab driver.
#
# The development host is a VM with no /dev/kvm, so the desktop under test
# runs inside a systemd container instead of a virtual machine. Nothing this
# script does touches the host outside Docker.
#
# The session is nested: an outer headless sway exists only to give the
# desktop under test a display that grim can capture and that sway's IPC can
# aim a real pointer at. The outer compositor is lab equipment, not part of
# the classroom design.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
NAME="${LAB_NAME:-classroom-v2-lab}"
IMAGE="${LAB_IMAGE:-classroom-v2-lab:plasma}"
DOCKERFILE="${LAB_DOCKERFILE:-tools/lab/Dockerfile.plasma}"
SHOTS="${LAB_SHOTS:-$ROOT/docs/screenshots}"
OUTER=wayland-1
session_env() {
docker exec -u classroom \
-e XDG_RUNTIME_DIR=/run/user/1000 \
-e HOME=/home/classroom \
-e LANG=C.UTF-8 \
-e DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
"$@"
}
# Talk to the outer lab compositor: grim, swaymsg.
outer() {
local sock
sock=$(docker exec "$NAME" sh -c 'ls /run/user/1000/sway-ipc.* 2>/dev/null | head -1')
session_env -e WAYLAND_DISPLAY="$OUTER" -e SWAYSOCK="$sock" "$NAME" "$@"
}
# Talk to the Plasma session under test.
inner() {
session_env -e WAYLAND_DISPLAY=wayland-0 -e XDG_CURRENT_DESKTOP=KDE \
-e QT_QPA_PLATFORM=wayland "$NAME" "$@"
}
case "${1:-}" in
build)
shift
exec docker build -f "$ROOT/$DOCKERFILE" -t "$IMAGE" "$@" "$ROOT"
;;
up)
docker rm -f "$NAME" >/dev/null 2>&1 || true
docker run -d --name "$NAME" \
--privileged --cgroupns=private \
--tmpfs /run --tmpfs /run/lock \
--shm-size=1g \
-v "$ROOT:/srv/classroom:ro" \
"$IMAGE" >/dev/null
for _ in $(seq 1 90); do
state=$(docker exec "$NAME" systemctl is-system-running 2>/dev/null || true)
[[ "$state" == running || "$state" == degraded ]] && break
sleep 1
done
for _ in $(seq 1 30); do
docker exec "$NAME" test -S /run/user/1000/bus && break
sleep 1
done
# startplasma looks for `qdbus`; Debian ships it as qdbus6.
docker exec "$NAME" ln -sf /usr/bin/qdbus6 /usr/local/bin/qdbus
echo "lab $NAME is up ($(docker exec "$NAME" systemctl is-system-running 2>/dev/null))"
;;
down)
docker rm -f "$NAME" >/dev/null 2>&1 || true
echo "lab $NAME is down"
;;
outer-up)
# A headless compositor has no input devices, and a wl_seat with no
# pointer capability never gives the session under test a pointer to
# click with — sway's cursor commands then report success and move
# nothing. So the lab runs wlroots' libinput backend as well, over the
# devices the privileged container inherits, with seatd standing in for
# the login session it does not have. Clicks in this experiment are
# real pointer events arriving through libinput.
docker exec "$NAME" sh -c 'pgrep -x seatd >/dev/null || \
(SEATD_VTBOUND=0 setsid seatd -g input > /tmp/seatd.log 2>&1 &)'
docker exec "$NAME" usermod -aG input classroom 2>/dev/null || true
sleep 2
session_env \
-e WLR_BACKENDS=headless,libinput -e LIBSEAT_BACKEND=seatd \
-e WLR_RENDERER=pixman -e LIBGL_ALWAYS_SOFTWARE=1 \
-d "$NAME" sh -c 'exec sway -c /srv/classroom/tools/lab/outer-sway.conf \
> /tmp/outer-sway.log 2>&1'
for _ in $(seq 1 30); do
docker exec "$NAME" test -S "/run/user/1000/$OUTER" && break
sleep 1
done
echo "outer compositor on $OUTER"
;;
plasma-up)
session_env \
-e WAYLAND_DISPLAY="$OUTER" -e XDG_SESSION_TYPE=wayland \
-e XDG_CURRENT_DESKTOP=KDE -e LIBGL_ALWAYS_SOFTWARE=1 \
-e QT_LOGGING_RULES="kwin_scripting.debug=true" \
-e XDG_CONFIG_DIRS=/etc/ekklesia-classroom/xdg:/etc/xdg \
-d "$NAME" sh -c 'exec startplasma-wayland > /tmp/plasma.log 2>&1'
for _ in $(seq 1 60); do
docker exec "$NAME" pgrep -x plasmashell >/dev/null 2>&1 && break
sleep 1
done
sleep 5
echo "plasma session up"
;;
plasma-down)
docker exec "$NAME" pkill -u classroom -f startplasma-wayland || true
docker exec "$NAME" pkill -u classroom -x plasmashell || true
docker exec "$NAME" pkill -u classroom -x kwin_wayland || true
sleep 2
echo "plasma session down"
;;
session)
"$0" outer-up
"$0" plasma-up
;;
shot)
# shot NAME [GEOMETRY] — geometry as grim wants it, e.g. "0,980 1920x100"
name="${2:?usage: lab shot NAME}"
if [[ -n "${3:-}" ]]; then
outer grim -g "$3" /tmp/lab-shot.png >/dev/null
else
outer grim /tmp/lab-shot.png >/dev/null
fi
mkdir -p "$SHOTS"
docker exec "$NAME" cat /tmp/lab-shot.png > "$SHOTS/$name.png"
echo "$SHOTS/$name.png"
;;
click)
# Absolute, through sway's own seat. This only works because the seat
# has a pointer capability at all, which is what the uinput device in
# `outer-up` is for.
outer swaymsg seat - cursor set "$2" "$3" >/dev/null
sleep 0.4
outer swaymsg seat - cursor press button1 >/dev/null
sleep 0.15
outer swaymsg seat - cursor release button1 >/dev/null
sleep 0.5
;;
rclick)
outer swaymsg seat - cursor set "$2" "$3" >/dev/null
sleep 0.4
outer swaymsg seat - cursor press button3 >/dev/null
sleep 0.15
outer swaymsg seat - cursor release button3 >/dev/null
sleep 0.5
;;
move)
outer swaymsg seat - cursor set "$2" "$3" >/dev/null
;;
drag)
outer swaymsg seat - cursor set "$2" "$3" >/dev/null
sleep 0.3
outer swaymsg seat - cursor press button1 >/dev/null
sleep 0.3
outer swaymsg seat - cursor set "$4" "$5" >/dev/null
sleep 0.3
outer swaymsg seat - cursor release button1 >/dev/null
sleep 0.4
;;
key)
shift
outer wtype "$@"
;;
sh)
shift
exec docker exec -it "$NAME" bash "$@"
;;
run) shift; session_env "$NAME" "$@" ;;
irun) shift; session_env -it "$NAME" "$@" ;;
inner) shift; inner "$@" ;;
inner-d) shift; session_env -d -e WAYLAND_DISPLAY=wayland-0 -e XDG_CURRENT_DESKTOP=KDE \
-e QT_QPA_PLATFORM=wayland "$NAME" "$@" ;;
inner-it) shift; session_env -it -e WAYLAND_DISPLAY=wayland-0 -e XDG_CURRENT_DESKTOP=KDE "$NAME" "$@" ;;
outer) shift; outer "$@" ;;
*)
cat <<USAGE
usage: tools/lab/lab COMMAND
build build the lab image
up start the lab container (systemd inside)
down remove it
session outer compositor + a Plasma session on top of it
outer-up just the outer compositor
plasma-up just the Plasma session
plasma-down stop the Plasma session
shot NAME capture the screen to docs/screenshots/NAME.png
click X Y press the pointer at a screen position
drag X1 Y1 X2 Y2 drag from one position to another
move X Y move the pointer
key ARGS... wtype, aimed at the session
inner CMD... run CMD as a client of the Plasma session
inner-d CMD... the same, detached
outer CMD... run CMD against the lab compositor
run CMD... run CMD as the classroom user, no display
sh root shell in the lab
USAGE
exit 1
;;
esac