mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-11 08:42:11 +00:00
feat(install.ps1): write .hermes-bootstrap-complete marker at end of install
The desktop app's main.cjs resolver ladder has a 'bootstrap-needed' rung
that fires when .hermes-bootstrap-complete is missing from
ACTIVE_HERMES_ROOT. Pre-Hermes-Setup, this marker was written by the
packaged-desktop's own bootstrap-runner.cjs at the end of its install
flow. Now that Hermes-Setup.exe runs install.ps1 directly, install.ps1
needs to own the marker — otherwise the desktop sees no marker on first
launch and triggers its legacy first-launch bootstrap (re-running
install.ps1 from inside Electron, the exact recursion Hermes-Setup.exe
was supposed to obviate).
Implementation:
* New Stage-BootstrapMarker (worker) → Write-BootstrapMarker (helper)
* Slotted in the manifest right after platform-sdks, before the
interactive configure/gateway stages, so it runs unconditionally
when the install reaches the finalize phase
* Schema mirrors apps/desktop/electron/main.cjs writeBootstrapMarker /
isBootstrapComplete EXACTLY: {schemaVersion: 1, pinnedCommit,
pinnedBranch, completedAt}. Schema version stays at 1 so old
desktops that read marker files written by future install.ps1s
can still parse them.
* pinnedCommit comes from -Commit flag (Hermes-Setup.exe passes it)
or falls back to 'git rev-parse HEAD' in InstallDir
* pinnedBranch from -Branch flag, defaults to 'main' matching
install.ps1's own param default
Two PS-5.1 gotchas baked into comments:
* The ?. null-conditional operator doesn't exist pre-PS7; use
explicit if-checks on Get-Command results
* Set-Content -Encoding UTF8 emits a BOM in 5.1 and Node's plain
JSON.parse rejects BOM — write via .NET's UTF8Encoding(false)
to produce BOM-less JSON the desktop's readJson() can parse
This commit is contained in:
parent
060c4f64a8
commit
a4cfc8b740
1 changed files with 79 additions and 0 deletions
|
|
@ -1524,6 +1524,83 @@ function Set-PathVariable {
|
|||
Write-Success "hermes command ready"
|
||||
}
|
||||
|
||||
function Write-BootstrapMarker {
|
||||
# Writes $InstallDir\.hermes-bootstrap-complete which tells the Hermes
|
||||
# desktop app (apps/desktop/electron/main.cjs) "install.ps1 ran
|
||||
# successfully — DON'T trigger the legacy first-launch bootstrap
|
||||
# runner."
|
||||
#
|
||||
# Schema mirrors what main.cjs's writeBootstrapMarker() / isBootstrap
|
||||
# Complete() expect. Keep this in lockstep when either side changes:
|
||||
# apps/desktop/electron/main.cjs lines 1199-1222
|
||||
# BOOTSTRAP_MARKER_SCHEMA_VERSION = 1 (line 187)
|
||||
#
|
||||
# Pinned commit/branch come from -Commit + -Branch flags (passed by
|
||||
# Hermes-Setup.exe) or fall back to whatever git resolves in the
|
||||
# checkout. The desktop validates schemaVersion + pinnedCommit
|
||||
# length but doesn't enforce that HEAD matches the pin (users
|
||||
# update via `hermes update` which moves HEAD legitimately).
|
||||
if (-not (Test-Path $InstallDir)) {
|
||||
Write-Warn "Skipping bootstrap marker: $InstallDir doesn't exist"
|
||||
return
|
||||
}
|
||||
|
||||
# Resolve the pinned commit: explicit -Commit wins, otherwise read
|
||||
# the checkout's HEAD via git. If git can't run, leave commit empty
|
||||
# and the marker will fail desktop validation (pinnedCommit.length
|
||||
# >= 7) — better to be invalid than wrong.
|
||||
$pinnedCommit = $Commit
|
||||
if (-not $pinnedCommit) {
|
||||
# PS 5.1 doesn't support the ?. null-conditional operator, so
|
||||
# check Get-Command's result explicitly before reading .Source.
|
||||
$gitCmd = Get-Command git -ErrorAction SilentlyContinue
|
||||
$gitExe = if ($gitCmd) { $gitCmd.Source } else { $null }
|
||||
if ($gitExe) {
|
||||
Push-Location $InstallDir
|
||||
try {
|
||||
$resolved = & $gitExe rev-parse HEAD 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and $resolved) {
|
||||
$pinnedCommit = $resolved.Trim()
|
||||
}
|
||||
} catch {
|
||||
# Ignore — pinnedCommit stays empty, marker stays invalid,
|
||||
# desktop falls through to its legacy bootstrap path.
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pinnedBranch = $Branch
|
||||
if (-not $pinnedBranch) {
|
||||
$pinnedBranch = "main" # install.ps1's own default for -Branch
|
||||
}
|
||||
|
||||
$markerPath = Join-Path $InstallDir ".hermes-bootstrap-complete"
|
||||
$marker = [ordered]@{
|
||||
schemaVersion = 1
|
||||
pinnedCommit = $pinnedCommit
|
||||
pinnedBranch = $pinnedBranch
|
||||
completedAt = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
|
||||
# desktopVersion field intentionally omitted — only the desktop
|
||||
# app knows its own version, and the marker validator doesn't
|
||||
# require it. The desktop fills it in if/when it writes its
|
||||
# own marker (e.g. after a future in-app upgrade).
|
||||
}
|
||||
$json = $marker | ConvertTo-Json -Compress:$false
|
||||
|
||||
# Write WITHOUT a UTF-8 BOM. PowerShell 5.1's `Set-Content -Encoding UTF8`
|
||||
# always emits a BOM, and Node's plain JSON.parse rejects the BOM as an
|
||||
# unexpected character — so a BOM'd marker would silently fail the
|
||||
# desktop's readJson(), make isBootstrapComplete() return null, and the
|
||||
# desktop would re-run the legacy bootstrap runner anyway. Defeats the
|
||||
# whole point. Use the .NET API directly for BOM-less UTF-8.
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
|
||||
[System.IO.File]::WriteAllText($markerPath, $json, $utf8NoBom)
|
||||
|
||||
Write-Success "Bootstrap marker written: $markerPath"
|
||||
}
|
||||
|
||||
function Copy-ConfigTemplates {
|
||||
Write-Info "Setting up configuration files..."
|
||||
|
||||
|
|
@ -2376,6 +2453,7 @@ $InstallStages += @(
|
|||
@{ Name = "path"; Title = "Adding Hermes to PATH"; Category = "finalize"; NeedsUserInput = $false; Worker = "Stage-Path" }
|
||||
@{ Name = "config-templates"; Title = "Writing configuration templates"; Category = "finalize"; NeedsUserInput = $false; Worker = "Stage-ConfigTemplates" }
|
||||
@{ Name = "platform-sdks"; Title = "Installing messaging platform SDKs"; Category = "finalize"; NeedsUserInput = $false; Worker = "Stage-PlatformSdks" }
|
||||
@{ Name = "bootstrap-marker"; Title = "Marking install complete"; Category = "finalize"; NeedsUserInput = $false; Worker = "Stage-BootstrapMarker" }
|
||||
# Interactive stages. In non-interactive mode these become no-ops; the
|
||||
# caller (GUI / CI) handles the equivalent UX themselves.
|
||||
@{ Name = "configure"; Title = "Configuring API keys and models"; Category = "post-install"; NeedsUserInput = $true; Worker = "Stage-Configure" }
|
||||
|
|
@ -2416,6 +2494,7 @@ function Stage-Desktop { Install-Desktop }
|
|||
function Stage-Path { Set-PathVariable }
|
||||
function Stage-ConfigTemplates { Copy-ConfigTemplates }
|
||||
function Stage-PlatformSdks { Resolve-UvCmd; Install-PlatformSdks }
|
||||
function Stage-BootstrapMarker { Write-BootstrapMarker }
|
||||
function Stage-Configure { Invoke-SetupWizard }
|
||||
function Stage-Gateway { Start-GatewayIfConfigured }
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue