382 lines
17 KiB
Markdown
382 lines
17 KiB
Markdown
# 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 '<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
|
||
|
||
```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
|
||
<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
|
||
|
||
```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.
|