Aller au contenu

The web console

pupitre-web (module pupitre.webd, unit pupitre-web.service) is the supervision page of a Pupitre server. Open http://127.0.0.1:8080 on the server, or from your own machine through ssh -L 8080:127.0.0.1:8080 server. It is a single page, refreshed every 3 s.

Security, in plain words

The console shows the live screen of every seat, whatever a pupil has on screen, passwords included, and it controls every client: it assigns seats, stops sessions, renames devices and restarts seats. Whoever reaches it is an administrator.

Two supported ways to run it, and nothing in between.

On loopback, without a password. The default, and the one the ssh -L tunnel above uses. Whoever reaches 127.0.0.1 already has an account on the server, so a password would add a step without adding a guarantee.

On the network, with a password. What a school needs, because a technician has no ssh tunnel. Set one:

pupitre-web --mot-de-passe /etc/pupitre/console-password

It asks twice, writes the hash in mode 600, and prints nothing else. Then point [web] password_file at that file and set [web] listen. There is deliberately no way to pass the password on the command line: it would be visible to everyone in ps and kept in the shell history.

The console REFUSES to start on a non loopback address without a password, and exits 2 saying so: a mere warning is not read at the moment it matters, and an unauthenticated console could be exposed by inattention. It also refuses a password file readable beyond its owner, because a hash is still attackable offline and a 0644 in /etc is a common enough mistake to deserve a refusal rather than a warning.

How it behaves once a password is set. /login serves a form; a page asked without a session is redirected there, while an API call gets 401, so a technician never sees raw JSON and the page can reopen a session without replacing its data with HTML. The session cookie is HttpOnly, SameSite=Strict, and lives four hours. Five failed attempts per source per five minutes, after which even the right password is refused for a while. Sessions live in memory only: restarting the console logs everyone out. Changing the password invalidates every session, and that is the only revocation there is.

The Host, Origin and Content-Type checks still apply to every action, session or not: they are what stops a page in your own browser from driving the console across origins, and they apply to the login itself.

What this does NOT give you

  • No encryption. The password and the session token travel in clear HTTP. On an admin VLAN that is a defensible choice; on the pupils' network it is not. A TLS reverse proxy is the answer, and pupitre does not provide one.
  • No accounts. One shared password, so no trace of WHO acted. A single classroom has one technician; an authority with several will want accounts.
  • No audit log. Actions are logged, the person behind them is not.

Configuration

/etc/pupitre/pupitre.conf, section [web]:

Key Default Meaning
listen 127.0.0.1 address the console binds
port 8080 TCP port
password_file empty file holding the password hash; required for a non loopback listen

The rest comes from the same file as the manager: [server] interface, ip, seats (15 by default, 0 means no seat limit), enroll, and the paths of the seats table and the run directory.

The page

Header. 13 seats, 11 assigned, 9 in session, 2 stations without a seat, the enrolment switch as configured, and a dot: green while the console answers, red with Console unreachable after 10 s without an answer. A red manager not running badge appears within about 10 s of the manager's heartbeat stopping (/run/pupitre/manager.alive).

Seat cards. One card per seat number in the table: the seat, its label (click to edit, Enter saves, Escape cancels), a badge, the live thumbnail (refreshed every 10 s, click for the 640 px view refreshed every 3 s, Escape or click closes it), the device name, serial, MAC, IP, when it was last seen, the feedback strip and the buttons.

Badge Meaning
In session the seat unit runs and the launcher reported ready
Starting 2/5 the unit runs, the launcher is on attempt 2 of 5; plain Starting while systemd waits to restart the unit
Stopped the unit is inactive
Held (renaming) the console is talking to the device, the manager leaves it alone
Failed systemd refuses to start the unit again: more than 5 starts within 300 s. Only a seat that fails within seconds gets there (a missing seat env file, a display that will not open). A seat whose client never reaches the live stream spends at least 375 s of launcher cooldowns in each start, so it cycles through Starting and never shows Failed. The manager resets a failed unit and starts it again itself, waiting twice as long each time up to 900 s; Restart resets it at once
Offline no broadcast from the client for 30 s (card dimmed)
Never seen the MAC is in the table but the client never announced itself (dimmed)
Button What it does
Identify in session: a big Seat N on the client's screen for 5 s; stopped or failed: the device is temporarily renamed SEAT-N for 20 s, then its previous name is restored (refused on a device still carrying its factory name, rename it first). The device's waiting screen does not show its name, so this rename has no visible effect on the client
Restart reset-failed then restart of the seat unit; the client goes dark for 30 to 90 s
Relink rebuilds the device link without touching the desktop (SIGUSR1 to the seat's launcher); the pupil keeps their session; 15 to 60 s
Logs the tail of one of the seat's logs (session, encoder, desktop, Xvfb, input, cursor, key), at most 500 lines; the client's log, which carries input reports, is never served
Rename device the name the device announces in its broadcasts (its waiting screen does not show it), 1 to 16 printable ASCII characters; only on a stopped or failed seat, unassign first otherwise; the client briefly leaves its waiting screen during the dialogue
Label free text kept on the server, never sent to the device
Unassign stops the seat and parks the station: it keeps its label, is listed below, and is not auto enrolled again

Stations without a seat. New and parked stations, with a seat picker (the lowest free number is preselected, other… takes any number), Assign, Identify, Rename device, Label, and Forget for a parked station (with enrolment on, a forgotten station gets a seat at its next announce).

Feedback. While an action runs its card is locked and shows the progress message; a green strip stays 8 s on success, a red one until dismissed on failure. A rename, or an identify on a seat that is not in session, is confirmed by the device itself: the name it announces in its next broadcast is the proof.

Two browser tabs, or two operators, cannot double an action: one action per client at a time, the second one is answered busy.

Dev mode

pupitre-web --state-dir DIR --no-systemd --port 0 runs the console unprivileged against a directory (DIR/seats.json, DIR/run/, DIR/units.json for the unit states) and prints listening on 127.0.0.1:PORT. systemctl calls, the DCP dialogue and the overlay are logged instead of run, and the identify hold lasts 2 s instead of 20 s.

The Playwright scenarios of tests/e2e/ run against it. Playwright is not a Debian dependency and numpy comes from python3-numpy, so use a venv that sees the system packages:

python3 -m venv --system-site-packages .venv
.venv/bin/pip install playwright
.venv/bin/playwright install chromium
PUPITRE_PLAYWRIGHT=1 .venv/bin/python -m pytest tests/e2e -m e2e

Without PUPITRE_PLAYWRIGHT=1 the scenarios are skipped, and the Debian build runs pytest -m "not e2e".