#Requires -Version 5.1 Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $serverUrl = if ($env:OTO_BOOTSTRAP_SERVER_URL) { $env:OTO_BOOTSTRAP_SERVER_URL } else { '' } $socketUrl = if ($env:OTO_BOOTSTRAP_SOCKET_URL) { $env:OTO_BOOTSTRAP_SOCKET_URL } else { '' } $agentId = if ($env:OTO_AGENT_ID) { $env:OTO_AGENT_ID } else { '' } $enrollmentToken = '' $releaseBaseUrl = if ($env:OTO_BOOTSTRAP_RELEASE_BASE_URL) { $env:OTO_BOOTSTRAP_RELEASE_BASE_URL } else { '' } $agentAlias = if ($env:OTO_AGENT_ALIAS) { $env:OTO_AGENT_ALIAS } else { '' } $installDir = Join-Path $env:LOCALAPPDATA 'oto\bin' $configPath = Join-Path $env:LOCALAPPDATA 'oto\agent\config.yaml' $workspaceRoot = Join-Path $env:LOCALAPPDATA 'oto\workspace' $logDir = Join-Path $env:LOCALAPPDATA 'oto\agent\log' $background = $true function Get-HashHex([string]$value) { $sha = [System.Security.Cryptography.SHA256]::Create() try { $bytes = [System.Text.Encoding]::UTF8.GetBytes($value) $hashBytes = $sha.ComputeHash($bytes) return -join ($hashBytes | ForEach-Object { $_.ToString('x2') }) } finally { $sha.Dispose() } } function Get-DeviceIdentitySource { $source = '' try { $machineGuid = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Cryptography' -Name MachineGuid -ErrorAction Stop).MachineGuid if ($machineGuid) { $source = [string]$machineGuid } } catch { $source = '' } if (-not $source -and $env:COMPUTERNAME) { $source = $env:COMPUTERNAME } if (-not $source) { $source = [System.Net.Dns]::GetHostName() } return $source } function Get-DefaultAgentId { $hash = Get-HashHex "oto:windows:$(Get-DeviceIdentitySource)" return "oto-$($hash.Substring(0, [Math]::Min(32, $hash.Length)))" } function Get-DefaultAgentAlias { if ($env:COMPUTERNAME) { return $env:COMPUTERNAME } $hostName = [System.Net.Dns]::GetHostName() if ($hostName) { return $hostName } return 'oto-runner' } $i = 0 while ($i -lt $args.Count) { switch ($args[$i]) { '--' { $i++ } '--server-url' { $serverUrl = $args[$i + 1]; $i += 2 } '--socket-url' { $socketUrl = $args[$i + 1]; $i += 2 } '--agent-id' { $agentId = $args[$i + 1]; $i += 2 } '--token' { $enrollmentToken = $args[$i + 1]; $i += 2 } '--release-base-url' { $releaseBaseUrl = $args[$i + 1]; $i += 2 } '--agent-alias' { $agentAlias = $args[$i + 1]; $i += 2 } '--install-dir' { $installDir = $args[$i + 1]; $i += 2 } '--config-path' { $configPath = $args[$i + 1]; $i += 2 } '--workspace-root' { $workspaceRoot = $args[$i + 1]; $i += 2 } '--log-dir' { $logDir = $args[$i + 1]; $i += 2 } '--no-background' { $background = $false; $i++ } default { Write-Error "Unknown option: $($args[$i])"; exit 1 } } } if (-not $serverUrl) { $serverUrl = '__OTO_BOOTSTRAP_DEFAULT_SERVER_URL__' } if (-not $socketUrl) { $socketUrl = '__OTO_BOOTSTRAP_DEFAULT_SOCKET_URL__' } if (-not $releaseBaseUrl) { $releaseBaseUrl = '__OTO_BOOTSTRAP_DEFAULT_RELEASE_BASE_URL__' } if ($serverUrl -eq '__OTO_BOOTSTRAP_DEFAULT_SERVER_URL__') { $serverUrl = '' } if ($socketUrl -eq '__OTO_BOOTSTRAP_DEFAULT_SOCKET_URL__') { $socketUrl = '' } if ($releaseBaseUrl -eq '__OTO_BOOTSTRAP_DEFAULT_RELEASE_BASE_URL__') { $releaseBaseUrl = '' } if (-not $enrollmentToken) { Write-Error 'Error: Missing required argument: --token' exit 1 } if (-not $serverUrl) { Write-Error 'Error: Missing OTO Core URL. Use --server-url or fetch this script from the OTO Core bootstrap endpoint.' exit 1 } if (-not $releaseBaseUrl) { Write-Error 'Error: Missing release base URL. Use --release-base-url or fetch this script from the OTO Core bootstrap endpoint.' exit 1 } if (-not $releaseBaseUrl.StartsWith('https://')) { Write-Error 'Error: --release-base-url must use https://' exit 1 } if (-not $agentId) { $agentId = Get-DefaultAgentId } if (-not $agentAlias) { $agentAlias = Get-DefaultAgentAlias } if (-not $agentId) { Write-Error 'Error: Unable to derive agent id from this device. Use --agent-id to override.' exit 1 } $arch = $env:PROCESSOR_ARCHITECTURE $archName = switch ($arch) { 'AMD64' { 'x64' } 'ARM64' { 'arm64' } default { Write-Error "Error: Unsupported architecture: $arch" exit 1 } } $assetName = "oto-windows-$archName.zip" $downloadUrl = "$releaseBaseUrl/$assetName" $tmpDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName()) New-Item -ItemType Directory -Path $tmpDir | Out-Null try { $zipPath = Join-Path $tmpDir $assetName Write-Host 'Downloading OTO agent...' Invoke-WebRequest -Uri $downloadUrl -OutFile $zipPath -UseBasicParsing Write-Host 'Extracting OTO agent...' Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force $null = New-Item -ItemType Directory -Path $installDir -Force Copy-Item -Path (Join-Path $tmpDir 'oto.exe') -Destination (Join-Path $installDir 'oto.exe') -Force $configDir = Split-Path $configPath -Parent $agentStateDir = Join-Path $env:LOCALAPPDATA 'oto\agent' $pidPath = Join-Path $agentStateDir 'oto-agent.pid' $null = New-Item -ItemType Directory -Path $configDir -Force $null = New-Item -ItemType Directory -Path $agentStateDir -Force $null = New-Item -ItemType Directory -Path $workspaceRoot -Force $null = New-Item -ItemType Directory -Path $logDir -Force $agentIdYaml = $agentId.Replace("'", "''") $agentAliasYaml = $agentAlias.Replace("'", "''") $enrollmentTokenYaml = $enrollmentToken.Replace("'", "''") $serverUrlYaml = $serverUrl.Replace("'", "''") $socketUrlYaml = $socketUrl.Replace("'", "''") $installDirYaml = ($installDir -replace '\\', '/').Replace("'", "''") $workspaceRootYaml = ($workspaceRoot -replace '\\', '/').Replace("'", "''") $logDirYaml = ($logDir -replace '\\', '/').Replace("'", "''") $socketUrlLine = '' if ($socketUrl) { $socketUrlLine = " socket_url: '$socketUrlYaml'`n" } $configContent = @" agent: id: '$agentIdYaml' alias: '$agentAliasYaml' enrollment_token: '$enrollmentTokenYaml' server: url: '$serverUrlYaml' $socketUrlLine runtime: install_dir: '$installDirYaml' workspace_root: '$workspaceRootYaml' log_dir: '$logDirYaml' "@ [System.IO.File]::WriteAllText($configPath, $configContent) $acl = Get-Acl $configPath $acl.SetAccessRuleProtection($true, $false) $rule = New-Object System.Security.AccessControl.FileSystemAccessRule( [System.Security.Principal.WindowsIdentity]::GetCurrent().Name, 'FullControl', 'Allow' ) $acl.SetAccessRule($rule) Set-Acl -Path $configPath -AclObject $acl $otoBin = Join-Path $installDir 'oto.exe' Write-Host 'Verifying OTO binary...' try { $versionProc = Start-Process -FilePath $otoBin -ArgumentList '--version' -NoNewWindow -Wait -PassThru if ($versionProc.ExitCode -ne 0) { Write-Error 'Error: OTO binary verification failed.' exit 1 } } catch { Write-Error 'Error: OTO binary verification failed.' exit 1 } if ($background) { Write-Host 'Starting OTO agent in the background...' $outLog = Join-Path $logDir 'oto-agent.out.log' $errLog = Join-Path $logDir 'oto-agent.err.log' $proc = Start-Process -FilePath $otoBin ` -ArgumentList "agent run --config `"$configPath`"" ` -RedirectStandardOutput $outLog ` -RedirectStandardError $errLog ` -WindowStyle Hidden ` -PassThru [System.IO.File]::WriteAllText($pidPath, $proc.Id.ToString()) Write-Host "OTO agent started in the background. PID: $($proc.Id)" Write-Host "Log path: $outLog" } else { Write-Host 'OTO agent is configured to run in the foreground.' Write-Host 'To run it manually, use the following command:' Write-Host " $otoBin agent run --config `"$configPath`"" } } finally { Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $tmpDir }