Per-role app-install picker for the Welcome wizard: catalog.json + AppsStep + winget install engine (phased, swappable source for a future curated mirror). Stack stays auto-installed; picker adds role apps + privacy-trimmed essentials. Approved in brainstorming. Next: writing-plans. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8.6 KiB
SilverOS Welcome — Role App Recipes
Status: design — 2026-06-09. Approved in brainstorming. Adds a per-role app-install picker to the first-logon Welcome wizard. Builds on the wizard in
windows/welcome/and the flavour model inwindows/flavours/.
1. Goal
Make the wizard's role/flavour selection do something: after choosing a role, the user sees a grouped, pre-checked list of apps to install during onboarding — role-relevant tools plus privacy-trimmed essentials — and the chosen apps are installed as part of Apply.
2. Decisions (locked in brainstorming)
- Install engine: winget, phased. v1 uses winget (
winget install --id <id> --silent). IoT Enterprise LTSC ships without winget, so the engine bootstraps the App Installer at apply time. Every catalog app has a winget id. The catalog's per-appsourceobject is swappable to a curated SilverMetal mirror later (the air-gap/privacy end-state) without changing the UI or engine contract. - Stack stays auto-installed. The SilverLABS Stack (
SilverBrowser=ungoogled-chromium rebrand,SilverVPN,SilverKeys, …) continues to install automatically via each flavour'sappSetand is not in the picker (shown as "included"). The picker adds role apps plus opt-in privacy-trimmed third-party apps (incl. vanilla ungoogled-chromium and a Thunderbird email option). - Per-role lists + Essentials defaults as in §4 (operator-approved; editable in the JSON).
- WDAC caveat: Developer/Daily-Driver run app-control in audit (apps run); Privacy-Max/ Journalist run enforce (third-party apps blocked until allow-listed). v1 installs anyway and surfaces a clear note for enforce-mode roles; full WDAC allow-listing is a follow-up.
3. Architecture (four small units)
windows/apps/catalog.json # the app catalog (staged into the image like flavours/)
SilverOS.Welcome.Core/Apps/
AppCatalog.cs # record + loader (mirrors FlavourManifest)
AppCatalogEntry.cs
IAppInstaller.cs / AppInstaller.cs # winget bootstrap + per-app install + configure
SilverOS.Welcome.UI/Components/Steps/
AppsStep.razor # new wizard step: grouped checkboxes
WizardState.cs # + SelectedApps (ids)
ApplyService.cs # calls IAppInstaller after the Stack/hardening
3a. Catalog schema (catalog.json)
{
"schemaVersion": 1,
"apps": [
{
"id": "vscodium",
"name": "VSCodium",
"description": "Telemetry-free VS Code build.",
"source": { "winget": "VSCodium.VSCodium" },
"group": "developer",
"roles": ["developer"],
"defaultFor": ["developer"],
"configure": null
},
{
"id": "ungoogled-chromium",
"name": "ungoogled-chromium",
"description": "Chromium with Google integration stripped.",
"source": { "winget": "eloston.ungoogled-chromium" },
"group": "essentials",
"roles": ["essentials"],
"defaultFor": [],
"configure": "ungoogled-chromium.ps1"
}
]
}
roles: which roles are offered this app (essentials= all roles).defaultFor: roles where the checkbox is pre-checked.source.winget: the winget id (onlysourcekey in v1; a futuremirrorkey is additive).configure: optional post-install script (relative towindows/apps/configure/), e.g. the ungoogled-chromium policy that enables the Chrome Web Store + sets safe search/suggestions.
3b. AppCatalog loader
Mirrors FlavourManifest/IFlavourLoader: AppCatalogEntry record + AppCatalog.Load(dir)
reading catalog.json with the same JsonSerializerOptions (case-insensitive, comments,
trailing commas). A pure function AppsForRole(role) returns the entries to display grouped,
and DefaultSelectionForRole(role) returns the pre-checked ids.
3c. AppsStep (new wizard step, after Flavour)
- Renders Essentials group first, then the chosen role's group; each app a checkbox with
name + description; pre-checked from
DefaultSelectionForRole. - Writes the set of selected ids into
WizardState.SelectedApps. - Always valid (zero apps is allowed) — Next is never blocked. Notifies the host like the other
steps (so Next state is correct immediately — same
OnSelected/StateHasChangedpattern as the FlavourStep fix). Routes.razorgains a step between Flavour (1) and Account; step indices/titles shift by one.
3d. AppInstaller (Apply-step engine)
IAppInstaller.InstallAsync(IReadOnlyList<AppCatalogEntry> apps, IProgress<ApplyProgress>, ct):
- Bootstrap winget if absent: install
Microsoft.DesktopAppInstaller+ deps (Microsoft.VCLibs…,Microsoft.UI.Xaml…) viaAdd-AppxProvisionedPackage/staged msix. - Per app:
winget install --id <source.winget> --silent --accept-package-agreements --accept-source-agreements; capture exit code; continue on failure (a bad app must not fail onboarding); runconfigurescript if present and the install succeeded. - Return a per-app result list (id → installed/failed); ApplyService stows a summary for the Done step ("Installed N of M apps; failed: …").
Runs after hardening + Stack + accounts, before BitLocker (so encryption is last), via a
new progress.Report(new("Installing apps", …)) stage.
4. Catalog contents (v1 — editable)
| Group | App | winget id | default-checked for |
|---|---|---|---|
| essentials | Thunderbird | Mozilla.Thunderbird |
all |
| essentials | VLC | VideoLAN.VLC |
all |
| essentials | 7-Zip | 7zip.7zip |
all |
| essentials | LibreOffice | TheDocumentFoundation.LibreOffice |
all |
| essentials | ungoogled-chromium | eloston.ungoogled-chromium |
— (opt-in; configured) |
| essentials | KeePassXC | KeePassXCTeam.KeePassXC |
— |
| developer | VSCodium | VSCodium.VSCodium |
developer |
| developer | Git | Git.Git |
developer |
| developer | .NET 9 SDK | Microsoft.DotNet.SDK.9 |
developer |
| developer | Node.js LTS | OpenJS.NodeJS.LTS |
developer |
| developer | Windows Terminal | Microsoft.WindowsTerminal |
developer |
| developer | PowerShell 7 | Microsoft.PowerShell |
developer |
| developer | Claude Desktop | Anthropic.Claude |
developer |
| developer | Visual Studio 2022 | Microsoft.VisualStudio.2022.Community |
— |
| developer | JetBrains Rider | JetBrains.Rider |
— |
| developer | Docker Desktop | Docker.DockerDesktop |
— |
| developer | Claude Code (CLI) | (npm @anthropic-ai/claude-code, needs Node) |
— |
| developer | Google Chrome | Google.Chrome |
— |
| developer | PostgreSQL | PostgreSQL.PostgreSQL |
— |
| developer | Bruno (API client) | Bruno.Bruno |
— |
| journalist | VeraCrypt | IDRIX.VeraCrypt |
journalist |
| journalist | KeePassXC | KeePassXCTeam.KeePassXC |
journalist |
| journalist | Joplin | Joplin.Joplin |
journalist |
| journalist | OBS Studio | OBSProject.OBSStudio |
— |
| journalist | Standard Notes | StandardNotes.StandardNotes |
— |
| journalist | Signal | OpenWhisperSystems.Signal |
— |
| journalist | Tor Browser | TorProject.TorBrowser |
— |
| daily-driver | Spotify | Spotify.Spotify |
— |
| daily-driver | Zoom | Zoom.Zoom |
— |
| daily-driver | Discord | Discord.Discord |
— |
| privacy-max | VeraCrypt | IDRIX.VeraCrypt |
— |
Claude Code (CLI) installs via npm, not winget — modelled with a
source.npmvariant the engine handles separately (and only if Node is selected/present). Listed but lower priority.
5. Build wiring
build.ps1: stagewindows/apps/(catalog.json + configure/) into the image (C:\Program Files\SilverOS\Welcome\apps\next to the flavours), same as flavours.- The Welcome app reads the catalog from
AppContext.BaseDirectory\apps\catalog.json.
6. Error handling
- Catalog missing/!parse → the Apps step shows an empty/"no extra apps" state and onboarding continues (never blocks).
- winget bootstrap fails (offline) → log it, skip the install stage with a Done-step note; onboarding still completes.
- Per-app install failure → recorded, surfaced in the Done summary, never throws.
7. Testing
AppCatalogdeserialization +AppsForRole/DefaultSelectionForRoleunit tests (xUnit).AppInstalleragainst a fakeIProcessRunner: asserts winget-bootstrap when absent, the exactwinget installinvocation per selected app, continue-on-failure, and configure-script run.AppsStepselection/validity (bUnit, matching the existing step tests' style).
8. Out of scope (follow-ups)
- Curated SilverMetal mirror (the
source.mirrorend-state) + signing for WDAC-enforce. - WDAC allow-listing of installed apps for Privacy-Max/Journalist enforce mode.
- Per-app version pinning / update policy.