diff --git a/.gitignore b/.gitignore index 880a1e5..95a1e9b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ Borealis-Server.exe /Dependencies/NodeJS/ /Dependencies/Python/ /Dependencies/AutoHotKey/ +/Dependencies/git/ /Data/Server/Python_API_Endpoints/Tesseract-OCR/ # Misc Files/Folders diff --git a/Borealis.ps1 b/Borealis.ps1 index 1419ee0..e1fde6a 100644 --- a/Borealis.ps1 +++ b/Borealis.ps1 @@ -259,6 +259,12 @@ $npxCmd = Join-Path (Split-Path $nodeExe) 'npx.cmd' $node7zUrl = "https://nodejs.org/dist/v23.11.0/node-v23.11.0-win-x64.7z" $nodeInstallDir = Join-Path $depsRoot "NodeJS" $node7zPath = Join-Path $depsRoot "node-v23.11.0-win-x64.7z" +$gitVersionTag = 'v2.47.1.windows.1' +$gitPackageName = 'MinGit-2.47.1-64-bit.zip' +$gitZipUrl = "https://github.com/git-for-windows/git/releases/download/$gitVersionTag/$gitPackageName" +$gitZipPath = Join-Path $depsRoot $gitPackageName +$gitInstallDir = Join-Path $depsRoot 'git' +$gitExePath = Join-Path $gitInstallDir 'cmd\git.exe' # ---------------------- Dependency Installation Functions ---------------------- function Install_Shared_Dependencies { @@ -415,6 +421,32 @@ function Install_Agent_Dependencies { } } } + + # Portable Git client for agent updates + Run-Step "Dependency: Git CLI" { + if (-not (Test-Path $gitExePath)) { + if (-not (Test-Path $gitZipPath)) { + Invoke-WebRequest -Uri $gitZipUrl -OutFile $gitZipPath + } + + if (-not (Test-Path $sevenZipExe)) { + throw "7-Zip CLI not found at: $sevenZipExe" + } + + if (Test-Path $gitInstallDir) { + Remove-Item $gitInstallDir -Recurse -Force -ErrorAction SilentlyContinue + } + + New-Item -ItemType Directory -Path $gitInstallDir | Out-Null + & $sevenZipExe x $gitZipPath "-o$gitInstallDir" -y | Out-Null + + Remove-Item $gitZipPath -Force -ErrorAction SilentlyContinue + + if (-not (Test-Path $gitExePath)) { + throw "Git executable not found after extraction." + } + } + } } function Ensure-AgentTasks { diff --git a/Update.ps1 b/Update.ps1 index 326f569..5ebf698 100644 --- a/Update.ps1 +++ b/Update.ps1 @@ -9,6 +9,8 @@ $symbols = @{ Info = [char]0x2139 } +$repositoryUrl = 'https://github.com/bunny-lab-io/Borealis.git' + function Write-ProgressStep { param ( [string]$Message, @@ -36,6 +38,61 @@ function Run-Step { } } +function Get-GitExecutablePath { + param( + [string]$ProjectRoot + ) + + $candidates = @() + if ($ProjectRoot) { + $candidates += (Join-Path $ProjectRoot 'Dependencies\git\cmd\git.exe') + $candidates += (Join-Path $ProjectRoot 'Dependencies\git\bin\git.exe') + } + + foreach ($candidate in ($candidates | Select-Object -Unique)) { + try { + if (Test-Path $candidate -PathType Leaf) { return $candidate } + } catch {} + } + + return '' +} + +function Invoke-GitCommand { + param( + [Parameter(Mandatory = $true)] + [string]$GitExe, + + [Parameter(Mandatory = $true)] + [string]$WorkingDirectory, + + [Parameter(Mandatory = $true)] + [string[]]$Arguments + ) + + if ([string]::IsNullOrWhiteSpace($GitExe) -or -not (Test-Path $GitExe -PathType Leaf)) { + throw "Git executable not found at '$GitExe'" + } + + if (-not (Test-Path $WorkingDirectory -PathType Container)) { + throw "Working directory '$WorkingDirectory' does not exist." + } + + $fullArgs = @('-C', $WorkingDirectory) + $Arguments + $output = & $GitExe @fullArgs 2>&1 + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0) { + $joined = ($Arguments -join ' ') + $message = "git $joined failed with exit code $exitCode." + if ($output) { + $message = "$message Output: $output" + } + throw $message + } + + return $output +} + function Stop-AgentScheduledTasks { param( [string[]]$TaskNames @@ -166,7 +223,9 @@ function Get-RepositoryCommitHash { [Parameter(Mandatory = $true)] [string]$ProjectRoot, - [string]$AgentRoot + [string]$AgentRoot, + + [string]$GitExe ) $candidates = @() @@ -179,6 +238,19 @@ function Get-RepositoryCommitHash { } } + if ($GitExe -and (Test-Path $GitExe -PathType Leaf)) { + foreach ($root in $candidates) { + try { + if (-not (Test-Path (Join-Path $root '.git') -PathType Container)) { continue } + $revParse = Invoke-GitCommand -GitExe $GitExe -WorkingDirectory $root -Arguments @('rev-parse','HEAD') + if ($revParse) { + $candidate = ($revParse | Select-Object -Last 1) + if ($candidate) { return ($candidate.Trim()) } + } + } catch {} + } + } + foreach ($root in $candidates) { try { $gitDir = Join-Path $root '.git' @@ -405,11 +477,24 @@ function Sync-AgentHashRecord { function Invoke-BorealisUpdate { param( + [Parameter(Mandatory = $true)] + [string]$GitExe, + + [Parameter(Mandatory = $true)] + [string]$RepositoryUrl, + + [Parameter(Mandatory = $true)] + [string]$TargetHash, + + [string]$BranchName = 'main', + [switch]$Silent ) - $updateZip = Join-Path $scriptDir "Update_Staging\main.zip" - $updateDir = Join-Path $scriptDir "Update_Staging\Borealis-main" + if ([string]::IsNullOrWhiteSpace($TargetHash)) { + throw 'Target commit hash is required for Borealis update.' + } + $preservePath = Join-Path $scriptDir "Data\Server\Python_API_Endpoints\Tesseract-OCR" $preserveBackupPath = Join-Path $scriptDir "Update_Staging\Tesseract-OCR" @@ -427,19 +512,60 @@ function Invoke-BorealisUpdate { (Join-Path $scriptDir "Server\web-interface\src"), ` (Join-Path $scriptDir "Server\web-interface\build"), ` (Join-Path $scriptDir "Server\web-interface\public"), ` - (Join-Path $scriptDir "Server\Borealis") + (Join-Path $scriptDir "Server\Borealis"), ` + (Join-Path $scriptDir '.git') } + $stagingPath = Join-Path $scriptDir "Update_Staging" + $cloneDir = Join-Path $stagingPath 'repo' + Run-Step "Updating: Create Update Staging Folder" { - $stagingPath = Join-Path $scriptDir "Update_Staging" if (-not (Test-Path $stagingPath)) { New-Item -ItemType Directory -Force -Path $stagingPath | Out-Null } - Set-Variable -Name updateZip -Scope 1 -Value (Join-Path $stagingPath "main.zip") - Set-Variable -Name updateDir -Scope 1 -Value (Join-Path $stagingPath "Borealis-main") + if (Test-Path $cloneDir) { + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $cloneDir + } } - Run-Step "Updating: Download Update" { Invoke-WebRequest -Uri "https://github.com/bunny-lab-io/Borealis/archive/refs/heads/main.zip" -OutFile $updateZip } - Run-Step "Updating: Extract Update Files" { Expand-Archive -Path $updateZip -DestinationPath (Join-Path $scriptDir "Update_Staging") -Force } - Run-Step "Updating: Copy Update Files into Production Borealis Root Folder" { Copy-Item "$updateDir\*" $scriptDir -Recurse -Force } + Run-Step "Updating: Clone Repository Source" { + $cloneArgs = @('clone','--no-tags','--depth','1') + if (-not [string]::IsNullOrWhiteSpace($BranchName)) { + $cloneArgs += @('--branch', $BranchName) + } + $cloneArgs += @($RepositoryUrl, $cloneDir) + Invoke-GitCommand -GitExe $GitExe -WorkingDirectory $stagingPath -Arguments $cloneArgs | Out-Null + } + + Run-Step "Updating: Checkout Target Revision" { + $normalizedHash = $TargetHash.Trim() + $haveHash = $false + try { + Invoke-GitCommand -GitExe $GitExe -WorkingDirectory $cloneDir -Arguments @('rev-parse', $normalizedHash) | Out-Null + $haveHash = $true + } catch { + $haveHash = $false + } + + if (-not $haveHash) { + Invoke-GitCommand -GitExe $GitExe -WorkingDirectory $cloneDir -Arguments @('fetch','origin',$normalizedHash) | Out-Null + } + + if ([string]::IsNullOrWhiteSpace($BranchName)) { + Invoke-GitCommand -GitExe $GitExe -WorkingDirectory $cloneDir -Arguments @('checkout', $normalizedHash) | Out-Null + } else { + Invoke-GitCommand -GitExe $GitExe -WorkingDirectory $cloneDir -Arguments @('checkout','-B',$BranchName,$normalizedHash) | Out-Null + } + } + + Run-Step "Updating: Copy Update Files into Production Borealis Root Folder" { + Get-ChildItem -Path $cloneDir -Force | ForEach-Object { + $destination = Join-Path $scriptDir $_.Name + if ($_.PSIsContainer) { + Copy-Item -Path $_.FullName -Destination $destination -Recurse -Force + } else { + Copy-Item -Path $_.FullName -Destination $scriptDir -Force + } + } + } Run-Step "Updating: Restore Tesseract-OCR Folder" { $restorePath = Join-Path $scriptDir "Data\Server\Python_API_Endpoints" @@ -449,7 +575,9 @@ function Invoke-BorealisUpdate { } } - Run-Step "Updating: Clean Up Update Staging Folder" { Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $scriptDir "Update_Staging") } + Run-Step "Updating: Clean Up Update Staging Folder" { + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $stagingPath + } if (-not $Silent) { Write-Host "Unattended Borealis update completed." -ForegroundColor Green @@ -480,7 +608,8 @@ function Invoke-BorealisAgentUpdate { return } - $currentHash = Get-RepositoryCommitHash -ProjectRoot $scriptDir -AgentRoot $agentRoot + $gitExe = Get-GitExecutablePath -ProjectRoot $scriptDir + $currentHash = Get-RepositoryCommitHash -ProjectRoot $scriptDir -AgentRoot $agentRoot -GitExe $gitExe $serverBaseUrl = Get-BorealisServerUrl -AgentRoot $agentRoot $agentId = Get-AgentServiceId -AgentRoot $agentRoot @@ -531,6 +660,12 @@ function Invoke-BorealisAgentUpdate { Write-Host "Repository hash mismatch detected; update required." } + if (-not ($gitExe) -or -not (Test-Path $gitExe -PathType Leaf)) { + Write-Host "Bundled Git dependency not found. Run '.\\Borealis.ps1 -Agent -AgentAction repair' to bootstrap dependencies and try again." -ForegroundColor Yellow + Write-Host "⚠️ Borealis update aborted." + return + } + $mutex = $null $gotMutex = $false $managedTasks = @() @@ -544,27 +679,12 @@ function Invoke-BorealisAgentUpdate { } $staging = Join-Path $scriptDir 'Update_Staging' - $zipPath = Join-Path $staging 'main.zip' - if (Test-Path $zipPath) { - Write-Verbose "Pre-flight: removing existing $zipPath" - for ($i = 1; $i -le 10; $i++) { - try { - Remove-Item -LiteralPath $zipPath -Force -ErrorAction Stop - break - } catch { - Start-Sleep -Milliseconds (100 * $i) - if ($i -eq 10) { - throw "Pre-flight delete failed; $zipPath appears locked by another process." - } - } - } - } $managedTasks = Stop-AgentScheduledTasks -TaskNames @('Borealis Agent','Borealis Agent (UserHelper)') $updateSucceeded = $false try { - Invoke-BorealisUpdate -Silent + Invoke-BorealisUpdate -GitExe $gitExe -RepositoryUrl $repositoryUrl -TargetHash $serverHash -BranchName $serverBranch -Silent $updateSucceeded = $true } finally { if ($managedTasks.Count -gt 0) { @@ -588,8 +708,15 @@ function Invoke-BorealisAgentUpdate { } catch {} } - $newHash = Get-RepositoryCommitHash -ProjectRoot $scriptDir -AgentRoot $agentRoot - if (-not $newHash -and $serverHash) { + $newHash = Get-RepositoryCommitHash -ProjectRoot $scriptDir -AgentRoot $agentRoot -GitExe $gitExe + + $normalizedNewHash = if ($newHash) { $newHash.Trim().ToLowerInvariant() } else { '' } + $normalizedServerHash = if ($serverHash) { $serverHash.Trim().ToLowerInvariant() } else { '' } + + if ($normalizedServerHash -and (-not $normalizedNewHash -or $normalizedNewHash -ne $normalizedServerHash)) { + $newHash = $serverHash + $normalizedNewHash = $normalizedServerHash + } elseif (-not $newHash -and $serverHash) { $newHash = $serverHash }