Release 0.1.37 deployment prep
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
"PasswordManagerEnabled": false,
|
"PasswordManagerEnabled": false,
|
||||||
"PasswordLeakDetectionEnabled": false,
|
"PasswordLeakDetectionEnabled": false,
|
||||||
"BackgroundModeEnabled": false,
|
"BackgroundModeEnabled": false,
|
||||||
|
"DisablePrintPreview": true,
|
||||||
|
|
||||||
"CookiesSessionOnlyForUrls": [
|
"CookiesSessionOnlyForUrls": [
|
||||||
"[*.]wonderink.org"
|
"[*.]wonderink.org"
|
||||||
|
|||||||
+2
-3
@@ -13,9 +13,8 @@ DVD_ENABLED=true
|
|||||||
Then restart the session (reboot, or restart `classroom-dock`).
|
Then restart the session (reboot, or restart `classroom-dock`).
|
||||||
|
|
||||||
With `DVD_ENABLED=false`, or the key absent, the DVD button is not built
|
With `DVD_ENABLED=false`, or the key absent, the DVD button is not built
|
||||||
at all and no drive polling happens. Older endpoints get a commented
|
at all and no drive polling happens. The LIVE placeholder is controlled
|
||||||
`DVD_ENABLED=false` appended on upgrade so the option is discoverable,
|
independently by `LIVE_ENABLED`; see [live.md](live.md).
|
||||||
which leaves their behaviour unchanged.
|
|
||||||
|
|
||||||
## Behaviour
|
## Behaviour
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Sanctuary LIVE dock capability
|
||||||
|
|
||||||
|
Sanctuary live viewing is not implemented. The existing placeholder can be
|
||||||
|
shown on development devices or hidden on ordinary classrooms with
|
||||||
|
`/etc/ekklesia-classroom/device.conf`:
|
||||||
|
|
||||||
|
```
|
||||||
|
LIVE_ENABLED=false
|
||||||
|
```
|
||||||
|
|
||||||
|
With `LIVE_ENABLED=false`, or when the key is absent, the LIVE button is not
|
||||||
|
built at all, so it leaves no empty space and starts no live-specific work.
|
||||||
|
|
||||||
|
With `LIVE_ENABLED=true`, the current LIVE icon and placeholder notification
|
||||||
|
appear unchanged. This flag does not enable playback or start a streaming
|
||||||
|
service.
|
||||||
|
|
||||||
|
Older endpoints receive one commented `LIVE_ENABLED=false` setting on
|
||||||
|
upgrade. Existing device configuration values and comments are preserved.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Classroom networking
|
||||||
|
|
||||||
|
Ethernet remains the normal classroom connection. The package also installs
|
||||||
|
and enables NetworkManager so an administrator can add Wi-Fi when a room needs
|
||||||
|
it; there is no classroom Wi-Fi screen and no network name or password in the
|
||||||
|
package.
|
||||||
|
|
||||||
|
## Add Wi-Fi
|
||||||
|
|
||||||
|
From a terminal or SSH session, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo nmtui
|
||||||
|
```
|
||||||
|
|
||||||
|
Choose **Activate a connection**, select the church Wi-Fi network, enter its
|
||||||
|
password, and activate it. NetworkManager saves the connection as a system
|
||||||
|
connection and reconnects it automatically after reboot.
|
||||||
|
|
||||||
|
Useful checks:
|
||||||
|
|
||||||
|
```
|
||||||
|
nmcli device status
|
||||||
|
nmcli connection show
|
||||||
|
systemctl status NetworkManager
|
||||||
|
```
|
||||||
|
|
||||||
|
There is nothing to configure on a computer without Wi-Fi hardware.
|
||||||
|
NetworkManager starts normally, sees no Wi-Fi device, and Ethernet continues
|
||||||
|
to work.
|
||||||
|
|
||||||
|
## Existing Ethernet configuration
|
||||||
|
|
||||||
|
Do not remove or rewrite `/etc/network/interfaces` as part of this procedure.
|
||||||
|
Debian Trixie's NetworkManager configuration uses the `ifupdown` plugin with
|
||||||
|
`managed=false`, so any Ethernet interface declared there remains under
|
||||||
|
ifupdown's control. NetworkManager manages other interfaces, including Wi-Fi.
|
||||||
|
This preserves an existing static or ifupdown Ethernet setup.
|
||||||
|
|
||||||
|
The package disables `NetworkManager-wait-online.service`; no classroom
|
||||||
|
feature is allowed to hold boot waiting for Wi-Fi or Internet access. Saved
|
||||||
|
connections still reconnect automatically.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# Classroom printing
|
||||||
|
|
||||||
|
The package installs and enables CUPS, its client/filter stack, and Debian's
|
||||||
|
compressed Foomatic PPD database. Chrome is configured to open the system
|
||||||
|
print dialog, which shows the queues configured in CUPS.
|
||||||
|
|
||||||
|
The target Ricoh Aficio MP C3502 supports PCL 5c and PCL 6 as standard;
|
||||||
|
PostScript 3 is optional. Debian Trixie does not provide a model-specific
|
||||||
|
C3502 PPD in the installed driver set, so the classroom helper uses Trixie's
|
||||||
|
recommended generic color PCL 6/PCL XL driver. This works with the printer's
|
||||||
|
standard language and does not require a vendor binary.
|
||||||
|
|
||||||
|
## Configure the church color printer
|
||||||
|
|
||||||
|
Give the helper the printer's IP address or DNS name:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo classroom-printer-setup 10.20.30.40
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates or updates one queue named `Classroom_Color`, points it directly
|
||||||
|
to `socket://10.20.30.40:9100`, enables it, accepts jobs, and makes it the
|
||||||
|
system default. The printer's address is never stored in the package.
|
||||||
|
|
||||||
|
An explicit URI is also accepted when deployment requires another supported
|
||||||
|
transport:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo classroom-printer-setup ipp://10.20.30.40/ipp/print
|
||||||
|
sudo classroom-printer-setup --no-default socket://10.20.30.40:9100
|
||||||
|
```
|
||||||
|
|
||||||
|
The command is idempotent: running it again updates the same queue. It does not
|
||||||
|
contact the printer while configuring CUPS, and an unreachable printer only
|
||||||
|
leaves jobs queued; it never affects boot.
|
||||||
|
|
||||||
|
Automatic remote queue browsing is disabled. `cups-browsed` is stopped and
|
||||||
|
disabled if another package installed it, so unrelated advertised label and
|
||||||
|
check-in printers are not turned into local classroom queues.
|
||||||
|
|
||||||
|
## Administration
|
||||||
|
|
||||||
|
List the available Debian drivers, including the selected PCL path:
|
||||||
|
|
||||||
|
```
|
||||||
|
lpinfo -m
|
||||||
|
lpinfo -m | grep -i -E 'Ricoh|PCL|PostScript|IPP Everywhere'
|
||||||
|
```
|
||||||
|
|
||||||
|
Show the classroom queue, its URI, and the system default:
|
||||||
|
|
||||||
|
```
|
||||||
|
lpstat -p Classroom_Color -v
|
||||||
|
lpstat -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the default manually:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo lpadmin -d Classroom_Color
|
||||||
|
```
|
||||||
|
|
||||||
|
Print CUPS's test page:
|
||||||
|
|
||||||
|
```
|
||||||
|
lp -d Classroom_Color /usr/share/cups/data/testprint
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspect or cancel queued jobs:
|
||||||
|
|
||||||
|
```
|
||||||
|
lpstat -o Classroom_Color
|
||||||
|
cancel -a Classroom_Color
|
||||||
|
```
|
||||||
|
|
||||||
|
The printer language choice is based on Ricoh's published
|
||||||
|
[MP C3002/C3502 specifications](https://assets.ricoh-usa.com/j2jqn9lauv41/1CzJla6qwny2d9oBVHHlID/5bfa366a01cf82162ea24287f99c8af9/aficiompc3002.pdf).
|
||||||
+10
-1
@@ -51,8 +51,17 @@ classroom package and apply as soon as Chrome exists.
|
|||||||
|
|
||||||
Only for rooms with `DVD_ENABLED=true`. See [dvd.md](dvd.md).
|
Only for rooms with `DVD_ENABLED=true`. See [dvd.md](dvd.md).
|
||||||
|
|
||||||
|
## Networking and printing
|
||||||
|
|
||||||
|
NetworkManager and CUPS are Debian package dependencies, not deferred network
|
||||||
|
provisioning steps. Their services are enabled by the package and neither
|
||||||
|
requires Internet access or hardware to be present.
|
||||||
|
|
||||||
|
- Wi-Fi administration: [networking.md](networking.md)
|
||||||
|
- Church color printer setup: [printing.md](printing.md)
|
||||||
|
|
||||||
## What provisioning does not do
|
## What provisioning does not do
|
||||||
|
|
||||||
It never upgrades or reconfigures anything already installed, and it
|
The boot provisioning command never upgrades or reconfigures anything already installed, and it
|
||||||
never touches a repository it did not add. Classroom software updates
|
never touches a repository it did not add. Classroom software updates
|
||||||
are the OTA channel's job, not this.
|
are the OTA channel's job, not this.
|
||||||
|
|||||||
@@ -19,6 +19,31 @@ before promoting to stable with `./update_stable.sh`.
|
|||||||
- [ ] A machine with no network still boots to a working shell, and
|
- [ ] A machine with no network still boots to a working shell, and
|
||||||
provisioning succeeds at a later boot once it is online
|
provisioning succeeds at a later boot once it is online
|
||||||
|
|
||||||
|
## Networking
|
||||||
|
|
||||||
|
- [ ] `nmtui` is installed and `NetworkManager.service` is enabled
|
||||||
|
- [ ] An Ethernet interface declared in `/etc/network/interfaces` remains
|
||||||
|
managed by ifupdown and keeps its existing address
|
||||||
|
- [ ] `sudo nmtui` can save and activate a Wi-Fi connection
|
||||||
|
- [ ] The saved Wi-Fi connection reconnects after reboot
|
||||||
|
- [ ] A machine with no Wi-Fi adapter reaches the classroom normally without
|
||||||
|
an online-wait delay or NetworkManager failure
|
||||||
|
- [ ] Ethernet-only operation is unchanged
|
||||||
|
|
||||||
|
## Printing
|
||||||
|
|
||||||
|
- [ ] `cups.service` is enabled and `lpstat -r` reports that the scheduler runs
|
||||||
|
- [ ] `lpinfo -m` contains the generic color PCL 6/PCL XL driver documented in
|
||||||
|
[printing.md](printing.md)
|
||||||
|
- [ ] `sudo classroom-printer-setup <printer-ip>` creates only
|
||||||
|
`Classroom_Color`, enables it, and makes it the system default
|
||||||
|
- [ ] Repeating the helper updates the same queue without creating a duplicate
|
||||||
|
- [ ] Chrome opens the system print dialog and shows the configured queue
|
||||||
|
- [ ] Unrelated label/check-in printers are not automatically added
|
||||||
|
- [ ] The CUPS test page prints in color on the Ricoh MP C3502
|
||||||
|
- [ ] With the Ricoh powered off, the classroom still boots normally and a job
|
||||||
|
simply remains queued
|
||||||
|
|
||||||
## UI scale
|
## UI scale
|
||||||
|
|
||||||
UI_SCALE sets sway's output scale, so it scales the whole session.
|
UI_SCALE sets sway's output scale, so it scales the whole session.
|
||||||
@@ -169,6 +194,19 @@ With `DVD_ENABLED=true`:
|
|||||||
- [ ] Commercial CSS disc plays without anyone having run codec setup
|
- [ ] Commercial CSS disc plays without anyone having run codec setup
|
||||||
by hand
|
by hand
|
||||||
|
|
||||||
|
## Sanctuary LIVE capability
|
||||||
|
|
||||||
|
With `LIVE_ENABLED=false`:
|
||||||
|
|
||||||
|
- [ ] No LIVE button or spacing hole appears in the dock
|
||||||
|
- [ ] No live-specific service or process starts
|
||||||
|
|
||||||
|
With `LIVE_ENABLED=true`:
|
||||||
|
|
||||||
|
- [ ] LIVE appears after DVD when DVD is enabled, and after Browser otherwise
|
||||||
|
- [ ] Clicking it shows the existing placeholder notification
|
||||||
|
- [ ] No live playback is attempted
|
||||||
|
|
||||||
### DVD mode
|
### DVD mode
|
||||||
|
|
||||||
- [ ] Pressing DVD covers the **whole** display immediately, dock and
|
- [ ] Pressing DVD covers the **whole** display immediately, dock and
|
||||||
@@ -206,3 +244,8 @@ With `DVD_ENABLED=true`:
|
|||||||
- [ ] SHA-256 verification passes
|
- [ ] SHA-256 verification passes
|
||||||
- [ ] Install succeeds and the endpoint reboots
|
- [ ] Install succeeds and the endpoint reboots
|
||||||
- [ ] Everything above still works after the update
|
- [ ] Everything above still works after the update
|
||||||
|
- [ ] Upgrade from 0.1.36 preserves every existing `device.conf` value
|
||||||
|
- [ ] `LIVE_ENABLED=false` is appended exactly once on upgrade and remains
|
||||||
|
single after reinstall
|
||||||
|
- [ ] `tools/validate-trixie-release.sh` passes against the 0.1.37 package and
|
||||||
|
the 0.1.36 package
|
||||||
|
|||||||
@@ -19,6 +19,70 @@ UI_SCALE_MIN = 0.8
|
|||||||
UI_SCALE_MAX = 1.5
|
UI_SCALE_MAX = 1.5
|
||||||
|
|
||||||
|
|
||||||
|
DEVICE_CONFIG_TEMPLATE = """# Ekklesia Classroom device configuration
|
||||||
|
|
||||||
|
UPDATE_URL=https://updates.riverofglorymn.org
|
||||||
|
CHANNEL=stable
|
||||||
|
|
||||||
|
# Set to true on classrooms that have a DVD drive.
|
||||||
|
DVD_ENABLED=false
|
||||||
|
|
||||||
|
# Show the Sanctuary LIVE placeholder in the dock. Live viewing is not
|
||||||
|
# implemented yet; this only controls whether the current placeholder appears.
|
||||||
|
LIVE_ENABLED=false
|
||||||
|
|
||||||
|
# Size of the dock, clock and notifications, 0.8 to 1.5.
|
||||||
|
# Larger rooms and larger TVs want a larger number.
|
||||||
|
UI_SCALE=1.0
|
||||||
|
|
||||||
|
# Church message broker. The room is this machine's hostname unless ROOM
|
||||||
|
# is set. Leave MQTT_ENABLED=false until a broker exists.
|
||||||
|
MQTT_ENABLED=false
|
||||||
|
MQTT_HOST=
|
||||||
|
MQTT_PORT=1883
|
||||||
|
MQTT_USERNAME=
|
||||||
|
MQTT_PASSWORD=
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Sections added after the first classroom release. Keeping them separate lets
|
||||||
|
# an upgrade make a setting discoverable without replacing anything an
|
||||||
|
# administrator has already put in device.conf.
|
||||||
|
DEVICE_CONFIG_SECTIONS = (
|
||||||
|
(
|
||||||
|
"DVD_ENABLED",
|
||||||
|
"""# Set to true on classrooms that have a DVD drive.
|
||||||
|
DVD_ENABLED=false
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"LIVE_ENABLED",
|
||||||
|
"""# Show the Sanctuary LIVE placeholder in the dock. Live viewing is not
|
||||||
|
# implemented yet; this only controls whether the current placeholder appears.
|
||||||
|
LIVE_ENABLED=false
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"UI_SCALE",
|
||||||
|
"""# Size of the dock, clock and notifications, 0.8 to 1.5.
|
||||||
|
# Larger rooms and larger TVs want a larger number.
|
||||||
|
UI_SCALE=1.0
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"MQTT_ENABLED",
|
||||||
|
"""# Church message broker. The room is this machine's hostname unless ROOM
|
||||||
|
# is set. Leave MQTT_ENABLED=false until a broker exists.
|
||||||
|
MQTT_ENABLED=false
|
||||||
|
MQTT_HOST=
|
||||||
|
MQTT_PORT=1883
|
||||||
|
MQTT_USERNAME=
|
||||||
|
MQTT_PASSWORD=
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def read_device_config(path=None):
|
def read_device_config(path=None):
|
||||||
config = {}
|
config = {}
|
||||||
path = Path(path) if path is not None else CONFIG_FILE
|
path = Path(path) if path is not None else CONFIG_FILE
|
||||||
@@ -45,6 +109,47 @@ def enabled(config, key, default=False):
|
|||||||
return config[key].lower() in TRUTHY
|
return config[key].lower() in TRUTHY
|
||||||
|
|
||||||
|
|
||||||
|
def dock_items(config):
|
||||||
|
"""Dock items in display order, with disabled capabilities omitted."""
|
||||||
|
items = ["browser"]
|
||||||
|
|
||||||
|
if enabled(config, "DVD_ENABLED"):
|
||||||
|
items.append("dvd")
|
||||||
|
|
||||||
|
if enabled(config, "LIVE_ENABLED"):
|
||||||
|
items.append("live")
|
||||||
|
|
||||||
|
return tuple(items)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_device_config(path=None):
|
||||||
|
"""Create device.conf or append any missing settings exactly once.
|
||||||
|
|
||||||
|
Existing bytes are otherwise left alone: per-room settings, comments and
|
||||||
|
even keys this release does not know about all survive an upgrade.
|
||||||
|
"""
|
||||||
|
path = Path(path) if path is not None else CONFIG_FILE
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(DEVICE_CONFIG_TEMPLATE)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = path.read_text()
|
||||||
|
config = read_device_config(path)
|
||||||
|
additions = []
|
||||||
|
|
||||||
|
for key, section in DEVICE_CONFIG_SECTIONS:
|
||||||
|
if key not in config:
|
||||||
|
additions.append(section.rstrip("\n"))
|
||||||
|
|
||||||
|
if not additions:
|
||||||
|
return
|
||||||
|
|
||||||
|
separator = "" if not text or text.endswith("\n") else "\n"
|
||||||
|
path.write_text(text + separator + "\n\n".join(additions) + "\n")
|
||||||
|
|
||||||
|
|
||||||
def ui_scale(config):
|
def ui_scale(config):
|
||||||
"""UI_SCALE, clamped to something a classroom can actually use."""
|
"""UI_SCALE, clamped to something a classroom can actually use."""
|
||||||
raw = config.get("UI_SCALE", "")
|
raw = config.get("UI_SCALE", "")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Section: misc
|
|||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: amd64
|
Architecture: amd64
|
||||||
Maintainer: Ekklesia
|
Maintainer: Ekklesia
|
||||||
Depends: sway, swaybg, waybar, mako-notifier, dbus-user-session, xwayland, fonts-dejavu, fonts-noto, network-manager, pipewire, pipewire-audio, wireplumber, cups, python3-gi, python3-gi-cairo, gir1.2-gtk-3.0, gir1.2-gtklayershell-0.1, adwaita-icon-theme, libnotify-bin, curl, gnupg, ca-certificates, python3-paho-mqtt
|
Depends: sway, swaybg, waybar, mako-notifier, dbus-user-session, xwayland, fonts-dejavu, fonts-noto, network-manager, pipewire, pipewire-audio, wireplumber, cups, cups-client, cups-filters, foomatic-db-compressed-ppds, python3-gi, python3-gi-cairo, gir1.2-gtk-3.0, gir1.2-gtklayershell-0.1, adwaita-icon-theme, libnotify-bin, curl, gnupg, ca-certificates, python3-paho-mqtt
|
||||||
Recommends: vlc
|
Recommends: vlc
|
||||||
Description: Ekklesia managed classroom environment
|
Description: Ekklesia managed classroom environment
|
||||||
A minimal Wayland classroom environment for Ekklesia-managed church computers.
|
A minimal Wayland classroom environment for Ekklesia-managed church computers.
|
||||||
|
|||||||
+24
-55
@@ -30,6 +30,7 @@ chmod 0755 /usr/local/bin/classroom-scale
|
|||||||
chmod 0755 /usr/local/bin/classroom-shutdown
|
chmod 0755 /usr/local/bin/classroom-shutdown
|
||||||
chmod 0755 /usr/local/bin/classroom-chrome-setup
|
chmod 0755 /usr/local/bin/classroom-chrome-setup
|
||||||
chmod 0755 /usr/local/bin/classroom-provision
|
chmod 0755 /usr/local/bin/classroom-provision
|
||||||
|
chmod 0755 /usr/local/bin/classroom-printer-setup
|
||||||
|
|
||||||
# Reading the optical drive needs group cdrom. Takes effect at the next
|
# Reading the optical drive needs group cdrom. Takes effect at the next
|
||||||
# login, which the OTA reboot provides.
|
# login, which the OTA reboot provides.
|
||||||
@@ -37,62 +38,14 @@ if getent group cdrom >/dev/null 2>&1; then
|
|||||||
adduser classroom cdrom >/dev/null 2>&1 || true
|
adduser classroom cdrom >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f /etc/ekklesia-classroom/device.conf ]; then
|
# Create device.conf on a new endpoint, or append only settings an older
|
||||||
cat > /etc/ekklesia-classroom/device.conf <<'CONFIG'
|
# endpoint does not yet know. The helper preserves every existing byte apart
|
||||||
# Ekklesia Classroom device configuration
|
# from the append, so per-room choices always survive an upgrade.
|
||||||
|
PYTHONPATH=/usr/local/lib/ekklesia-classroom python3 - <<'PY'
|
||||||
|
from classroom_config import ensure_device_config
|
||||||
|
|
||||||
UPDATE_URL=https://updates.riverofglorymn.org
|
ensure_device_config()
|
||||||
CHANNEL=stable
|
PY
|
||||||
|
|
||||||
# Set to true on classrooms that have a DVD drive.
|
|
||||||
DVD_ENABLED=false
|
|
||||||
|
|
||||||
# Size of the dock, clock and notifications, 0.8 to 1.5.
|
|
||||||
# Larger rooms and larger TVs want a larger number.
|
|
||||||
UI_SCALE=1.0
|
|
||||||
|
|
||||||
# Church message broker. The room is this machine's hostname unless ROOM
|
|
||||||
# is set. Leave MQTT_ENABLED=false until a broker exists.
|
|
||||||
MQTT_ENABLED=false
|
|
||||||
MQTT_HOST=
|
|
||||||
MQTT_PORT=1883
|
|
||||||
MQTT_USERNAME=
|
|
||||||
MQTT_PASSWORD=
|
|
||||||
CONFIG
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Make the settings discoverable on classrooms installed before they
|
|
||||||
# existed. Both defaults are what those rooms already do, so nothing
|
|
||||||
# about their behaviour changes.
|
|
||||||
if ! grep -q '^DVD_ENABLED=' /etc/ekklesia-classroom/device.conf; then
|
|
||||||
cat >> /etc/ekklesia-classroom/device.conf <<'CONFIG'
|
|
||||||
|
|
||||||
# Set to true on classrooms that have a DVD drive.
|
|
||||||
DVD_ENABLED=false
|
|
||||||
CONFIG
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! grep -q '^UI_SCALE=' /etc/ekklesia-classroom/device.conf; then
|
|
||||||
cat >> /etc/ekklesia-classroom/device.conf <<'CONFIG'
|
|
||||||
|
|
||||||
# Size of the dock, clock and notifications, 0.8 to 1.5.
|
|
||||||
# Larger rooms and larger TVs want a larger number.
|
|
||||||
UI_SCALE=1.0
|
|
||||||
CONFIG
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! grep -q '^MQTT_ENABLED=' /etc/ekklesia-classroom/device.conf; then
|
|
||||||
cat >> /etc/ekklesia-classroom/device.conf <<'CONFIG'
|
|
||||||
|
|
||||||
# Church message broker. The room is this machine's hostname unless ROOM
|
|
||||||
# is set. Leave MQTT_ENABLED=false until a broker exists.
|
|
||||||
MQTT_ENABLED=false
|
|
||||||
MQTT_HOST=
|
|
||||||
MQTT_PORT=1883
|
|
||||||
MQTT_USERNAME=
|
|
||||||
MQTT_PASSWORD=
|
|
||||||
CONFIG
|
|
||||||
fi
|
|
||||||
|
|
||||||
# The broker password lives here, so the file stops being world readable.
|
# The broker password lives here, so the file stops being world readable.
|
||||||
# Everything that reads it runs as root or as the classroom user.
|
# Everything that reads it runs as root or as the classroom user.
|
||||||
@@ -101,6 +54,22 @@ chmod 0640 /etc/ekklesia-classroom/device.conf
|
|||||||
|
|
||||||
systemctl daemon-reload || true
|
systemctl daemon-reload || true
|
||||||
|
|
||||||
|
# NetworkManager ships nmtui and deliberately leaves interfaces declared in
|
||||||
|
# /etc/network/interfaces under ifupdown's control (managed=false). This gives
|
||||||
|
# administrators saved, auto-reconnecting Wi-Fi without taking over an
|
||||||
|
# existing static Ethernet setup. No classroom service needs to hold boot for
|
||||||
|
# NetworkManager's online wait, so keep that waiter disabled.
|
||||||
|
systemctl enable NetworkManager.service || true
|
||||||
|
systemctl disable NetworkManager-wait-online.service || true
|
||||||
|
systemctl start NetworkManager.service || true
|
||||||
|
|
||||||
|
# CUPS is ready at boot, but no queue is created until an administrator runs
|
||||||
|
# classroom-printer-setup. cups-browsed is unnecessary here and would expose
|
||||||
|
# every advertised printer on the church network if installed independently.
|
||||||
|
systemctl enable cups.socket cups.service || true
|
||||||
|
systemctl disable --now cups-browsed.service || true
|
||||||
|
systemctl start cups.socket || true
|
||||||
|
|
||||||
systemctl enable ekklesia-classroom-update.timer || true
|
systemctl enable ekklesia-classroom-update.timer || true
|
||||||
systemctl start ekklesia-classroom-update.timer || true
|
systemctl start ekklesia-classroom-update.timer || true
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ sys.path.insert(0, "/usr/local/lib/ekklesia-classroom")
|
|||||||
import classroom_style as style # noqa: E402
|
import classroom_style as style # noqa: E402
|
||||||
from classroom_config import ( # noqa: E402
|
from classroom_config import ( # noqa: E402
|
||||||
SHELL_SCALE,
|
SHELL_SCALE,
|
||||||
enabled,
|
dock_items,
|
||||||
read_device_config,
|
read_device_config,
|
||||||
scaled,
|
scaled,
|
||||||
)
|
)
|
||||||
@@ -311,6 +311,8 @@ class ClassroomShell(Gtk.Window):
|
|||||||
)
|
)
|
||||||
dock.get_style_context().add_class("dock")
|
dock.get_style_context().add_class("dock")
|
||||||
|
|
||||||
|
items = dock_items(self.config)
|
||||||
|
|
||||||
dock.pack_start(
|
dock.pack_start(
|
||||||
self.make_button(
|
self.make_button(
|
||||||
"browser",
|
"browser",
|
||||||
@@ -322,11 +324,7 @@ class ClassroomShell(Gtk.Window):
|
|||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
|
|
||||||
if enabled(
|
if "dvd" in items:
|
||||||
self.config,
|
|
||||||
"DVD_ENABLED",
|
|
||||||
False,
|
|
||||||
):
|
|
||||||
dock.pack_start(
|
dock.pack_start(
|
||||||
self.make_button(
|
self.make_button(
|
||||||
"dvd",
|
"dvd",
|
||||||
@@ -342,6 +340,7 @@ class ClassroomShell(Gtk.Window):
|
|||||||
|
|
||||||
self.start_disc_watch()
|
self.start_disc_watch()
|
||||||
|
|
||||||
|
if "live" in items:
|
||||||
dock.pack_start(
|
dock.pack_start(
|
||||||
self.make_button(
|
self.make_button(
|
||||||
"sanctuary",
|
"sanctuary",
|
||||||
|
|||||||
Executable
+111
@@ -0,0 +1,111 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Configure the one explicitly managed classroom printer. The Ricoh Aficio
|
||||||
|
# MP C3502 supports PCL 6 as standard; Debian Trixie's compressed Foomatic
|
||||||
|
# database supplies this generic color PCL 6/PCL XL path without vendor code.
|
||||||
|
|
||||||
|
QUEUE="Classroom_Color"
|
||||||
|
DRIVER="foomatic-db-compressed-ppds:0/ppd/foomatic-ppd/Generic-PCL_6_PCL_XL_Printer-pxlcolor.ppd"
|
||||||
|
MAKE_MODEL="Ricoh Aficio MP C3502 (Generic PCL 6 Color)"
|
||||||
|
LOCATION="Ekklesia Classroom"
|
||||||
|
MAKE_DEFAULT=true
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage: classroom-printer-setup [--no-default] PRINTER-IP-OR-URI
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
sudo classroom-printer-setup 10.20.30.40
|
||||||
|
sudo classroom-printer-setup socket://printer.example.org:9100
|
||||||
|
|
||||||
|
An IP address or hostname uses JetDirect/AppSocket on port 9100. Explicit
|
||||||
|
socket://, ipp://, ipps:// and lpd:// URIs are also accepted.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[classroom-printer-setup] $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
log "ERROR: $1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${CLASSROOM_PRINTER_SETUP_TESTING:-false}" != "true" && $EUID -ne 0 ]]; then
|
||||||
|
fail "run this command as root: sudo classroom-printer-setup <printer-ip>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--no-default" ]]; then
|
||||||
|
MAKE_DEFAULT=false
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $# -ne 1 || "$1" == "-h" || "$1" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
[[ $# -eq 1 ]] && exit 0
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
address="$1"
|
||||||
|
|
||||||
|
case "$address" in
|
||||||
|
socket://*|ipp://*|ipps://*|lpd://*)
|
||||||
|
uri="$address"
|
||||||
|
;;
|
||||||
|
*://*)
|
||||||
|
fail "unsupported printer URI scheme: $address"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ ! "$address" =~ ^[A-Za-z0-9._:-]+$ ]]; then
|
||||||
|
fail "printer address contains unsupported characters: $address"
|
||||||
|
fi
|
||||||
|
uri="socket://${address}:9100"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
for command in lpadmin lpinfo lpstat cupsenable cupsaccept; do
|
||||||
|
command -v "$command" >/dev/null 2>&1 || \
|
||||||
|
fail "$command is missing; reinstall the ekklesia-classroom package"
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! lpstat -r >/dev/null 2>&1; then
|
||||||
|
command -v systemctl >/dev/null 2>&1 && \
|
||||||
|
systemctl start cups.service >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
lpstat -r >/dev/null 2>&1 || \
|
||||||
|
fail "CUPS is not running; check: systemctl status cups"
|
||||||
|
|
||||||
|
models="$(lpinfo -m)" || fail "CUPS could not list installed printer drivers"
|
||||||
|
|
||||||
|
if ! grep -q "^${DRIVER} " <<< "$models"; then
|
||||||
|
fail "the Debian generic color PCL 6 driver is not installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "configuring ${QUEUE} at ${uri}"
|
||||||
|
|
||||||
|
# Repeating this command updates the same named queue; it never creates a
|
||||||
|
# second destination. None of these operations connects to the printer.
|
||||||
|
lpadmin \
|
||||||
|
-p "$QUEUE" \
|
||||||
|
-v "$uri" \
|
||||||
|
-m "$DRIVER" \
|
||||||
|
-D "$MAKE_MODEL" \
|
||||||
|
-L "$LOCATION" || fail "CUPS could not configure ${QUEUE}"
|
||||||
|
|
||||||
|
cupsenable "$QUEUE" || fail "CUPS could not enable ${QUEUE}"
|
||||||
|
cupsaccept "$QUEUE" || fail "CUPS could not accept jobs for ${QUEUE}"
|
||||||
|
|
||||||
|
if [[ "$MAKE_DEFAULT" == true ]]; then
|
||||||
|
lpadmin -d "$QUEUE" || fail "CUPS could not make ${QUEUE} the default"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "${QUEUE} is configured, enabled and accepting jobs."
|
||||||
|
|
||||||
|
if [[ "$MAKE_DEFAULT" == true ]]; then
|
||||||
|
log "${QUEUE} is the system default printer."
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "The printer may remain offline until it is reachable; this does not affect boot."
|
||||||
Executable
+104
@@ -0,0 +1,104 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
PYTHONPATH="$ROOT/lib" python3 - <<'PY'
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from classroom_config import dock_items, ensure_device_config, read_device_config
|
||||||
|
|
||||||
|
passed = failed = 0
|
||||||
|
|
||||||
|
|
||||||
|
def check(label, condition, detail=""):
|
||||||
|
global passed, failed
|
||||||
|
if condition:
|
||||||
|
print(f" ok {label}")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
print(f" FAIL {label}")
|
||||||
|
if detail:
|
||||||
|
print(f" {detail}")
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
|
||||||
|
print("1. dock capabilities")
|
||||||
|
cases = [
|
||||||
|
({}, ("browser",)),
|
||||||
|
({"DVD_ENABLED": "true"}, ("browser", "dvd")),
|
||||||
|
({"LIVE_ENABLED": "true"}, ("browser", "live")),
|
||||||
|
(
|
||||||
|
{"DVD_ENABLED": "true", "LIVE_ENABLED": "true"},
|
||||||
|
("browser", "dvd", "live"),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
for config, expected in cases:
|
||||||
|
actual = dock_items(config)
|
||||||
|
check(f"{config or 'both disabled'} -> {expected}", actual == expected, actual)
|
||||||
|
|
||||||
|
check(
|
||||||
|
"false spellings do not leave placeholder items",
|
||||||
|
dock_items({"DVD_ENABLED": "false", "LIVE_ENABLED": "off"}) == ("browser",),
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
"existing dock order is browser, DVD, LIVE",
|
||||||
|
dock_items({"DVD_ENABLED": "yes", "LIVE_ENABLED": "1"})
|
||||||
|
== ("browser", "dvd", "live"),
|
||||||
|
)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("2. fresh device configuration")
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
path = Path(directory) / "etc" / "device.conf"
|
||||||
|
ensure_device_config(path)
|
||||||
|
config = read_device_config(path)
|
||||||
|
check("fresh config is created", path.is_file())
|
||||||
|
check("DVD defaults off", config.get("DVD_ENABLED") == "false")
|
||||||
|
check("LIVE defaults off", config.get("LIVE_ENABLED") == "false")
|
||||||
|
check("MQTT defaults off", config.get("MQTT_ENABLED") == "false")
|
||||||
|
check("stable remains the fresh-install default", config.get("CHANNEL") == "stable")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("3. upgrade from 0.1.36")
|
||||||
|
old = """# endpoint configuration from 0.1.36
|
||||||
|
UPDATE_URL=https://updates.example.test
|
||||||
|
CHANNEL=canary
|
||||||
|
DVD_ENABLED=true
|
||||||
|
UI_SCALE=1.25
|
||||||
|
MQTT_ENABLED=true
|
||||||
|
MQTT_HOST=broker.example.test
|
||||||
|
MQTT_PORT=1884
|
||||||
|
MQTT_USERNAME=room
|
||||||
|
MQTT_PASSWORD=secret
|
||||||
|
CUSTOM_SETTING=keep-me
|
||||||
|
"""
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
path = Path(directory) / "device.conf"
|
||||||
|
path.write_text(old)
|
||||||
|
|
||||||
|
ensure_device_config(path)
|
||||||
|
first = path.read_text()
|
||||||
|
config = read_device_config(path)
|
||||||
|
|
||||||
|
check("old bytes remain at the start", first.startswith(old))
|
||||||
|
check("channel survives", config.get("CHANNEL") == "canary")
|
||||||
|
check("DVD choice survives", config.get("DVD_ENABLED") == "true")
|
||||||
|
check("UI scale survives", config.get("UI_SCALE") == "1.25")
|
||||||
|
check("MQTT host survives", config.get("MQTT_HOST") == "broker.example.test")
|
||||||
|
check("MQTT password survives", config.get("MQTT_PASSWORD") == "secret")
|
||||||
|
check("unknown settings survive", config.get("CUSTOM_SETTING") == "keep-me")
|
||||||
|
check("LIVE is appended once", first.count("LIVE_ENABLED=") == 1)
|
||||||
|
|
||||||
|
ensure_device_config(path)
|
||||||
|
second = path.read_text()
|
||||||
|
check("reinstall makes no further change", second == first)
|
||||||
|
check("LIVE still occurs once", second.count("LIVE_ENABLED=") == 1)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f"passed: {passed} failed: {failed}")
|
||||||
|
raise SystemExit(1 if failed else 0)
|
||||||
|
PY
|
||||||
Executable
+116
@@ -0,0 +1,116 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
SCRIPT="$ROOT/scripts/classroom-printer-setup"
|
||||||
|
WORK="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$WORK"' EXIT
|
||||||
|
|
||||||
|
passed=0
|
||||||
|
failed=0
|
||||||
|
|
||||||
|
check() {
|
||||||
|
local label="$1" expected="$2" actual="$3"
|
||||||
|
if [[ "$actual" == *"$expected"* ]]; then
|
||||||
|
echo " ok $label"
|
||||||
|
passed=$((passed + 1))
|
||||||
|
else
|
||||||
|
echo " FAIL $label"
|
||||||
|
echo " expected to contain: $expected"
|
||||||
|
echo " got: $actual"
|
||||||
|
failed=$((failed + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_absent() {
|
||||||
|
local label="$1" unexpected="$2" actual="$3"
|
||||||
|
if [[ "$actual" != *"$unexpected"* ]]; then
|
||||||
|
echo " ok $label"
|
||||||
|
passed=$((passed + 1))
|
||||||
|
else
|
||||||
|
echo " FAIL $label"
|
||||||
|
echo " expected not to contain: $unexpected"
|
||||||
|
echo " got: $actual"
|
||||||
|
failed=$((failed + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$WORK/bin"
|
||||||
|
|
||||||
|
for command in lpadmin cupsenable cupsaccept; do
|
||||||
|
cat > "$WORK/bin/$command" <<'SHIM'
|
||||||
|
#!/bin/bash
|
||||||
|
echo "$(basename "$0") $*" >> "$CUPS_LOG"
|
||||||
|
SHIM
|
||||||
|
done
|
||||||
|
|
||||||
|
cat > "$WORK/bin/lpstat" <<'SHIM'
|
||||||
|
#!/bin/bash
|
||||||
|
[[ "${FAKE_CUPS_RUNNING:-true}" == "true" ]]
|
||||||
|
SHIM
|
||||||
|
|
||||||
|
cat > "$WORK/bin/lpinfo" <<'SHIM'
|
||||||
|
#!/bin/bash
|
||||||
|
if [[ "${FAKE_DRIVER_PRESENT:-true}" == "true" ]]; then
|
||||||
|
echo "foomatic-db-compressed-ppds:0/ppd/foomatic-ppd/Generic-PCL_6_PCL_XL_Printer-pxlcolor.ppd Generic PCL 6/PCL XL Printer Foomatic/pxlcolor (recommended)"
|
||||||
|
else
|
||||||
|
echo "everywhere IPP Everywhere"
|
||||||
|
fi
|
||||||
|
SHIM
|
||||||
|
|
||||||
|
chmod +x "$WORK/bin"/*
|
||||||
|
export PATH="$WORK/bin:$PATH"
|
||||||
|
export CUPS_LOG="$WORK/cups.log"
|
||||||
|
export CLASSROOM_PRINTER_SETUP_TESTING=true
|
||||||
|
|
||||||
|
run() {
|
||||||
|
"$SCRIPT" "$@" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "1. creates the explicit classroom queue"
|
||||||
|
: > "$CUPS_LOG"
|
||||||
|
out="$(run 10.20.30.40)"
|
||||||
|
log="$(cat "$CUPS_LOG")"
|
||||||
|
check "uses AppSocket by IP" "-v socket://10.20.30.40:9100" "$log"
|
||||||
|
check "uses Trixie's generic color PCL 6 driver" \
|
||||||
|
"Generic-PCL_6_PCL_XL_Printer-pxlcolor.ppd" "$log"
|
||||||
|
check "uses one stable queue name" "lpadmin -p Classroom_Color" "$log"
|
||||||
|
check "enables the queue" "cupsenable Classroom_Color" "$log"
|
||||||
|
check "accepts jobs" "cupsaccept Classroom_Color" "$log"
|
||||||
|
check "defaults the queue" "lpadmin -d Classroom_Color" "$log"
|
||||||
|
check "reports offline-safe behavior" "may remain offline" "$out"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "2. repeated setup updates the same queue"
|
||||||
|
run 10.20.30.40 >/dev/null
|
||||||
|
configs="$(grep -c '^lpadmin -p Classroom_Color' "$CUPS_LOG")"
|
||||||
|
check "two runs configured the same queue twice" "2" "$configs"
|
||||||
|
check_absent "no generated queue suffix" "Classroom_Color_" "$(cat "$CUPS_LOG")"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "3. explicit URIs and non-default setup"
|
||||||
|
: > "$CUPS_LOG"
|
||||||
|
run --no-default ipp://printer.example.test/ipp/print >/dev/null
|
||||||
|
log="$(cat "$CUPS_LOG")"
|
||||||
|
check "keeps an explicit IPP URI" "-v ipp://printer.example.test/ipp/print" "$log"
|
||||||
|
check_absent "--no-default omits the default change" "lpadmin -d" "$log"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "4. errors are useful"
|
||||||
|
if out="$(run smb://printer/share)"; then
|
||||||
|
check "unsupported scheme fails" "failure" "unexpected success"
|
||||||
|
else
|
||||||
|
check "unsupported scheme is named" "unsupported printer URI scheme" "$out"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export FAKE_DRIVER_PRESENT=false
|
||||||
|
if out="$(run 10.20.30.40)"; then
|
||||||
|
check "missing driver fails" "failure" "unexpected success"
|
||||||
|
else
|
||||||
|
check "missing driver is explained" "generic color PCL 6 driver is not installed" "$out"
|
||||||
|
fi
|
||||||
|
unset FAKE_DRIVER_PRESENT
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "passed: $passed failed: $failed"
|
||||||
|
[[ "$failed" -eq 0 ]]
|
||||||
Executable
+74
@@ -0,0 +1,74 @@
|
|||||||
|
#!/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()
|
||||||
|
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("printer setup is never a boot service", "classroom-printer-setup" not in units)
|
||||||
|
check("printer setup is never provisioning", "classroom-printer-setup" not in provision)
|
||||||
|
check("Chrome uses the system print dialog", policy.get("DisablePrintPreview") is True)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("4. 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
|
||||||
@@ -145,6 +145,10 @@ install -Dm755 \
|
|||||||
"$ROOT/scripts/classroom-provision" \
|
"$ROOT/scripts/classroom-provision" \
|
||||||
"$STAGE/usr/local/bin/classroom-provision"
|
"$STAGE/usr/local/bin/classroom-provision"
|
||||||
|
|
||||||
|
install -Dm755 \
|
||||||
|
"$ROOT/scripts/classroom-printer-setup" \
|
||||||
|
"$STAGE/usr/local/bin/classroom-printer-setup"
|
||||||
|
|
||||||
# Shared helpers imported by the classroom commands.
|
# Shared helpers imported by the classroom commands.
|
||||||
install -Dm644 \
|
install -Dm644 \
|
||||||
"$ROOT/lib/classroom_config.py" \
|
"$ROOT/lib/classroom_config.py" \
|
||||||
|
|||||||
Executable
+99
@@ -0,0 +1,99 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
if [[ $# -lt 1 || $# -gt 2 ]]; then
|
||||||
|
echo "Usage: $0 CANDIDATE.deb [PREVIOUS.deb]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
candidate="$(realpath "$1")"
|
||||||
|
previous=""
|
||||||
|
|
||||||
|
[[ -f "$candidate" ]] || { echo "Package not found: $candidate" >&2; exit 1; }
|
||||||
|
|
||||||
|
if [[ $# -eq 2 ]]; then
|
||||||
|
previous="$(realpath "$2")"
|
||||||
|
[[ -f "$previous" ]] || { echo "Package not found: $previous" >&2; exit 1; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Validating $(basename "$candidate") on Debian Trixie"
|
||||||
|
|
||||||
|
docker_args=(
|
||||||
|
run --rm -i
|
||||||
|
-v "$candidate:/packages/candidate.deb:ro"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -n "$previous" ]]; then
|
||||||
|
docker_args+=( -v "$previous:/packages/previous.deb:ro" )
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker "${docker_args[@]}" debian:trixie-slim \
|
||||||
|
bash -s -- "${previous:+/packages/previous.deb}" <<'CONTAINER'
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq
|
||||||
|
|
||||||
|
previous="${1:-}"
|
||||||
|
|
||||||
|
if [[ -n "$previous" ]]; then
|
||||||
|
apt-get install -y -qq "$previous" >/tmp/previous-install.log
|
||||||
|
|
||||||
|
cat > /etc/ekklesia-classroom/device.conf <<'CONFIG'
|
||||||
|
# Configuration carried forward from 0.1.36
|
||||||
|
UPDATE_URL=https://updates.riverofglorymn.org
|
||||||
|
CHANNEL=canary
|
||||||
|
DVD_ENABLED=true
|
||||||
|
UI_SCALE=1.25
|
||||||
|
MQTT_ENABLED=true
|
||||||
|
MQTT_HOST=broker.example.test
|
||||||
|
MQTT_PORT=1884
|
||||||
|
MQTT_USERNAME=classroom
|
||||||
|
MQTT_PASSWORD=preserve-me
|
||||||
|
CUSTOM_SETTING=preserve-me-too
|
||||||
|
CONFIG
|
||||||
|
fi
|
||||||
|
|
||||||
|
apt-get install -y -qq /packages/candidate.deb >/tmp/candidate-install.log
|
||||||
|
|
||||||
|
command -v nmtui >/dev/null
|
||||||
|
command -v lpadmin >/dev/null
|
||||||
|
command -v lpinfo >/dev/null
|
||||||
|
|
||||||
|
systemctl is-enabled NetworkManager.service >/dev/null
|
||||||
|
systemctl is-enabled cups.service >/dev/null
|
||||||
|
! systemctl is-enabled NetworkManager-wait-online.service >/dev/null 2>&1
|
||||||
|
|
||||||
|
grep -q '^managed=false$' /etc/NetworkManager/NetworkManager.conf
|
||||||
|
|
||||||
|
# A normal Docker container has Ethernet and no Wi-Fi adapter. Reaching this
|
||||||
|
# point proves package configuration did not require Wi-Fi hardware.
|
||||||
|
! find /sys/class/net -mindepth 2 -maxdepth 2 -type d -name wireless | grep -q .
|
||||||
|
|
||||||
|
/usr/sbin/cupsd
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
driver='foomatic-db-compressed-ppds:0/ppd/foomatic-ppd/Generic-PCL_6_PCL_XL_Printer-pxlcolor.ppd'
|
||||||
|
lpinfo -m | grep -q "^${driver} "
|
||||||
|
|
||||||
|
classroom-printer-setup 192.0.2.10 >/tmp/printer-first.log
|
||||||
|
classroom-printer-setup 192.0.2.10 >/tmp/printer-second.log
|
||||||
|
lpstat -p Classroom_Color -d -v | grep -q 'socket://192.0.2.10:9100'
|
||||||
|
|
||||||
|
if [[ -n "$previous" ]]; then
|
||||||
|
grep -q '^CHANNEL=canary$' /etc/ekklesia-classroom/device.conf
|
||||||
|
grep -q '^DVD_ENABLED=true$' /etc/ekklesia-classroom/device.conf
|
||||||
|
grep -q '^UI_SCALE=1.25$' /etc/ekklesia-classroom/device.conf
|
||||||
|
grep -q '^MQTT_PASSWORD=preserve-me$' /etc/ekklesia-classroom/device.conf
|
||||||
|
grep -q '^CUSTOM_SETTING=preserve-me-too$' /etc/ekklesia-classroom/device.conf
|
||||||
|
[[ "$(grep -c '^LIVE_ENABLED=' /etc/ekklesia-classroom/device.conf)" -eq 1 ]]
|
||||||
|
|
||||||
|
apt-get install --reinstall -y -qq /packages/candidate.deb \
|
||||||
|
>/tmp/candidate-reinstall.log
|
||||||
|
[[ "$(grep -c '^LIVE_ENABLED=' /etc/ekklesia-classroom/device.conf)" -eq 1 ]]
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Trixie package, upgrade, networking, printing and reinstall checks passed."
|
||||||
|
CONTAINER
|
||||||
@@ -22,6 +22,22 @@ echo
|
|||||||
|
|
||||||
"$ROOT/tools/build-release.sh" "$version"
|
"$ROOT/tools/build-release.sh" "$version"
|
||||||
|
|
||||||
|
# Validate the exact artifact that will be published. If a previous canary is
|
||||||
|
# available locally, exercise the real upgrade path as well as a fresh install.
|
||||||
|
validation_args=("$package_path")
|
||||||
|
|
||||||
|
if [[ -f "$ROOT/repo/canary/manifest.json" ]]; then
|
||||||
|
previous_package_name="$(python3 -c \
|
||||||
|
'import json; print(json.load(open("repo/canary/manifest.json"))["package"].rsplit("/", 1)[-1])')"
|
||||||
|
previous_package="$ROOT/repo/packages/$previous_package_name"
|
||||||
|
|
||||||
|
if [[ -f "$previous_package" && "$previous_package" != "$package_path" ]]; then
|
||||||
|
validation_args+=("$previous_package")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$ROOT/tools/validate-trixie-release.sh" "${validation_args[@]}"
|
||||||
|
|
||||||
hash="$(sha256sum "$package_path" | awk '{print $1}')"
|
hash="$(sha256sum "$package_path" | awk '{print $1}')"
|
||||||
|
|
||||||
mkdir -p "$ROOT/repo/packages" "$ROOT/repo/canary"
|
mkdir -p "$ROOT/repo/packages" "$ROOT/repo/canary"
|
||||||
|
|||||||
Reference in New Issue
Block a user