Vestibule/DEPLOYMENT.md

17 KiB
Raw Permalink Blame History

Deployment — Vestibule

This is the operator runbook for turning a stock machine into a Vestibule kiosk: OS-level lockdown provisioning and packaging, on both platforms, with one-command deprovision.

What provisioning configures (Windows)          What it configures (Linux)
────────────────────────────────────────          ──────────────────────────
AssignedAccess single-app kiosk (MDM bridge)    cage Wayland session on a VT
  └ step-down: Shell Launcher (Ent/Edu)           └ /etc/systemd/system/
Start Menu shortcut with stable AUMID              vestibule-kiosk.service
Dedicated kiosk local account                   /usr/local/bin/usher
Automatic logon (registry)                      /usr/local/bin/vestibule-kiosk-launch
C:\ProgramData\Vestibule\kiosk.env              /etc/vestibule/kiosk.env
policies.json (extension force-installed)       policies.json (same mechanism)
Per-user bootstrap at kiosk logon               Native Messaging manifests for
(kiosk-launch.ps1: usher, HKCU host             the kiosk user
 registration, profile, crash restart)          /opt/vestibule staging tree

Everything the scripts do is reversible — see Deprovision for the one-command exit on each platform.

Platform priority: Linux. The Linux path is the supported production target — provisioned, packaged, and installed with no signing gate. The Windows path is complete and CI-validated but ships unsigned: a code-signing certificate is an unfunded line item and stays that way unless a donation earmarks it (info@dcos.net), so SmartScreen warns on the installer and the usher binary. Windows operators verify the SHA-256 from the release notes; Linux operators install and go.


Choosing a path

Situation Command
Linux kiosk, native LibreWolf sudo scripts/provision-kiosk.sh --librewolf native
Linux kiosk, Flatpak LibreWolf sudo scripts/provision-kiosk.sh --librewolf flatpak
Linux kiosk, native Firefox sudo scripts/provision-kiosk.sh --firefox native
Linux kiosk, Flatpak Firefox sudo scripts/provision-kiosk.sh --firefox flatpak
Windows kiosk, interactive Run the installer, tick the wizard checkbox — or provision-kiosk.ps1 standalone
Windows kiosk, unattended (MDM/CI) Vestibule-Setup-1.2.2.exe /VERYSILENT then provision-kiosk.ps1 -Quiet (exit codes below)
Just want to see what it would do --check on either platform

Browser support

Vestibule runs on two Gecko browsers. The extension, usher, and lockdown policies are identical for both — only the deployment paths differ.

Browser / flavor Windows Linux native Linux Flatpak Linux snap
LibreWolf Program Files install, distribution\policies.json install-dir distribution/ system Flatpak (policies in app dir) —
Firefox / ESR Program Files install, distribution\policies.json distro package → /etc/firefox/policies system Flatpak (policies + XPI in kiosk home) Ubuntu snap (policies + XPI in kiosk home)

Notes:

  • Auto-detect order: LibreWolf native → LibreWolf Flatpak → Firefox native → Firefox Flatpak → Firefox snap. Override with --librewolf/--firefox (Linux) or -Browser (Windows).
  • Firefox ESR is recommended for kiosks — slower release cadence, same enterprise-policy engine.
  • Flatpak/snap Firefox keep policies and the XPI in the kiosk user's home (~/.var/app/... / ~/snap/firefox/common), so they survive browser updates — unlike LibreWolf's system-Flatpak deploy.
  • Trademark: Vestibule configures an existing Firefox install; it never downloads or redistributes Firefox. (The original choice of LibreWolf as the default base was about redistribution — deploying against an installed Firefox has no trademark exposure.)
  • librewolf.overrides.cfg is LibreWolf-only; on Firefox it is skipped with a notice (Firefox ignores it safely anyway).

Windows

Prerequisites

  • Windows 10/11 Pro, Enterprise, or Education (Home has no AssignedAccess/Shell Launcher — the script fails fast with exit 2)
  • LibreWolf or Firefox/ESR installed (C:\Program Files\... — auto-detected; override with -Browser librewolf|firefox)
  • The Vestibule installer run (or a repo checkout with a built usher: cd helper; cargo build --release)
  • PowerShell run as administrator

Interactive provisioning

powershell -ExecutionPolicy Bypass -File scripts\provision-kiosk.ps1

The wizard prompts for the home URL and generates the kiosk account password. Everything else is automatic: staging, XPI build, policies merge, shortcut + AUMID, AssignedAccess, autologon. Exit code 10 means "reboot to activate".

Unattended provisioning

powershell -ExecutionPolicy Bypass -File scripts\provision-kiosk.ps1 `
  -KioskUser Kiosk `
  -KioskPassword '<from-your-secrets-store>' `
  -HomeUrl https://checkin.example.org `
  -Quiet -Restart

Parameters:

Parameter Default Meaning
-Browser auto librewolf / firefox / auto (LibreWolf preferred)
-KioskUser VestibuleKiosk Dedicated local account (created or reused)
-KioskPassword generated + printed Account password (also used for autologon)
-HomeUrl about:blank Page the kiosk session opens
-InstallRoot detected Staged install location
-Quiet off No prompts — for MDM/CI rollout
-Check off Validate prerequisites only, change nothing
-ShellLauncher off Force Shell Launcher instead of the AssignedAccess bridge
-NoAutoLogon off Skip autologon (kiosk starts after manual logon)
-Restart off Reboot automatically when required
-InstallOverridesCfg off Also deploy librewolf.overrides.cfg (SSO/telehealth)

Exit codes (also used by CI):

Code Meaning
0 Success, no reboot needed
10 Success — reboot required to activate
2 Unsupported (Home edition / not elevated / not Windows)
3 Prerequisite missing (LibreWolf, usher)
4 Kiosk account error
5 Lockdown apply failed (AssignedAccess AND Shell Launcher)
6 Invalid parameters

How the lockdown is applied

  1. AssignedAccess via the MDM WMI bridge (MDM_AssignedAccess SetSingleAppKiosk) — the supported scriptable path on Pro and higher. The kiosk app is a Start Menu shortcut carrying a stable AppUserModelID (Vestibule.Kiosk), verified through Get-StartApps before the XML is applied.
  2. Shell Launcher step-down (WESL_UserSetting.SetCustomShell) on Enterprise/Education when the bridge rejects the config. The feature is enabled on demand; that path needs one extra reboot.
  3. kiosk-launch.ps1 is the actual shell process. It runs as the kiosk user (no admin): installs usher per-user, registers the Native Messaging host under HKCU, creates vestibule-profile, then launches librewolf --kiosk -P vestibule-profile <home-url> and relaunches it if it exits. Log: %LOCALAPPDATA%\Vestibule\kiosk-launch.log.

Verification checklist

  • Reboot → machine logs in as the kiosk account automatically
  • LibreWolf opens full-screen on the home URL, no tabs/URL bar
  • Browser Console (Ctrl+Shift+J) shows [vestibule] usher hello: usher 1.2.2
  • Any domain not on the safelist shows the Vestibule block page (default policy; the home URL's domain is always permitted)
  • Idle 5 min → session resets to home URL, data cleared

Admin escape hatches

  • Ctrl+Alt+Del still works under AssignedAccess — Sign out / switch user to reach an admin account.
  • Hold Shift during boot to bypass automatic logon once.
  • Kiosk account has no interactive way to change its password (locked via UserMayNotChangePassword).

Building the installer

cd helper; cargo build --release; cd ..
powershell -ExecutionPolicy Bypass -File packaging\build-installer.ps1

Requires Inno Setup 6 (choco install innosetup -y). Output: packaging\Output\Vestibule-Setup-1.2.2.exe. CI builds it on every push (see CI artifacts).


Linux

Prerequisites

  • Any systemd distribution
  • cage (the Wayland kiosk compositor) — Debian 12+/Ubuntu 24.04+/ Fedora/Arch packages it; elsewhere build from cage-kiosk/cage
  • LibreWolf or Firefox (ESR recommended) — native package (LibreWolf install docs, distro Firefox, system-wide Flatpak, or the Ubuntu snap)
  • python3 (policies merge + XPI build), dbus (session bus for cage children)
  • A built usher: cd helper && cargo build --release

The script never auto-installs anything: if something is missing it prints the exact per-distro commands and exits 3.

Interactive provisioning

sudo ./scripts/provision-kiosk.sh

Unattended provisioning

sudo ./scripts/provision-kiosk.sh \
  --home-url https://checkin.example.org \
  --kiosk-user kiosk \
  --tty 2 \
  --librewolf native \
  --yes --start

Parameters:

Parameter Default Meaning
--home-url URL about:blank Page the kiosk session opens
--kiosk-user NAME vestibule-kiosk Account (created with locked password)
--tty N 2 VT for the cage session (1–12)
--librewolf FLAVOR auto Use LibreWolf: native / flatpak
--firefox FLAVOR auto Use Firefox: native / flatpak / snap (mutually exclusive with --librewolf)
--usher-bin PATH auto Explicit usher binary
--start off Start the session immediately (otherwise: enable only)
--yes off Skip confirmation
--check off Validate prerequisites only

Exit codes: 0 success · 2 not root / no systemd · 3 prerequisite missing · 4 account error · 5 unit failure · 6 bad parameters.

How the lockdown is applied

  • vestibule-kiosk.service is a system unit that IS the graphical session: cage starts on the chosen VT at boot as the kiosk user via PAMName=login (runtime dir from pam_systemd), no display manager involved, Restart=always for crash recovery.
  • /usr/local/bin/vestibule-kiosk-launch execs dbus-run-session -- cage -d -- librewolf --kiosk -P vestibule-profile <url> (or flatpak run io.gitlab.librewolf-community … for the Flatpak flavor). MOZ_ENABLE_WAYLAND=1 is mandatory — Gecko defaults to X11 and cage ships no Xwayland.
  • The kiosk account is created with a locked password: it can never be logged into interactively, only entered via the systemd session.
  • policies.json is deep-merged into LibreWolf's distribution/ dir (existing file backed up to policies.json.vestibule-bak) and force-installs the extension XPI on every browser start — no about:debugging step on the kiosk.

Reconfigure at any time by editing /etc/vestibule/kiosk.env (home URL, browser + flavor, cage flags) — the unit re-reads it on every start.

Where policies land per browser (Linux)

Flavor policies.json XPI Update-safe?
LibreWolf native <install>/distribution/ /opt/vestibule/extension/ yes
LibreWolf Flatpak Flatpak app dir files/librewolf/distribution/ kiosk home ~/.var/app/<id>/ no — re-run after updates
Firefox native /etc/firefox/policies/ /opt/vestibule/extension/ yes
Firefox Flatpak kiosk home ~/.var/app/org.mozilla.firefox/.mozilla/policies/ kiosk home yes
Firefox snap kiosk home ~/snap/firefox/common/.mozilla/policies/ kiosk home yes

For Flatpak/snap flavors the browser's filesystem is the kiosk home remap — that is why the XPI is copied there and install_url points inside the sandbox-visible home rather than /opt/vestibule.

Verification checklist

  • systemctl status vestibule-kiosk — active (running)
  • The VT shows LibreWolf full-screen on the home URL
  • journalctl -u vestibule-kiosk -f shows the launcher + cage
  • Ctrl+Alt+F3 switches to a text VT (cage -d); Ctrl+Alt+F2 returns
  • sudo systemctl restart vestibule-kiosk recovers from a killed browser within seconds

Flatpak LibreWolf notes (honest limitations)

  1. Updates wipe the policy layer. The policies live inside /var/lib/flatpak/app/io.gitlab.librewolf-community/…, which is replaced on every update. Re-run provision-kiosk.sh afterwards. The extension's own session sanitization (layers 2–3) is unaffected.
  2. Native Messaging under the Flatpak depends on the flatpak's host visibility. Manifests are written to all five conventional locations for the kiosk user; if the flatpak ignores them, unlock falls back to "not configured" while URL policy and session resets keep working. Native LibreWolf (or Firefox native) has no such caveat.

The Firefox Flatpak and snap do not share limitation 1: their policies and XPI live in the kiosk user's home, which updates never touch.

Building the Flatpak

./packaging/build-flatpak.sh

Output: packaging/Vestibule-1.2.2.flatpak (requires flatpak-builder; first run downloads the Freedesktop 24.08 SDK + Rust extension). The Flatpak carries the whole deployment kit — flatpak run net.dcos.Vestibule provision prints the exact host-side command.


Deprovision

Both deprovision scripts reset the machine to its pre-Vestibule state. Each policy directory is returned to its baseline: the operator's backed-up policies.json is reinstalled, or Vestibule's generated file is deleted where no baseline exists (byte-for-byte equality is asserted by the test suite).

# Windows — remove lockdown, autologon, shortcut, policies.
# Optional: -RemoveKioskAccount -RemoveInstall -Restart
powershell -ExecutionPolicy Bypass -File scripts\deprovision-kiosk.ps1
# Linux — stop/disable/remove the unit, launcher, env, policies, manifests.
# Optional: --remove-user --remove-opt --remove-usher
sudo ./scripts/deprovision-kiosk.sh

The Windows uninstaller (Add/Remove Programs) offers to run the deprovision step automatically.


Security notes operators must read

  1. Windows autologon stores the kiosk password in plaintext registry (Winlogon \ DefaultPassword). On a locked-down single-purpose appliance this is an accepted trade-off — the account is the least privileged thing on the machine and the browser is the only shell. Use -NoAutoLogon and manual logon if your threat model disagrees.
  2. The installer and usher binary are unsigned — a standing decision, not an oversight. A code-signing certificate costs $200–500/year and stays an unfunded line item unless a donation earmarks it (info@dcos.net). SmartScreen will warn; verify the SHA-256 from the release notes. This is why Linux is the primary target — it has no signing gate. See README "Honest limitations".
  3. The extension is policy-installed from disk (force_installed). That is deliberate: kiosks have no AMO session, and the policy re-installs the XPI on every start if it is removed.
  4. Linux kiosk account password stays locked. There is nothing to brute-force; the session is entered only via systemd.

Troubleshooting

Symptom Platform Fix
Black screen after enabling cage Linux MOZ_ENABLE_WAYLAND=1 missing (our launcher sets it); check journalctl -u vestibule-kiosk
cage fails in a VM Linux No DRM: add WLR_LIBINPUT_NO_DEVICES=1 and WLR_RENDERER=pixman to the unit (systemctl edit vestibule-kiosk)
Flatpak LibreWolf won't start under cage Linux Missing session bus — the launcher wraps everything in dbus-run-session; check flatpak is installed system-wide, not per-user
Snap Firefox detected but policies ignored Linux Verify ~/snap/firefox/common/.mozilla/policies/policies.json exists for the kiosk user (not your own); re-run provision with --firefox snap
AssignedAccess applies but kiosk shows black/Start Windows AUMID didn't resolve — check Get-StartApps | ? AppID -eq Vestibule.Kiosk; re-run provisioning after a reboot
[vestibule] usher hello missing both Per-user bootstrap failed: read %LOCALAPPDATA%\Vestibule\kiosk-launch.log (Windows) or check NM manifests in the kiosk home (Linux)
AssignedAccess XML rejected (exit 5) Windows Use -ShellLauncher on Enterprise/Education, or apply via Settings → Accounts → Other users → Set up kiosk
Policies not applied both policies.json needs a full browser restart; verify it parses and that the distribution dir matches the running LibreWolf
exit 3 on --check both Prerequisite missing — the script prints the exact install commands

CI artifacts

Every push to main builds (.github/workflows/ci.yml):

Artifact Job Contents
vestibule-setup-windows package-windows Vestibule-Setup-1.2.2.exe (Inno Setup), the extension XPI
vestibule-flatpak-linux package-linux Vestibule-1.2.2.flatpak (Freedesktop 24.08)
vestibule-usher-linux package-linux The Linux usher binary from the same commit

Download from the workflow run page. Tag a release to attach them to a GitHub Release.