# Install-LifeFirst.ps1 — set up Laurie's laptop: her key safe + her Life First page. Pink Bee Me. # UnitedSys — United Systems | jwl247 | GPL-3.0 # # Her one-line install (PowerShell): irm authenticcoder.com/pinkbee | iex # Also runs from a USB kit (double-click "Install Life First.cmd" next to this file). # Her safe (Laurie-safe.kdbx) is never online: it is found on the USB stick (any drive's top # folder or "Life First Setup" folder, or next to this file). # If the KeePassXC installer is not in the kit, it is downloaded from KeePassXC's official GitHub # release and refused unless the SHA-256 AND the Windows signature match what was verified 2026-10-11. # Safe to run again: nothing is overwritten, steps already done are skipped. param( [string]$TestRoot = $env:LF_TEST_ROOT # testing only: Documents/Start menu/Desktop go under this folder, nothing is launched ) $ErrorActionPreference = 'Stop' $Kit = $PSScriptRoot # empty when run as irm ... | iex $PageUrl = 'https://laurie-worker.phoenix-jwl.workers.dev/' $KpVer = '2.7.12' $KpMsi = "KeePassXC-$KpVer-Win64.msi" $KpUrl = "https://github.com/keepassxreboot/keepassxc/releases/download/$KpVer/$KpMsi" $KpSha256 = 'feee096c1d5d0d7bb7b36b18174818f7bac889be7668c550ff0e5ef9206ea9a5' $KpSigner = 'DroidMonkey Apps, LLC' $Docs = [Environment]::GetFolderPath('MyDocuments') $Programs = [Environment]::GetFolderPath('Programs') $Desktop = [Environment]::GetFolderPath('Desktop') $Testing = [bool]$TestRoot if ($Testing) { $Docs = Join-Path $TestRoot 'Documents'; $Programs = Join-Path $TestRoot 'Programs'; $Desktop = Join-Path $TestRoot 'Desktop' New-Item -ItemType Directory -Force $Docs, $Programs, $Desktop | Out-Null } $Home2 = Join-Path $Docs 'Life First' Add-Type -AssemblyName PresentationFramework function Say($text) { Write-Host " $text" } function Step($n, $text) { Write-Host ''; Write-Host "[$n of 4] $text" -ForegroundColor Magenta } function Done($text) { Write-Host " ✓ $text" -ForegroundColor Green } function Fail($text) { if (-not $Testing) { [System.Windows.MessageBox]::Show("$text`n`nNothing is broken. Tell Jerry what this says.", 'Life First setup', 'OK', 'Warning') | Out-Null } throw $text } Write-Host '' Write-Host ' Life First setup · Pink Bee Me' -ForegroundColor Magenta Say 'This puts your key safe and your Life First page on this laptop. It takes a few minutes.' # ── 1. KeePassXC (the safe's lock) ───────────────────────────────────────── Step 1 'Your key safe program (KeePassXC)' $kpExe = Join-Path $env:ProgramFiles 'KeePassXC\KeePassXC.exe' if (Test-Path $kpExe) { Done 'Already installed.' } else { $msi = if ($Kit) { Join-Path $Kit $KpMsi } else { '' } if (-not $msi -or -not (Test-Path $msi)) { Say 'Getting it from KeePassXC''s official page on GitHub...' $dl = Join-Path $env:TEMP ("lf-setup-" + [guid]::NewGuid().ToString('N')) New-Item -ItemType Directory $dl | Out-Null $msi = Join-Path $dl $KpMsi Invoke-WebRequest $KpUrl -OutFile $msi -UseBasicParsing } $hash = (Get-FileHash $msi -Algorithm SHA256).Hash.ToLower() if ($hash -ne $KpSha256) { Fail "The KeePassXC installer doesn't match the one Jerry checked (SHA-256 $hash). Not installing it." } $sig = Get-AuthenticodeSignature $msi if ($sig.Status -ne 'Valid' -or $sig.SignerCertificate.Subject -notmatch [regex]::Escape($KpSigner)) { Fail "The KeePassXC installer's signature isn't right ($($sig.Status)). Not installing it." } Say 'Checked: it is the real one. Windows will ask "Do you want to allow this app to make changes?" Click Yes.' $p = Start-Process msiexec.exe -ArgumentList '/i', "`"$msi`"", '/passive', '/norestart' -Verb RunAs -Wait -PassThru if ($p.ExitCode -ne 0 -or -not (Test-Path $kpExe)) { Fail "KeePassXC didn't install (code $($p.ExitCode))." } Done 'Installed.' } # ── 2. Her safe ──────────────────────────────────────────────────────────── Step 2 'Your key safe' New-Item -ItemType Directory -Force $Home2 | Out-Null $safeDst = Join-Path $Home2 'Laurie-safe.kdbx' $where = @() if ($Kit) { $where += $Kit, (Split-Path $Kit -Parent) } if ($Testing) { $where += (Join-Path $TestRoot 'usb') } else { Get-PSDrive -PSProvider FileSystem | ForEach-Object { $where += $_.Root, (Join-Path $_.Root 'Life First Setup') } } $safeSrc = $where | Where-Object { $_ } | ForEach-Object { Join-Path $_ 'Laurie-safe.kdbx' } | Where-Object { Test-Path $_ } | Where-Object { $_ -notlike "$Home2*" } | Select-Object -First 1 if ($safeSrc) { Say "Found your safe at $safeSrc" } if (-not $safeSrc) { if (Test-Path $safeDst) { Done "Already on this laptop: $safeDst" } else { Fail 'Your safe (Laurie-safe.kdbx) is not on the USB stick. Jerry makes it first.' } } elseif (Test-Path $safeDst) { if ((Get-FileHash $safeSrc).Hash -eq (Get-FileHash $safeDst).Hash) { Done 'Already on this laptop (same copy).' } else { $alt = Join-Path $Home2 ("Laurie-safe-" + (Get-Date -Format 'yyyy-MM-dd-HHmm') + '.kdbx') Copy-Item $safeSrc $alt Done "A different copy was already here, so nothing was replaced. The new one is: $alt" $safeDst = $alt } } else { Copy-Item $safeSrc $safeDst Done "Copied to $safeDst. Keep the USB stick somewhere safe: it's your backup." } # ── 3. Shortcuts: Start menu + desktop ───────────────────────────────────── Step 3 'Shortcuts in your Start menu and on your desktop' $icon = Join-Path $Home2 'bee.ico' if ($Kit -and (Test-Path (Join-Path $Kit 'bee.ico'))) { Copy-Item (Join-Path $Kit 'bee.ico') $icon -Force } else { Invoke-WebRequest ($PageUrl + 'bee.ico') -OutFile $icon -UseBasicParsing } $edge = @("${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe", "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe") | Where-Object { Test-Path $_ } | Select-Object -First 1 if (-not $edge) { Fail 'Microsoft Edge was not found on this laptop.' } $sh = New-Object -ComObject WScript.Shell function Make-Link($dir, $name, $target, $arguments, $ico, $desc) { $l = $sh.CreateShortcut((Join-Path $dir "$name.lnk")) $l.TargetPath = $target; $l.Arguments = $arguments; $l.IconLocation = $ico; $l.Description = $desc $l.WorkingDirectory = $Home2; $l.Save() } foreach ($d in $Programs, $Desktop) { Make-Link $d 'Life First' $edge "--app=$PageUrl" $icon 'Your day and your messages' Make-Link $d 'My Key Safe' $kpExe "`"$safeDst`"" "$kpExe,0" 'Your key safe' } Done 'Life First and My Key Safe are in your Start menu and on your desktop.' Say 'To put Life First on the taskbar: open Start, right-click Life First, then "Pin to taskbar".' # ── 4. First open of her page ────────────────────────────────────────────── Step 4 'Open your Life First page for the first time' Say 'Open My Key Safe (it is on your desktop now), find "Your Life First page link key",' Say 'click the copy button next to the password, then come back here and paste it (right-click pastes).' if ($Testing) { Done 'TEST: skipped opening the safe and her page.'; return } Start-Process $kpExe "`"$safeDst`"" $key = '' while ($key -notmatch '^[A-Za-z0-9_-]{20,}$') { $key = (Read-Host ' Paste your page key here').Trim() if ($key -eq '') { Say 'Skipped. Jerry can do this part later.'; break } if ($key -notmatch '^[A-Za-z0-9_-]{20,}$') { Say "That doesn't look like the key. Try copying it again." } } if ($key) { Start-Process $edge "--app=$PageUrl#k=$key" $key = $null Done 'Your page is open. It remembers you now, so next time just click Life First.' } [System.Windows.MessageBox]::Show("All set.`n`nClick Life First (the bee) to see your day.`nClick My Key Safe if Jerry asks you for a key.", 'Life First · Pink Bee Me', 'OK', 'Information') | Out-Null