From 6d6eb2cdc8766a0cd986a7044e07d12f66e1c2c8 Mon Sep 17 00:00:00 2001 From: sysadmin Date: Tue, 9 Jun 2026 21:45:20 +0100 Subject: [PATCH 1/2] fix(welcome): FlavourStep notifies host on select so Next enables immediately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WIP on local branch feat/wizard-recipes (NOT pushed) — holding per operator while more wizard changes (role app-recipes) are designed. --- .../src/SilverOS.Welcome.UI/Components/Routes.razor | 2 +- .../Components/Steps/FlavourStep.razor | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor b/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor index 082ca0f..f4bb24c 100644 --- a/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor +++ b/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor @@ -37,7 +37,7 @@ break; case 1: - + break; case 2: diff --git a/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/FlavourStep.razor b/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/FlavourStep.razor index 18a893f..7892d3a 100644 --- a/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/FlavourStep.razor +++ b/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/FlavourStep.razor @@ -19,13 +19,19 @@ @code { [Parameter] public IReadOnlyList Flavours { get; set; } = Array.Empty(); - protected override void OnInitialized() + /// Notifies the wizard host when the selection changes so it re-evaluates + /// the Next button (otherwise Next stays disabled until a back/forward re-render). + [Parameter] public EventCallback OnSelected { get; set; } + + protected override async Task OnInitializedAsync() { State.Flavour ??= Flavours.FirstOrDefault(f => f.IsDefault); + await OnSelected.InvokeAsync(); } - void Select(FlavourManifest f) + async Task Select(FlavourManifest f) { State.Flavour = f; + await OnSelected.InvokeAsync(); } } -- 2.39.5 From a3623b1fbb119b7c5c09edc9ed73cd8319de1cd0 Mon Sep 17 00:00:00 2001 From: sysadmin Date: Tue, 9 Jun 2026 21:57:47 +0100 Subject: [PATCH 2/2] fix(welcome): BitLocker PIN works first boot (drop -SkipHardwareTest) + show recovery key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BitLocker: remove -SkipHardwareTest so BitLocker validates the TPM+PIN unseal via its hardware test on the next reboot (the wizard's end-of-flow reboot) before encrypting — fixes the E_FVE_SECURE_BOOT_CHANGED / PCR-11 drop-to-recovery on the first post-enroll boot. The PIN now works first time instead of needing recovery. - Done step now DISPLAYS the 48-digit BitLocker recovery key (read from the file the enrollment saves) with a 'save this' warning — previously it was never surfaced. Co-Authored-By: Claude Opus 4.8 --- .../SilverOS.Welcome.App/wwwroot/css/app.css | 24 ++++++++++++++ .../Apply/BitLockerService.cs | 7 ++++- .../Components/Steps/DoneStep.razor | 31 ++++++++++++++++++- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/windows/welcome/src/SilverOS.Welcome.App/wwwroot/css/app.css b/windows/welcome/src/SilverOS.Welcome.App/wwwroot/css/app.css index 555a055..13309f1 100644 --- a/windows/welcome/src/SilverOS.Welcome.App/wwwroot/css/app.css +++ b/windows/welcome/src/SilverOS.Welcome.App/wwwroot/css/app.css @@ -877,3 +877,27 @@ h1:focus { outline: none; } padding-left: env(safe-area-inset-left); } } + +/* ── BitLocker recovery key (Done step) ─────────────────────────────── */ +.recovery-panel { + margin: 1.25rem 0; + padding: 1rem 1.25rem; + border: 1px solid var(--clr-accent); + border-radius: var(--radius-sm, 8px); + background: var(--clr-accent-glow, rgba(0,212,255,0.10)); +} +.recovery-panel h3 { margin: 0 0 0.5rem; color: var(--clr-accent); font-family: var(--font-mono); } +.recovery-key { + font-family: var(--font-mono); + font-size: 1.05rem; + letter-spacing: 0.04em; + color: var(--clr-text-hi); + background: rgba(0,0,0,0.30); + padding: 0.75rem 1rem; + border-radius: var(--radius-sm, 8px); + white-space: pre-wrap; + word-break: break-all; + user-select: all; + margin: 0.5rem 0; +} +.recovery-note { color: var(--clr-text-lo); } diff --git a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs index fb6792c..f47dc8c 100644 --- a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs +++ b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs @@ -31,7 +31,12 @@ public sealed class BitLockerService(IProcessRunner runner) : IBitLockerService "$p=ConvertTo-SecureString '", p, "' -AsPlainText -Force; ", "$v=Get-BitLockerVolume -MountPoint $mp; ", "if ($v.VolumeStatus -eq 'FullyDecrypted') { ", - "Enable-BitLocker -MountPoint $mp -EncryptionMethod XtsAes256 -TpmAndPinProtector -Pin $p -SkipHardwareTest } ", + // NO -SkipHardwareTest: let BitLocker run its hardware test on the next reboot so it + // VALIDATES the TPM+PIN unseal against the real boot measurements before encrypting. + // -SkipHardwareTest seals immediately against possibly-wrong PCRs -> drops to recovery + // on first boot (E_FVE_SECURE_BOOT_CHANGED, PCR 11). The wizard's end-of-flow reboot + // is that validation pass, so the PIN works on first boot instead of bouncing. + "Enable-BitLocker -MountPoint $mp -EncryptionMethod XtsAes256 -TpmAndPinProtector -Pin $p } ", "elseif (-not ($v.KeyProtector | Where-Object { $_.KeyProtectorType -eq 'TpmPin' })) { ", "Add-BitLockerKeyProtector -MountPoint $mp -TpmAndPinProtector -Pin $p }; ", "$kp=(Get-BitLockerVolume -MountPoint $mp).KeyProtector; ", diff --git a/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/DoneStep.razor b/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/DoneStep.razor index a42f027..35ed602 100644 --- a/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/DoneStep.razor +++ b/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/DoneStep.razor @@ -2,11 +2,40 @@

All Done!

-

Your SilverOS device is configured and ready. Click below to restart and start using it.

+

Your SilverMetal device is configured and ready.

+ + @if (!string.IsNullOrWhiteSpace(_recoveryKey)) + { +
+

⚠ Save your BitLocker recovery key

+

+ This is the only way back into your drive if you ever forget your PIN. + Write it down or photograph it now and keep it somewhere safe and separate from this device. +

+
@_recoveryKey
+

Also saved to C:\ProgramData\SilverMetal\bitlocker-recovery.txt on this device.

+
+ } + +

Click below to restart and start using it.

@code { + private string? _recoveryKey; + + protected override void OnInitialized() + { + // The BitLocker step saved the 48-digit recovery key to ProgramData; surface it + // here so the user records it before finishing (TPM+PIN alone is unrecoverable). + try + { + const string path = @"C:\ProgramData\SilverMetal\bitlocker-recovery.txt"; + if (File.Exists(path)) _recoveryKey = File.ReadAllText(path).Trim(); + } + catch { /* best-effort display */ } + } + private async Task RestartNow() { await ProcessRunner.RunAsync("cmd.exe", "/c shutdown /r /t 5", CancellationToken.None); -- 2.39.5