$ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $Origin = 'https://install.staging.boundlink.app' $AccountAuthOrigin = 'https://boundlink-staging-auth.jmaxdmega.workers.dev' $GatewayOrigin = 'https://boundlink-staging-mcp.jmaxdmega.workers.dev' $CallSigningKeyUrl = 'https://boundlink-staging-auth.jmaxdmega.workers.dev/.well-known/machine-bridge-call-key' $ExpectedSource = 'a5c93830ca1893e3388a7c04b4a51316f6274f00' $ExpectedFile = 'boundlink-v0.7.1-a5c93830-windows-x86_64.zip' $ExpectedSha256 = 'f43bd93e85db90eb5027e6cd2c82de9186d002396a0bd50f36b698769d563c0e' $ExpectedBytes = 4239071 $ExpectedConsumerSha256 = 'c1abe840a4df8cc4d924629a2226d20bdfddc6632866fc374423a9d6ac852b4f' $SupportedPredecessorConsumerSha256 = @( '247e524bdbf0ad550a42632d1f083c4b50fea355773172ca3f52995d0558ac1c', '00492b07a7fbfa427ccd529a8153b2a5e2d083fa313ca54a12101c800d33d1ed', 'e958026c6ec27391b787827a8379efae0e6ffe06818d13a2a411801e96050b3e', 'bceebb7f2a3ff2de066db819faef5a1d6a04f781e26a6ffe695012de5a6205ed', '5c373a06a8cc214762a9e8e8ed88dfc6cf1d2f89870828a47086fb3d734d241f', '85638b679860a3c01ac5fc3eed0f80a2bac9c48c191106544650a04a9a8bc7a2' ) Write-Host '' Write-Host 'BoundLink V0.7.1 staging installer' Write-Host 'Candidate: a5c93830' Write-Host '' if (-not [Environment]::Is64BitOperatingSystem) { throw 'BoundLink V0.7.1 staging requires 64-bit Windows.' } $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) if ($principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Run the BoundLink installer from a normal Windows PowerShell session, not an elevated Administrator terminal.' } $release = Invoke-RestMethod -Uri "$Origin/release.json" -Method Get if ([string]$release.schema -ne 'boundlink-staging-release-v1' -or [string]$release.release -ne '0.7.1' -or [string]$release.platform -ne 'windows-x86_64' -or [string]$release.sourceCommit -ne $ExpectedSource -or -not [bool]$release.installable -or [string]$release.package.file -ne $ExpectedFile -or [int64]$release.package.bytes -ne $ExpectedBytes -or ([string]$release.package.sha256).ToLowerInvariant() -ne $ExpectedSha256) { throw 'BoundLink staging release metadata does not match the pinned V0.7.1 acceptance candidate.' } $installedExe = Join-Path $env:ProgramFiles 'BoundLink\\boundlink.exe' $consumerConfig = Join-Path $env:ProgramData 'BoundLink\\config\\consumer.json' $releaseIdentityPath = Join-Path $env:ProgramFiles 'BoundLink\\release-identity.json' $deviceConfig = Join-Path $env:ProgramData 'MachineBridge/config/device.json' $installedRuntime = Join-Path $env:ProgramFiles 'BoundLink/boundlink-user-runtime.exe' $generationWitness = Join-Path $env:ProgramData 'BoundLink/generation/installed-generation.json' $updateJournal = Join-Path $env:ProgramData 'BoundLink\\state\\update-v0.7.1-journal.json' $cleanInstallMarker = Join-Path $env:ProgramData 'BoundLink\\install-recovery\\clean-install-pending.json' $legacyCleanInstallMarker = Join-Path $env:ProgramData 'BoundLink\\state\\clean-install-pending.json' $installMode = 'clean' $installedSha = $null $recoveryMode = $null $repairCurrentRuntime = $false $repairReleaseIdentity = $false if (Test-Path -LiteralPath $updateJournal -PathType Leaf) { $installMode = 'update' $recoveryMode = 'interrupted_verified_operation' if (Test-Path -LiteralPath $installedExe -PathType Leaf) { $installedSha = (Get-FileHash -LiteralPath $installedExe -Algorithm SHA256).Hash.ToLowerInvariant() } Write-Host 'BoundLink found an interrupted verified operation.' Write-Host 'Finishing deterministic recovery before normal startup.' Write-Host '' } else { $cleanInstallCommitted = $false if (Test-Path -LiteralPath $cleanInstallMarker -PathType Leaf) { try { $cleanMarker = Get-Content -LiteralPath $cleanInstallMarker -Raw | ConvertFrom-Json } catch { throw 'BoundLink found an unreadable clean-install recovery record. Refusing to guess recovery state.' } if ([string]$cleanMarker.schema -ne 'boundlink-clean-install-pending-v2') { throw 'BoundLink found an unsupported clean-install recovery record. Refusing to guess recovery state.' } $allowedCleanPhases = @('STARTED','CONSUMER_INSTALLED','IDENTITY_READY','RUNTIME_CONFIGURED','SERVICE_RUNNING','INSTALL_COMMITTED') if ([string]$cleanMarker.phase -notin $allowedCleanPhases) { throw 'BoundLink clean-install recovery record contains an unknown phase.' } if ([string]$cleanMarker.phase -ne 'INSTALL_COMMITTED') { $installMode = 'clean' $recoveryMode = 'interrupted_clean_install' if (Test-Path -LiteralPath $installedExe -PathType Leaf) { $installedSha = (Get-FileHash -LiteralPath $installedExe -Algorithm SHA256).Hash.ToLowerInvariant() } Write-Host 'BoundLink found an interrupted clean installation.' Write-Host 'Resuming verified installation recovery before setup continues.' Write-Host '' } else { $cleanInstallCommitted = $true } } elseif (Test-Path -LiteralPath $legacyCleanInstallMarker -PathType Leaf) { try { $legacyMarker = Get-Content -LiteralPath $legacyCleanInstallMarker -Raw | ConvertFrom-Json } catch { throw 'BoundLink found an unreadable legacy clean-install marker. Refusing to guess recovery state.' } if ([string]$legacyMarker.schema -ne 'boundlink-clean-install-pending-v1') { throw 'BoundLink found an unsupported legacy clean-install marker. Refusing to guess recovery state.' } } if (-not $recoveryMode) { if (-not (Test-Path -LiteralPath $installedExe -PathType Leaf)) { if ($cleanInstallCommitted) { throw 'BoundLink clean installation is marked committed but the consumer executable is missing. Refusing to guess package repair.' } } else { $installedSha = (Get-FileHash -LiteralPath $installedExe -Algorithm SHA256).Hash.ToLowerInvariant() if ($installedSha -eq $ExpectedConsumerSha256) { $exactGeneration = $false $releaseIdentityValid = $false $releaseIdentityReason = 'missing' if (Test-Path -LiteralPath $releaseIdentityPath -PathType Leaf) { $releaseIdentityReason = 'invalid' try { $installedRelease = Get-Content -LiteralPath $releaseIdentityPath -Raw | ConvertFrom-Json $releaseIdentityValid = ( [string]$installedRelease.schema -eq 'boundlink-installed-release-v1' -and [string]$installedRelease.release -eq '0.7.1' -and [string]$installedRelease.platform -eq 'windows-x86_64' -and [string]$installedRelease.sourceCommit -match '^[0-9a-f]{40}$' ) if ($releaseIdentityValid) { $releaseIdentityReason = 'valid' $exactGeneration = ([string]$installedRelease.sourceCommit -eq $ExpectedSource) } } catch { $releaseIdentityValid = $false } } if ($exactGeneration) { if (-not (Test-Path -LiteralPath $consumerConfig -PathType Leaf)) { throw 'An exact BoundLink package generation exists, but its consumer configuration is missing. Refusing to guess recovery state.' } if (-not (Test-Path -LiteralPath $deviceConfig -PathType Leaf)) { throw 'An exact BoundLink package generation exists, but Device Service configuration is missing. Refusing to guess repair state.' } try { $device = Get-Content -LiteralPath $deviceConfig -Raw | ConvertFrom-Json } catch { throw 'An exact BoundLink package generation exists, but Device Service configuration is unreadable. Refusing to guess repair state.' } if (-not $device.expectedRuntimePath -or [IO.Path]::GetFullPath([string]$device.expectedRuntimePath) -ine [IO.Path]::GetFullPath($installedRuntime)) { throw 'The exact BoundLink generation has an unexpected User Runtime path binding. Refusing package repair.' } $expectedRuntimeBuild = [string]$device.expectedRuntimeBuildId if ($expectedRuntimeBuild -notmatch '^sha256:[0-9a-fA-F]{64}$') { throw 'The exact BoundLink generation has an invalid User Runtime build pin. Refusing package repair.' } if (-not (Test-Path -LiteralPath $installedRuntime -PathType Leaf)) { $installMode = 'repair' $repairCurrentRuntime = $true Write-Host 'Exact BoundLink package generation detected with a missing User Runtime binary.' Write-Host 'BoundLink will restore only the verified User Runtime package while preserving trusted identity and authority.' Write-Host '' } else { $installedRuntimeBuild = 'sha256:' + ((Get-FileHash -LiteralPath $installedRuntime -Algorithm SHA256).Hash.ToLowerInvariant()) if ($installedRuntimeBuild -ne $expectedRuntimeBuild) { $installMode = 'repair' $repairCurrentRuntime = $true Write-Host 'Exact BoundLink package generation detected with a mismatched User Runtime binary.' Write-Host 'BoundLink will restore only the verified User Runtime package while preserving trusted identity and authority.' Write-Host '' } } if (-not $repairCurrentRuntime) { $deviceService = Get-Service -Name 'MachineBridgeDevice' -ErrorAction SilentlyContinue if (-not $deviceService) { throw 'An exact BoundLink package generation exists, but the Device Service is missing. Refusing to guess repair state.' } if ($deviceService.Status -eq 'Running') { Write-Host 'Exact BoundLink package generation detected.' Write-Host 'Opening BoundLink...' Write-Host '' & $installedExe if ($LASTEXITCODE -ne 0) { throw "BoundLink exited with code $LASTEXITCODE." } return } $installMode = 'update' Write-Host 'Exact BoundLink package generation detected, but the Device Service is stopped.' Write-Host 'BoundLink will repair runtime services while preserving trusted device and lifecycle state.' Write-Host '' } } elseif ($releaseIdentityValid) { $installMode = 'update' Write-Host 'A verified BoundLink executable from an earlier package generation was found.' Write-Host 'BoundLink will reconcile the complete package generation while preserving trusted device and lifecycle state.' Write-Host '' } else { if (-not (Test-Path -LiteralPath $generationWitness -PathType Leaf)) { throw 'The current BoundLink executable is recognized, but release identity cannot be proven and no protected generation witness exists. Refusing to guess package generation.' } try { $witness = Get-Content -LiteralPath $generationWitness -Raw | ConvertFrom-Json } catch { throw 'BoundLink generation witness is unreadable. Refusing to guess package generation.' } if ([string]$witness.schema -ne 'boundlink-installed-generation-witness-v1' -or [string]$witness.release -ne '0.7.1' -or [string]$witness.platform -ne 'windows-x86_64' -or [string]$witness.sourceCommit -notmatch '^[0-9a-f]{40}$') { throw 'BoundLink generation witness is invalid. Refusing to guess package generation.' } $currentSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value if ([string]$witness.selectedUserSid -ne $currentSid) { throw 'BoundLink generation witness belongs to a different Windows user. Refusing package repair.' } if ([string]$witness.sourceCommit -eq $ExpectedSource) { $installMode = 'repair' $repairReleaseIdentity = $true if ($releaseIdentityReason -eq 'missing') { Write-Host 'Exact BoundLink generation proven by protected witness; release identity is missing.' } else { Write-Host 'Exact BoundLink generation proven by protected witness; release identity is invalid.' } Write-Host 'BoundLink will restore only verified release-generation metadata.' Write-Host '' } elseif ([string]$witness.sourceCommit -eq '38a9cb810e26b7b0912565dd6dad0c6c86713d05') { $installMode = 'update' Write-Host 'A protected BoundLink generation witness identifies a supported earlier package.' Write-Host 'BoundLink will update it while preserving trusted device and lifecycle state.' Write-Host '' } else { throw 'BoundLink generation witness does not identify the current package or a supported predecessor. Refusing to guess update state.' } } } elseif ($SupportedPredecessorConsumerSha256 -contains $installedSha) { $installMode = 'update' Write-Host 'A verified earlier BoundLink installation was found.' Write-Host 'BoundLink will update it while preserving trusted device and lifecycle state.' Write-Host '' } else { throw "An existing BoundLink installation was found, but it is not a recognized version that this installer can update safely. BoundLink will not overwrite or adopt it automatically. Run 'boundlink uninstall' from a normal user terminal, then rerun this installer for a clean reinstall." } } } } $work = Join-Path $env:TEMP ('boundlink-v0.7.1-' + [Guid]::NewGuid().ToString('N')) $zip = Join-Path $work $ExpectedFile $package = Join-Path $work 'package' New-Item -ItemType Directory -Force -Path $work,$package | Out-Null try { Write-Host 'Downloading exact BoundLink package...' Invoke-WebRequest -Uri "$Origin/artifact.zip" -OutFile $zip -UseBasicParsing $item = Get-Item -LiteralPath $zip if ($item.Length -ne $ExpectedBytes) { throw "BoundLink package size mismatch. Expected $ExpectedBytes bytes; received $($item.Length)." } Write-Host 'Verifying SHA-256...' $actualSha = (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash.ToLowerInvariant() if ($actualSha -ne $ExpectedSha256) { throw "BoundLink package SHA-256 mismatch. Expected $ExpectedSha256; received $actualSha." } Write-Host 'Package verified.' Expand-Archive -LiteralPath $zip -DestinationPath $package -Force if ($installMode -eq 'update' -or $installMode -eq 'repair') { $updater = Join-Path $package 'scripts/update-v0.7.1-existing.ps1' if (-not (Test-Path -LiteralPath $updater -PathType Leaf)) { throw 'Verified package is missing the recognized-predecessor updater.' } $updateArgs = @{ ExpectedInstalledConsumerSha256=$installedSha; AccountAuthOrigin=$AccountAuthOrigin; GatewayOrigin=$GatewayOrigin } if ($installMode -eq 'repair') { Write-Host 'Starting verified BoundLink package repair...' Write-Host 'Windows may request elevation for the machine-repair phase only.' if ($repairReleaseIdentity) { $updateArgs.RepairReleaseIdentity = $true } elseif ($repairCurrentRuntime) { $updateArgs.RepairCurrentRuntime = $true } else { throw 'BoundLink repair mode was selected without a supported repair target.' } } else { Write-Host 'Starting verified BoundLink update...' Write-Host 'Windows may request elevation for the machine-update phase only.' } & $updater @updateArgs if ($LASTEXITCODE -ne 0) { throw "BoundLink verified lifecycle operation exited with code $LASTEXITCODE." } return } $installer = Join-Path $package 'scripts\install-v0.7.1-clean-machine.ps1' if (-not (Test-Path -LiteralPath $installer -PathType Leaf)) { throw 'Verified package is missing the clean-machine installer.' } Write-Host 'Starting BoundLink installation...' Write-Host 'Windows may request elevation for the machine-install phase only.' $installArgs = @{ AccountAuthOrigin=$AccountAuthOrigin; GatewayOrigin=$GatewayOrigin; CallSigningKeyUrl=$CallSigningKeyUrl; AllowedRoots=@($env:USERPROFILE) } & $installer @installArgs if ($LASTEXITCODE -ne 0) { throw "BoundLink clean-machine installer exited with code $LASTEXITCODE." } } finally { Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue }