Building the package showed what reading the tree did not: classroom-dvd still calls classroom-window, classroom-mqtt still calls classroom-notify, and the DVD overlay still imports the icon and stylesheet helpers the dock shared. They go back in, and the write-up now counts what was actually removed rather than what could be. Co-Authored-By: Claude Opus 5 <[email protected]>
127 lines
5.7 KiB
Bash
Executable File
127 lines
5.7 KiB
Bash
Executable File
#!/bin/bash
|
|
set -uo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
python3 - "$ROOT" <<'PY'
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
root = Path(sys.argv[1])
|
|
control = (root / "packaging/debian/control.template").read_text()
|
|
postinst = (root / "packaging/debian/postinst").read_text()
|
|
provision = (root / "scripts/classroom-provision").read_text()
|
|
units = "\n".join(path.read_text() for path in (root / "systemd").rglob("*.service"))
|
|
canary = (root / "update_canary.sh").read_text()
|
|
validation_script = (root / "tools/validate-trixie-release.sh").read_text()
|
|
layout = (root / "config/plasma/lookandfeel/contents/layouts/org.kde.plasma.desktop-layout.js").read_text()
|
|
rules = (root / "config/plasma/xdg/kwinrulesrc").read_text()
|
|
restrictions = (root / "config/plasma/xdg/kdeglobals").read_text()
|
|
supervision = (root / "systemd/user/plasma-plasmashell.service.d/ekklesia-classroom.conf").read_text()
|
|
build = (root / "tools/build-release.sh").read_text()
|
|
policy = json.loads(
|
|
(root / "config/chrome/policies/managed/ekklesia-classroom.json").read_text()
|
|
)
|
|
|
|
passed = failed = 0
|
|
|
|
|
|
def check(label, condition):
|
|
global passed, failed
|
|
if condition:
|
|
print(f" ok {label}")
|
|
passed += 1
|
|
else:
|
|
print(f" FAIL {label}")
|
|
failed += 1
|
|
|
|
|
|
depends = re.search(r"^Depends: (.*)$", control, re.MULTILINE).group(1).split(", ")
|
|
|
|
print("1. Trixie package contract")
|
|
for package in (
|
|
"network-manager",
|
|
"cups",
|
|
"cups-client",
|
|
"cups-filters",
|
|
"foomatic-db-compressed-ppds",
|
|
):
|
|
check(f"depends on {package}", package in depends)
|
|
|
|
print()
|
|
print("2. networking service behavior")
|
|
check("NetworkManager is explicitly enabled", "systemctl enable NetworkManager.service || true" in postinst)
|
|
check("NetworkManager start is best effort", "systemctl start NetworkManager.service || true" in postinst)
|
|
check("online waiter is disabled", "systemctl disable NetworkManager-wait-online.service || true" in postinst)
|
|
check("no classroom NetworkManager config overrides ifupdown", not (root / "config/network").exists())
|
|
|
|
print()
|
|
print("3. printing service behavior")
|
|
check("CUPS service and socket are enabled", "systemctl enable cups.socket cups.service || true" in postinst)
|
|
check("CUPS start is best effort", "systemctl start cups.socket || true" in postinst)
|
|
check("automatic remote queue browsing is disabled", "systemctl disable --now cups-browsed.service || true" in postinst)
|
|
check("Avahi service and socket are disabled", "systemctl disable --now avahi-daemon.socket avahi-daemon.service || true" in postinst)
|
|
check("Avahi service and socket are masked", "systemctl mask avahi-daemon.socket avahi-daemon.service || true" in postinst)
|
|
check("printer setup is not a blocking boot dependency", "classroom-printer-setup" not in units)
|
|
check("printer setup runs automatically in provisioning", "/usr/local/bin/classroom-printer-setup" in provision)
|
|
check("printer provisioning is bounded", "--kill-after=5s 30s" in provision)
|
|
check("Chrome uses the system print dialog", policy.get("DisablePrintPreview") is True)
|
|
|
|
print()
|
|
print("4. secondary windows are the window manager's business")
|
|
# Chrome gives its main window and every window a page opens the same
|
|
# class, so a rule keyed on that class maximises the lesson's popup too.
|
|
# It was measured doing exactly that. The browser is told to start
|
|
# maximized instead, and no rule mentions it.
|
|
check("no window rule claims the browser", "google-chrome" not in rules)
|
|
check("the browser asks for its own first window", "--start-maximized" in
|
|
(root / "scripts/classroom-browser").read_text())
|
|
check("Wonder Ink asks for the same", "--start-maximized" in
|
|
(root / "scripts/classroom-wonderink").read_text())
|
|
check("the Chrome window arbiter is gone", not (root / "scripts/classroom-chrome-windows").exists())
|
|
check("and is not packaged", "classroom-chrome-windows" not in build)
|
|
|
|
print()
|
|
print("4b. the panel is never allowed to stay dead")
|
|
# Plasma restarts a crashed panel three times a minute and then gives up.
|
|
# Measured: dead on the third kill. These lines are what make it
|
|
# unconditional, and losing them would lose the room its only way around.
|
|
check("restart is unconditional", "Restart=always" in supervision)
|
|
check("the burst limit is lifted", "StartLimitIntervalSec=0" in supervision)
|
|
check("the drop-in is packaged", "plasma-plasmashell.service.d" in build)
|
|
|
|
print()
|
|
print("4c. the teacher cannot reach a command line")
|
|
for restriction in ("run_command=false", "shell_access=false",
|
|
"action/lock_screen=false",
|
|
"plasma/plasmashell/unlockedDesktop=false"):
|
|
check(f"{restriction}", restriction in restrictions)
|
|
check("the restrictions are immutable", "[KDE Action Restrictions][$i]" in restrictions)
|
|
|
|
print()
|
|
print("4d. clicking a launcher never hides an application")
|
|
check("minimise on click is off", 'minimizeActiveTaskOnClick", false' in layout)
|
|
check("there is no minimise button", "ButtonsOnRight=FX" in
|
|
(root / "config/plasma/xdg/kwinrc").read_text())
|
|
|
|
print()
|
|
print("5. bounded Trixie validation")
|
|
check("container has an outer deadline", "720s" in validation_script)
|
|
check("package installs have deadlines", "candidate-install 360" in validation_script)
|
|
check("container service startup is suppressed", "/usr/sbin/policy-rc.d" in validation_script and "exit 101" in validation_script)
|
|
|
|
print()
|
|
print("6. canary release gate")
|
|
validation = canary.index('"$ROOT/tools/validate-trixie-release.sh"')
|
|
package_copy = canary.index('cp "$package_path"')
|
|
first_rsync = canary.index("rsync -av")
|
|
check("Trixie validation runs before the repository copy", validation < package_copy)
|
|
check("Trixie validation runs before any upload", validation < first_rsync)
|
|
|
|
print()
|
|
print(f"passed: {passed} failed: {failed}")
|
|
raise SystemExit(1 if failed else 0)
|
|
PY
|