# 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](#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 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 powershell -ExecutionPolicy Bypass -File scripts\provision-kiosk.ps1 ` -KioskUser Kiosk ` -KioskPassword '' ` -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 ` 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 ```powershell 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](#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](https://github.com/cage-kiosk/cage) - **LibreWolf or Firefox** (ESR recommended) — native package ([LibreWolf install docs](https://librewolf.net/installation/linux/), 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 ```sh sudo ./scripts/provision-kiosk.sh ``` ### Unattended provisioning ```sh 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 ` (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 | `/distribution/` | `/opt/vestibule/extension/` | yes | | LibreWolf Flatpak | Flatpak app dir `files/librewolf/distribution/` | kiosk home `~/.var/app//` | 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 ```sh ./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). ```powershell # Windows — remove lockdown, autologon, shortcut, policies. # Optional: -RemoveKioskAccount -RemoveInstall -Restart powershell -ExecutionPolicy Bypass -File scripts\deprovision-kiosk.ps1 ``` ```sh # 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.