fix(kiosk): stop GDM covering the wall with a greeter after auto-login
Auto-login succeeded and the wall kept streaming, but about ten seconds after the kiosk session came up GDM autospawned a greeter on the same GPU and a login screen covered it. gnome-kiosk ships X-GDM-SessionRegisters=false, so nothing ever registers and GDM concludes the display is still free — which is why every remote check (loginctl, the mirror roster) stayed green while the physical screen showed a password prompt. Override that session file with the flag set to true, keeping the file name and session id identical: inventing an id makes GDM quietly fall back to the plain GNOME session, top bar and all. Add a per-minute guard that reclaims the foreground VT, because this class of failure is invisible from the outside, and have the checker assert both the foreground VT and the greeter-session count. That count had a silent false green: journalctl has no --no-legend, so the check read an empty stream and passed unconditionally. Also keep a copy of the payload in /usr/local/share/mirror-kiosk. /tmp is cleared at every boot, which deletes the checker just after the one event you are most likely to want to re-run it for. Verified on the reference client: a cold boot now creates no greeter session at all, the kiosk compositor and Firefox come up undisturbed, and a console stolen with chvt is taken back within a minute. LLM-Generated: true
This commit is contained in:
@@ -16,6 +16,9 @@ or to change its URL.
|
||||
| `kiosk.conf` | `~kiosk/.config/mirror/kiosk.conf` | **the one per-client value: the URL** |
|
||||
| `policies.json` | `/etc/firefox/policies/policies.json` | no updates, accounts, telemetry, first-run pages |
|
||||
| `window-config.ini` | `/usr/local/share/gnome-kiosk/window-config.ini` | force the Firefox window fullscreen |
|
||||
| `gnome-kiosk-script-wayland.desktop` | `/usr/local/share/wayland-sessions/` | **stops GDM covering the wall with a greeter** |
|
||||
| `mirror-kiosk-console-guard` | `/usr/local/libexec/` | reclaims the console if the wall loses it |
|
||||
| `mirror-kiosk-console-guard.{service,timer}` | `/etc/systemd/system/` | runs that check every minute |
|
||||
| `dim-probe.py` | run on any reachable host | measures what the kiosk actually renders |
|
||||
|
||||
---
|
||||
@@ -29,15 +32,18 @@ or to change its URL.
|
||||
# 2. Layer the kiosk packages. This is the only step that needs a reboot.
|
||||
ssh client 'sudo rpm-ostree install gnome-kiosk gnome-kiosk-script-session && sudo reboot'
|
||||
|
||||
# 3. Wait ~60s, then install everything else.
|
||||
scp -r docs/kiosk-silverblue client:/tmp/ && ssh client \
|
||||
'cd /tmp/kiosk-silverblue && sudo sh install-kiosk.sh'
|
||||
# 3. Wait ~60s, then install everything else. Stage it in /var/tmp, not /tmp:
|
||||
# /tmp is wiped at every boot, so a checker staged there is gone exactly when
|
||||
# you want it. The installer keeps a permanent copy of the payload in
|
||||
# /usr/local/share/mirror-kiosk.
|
||||
scp -r docs/kiosk-silverblue client:/var/tmp/ && ssh client \
|
||||
'cd /var/tmp/kiosk-silverblue && sudo sh install-kiosk.sh'
|
||||
|
||||
# 4. Hand the console to the kiosk.
|
||||
ssh client 'sudo systemctl restart gdm'
|
||||
|
||||
# 5. Prove it, without touching the machine.
|
||||
ssh client 'cd /tmp/kiosk-silverblue && sudo sh check-kiosk.sh'
|
||||
ssh client 'sudo sh /usr/local/share/mirror-kiosk/check-kiosk.sh'
|
||||
```
|
||||
|
||||
`install-kiosk.sh` creates the `kiosk` user if it does not exist, so step 2 can
|
||||
@@ -170,6 +176,76 @@ ConditionPathExists=!/var/home/kiosk/.config/gnome-initial-setup-done`.
|
||||
|
||||
---
|
||||
|
||||
## The greeter that eats the wall
|
||||
|
||||
The shipped `gnome-kiosk-script-wayland.desktop` declares:
|
||||
|
||||
```ini
|
||||
X-GDM-SessionRegisters=false
|
||||
```
|
||||
|
||||
That is true of the session itself — gnome-kiosk is a bare mutter kiosk, so
|
||||
nothing ever registers with GDM. But GDM reads "never registered" as "this
|
||||
display is still mine", and about ten seconds after a perfectly successful
|
||||
auto-login it **autospawns a full greeter session on the same GPU**. A login
|
||||
screen covers the wall.
|
||||
|
||||
The part that makes this maddening: the kiosk keeps running underneath. The
|
||||
session stays `Active`, Firefox keeps streaming, the mirror roster keeps
|
||||
counting the wall as a viewer, `loginctl` reports the kiosk on `seat0` — every
|
||||
remote check says nothing is wrong while a password prompt is on the physical
|
||||
screen. This is what "it didn't auto-login" actually looks like.
|
||||
|
||||
From a real boot, `journalctl -b`:
|
||||
|
||||
```
|
||||
19:07:28 PAM:authentication grantors=pam_permit acct="kiosk" ... res=success
|
||||
19:07:28 New session '1' of user 'kiosk' with class 'user' and type 'wayland'
|
||||
19:07:30 Started org.gnome.Kiosk@wayland.service ← the wall is up
|
||||
19:07:38 New session 'c1' of user 'gdm-greeter' with class 'greeter'
|
||||
19:07:39 gnome-shell: Registering session with GDM ← the greeter eats it
|
||||
19:08:46 Stopped org.gnome.Shell@gdm.service ← 68s of login screen
|
||||
```
|
||||
|
||||
**The fix** is the local override of that one file, changing only
|
||||
`X-GDM-SessionRegisters` to `true`, so GDM treats the kiosk session as the owner
|
||||
of the display and never brings up a greeter. Two things about it are traps:
|
||||
|
||||
* **Keep the file name and session id identical to the shipped one.** Inventing
|
||||
`mirror-kiosk.desktop` and pointing AccountsService at it does *not* work —
|
||||
GDM silently falls back to the plain GNOME session, and you get a normal
|
||||
desktop with a top bar instead of a kiosk. Verified the hard way.
|
||||
* A normal GNOME session already sets `true`, which is why this bug only ever
|
||||
shows up on the kiosk.
|
||||
|
||||
After the override, the same boot looks like this: `gdm-greeter` appears **zero**
|
||||
times, the session registers, and the kiosk compositor and Firefox come up
|
||||
undisturbed.
|
||||
|
||||
## And if the wall loses the console anyway
|
||||
|
||||
Because the failure above is invisible from the outside, the install also drops
|
||||
in a guard: once a minute it checks that the kiosk's `seat0` session is still the
|
||||
foreground VT, and if not it runs `loginctl activate` on it and writes one line
|
||||
to the journal.
|
||||
|
||||
It is deliberately timid — it never restarts gdm and never logs anyone out, and
|
||||
if there is no kiosk session at all it only reports that, because a display
|
||||
manager is a bad thing to fight automatically. To stop it fighting you while you
|
||||
debug the console by hand:
|
||||
|
||||
```bash
|
||||
sudo systemctl stop mirror-kiosk-console-guard.timer
|
||||
sudo systemctl start mirror-kiosk-console-guard.timer # when finished
|
||||
sudo journalctl -u mirror-kiosk-console-guard.service # what it has done
|
||||
```
|
||||
|
||||
Tested by stealing the console with `chvt 1`; the wall came back on its own:
|
||||
|
||||
```
|
||||
mirror-kiosk-guard[4598]: kiosk session 1 brought back to the foreground
|
||||
```
|
||||
|
||||
## Verifying a kiosk you cannot see
|
||||
|
||||
The console is not an interface. Every claim below is checkable over SSH.
|
||||
@@ -184,6 +260,8 @@ The console is not an interface. Every claim below is checkable over SSH.
|
||||
| Media really flows | the sharer's own page renders each viewer's `RTCPeerConnection.connectionState`; `connected` = ICE+DTLS+SRTP up |
|
||||
| It is truly fullscreen | `dim-probe.py`: `viewport == window == screen == available` |
|
||||
| The URL is serving *mirror* | `check-kiosk.sh` greps the page for `<title>Watch · mirror` |
|
||||
| The kiosk holds the screen | `/sys/class/tty/tty0/active` equals the kiosk session's TTY and the session is `Active` — the one check that catches a covered wall |
|
||||
| No greeter is fighting it | `journalctl -b \| grep -c "New session.*gdm-greeter"` is `0`; any non-zero number is a login screen on your wall |
|
||||
| It survives a power cut | `sudo systemctl reboot`, then re-run `check-kiosk.sh` |
|
||||
|
||||
Measured on the reference client, which is attached to a 4K output:
|
||||
@@ -218,7 +296,9 @@ convenient IP.
|
||||
|
||||
| Symptom | Cause and fix |
|
||||
|---|---|
|
||||
| Greeter on screen, not the wall | `seat0` logged in as someone else — `sudo systemctl restart gdm`. GDM reads `custom.conf` at start only |
|
||||
| **Login screen on the wall, but every remote check passes** | The session is not registered with GDM, so GDM autospawns a greeter over it. See [The greeter that eats the wall](#the-greeter-that-eats-the-wall); confirm with `ls /usr/local/share/wayland-sessions/` |
|
||||
| Wall vanished, console on a blank VT | Someone or something switched VT — the guard reclaims it within a minute; `journalctl -u mirror-kiosk-console-guard.service` |
|
||||
| Greeter on screen after boot and `seat0` is another user | `sudo systemctl restart gdm`. GDM reads `custom.conf` at start only |
|
||||
| Kiosk logs in to a normal desktop | `Session=` empty in `/var/lib/AccountsService/users/kiosk`; re-run the installer (it restarts `accounts-daemon`) |
|
||||
| Wall shows a non-mirror page | the URL serves something else. `check-kiosk.sh` calls this out; a stale vhost/DNS looks like a working kiosk showing a login form |
|
||||
| Black screen, no browser at all | the URL is unreachable so the loop is waiting. `curl -fsSL <url>` from the client; then look in `~/.local/state/mirror-kiosk.log` |
|
||||
|
||||
Reference in New Issue
Block a user