# SilverOS Welcome — Specification (v1) > **Status**: design approved 2026-06-09. Part of **SilverMetal Enhanced — Windows**. > First-logon onboarding app that turns a hardened image into a personalised device: > the user picks a **flavour**, creates their **account + BitLocker PIN**, and the app > applies the chosen configuration by orchestrating the existing §A–H hardening modules. Bound by [`hardening-spec.md`](hardening-spec.md), [`iso-builder.md`](iso-builder.md), and the SilverMetal [`../docs/design-principles.md`](../docs/design-principles.md). ## 1. Purpose & positioning Today the image applies **one fixed** hardening config at first boot (`SetupComplete.cmd` → §A–H modules). The Welcome app makes "SilverMetal Enhanced — Windows" a **selectable platform**: a friendly first-logon wizard where the buyer chooses *what the device is for* (a **flavour** = config profile + app set), sets up their own account, and the device configures itself accordingly. It also resolves two real gaps found during VM validation: - **No throwaway credential in the public repo** — the committed account becomes a locked **bootstrap** account that **self-destructs** after onboarding. - **BitLocker PIN enrollment** — collected interactively in the wizard (BitLocker TPM+PIN can't be fully automated; this is the right place for it). **v1 = interactive auto-launch only.** A fully-silent pre-baked SKU mode is a later phase (§8). ## 2. Architecture & components A **Blazor Hybrid (.NET MAUI + WebView2)** desktop app, published **self-contained** (bundled runtime) and baked into the image. Reuses SilverSHELL components + the SilverDESK "Welcome" wizard aesthetic (Mercury). Four well-bounded units: | Unit | Responsibility | Depends on | |---|---|---| | **Welcome.Shell** | MAUI host + wizard UI (the steps in §3). What the user sees. | Welcome.Flavours, Welcome.Apply | | **Welcome.Flavours** | Load + validate flavour manifests from `windows/flavours/*.json` (baked into the image). | manifest schema | | **Welcome.Apply** | Orchestrator: run the §A–H PowerShell modules per the manifest, install the app set, create the real account, enrol BitLocker, tear down the bootstrap. | PowerShell modules, Windows account/BitLocker APIs | | **Welcome.Bootstrap** | Answer-file / AutoLogon / RunOnce wiring that auto-logs-in and launches the app. | autounattend, build.ps1 | Each unit is independently testable: Welcome.Apply can run headless against a flavour manifest (no UI); Welcome.Flavours validates manifests without applying; the Shell can be run with a mock applier. ## 3. Boot → onboard → handoff flow (bootstrap lifecycle) 1. **Image** ships a **locked bootstrap admin account** (`sm-bootstrap`) with **AutoLogon (LogonCount=1)** and a RunOnce/custom-shell that launches the Welcome app in a kiosk-ish session (no taskbar/Explorer escape). The §A–H modules **no longer run from `SetupComplete`** — `SetupComplete` only stages the payload; the Welcome app applies it. 2. **First logon** (auto, as `sm-bootstrap`) → Welcome app launches. Wizard steps: - **Welcome** — brand intro. - **What's this device for?** — persona → selects a **flavour** (cards: Privacy-Max / Daily-Driver / Journalist / Developer). - **Create your account** — daily username + password (a **Standard User**), plus a separate **device-administrator password** (for the `SilverOS Admin` elevation account). - **Secure this device** — set a **BitLocker PIN** (≥ flavour-min). - **Preferences** — a few toggles (e.g. telemetry-floor confirmation, browser default, timezone). - **Apply** — progress UI while Welcome.Apply runs. - **Done** — "restart to finish." 3. **Apply** (Welcome.Apply, elevated): - run the flavour's **hardening modules** (per manifest) + **app-set** install; - **create the daily Standard account** + the separate **`SilverOS Admin`** account; - **enrol BitLocker TPM+PIN** with the entered PIN; - remove AutoLogon + the RunOnce. 4. **Self-destruct + reboot** — delete the `sm-bootstrap` account, then **reboot** into the real account (also activates VBS/Credential Guard, which need a reboot). ## 4. Flavour manifest model A flavour is a JSON file in `windows/flavours/` baked into the image: ```jsonc { "id": "privacy-max", "label": "Privacy-Max", "description": "Maximum lockdown. Everything Microsoft exposes, turned to its tightest.", "hardening": { "modules": ["00","01","02","03","04","05","06","07"], // which §A-H modules "params": { "wdac": "enforce", "telemetry": "security" } // per-module overrides }, "appSet": ["SilverBrowser","SilverVPN","SilverKeys","SilverDuress"], "settings": { "defaultBrowser": "SilverBrowser", "autoLock": 120 } } ``` **Welcome.Apply orchestrates the existing PowerShell §A–H modules** per `hardening.modules`/`params` — PowerShell stays the single source of truth for *what hardening does* (also reusable headless for the future silent SKU). *(Rejected alternative: re-implement hardening in a .NET library — cleaner for an all-.NET app but duplicates the proven logic.)* The Shell renders one card per manifest; adding a flavour = adding a JSON file. **Hardened baseline applies to ALL flavours** — no flavour is unhardened. Every flavour runs the core §A–H baseline (telemetry floor, BitLocker+PIN, VBS/HVCI, firewall default-deny, Defender/ASR, etc.); flavours only **tune strictness above that baseline** and choose the app set. There is no "off." **Starter flavours (v1):** **Daily-Driver** *(the **default / recommended** — balanced: full baseline, WDAC in audit, everyday app set)*, **Privacy-Max** *(strictest: WDAC enforce, tightest toggles, removable-storage controls — opt-in, not default)*, **Journalist** *(privacy + duress/comms emphasis)*, **Developer** *(baseline + dev tooling allowances, e.g. WDAC audit + dev-signed allowances)*. The wizard pre-selects **Daily-Driver**. ## 5. Account & BitLocker integration - The user's **daily account is a Standard User** (least-privilege by default — a real hardening win, and the SilverLABS default). Elevation uses a **separate local Administrator account** (`SilverOS Admin`) created alongside it; the user sets the admin password in the account step (distinct from their daily password). *(Single-admin/"personal-device owner" remains available as a flavour/pref option, but is not the default.)* - **BitLocker TPM+PIN** is enrolled with the user's PIN during Apply (`Enable-BitLocker -TpmAndPinProtector`), satisfying module C's interactive step. Recovery key stored offline / shown to the user (never cloud-escrowed) per `hardening-spec.md` §C. - The **bootstrap account** is admin only so the app can run hardening + manage accounts; it is deleted in step 4 so no standing extra admin remains. ## 6. Build & repo integration - New **`windows/welcome/`** (MAUI app source) + **`windows/flavours/*.json`**. - **`build.ps1`** gains a stage that drops the **published Welcome app + flavours** into the image (`/Program Files/SilverMetal/Welcome` + a flavours dir), and wires the bootstrap (AutoLogon + RunOnce) via the answer file / offline registry. - The **answer file** switches from "create `silvermetal`/`open sesame`" to "create **`sm-bootstrap`** + AutoLogon + launch Welcome." `SetupComplete.cmd` stops running the §A–H modules directly (the Welcome app runs them). - A repo switch (**`vars.SILVERMETAL_WELCOME_ENABLED`**, default on) controls whether the build wires onboarding or falls back to today's direct-hardening behaviour (so the current validated path remains available). ## 7. Testing - **Welcome.Flavours**: unit tests validate every shipped manifest against the schema. - **Welcome.Apply**: headless run against each flavour on the VM (reuses the SLAB01 VM-test harness) → assert the firstboot/apply log shows the expected modules + the real account + BitLocker protector + bootstrap removed. - **Welcome.Shell**: bUnit-style component tests for step navigation/validation; a manual VM pass for the full wizard. - The existing **offline ISO assertions** extend to check the Welcome app + flavours are baked. ## 8. v1 scope **In:** interactive auto-launch onboarding; persona→flavour; account + BitLocker-PIN creation; prefs; apply-via-PowerShell-modules; bootstrap self-destruct; 3–4 starter flavours; build/answer-file wiring + `WELCOME_ENABLED` switch. **Out (later phases):** - **Silent pre-baked mode** (apply a baked flavour + pre-set account with no wizard — the "we did everything for you" SKU). - **SilverDESK / fleet enrollment** role. - **Shared flavour model with the Linux line.** - **Real Stack app installs** — the app-set installer is wired, but Stack entries remain stubs until native Windows Stack builds exist (M4). - **Single-admin / "personal-device owner"** account model as a selectable option (Standard-user daily + separate admin is the v1 default). ## 9. Open questions / risks - **WebView2 + .NET runtime in the image** — confirm WebView2 Evergreen is present on IoT Enterprise LTSC (or bundle the fixed-version runtime); self-contained publish covers .NET. - **Kiosk lock-down of the bootstrap session** — ensure the user can't escape the wizard to a privileged shell before the real account exists. - **Apply failure handling** — if a module fails mid-apply, the wizard must surface it and leave the device in a recoverable state (don't delete bootstrap until apply + account creation succeed). - **Admin-credential UX** — confirm the daily/admin two-password step is clear (vs. generating + showing the admin secret once); ensure password strength rules on both. ## 10. VM end-to-end validation — VALIDATED 2026-06-09 Built the packed ISO via CI (`build-iso-windows.yaml`, all green: 22 unit/bUnit tests + ISO bake + offline payload assertions) and ran it on SLAB01 VM 102 (UEFI + SecureBoot pre-enrolled keys + vTPM 2.0). **Verified end-to-end:** - Boots UEFI/SecureBoot; forced-legacy hands-off install onto a blank disk; OOBE auto-completes. - `sm-bootstrap` auto-logs in; the Welcome app **auto-launches elevated** and renders the wizard (Mercury theme, bundled fonts). - All four flavours load from the baked `flavours/*.json`; **Daily-Driver** pre-selected. - Drove the full wizard: Welcome → Flavour → Account (created `alice` + admin password + BitLocker PIN) → Prefs → Apply → "All Done!". - Post-apply, offline + on-login assertions: **`alice` created** (Standard daily user) and **`SilverOS Admin` created** (separate elevation account) both appear on the login screen; **`sm-bootstrap` removed**; **no auto-login** (AutoAdminLogon disabled → teardown ran); **BitLocker enabled** (volume encrypted, `-FVE-FS-`). Account creation + bootstrap teardown require elevation, confirming the app runs elevated. **Three deployment bugs found + fixed during validation** (only a real run surfaced them): (1) WebView2 user-data folder defaulted to non-writable `Program Files` → redirected to LocalAppData; (2) FirstLogonCommands run un-elevated → bake `ConsentPromptBehaviorAdmin=0` offline + launch via `Start-Process -Verb RunAs`; (3) AccountStep `Next` never enabled because the wizard host didn't re-render on child validity change → added an `OnValidityChanged` callback. Also a separate CI fix: bUnit tests must reference a Razor Class Library, not the MAUI app assembly (else `WindowsAppRuntime` `DllNotFound` on the clean runner). **Follow-ups (not v1-pipeline blockers):** - **BitLocker PIN not enforced** — the device booted with no pre-boot PIN prompt (TPM-only auto-unlock). The intended TPM+PIN protector did not take effect, most likely because the FVE "require startup PIN" policy (`UseAdvancedStartup`/`UseTPMPIN`) wasn't set before `Enable-BitLocker`. The user's PIN currently provides no boot protection — fix before shipping. - **Apply services don't check PowerShell exit codes** — `AccountService`/`BitLockerService`/`BootstrapService` run `powershell.exe` but ignore `ProcessResult.ExitCode`, so silent failures (like the BitLocker PIN degradation) go unsurfaced. They should check exit codes and fail/log loudly.