# BrandBrain agent installer for Windows (preview) — https://brandbrain.dev/install.ps1 # # irm https://brandbrain.dev/install.ps1 | iex # $env:BRANDBRAIN_ENROLLMENT_KEY = 'bbe_…'; irm https://brandbrain.dev/install.ps1 | iex # # Installs the headless agent for the current user (no admin needed): # binary %LOCALAPPDATA%\BrandBrain\agent\bin\brandbrain-agent.exe # state %LOCALAPPDATA%\BrandBrain\agent\data (settings in .env, rig identity) # service Scheduled Task "BrandBrain Agent" (runs at logon, restarts on failure) # # Settings come from environment variables (same names as install.sh): # BRANDBRAIN_ENROLLMENT_KEY, BRANDBRAIN_AGENT_TOKEN, BRANDBRAIN_BACKEND_URL, # BRANDBRAIN_AGENT_ID, BRANDBRAIN_RIG_TYPE, BRANDBRAIN_RIG_CAPABILITIES, # BRANDBRAIN_RIG_LABELS, BRANDBRAIN_AGENT_CONCURRENCY, BRANDBRAIN_VERSION. # Uninstall: $env:BRANDBRAIN_UNINSTALL = '1'; irm https://brandbrain.dev/install.ps1 | iex # # Windows support is a preview: the agent runs headless jobs; the browser tier # needs Chrome or Edge installed. WSL (curl … install.sh | sh) or Docker are the # most tested ways to run a rig on a Windows machine. function Install-BrandBrainAgent { [CmdletBinding()] param() $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' # Invoke-WebRequest is ~10x faster without the progress bar [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $DownloadBase = if ($env:BRANDBRAIN_DOWNLOAD_BASE) { $env:BRANDBRAIN_DOWNLOAD_BASE.TrimEnd('/') } else { 'https://brandbrain.dev/agent' } $VersionReq = if ($env:BRANDBRAIN_VERSION) { $env:BRANDBRAIN_VERSION.TrimStart('v') } else { 'latest' } $Root = Join-Path $env:LOCALAPPDATA 'BrandBrain\agent' $BinDir = Join-Path $Root 'bin' $StateDir = Join-Path $Root 'data' $Exe = Join-Path $BinDir 'brandbrain-agent.exe' $EnvFile = Join-Path $StateDir '.env' $Launcher = Join-Path $Root 'run-agent.cmd' $TaskName = 'BrandBrain Agent' function Step($m) { Write-Host "==> $m" -ForegroundColor Magenta } function Ok($m) { Write-Host " [ok] $m" -ForegroundColor Green } function Note($m) { Write-Host " $m" -ForegroundColor DarkGray } function Warn($m) { Write-Host " [!] $m" -ForegroundColor Yellow } Write-Host '' Write-Host 'BrandBrain agent installer (Windows preview)' -ForegroundColor White if ($env:BRANDBRAIN_UNINSTALL -eq '1') { Step 'Uninstalling' Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue | ForEach-Object { Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false Ok "removed scheduled task '$TaskName'" } Get-Process -Name 'brandbrain-agent' -ErrorAction SilentlyContinue | Stop-Process -Force Remove-Item -Recurse -Force $BinDir, $Launcher -ErrorAction SilentlyContinue if ($env:BRANDBRAIN_PURGE -eq '1') { Remove-Item -Recurse -Force $Root -ErrorAction SilentlyContinue Ok 'removed state and settings' } else { Note "kept $StateDir (rig identity); set BRANDBRAIN_PURGE=1 to delete it" } return } $arch = switch ($env:PROCESSOR_ARCHITECTURE) { 'AMD64' { 'amd64' } 'ARM64' { 'arm64' } default { throw "Unsupported architecture '$($env:PROCESSOR_ARCHITECTURE)' (amd64 and arm64 are supported)." } } Step 'Resolving release' # SemVer 2.0 without build metadata; the version goes into URLs and paths. $semver = '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$' if ($VersionReq -ne 'latest' -and $VersionReq -notmatch $semver) { throw "BRANDBRAIN_VERSION must be a release like 1.8.0 (got '$VersionReq')." } $manifestUrl = if ($VersionReq -eq 'latest') { "$DownloadBase/latest.json" } else { "$DownloadBase/$VersionReq/manifest.json" } $manifest = Invoke-RestMethod -UseBasicParsing -Uri $manifestUrl $version = $manifest.version if (-not ([string]$version -match $semver)) { throw "Release manifest at $manifestUrl is malformed." } Ok "brandbrain-agent $version (windows/$arch)" Step 'Downloading' $file = "brandbrain-agent-$version-windows-$arch.zip" $tmp = Join-Path ([IO.Path]::GetTempPath()) ("brandbrain-" + [Guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Force -Path $tmp | Out-Null try { $sums = (Invoke-WebRequest -UseBasicParsing -Uri "$DownloadBase/$version/SHA256SUMS").Content if ($sums -is [byte[]]) { $sums = [Text.Encoding]::UTF8.GetString($sums) } $want = ($sums -split "`n" | Where-Object { $_ -match "\s\*?$([Regex]::Escape($file))\s*$" } | Select-Object -First 1) -replace '\s.*$', '' if (-not $want) { throw "$file is not listed in the release checksums." } $zip = Join-Path $tmp $file Invoke-WebRequest -UseBasicParsing -Uri "$DownloadBase/$version/$file" -OutFile $zip $got = (Get-FileHash -Algorithm SHA256 -Path $zip).Hash.ToLowerInvariant() if ($got -ne $want.ToLowerInvariant()) { throw "Checksum mismatch for $file (expected $want, got $got)." } Ok "verified sha256 $($got.Substring(0,16))..." Step 'Installing' $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue if ($task) { Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue } Get-Process -Name 'brandbrain-agent' -ErrorAction SilentlyContinue | Stop-Process -Force New-Item -ItemType Directory -Force -Path $BinDir, $StateDir | Out-Null Expand-Archive -Force -Path $zip -DestinationPath (Join-Path $tmp 'x') Copy-Item -Force (Join-Path $tmp 'x\brandbrain-agent.exe') $Exe Ok "installed $Exe" } finally { Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue } Step 'Configuring' $settings = [ordered]@{} if (Test-Path $EnvFile) { foreach ($line in Get-Content $EnvFile) { if ($line -match '^([A-Z0-9_]+)="?([^"]*)"?$') { $settings[$Matches[1]] = $Matches[2] } } } foreach ($key in 'BRANDBRAIN_BACKEND_URL', 'BRANDBRAIN_ENROLLMENT_KEY', 'BRANDBRAIN_AGENT_TOKEN', 'BRANDBRAIN_AGENT_ID', 'BRANDBRAIN_RIG_TYPE', 'BRANDBRAIN_RIG_CAPABILITIES', 'BRANDBRAIN_RIG_LABELS', 'BRANDBRAIN_AGENT_CONCURRENCY') { $value = [Environment]::GetEnvironmentVariable($key) if ($value) { if ($value -match '["`$\\]') { throw "$key may not contain quotes, backslashes, `$ or backticks." } $settings[$key] = $value } } $settings['BRANDBRAIN_CONFIG_DIR'] = $StateDir foreach ($browser in "$env:ProgramFiles\Google\Chrome\Application\chrome.exe", "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe", "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe") { if ($browser -and (Test-Path $browser)) { $settings['CHROMIUM_PATH'] = $browser $settings['DISCOVERY_STEALTH_CRAWLER_ENABLED'] = 'true' Ok "browser: $browser" break } } if (-not $settings.Contains('CHROMIUM_PATH')) { Warn 'no Chrome/Edge found: the rig will run HTTP-only crawls' } if (-not ($settings['BRANDBRAIN_ENROLLMENT_KEY'] -or $settings['BRANDBRAIN_AGENT_TOKEN'] -or (Test-Path (Join-Path $StateDir 'rig-token')))) { Warn 'no enrollment key or token: the agent installs but cannot take jobs until it has one' Note "get a key from CRM -> Fleet -> Enrollment, then: `$env:BRANDBRAIN_ENROLLMENT_KEY='bbe_...'; irm https://brandbrain.dev/install.ps1 | iex" } $lines = @('# BrandBrain agent settings (managed by install.ps1; edits are kept on upgrade)') foreach ($k in $settings.Keys) { $lines += "$k=`"$($settings[$k])`"" } Set-Content -Path $EnvFile -Value $lines -Encoding ASCII # Owner-only ACL: the file holds the enrollment key / token. $acl = New-Object Security.AccessControl.FileSecurity $acl.SetAccessRuleProtection($true, $false) $me = [Security.Principal.WindowsIdentity]::GetCurrent().User $acl.AddAccessRule((New-Object Security.AccessControl.FileSystemAccessRule($me, 'FullControl', 'Allow'))) Set-Acl -Path $EnvFile -AclObject $acl Ok "settings written to $EnvFile" # The agent loads $BRANDBRAIN_CONFIG_DIR\.env itself; the launcher only # points it at the state directory. Set-Content -Path $Launcher -Encoding ASCII -Value @( '@echo off', "set `"BRANDBRAIN_CONFIG_DIR=$StateDir`"", "`"$Exe`" --standalone >> `"$StateDir\agent.console.log`" 2>&1" ) Step 'Starting' $action = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument "/c `"$Launcher`"" $trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME $taskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ` -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero) -StartWhenAvailable Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Settings $taskSettings ` -Description 'BrandBrain agent rig (installed by install.ps1)' -Force | Out-Null Start-ScheduledTask -TaskName $TaskName Ok "scheduled task '$TaskName' registered and started (runs at logon)" $healthy = $false for ($i = 0; $i -lt 30; $i++) { $port = 19179 $portFile = Join-Path $StateDir 'diag-port' if (Test-Path $portFile) { $port = [int](Get-Content $portFile -Raw) } try { Invoke-WebRequest -UseBasicParsing -TimeoutSec 2 -Uri "http://127.0.0.1:$port/health" | Out-Null $healthy = $true; break } catch { Start-Sleep -Seconds 1 } } if ($healthy) { Ok 'agent is running' } else { Warn "the agent hasn't answered yet; see $StateDir\agent.log" } Write-Host '' Write-Host "BrandBrain agent $version is installed." -ForegroundColor Green Write-Host " binary $Exe" Write-Host " settings $EnvFile" Write-Host " logs $StateDir\agent.log" Write-Host ' upgrade re-run this installer' Write-Host '' } Install-BrandBrainAgent