Borealis/Borealis.ps1

529 lines
22 KiB
PowerShell

#////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Launch-Borealis.ps1
<#
Borealis.ps1
----------------------
This script deploys the Borealis Workflow Automation Tool with three modules:
- Server (Web Dashboard)
- Agent (Client / Data Collector)
- Desktop App (Electron)
It begins by presenting a menu to the user. Based on the selection,
the corresponding module is launched or deployed.
Usage:
Set-ExecutionPolicy Unrestricted -Scope Process; .\Borealis.ps1
#>
# ---------------------- ASCII Art Terminal Required Changes ----------------------
# Set the .NET Console output encoding to UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# Change the Windows OEM code page to 65001 (UTF-8)
chcp.com 65001 > $null
# ---------------------- Add Common Functions Used Throughout Script ----------------------
$symbols = @{
Success = [char]0x2705
Running = [char]0x23F3
Fail = [char]0x274C
Info = [char]0x2139
}
function Write-ProgressStep {
param (
[string]$Message,
[string]$Status = $symbols["Info"]
)
Write-Host "`r$Status $Message... " -NoNewline
}
function Run-Step {
param (
[string] $Message,
[scriptblock]$Script
)
Write-ProgressStep -Message $Message -Status "$($symbols.Running)"
try {
& $Script
if ($LASTEXITCODE -eq 0 -or $?) {
Write-Host "`r$($symbols.Success) $Message "
} else {
throw "Non-zero exit code"
}
} catch {
Write-Host "`r$($symbols.Fail) $Message - Failed: $_ " -ForegroundColor Red
exit 1
}
}
# ---------------------- Server Deployment / Operation Mode Variables ----------------------
# Define the default operation mode: production | developer
[string]$borealis_operation_mode = 'production'
# ---------------------- Bundle Executables Setup ----------------------
$scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
$depsRoot = Join-Path $scriptDir 'Dependencies'
$pythonExe = Join-Path $depsRoot 'Python\python.exe'
$nodeExe = Join-Path $depsRoot 'NodeJS\node.exe'
$sevenZipExe = Join-Path $depsRoot "7zip\7z.exe"
$npmCmd = Join-Path (Split-Path $nodeExe) 'npm.cmd'
$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"
# ---------------------- Ensure NodeJS is Present (Bundled via 7-Zip) ----------------------
Run-Step "Dependencies: Download NodeJS and Bundle into Borealis" {
if (-not (Test-Path $nodeExe)) {
# Download archive if not present
if (-not (Test-Path $node7zPath)) {
Invoke-WebRequest -Uri $node7zUrl -OutFile $node7zPath
}
# Extract using bundled 7z
if (-not (Test-Path $sevenZipExe)) {
throw "7-Zip CLI not found at: $sevenZipExe"
}
Write-Host "Extracting Node.js to $nodeInstallDir..."
& $sevenZipExe x $node7zPath "-o$nodeInstallDir" -y | Out-Null
# The extracted contents might live under a subfolder; flatten if needed
$extracted = Get-ChildItem $nodeInstallDir | Where-Object { $_.PSIsContainer } | Select-Object -First 1
if ($extracted) {
Get-ChildItem $extracted.FullName | Move-Item -Destination $nodeInstallDir -Force
Remove-Item $extracted.FullName -Recurse -Force
}
# Clean Up 7z File After Extraction
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $node7zPath
}
}
# ---------------------- Ensure Python is Present (via Installer Extraction) ----------------------
Run-Step "Dependencies: Download Python and Bundle into Borealis" {
$pythonInstallDir = Join-Path $scriptDir "Dependencies\Python"
$pythonExe = Join-Path $pythonInstallDir "python.exe"
$pythonMsiBaseUrl = "https://www.python.org/ftp/python/3.13.3/amd64/"
$pythonMsiFiles = @(
"core.msi",
"exe.msi",
"lib.msi",
"pip.msi",
"dev.msi"
)
if (-not (Test-Path $pythonExe)) {
if (-not (Test-Path $pythonInstallDir)) {
New-Item -ItemType Directory -Path $pythonInstallDir | Out-Null
}
foreach ($file in $pythonMsiFiles) {
$url = "$pythonMsiBaseUrl$file"
$localPath = Join-Path $scriptDir "Dependencies\$file"
# Download if missing
if (-not (Test-Path $localPath)) {
Invoke-WebRequest -Uri $url -OutFile $localPath
}
# Extract MSI into install directory
Start-Process -Wait -NoNewWindow -FilePath "msiexec.exe" `
-ArgumentList "/a `"$localPath`" /qn TARGETDIR=`"$pythonInstallDir`""
}
# Clean up downloaded MSIs
foreach ($file in $pythonMsiFiles) {
$localPath = Join-Path $scriptDir "Dependencies\$file"
Remove-Item $localPath -Force -ErrorAction SilentlyContinue
}
# Validate success
if (-not (Test-Path $pythonExe)) {
throw "Python executable not found after MSI extraction."
}
Write-Host "Python successfully bundled to: $pythonExe"
} else {
Write-Host "Python already present at $pythonExe"
}
}
# ---------------------- Ensure Tesseract OCR is Present (Extract from SFX EXE) ----------------------
Run-Step "Dependencies: Download Tesseract OCR Installer and Extract" {
$tessExeUrl = "https://github.com/tesseract-ocr/tesseract/releases/download/5.5.0/tesseract-ocr-w64-setup-5.5.0.20241111.exe"
$tessExePath = Join-Path $depsRoot "tesseract-installer.exe"
$tessInstallDir = Join-Path $scriptDir "Data\Server\Python_API_Endpoints\Tesseract-OCR"
if (-not (Test-Path (Join-Path $tessInstallDir "tesseract.exe"))) {
# Download the installer if it doesn't exist
if (-not (Test-Path $tessExePath)) {
Invoke-WebRequest -Uri $tessExeUrl -OutFile $tessExePath
}
# Extract using 7-Zip
if (-not (Test-Path $sevenZipExe)) {
throw "7-Zip CLI not found at: $sevenZipExe"
}
Write-Host "Extracting Tesseract installer to: $tessInstallDir"
if (Test-Path $tessInstallDir) {
Remove-Item $tessInstallDir -Recurse -Force -ErrorAction SilentlyContinue
}
New-Item -ItemType Directory -Path $tessInstallDir | Out-Null
& $sevenZipExe x $tessExePath "-o$tessInstallDir" -y | Out-Null
# Optional cleanup
Remove-Item $tessExePath -Force -ErrorAction SilentlyContinue
}
}
# ---------------------- Download Tesseract English Language Trained Data ----------------------
Run-Step "Dependencies: Download Tesseract English Language Trained Data" {
$langDataDir = Join-Path $scriptDir "Data\Server\Python_API_Endpoints\Tesseract-OCR\tessdata"
$engPath = Join-Path $langDataDir "eng.traineddata"
$osdPath = Join-Path $langDataDir "osd.traineddata"
if (-not (Test-Path $engPath)) {
Invoke-WebRequest -Uri "https://github.com/tesseract-ocr/tessdata/raw/main/eng.traineddata" -OutFile $engPath
}
if (-not (Test-Path $osdPath)) {
Invoke-WebRequest -Uri "https://github.com/tesseract-ocr/tessdata/raw/main/osd.traineddata" -OutFile $osdPath
}
}
# ---------------------- Common Initialization & Visuals ----------------------
Clear-Host
# ASCII Art Banner
@'
███████████ ████ ███
░░███░░░░░███ ░░███ ░░░
░███ ░███ ██████ ████████ ██████ ██████ ░███ ████ █████
░██████████ ███░░███░░███░░███ ███░░███ ░░░░░███ ░███ ░░███ ███░░
░███░░░░░███░███ ░███ ░███ ░░░ ░███████ ███████ ░███ ░███ ░░█████
░███ ░███░███ ░███ ░███ ░███░░░ ███░░███ ░███ ░███ ░░░░███
███████████ ░░██████ █████ ░░██████ ░░████████ █████ █████ ██████
░░░░░░░░░░░ ░░░░░░ ░░░░░ ░░░░░░ ░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░
'@ | Write-Host -ForegroundColor DarkCyan
Write-Host "Drag-&-Drop Automation Orchestration | Macros | Data Collection & Analysis" -ForegroundColor DarkGray
foreach ($tool in @($pythonExe, $nodeExe, $npmCmd, $npxCmd)) {
if (-not (Test-Path $tool)) {
Write-Host "`r$($symbols.Fail) Bundled executable not found at '$tool'." -ForegroundColor Red
exit 1
}
}
$env:PATH = '{0};{1};{2}' -f (Split-Path $pythonExe), (Split-Path $nodeExe), $env:PATH
# ---------------------- Menu Prompt & User Input ----------------------
Write-Host " "
Write-Host "Please choose which function you want to launch:"
Write-Host " 1) Borealis Server" -ForegroundColor DarkGray
Write-Host " 2) Borealis Agent" -ForegroundColor DarkGray
Write-Host " 3) Build Electron App " -NoNewline -ForegroundColor DarkGray
Write-Host "[Experimental]" -ForegroundColor Red
Write-Host " 4) Package Self-Contained EXE of Server or Agent " -NoNewline -ForegroundColor DarkGray
Write-Host "[Experimental]" -ForegroundColor Red
Write-Host " 5) Update Borealis " -NoNewLine -ForegroundColor DarkGray
Write-Host "[Requires Re-Build]" -ForegroundColor Red
Write-Host "Type a number and press " -NoNewLine
Write-Host "<ENTER>" -ForegroundColor DarkCyan
$choice = Read-Host
switch ($choice) {
"1" {
Write-Host " "
Write-Host "Configure Borealis Server Mode:" -ForegroundColor DarkYellow
Write-Host " 1) Build & Launch > " -NoNewLine -ForegroundColor DarkGray
Write-Host "Production Flask Server @ " -NoNewLine
Write-Host "http://localhost:5000" -ForegroundColor DarkCyan
Write-Host " 2) [Skip Build] & Immediately Launch > " -NoNewLine -ForegroundColor DarkGray
Write-Host "Production Flask Server @ " -NoNewLine
Write-Host "http://localhost:5000" -ForegroundColor DarkCyan
Write-Host " 3) Launch > " -NoNewLine -ForegroundColor DarkGray
Write-Host "[Hotload-Ready] " -NoNewLine -ForegroundColor Green
Write-Host "Vite Dev Server @ " -NoNewLine
Write-Host "http://localhost:5173" -ForegroundColor DarkCyan
$modeChoice = Read-Host "Enter choice [1/2/3]"
switch ($modeChoice) {
"1" { $borealis_operation_mode = "production" }
"2" {
Run-Step "Borealis: Launch Flask Server" {
Push-Location (Join-Path $scriptDir "Server")
& (Join-Path $scriptDir "Server\Scripts\python.exe") (Join-Path $scriptDir "Server\Borealis\server.py")
Pop-Location
}
Exit 0
}
"3" { $borealis_operation_mode = "developer" }
default {
Write-Host "Invalid mode choice: $modeChoice" -ForegroundColor Red
Exit 1
}
}
# ───── Now run your deploy logic ─────
Write-Host "Deploying Borealis Server in '$borealis_operation_mode' mode" -ForegroundColor Blue
$venvFolder = "Server"
$dataSource = "Data"
$dataDestination = "$venvFolder\Borealis"
$customUIPath = "$dataSource\Server\WebUI"
$webUIDestination = "$venvFolder\web-interface"
$venvPython = Join-Path $venvFolder 'Scripts\python.exe'
# Create Virtual Environment & Copy Server Assets
Run-Step "Create Borealis Virtual Python Environment" {
# Leverage Bundled Python Dependency to Construct Virtual Python Environment
if (-not (Test-Path "$venvFolder\Scripts\Activate")) { & $pythonExe -m venv $venvFolder | Out-Null }
if (Test-Path $dataSource) {
Remove-Item $dataDestination -Recurse -Force -ErrorAction SilentlyContinue
New-Item -Path $dataDestination -ItemType Directory -Force | Out-Null
Copy-Item "$dataSource\Server\Python_API_Endpoints" $dataDestination -Recurse
Copy-Item "$dataSource\Server\Sounds" $dataDestination -Recurse
Copy-Item "$dataSource\Server\server.py" $dataDestination
}
. "$venvFolder\Scripts\Activate"
}
# Install Python Dependencies
Run-Step "Install Python Dependencies into Virtual Python Environment" {
if (Test-Path "$dataSource\Server\server-requirements.txt") {
& $venvPython -m pip install --disable-pip-version-check -q -r "$dataSource\Server\server-requirements.txt" | Out-Null
}
}
# Copy Vite WebUI Assets
Run-Step "Copy Borealis WebUI Files into: $webUIDestination" {
if (Test-Path $webUIDestination) {
Remove-Item "$webUIDestination\public\*" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item "$webUIDestination\src\*" -Recurse -Force -ErrorAction SilentlyContinue
} else {
New-Item -Path $webUIDestination -ItemType Directory -Force | Out-Null
}
Copy-Item "$customUIPath\*" $webUIDestination -Recurse -Force
}
# NPM Install for WebUI
Run-Step "Vite Web Frontend: Install NPM Packages" {
Push-Location $webUIDestination
$env:npm_config_loglevel = "silent"
& $npmCmd install --silent --no-fund --audit=false | Out-Null
Pop-Location
}
# Vite Operation Mode Control (build vs dev)
Run-Step "Vite Web Frontend: Start ($borealis_operation_mode)" {
Push-Location $webUIDestination
if ($borealis_operation_mode -eq "developer") { $viteSubCommand = "dev" } else { $viteSubCommand = "build" }
Start-Process -NoNewWindow -FilePath $npmCmd -ArgumentList @("run",$viteSubCommand)
Pop-Location
}
# Launch Flask Server
Run-Step "Borealis: Launch Flask Server" {
Push-Location (Join-Path $scriptDir "Server")
$py = Join-Path $scriptDir "Server\Scripts\python.exe"
$server_py = Join-Path $scriptDir "Server\Borealis\server.py"
Write-Host "`nLaunching Borealis..." -ForegroundColor Green
Write-Host "===================================================================================="
Write-Host "$($symbols.Running) Python Flask API Server Started..."
& $py $server_py
Pop-Location
}
break
}
"2" {
# Agent Deployment (Client / Data Collector)
Write-Host " "
Write-Host "Deploying Borealis Agent..." -ForegroundColor Blue
$venvFolder = "Agent"
$agentSourcePath = "Data\Agent\borealis-agent.py"
$agentRequirements = "Data\Agent\agent-requirements.txt"
$agentDestinationFolder = "$venvFolder\Borealis"
$agentDestinationFile = "$venvFolder\Borealis\borealis-agent.py"
$venvPython = Join-Path $scriptDir $venvFolder | Join-Path -ChildPath 'Scripts\python.exe'
Run-Step "Create Virtual Python Environment" {
if (-not (Test-Path "$venvFolder\Scripts\Activate")) {
& $pythonExe -m venv $venvFolder
}
if (Test-Path $agentSourcePath) {
Remove-Item $agentDestinationFolder -Recurse -Force -ErrorAction SilentlyContinue
New-Item -Path $agentDestinationFolder -ItemType Directory -Force | Out-Null
Copy-Item $agentSourcePath $agentDestinationFile -Force
}
. "$venvFolder\Scripts\Activate"
}
Run-Step "Install Python Dependencies" {
if (Test-Path $agentRequirements) {
& $venvPython -m pip install --disable-pip-version-check -q -r $agentRequirements | Out-Null
}
}
Write-Host "`nLaunching Borealis Agent..." -ForegroundColor Blue
Write-Host "===================================================================================="
& $venvPython -W ignore::SyntaxWarning $agentDestinationFile
}
"3" {
# Desktop App Deployment (Electron)
Clear-Host
Write-Host "Deploying Borealis Desktop App..." -ForegroundColor Cyan
Write-Host "===================================================================================="
$electronSource = "Data\Electron"
$electronDestination = "ElectronApp"
$scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
# 1) Prepare ElectronApp folder
Run-Step "Prepare ElectronApp folder" {
if (Test-Path $electronDestination) {
Remove-Item $electronDestination -Recurse -Force
}
New-Item -Path $electronDestination -ItemType Directory | Out-Null
# Copy deployed Flask server
$deployedServer = Join-Path $scriptDir 'Server\Borealis'
if (-not (Test-Path $deployedServer)) {
throw "Server\Borealis not found - please run choice 1 first."
}
Copy-Item $deployedServer "$electronDestination\Server" -Recurse
# Copy Electron scaffold files
Copy-Item "$electronSource\package.json" "$electronDestination" -Force
Copy-Item "$electronSource\main.js" "$electronDestination" -Force
# Copy built WebUI into renderer
$staticBuild = Join-Path $scriptDir 'Server\web-interface\build'
if (-not (Test-Path $staticBuild)) {
throw "WebUI build not found - run choice 1 to build WebUI first."
}
Copy-Item "$staticBuild\*" "$electronDestination\renderer" -Recurse -Force
}
# 2) Install Electron & Builder
Run-Step "ElectronApp: Install Node dependencies" {
Push-Location $electronDestination
$env:NODE_ENV = ''
$env:npm_config_production = ''
& $npmCmd install --silent --no-fund --audit=false
Pop-Location
}
# 3) Package desktop app
Run-Step "ElectronApp: Package with electron-builder" {
Push-Location $electronDestination
& $npmCmd run dist
Pop-Location
}
# 4) Launch in dev mode
Run-Step "ElectronApp: Launch in dev mode" {
Push-Location $electronDestination
& $npmCmd run dev
Pop-Location
}
}
"4" {
# Prompt the User for Which System to Package using Pyinstaller
Write-Host "Choose which module to package into a self-contained EXE file:" -ForegroundColor DarkYellow
Write-Host " 1) Server" -ForegroundColor DarkGray
Write-Host " 2) Agent" -ForegroundColor DarkGray
$exePackageChoice = Read-Host "Enter choice [1/2]"
switch ($exePackageChoice) {
"1" {
$serverScriptDir = Join-Path -Path $PSScriptRoot -ChildPath "Data\Server"
Set-Location -Path $serverScriptDir
& (Join-Path -Path $serverScriptDir -ChildPath "Package-Borealis-Server.ps1")
}
"2" {
$agentScriptDir = Join-Path -Path $PSScriptRoot -ChildPath "Data\Agent"
Set-Location -Path $agentScriptDir
& (Join-Path -Path $agentScriptDir -ChildPath "Package_Borealis-Agent.ps1")
}
default {
Write-Host "Invalid Choice. Exiting..." -ForegroundColor Red
exit 1
}
}
}
default {
Write-Host "Invalid selection. Exiting..." -ForegroundColor Red
exit 1
}
"5" {
Write-Host " "
Write-Host "Updating Borealis..." -ForegroundColor Green
# Prepare paths
$updateZip = Join-Path $scriptDir "Update_Staging\main.zip"
$updateDir = Join-Path $scriptDir "Update_Staging\borealis"
# Delete old directories/files
Run-Step "Updating: Clean Up Folders to Prepare for Update" {
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue `
(Join-Path $scriptDir "Data"), `
(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")
# Backup Launcher Scripts
#Rename-Item -ErrorAction SilentlyContinue (Join-Path $scriptDir "Launch-Borealis.ps1") "Launch-Borealis.ps1.bak"
#Rename-Item -ErrorAction SilentlyContinue (Join-Path $scriptDir "Launch-Borealis.sh") "Launch-Borealis.sh.bak"
}
Run-Step "Updating: Create Update Staging Folder" {
# Ensure staging folder exists
$stagingPath = Join-Path $scriptDir "Update_Staging"
if (-not (Test-Path $stagingPath)) {
New-Item -ItemType Directory -Force -Path $stagingPath | Out-Null
}
$updateZip = Join-Path $stagingPath "main.zip"
$updateDir = Join-Path $stagingPath "borealis"
}
Run-Step "Updating: Download Update from https://git.bunny-lab.io/Borealis/Borealis/archive/main.zip" {
Invoke-WebRequest -Uri "https://git.bunny-lab.io/Borealis/Borealis/archive/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: Clean Up Update Staging Folder" {
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $scriptDir "Update_Staging")
}
Write-Host "`nUpdate Complete! Please Re-Launch the Borealis Script." -ForegroundColor Green
Read-Host "Press any key to re-launch Borealis..."
& (Join-Path $scriptDir "Borealis.ps1")
Exit 0
}
}