& { # Ryvion Node Windows Installer # Universal install: iwr https://api.ryvion.ai/install.ps1 | iex # Native-only mode: iwr 'https://api.ryvion.ai/install.ps1?mode=native' | iex $ErrorActionPreference = "Stop" $HubURL = "https://ryvion-hub.fly.dev" $BindToken = "" $NativeOnly = "0" $ProgramFilesRoot = $env:ProgramFiles if (-not $ProgramFilesRoot) { $ProgramFilesRoot = "C:\Program Files" } $InstallPath = Join-Path $ProgramFilesRoot "Ryvion" $UIPort = "45890" $RuntimeChannel = "managed_oci_v1" $RuntimeVersion = "2026.04.29.2" $RuntimeProvider = "oci_desktop_adapter" $RuntimeMode = "host_package" $RuntimeSource = "ryvion_runtime_kit" $RuntimeInstallRoot = Join-Path $ProgramFilesRoot "Ryvion\runtime" $RuntimeBinaryPath = Join-Path $ProgramFilesRoot "Ryvion\runtime\ryvion-runtime.cmd" $RuntimeBackendBinaryPath = Join-Path $ProgramFilesRoot "Ryvion\runtime\backend\ryvion-oci.cmd" $RuntimePodmanConnectionsPath = Join-Path $ProgramFilesRoot "Ryvion\runtime\state\podman-connections.json" $RuntimeEngineBinaryPath = "" $RuntimeEngineKind = "" $RuntimeManifestHash = "035b27209cc2b31740d3085c458951fc3d81da95df1f1bafd2af0e1775f3b205" $RuntimeArtifactURL = "https://ryvion-hub.fly.dev/download/runtime/windows/kit.zip" $RuntimeChecksumURL = "https://ryvion-hub.fly.dev/download/runtime/windows/kit.zip.sha256" $RuntimeArtifactName = "ryvion-runtime-kit-windows-amd64-2026.04.29.2.zip" $ProgramDataDir = $env:ProgramData if (-not $ProgramDataDir) { $ProgramDataDir = "C:\ProgramData" } $NodeKeyPath = Join-Path $ProgramDataDir "Ryvion\node-key" $HadExistingNodeIdentity = Test-Path $NodeKeyPath function Get-OCIEngineCLI { if ($env:RYV_RUNTIME_ENGINE_BINARY -and (Test-Path $env:RYV_RUNTIME_ENGINE_BINARY)) { $envName = [System.IO.Path]::GetFileName($env:RYV_RUNTIME_ENGINE_BINARY).ToLowerInvariant() if ($envName -like "podman*") { return $env:RYV_RUNTIME_ENGINE_BINARY } } $engineCandidates = @("podman") foreach ($engineName in $engineCandidates) { $engineCommand = Get-Command $engineName -ErrorAction SilentlyContinue if ($engineCommand -and $engineCommand.Source) { return $engineCommand.Source } } $candidates = @( "$env:ProgramFiles\RedHat\Podman\podman.exe", "$env:ProgramW6432\RedHat\Podman\podman.exe", "$env:ProgramFiles\Podman\podman.exe", "$env:ProgramW6432\Podman\podman.exe", "$env:ProgramFiles\RedHat\Podman Desktop\podman.exe", "$env:ProgramW6432\RedHat\Podman Desktop\podman.exe", "$env:LOCALAPPDATA\Programs\RedHat\Podman\podman.exe", "$env:LOCALAPPDATA\Programs\RedHat\Podman Desktop\podman.exe", "$env:LOCALAPPDATA\Programs\Podman\podman.exe", "$env:LOCALAPPDATA\Programs\Podman Desktop\podman.exe", "$env:LOCALAPPDATA\Microsoft\WinGet\Links\podman.exe" ) foreach ($candidate in $candidates) { if ($candidate -and (Test-Path $candidate)) { return $candidate } } $searchRoots = @( "$env:ProgramFiles", "$env:ProgramW6432", "$env:LOCALAPPDATA\Microsoft\WinGet\Packages", "$env:LOCALAPPDATA\Programs" ) foreach ($root in $searchRoots) { if (-not $root -or -not (Test-Path $root)) { continue } try { $match = Get-ChildItem -Path $root -Filter "podman.exe" -File -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 if ($match -and $match.FullName) { return $match.FullName } } catch { } } return $null } function Get-OCIEngineKind { param([string]$EngineCLI = $null) if (-not $EngineCLI) { $EngineCLI = Get-OCIEngineCLI } if (-not $EngineCLI) { return "" } $name = [System.IO.Path]::GetFileName($EngineCLI).ToLowerInvariant() if ($name -like "podman*") { return "podman" } return "unknown" } function Get-OCIBackendCLI { if (Test-Path $RuntimeBackendBinaryPath) { return $RuntimeBackendBinaryPath } return Get-OCIEngineCLI } function Test-ManagedRuntimeReady { $manifestPath = Join-Path $RuntimeInstallRoot "manifest.json" if (-not (Test-Path $manifestPath)) { return $false } try { $manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json $installedHash = [string]$manifest.manifest_hash if ([string]::IsNullOrWhiteSpace($installedHash) -or $installedHash -ne $RuntimeManifestHash) { return $false } } catch { return $false } if (-not (Test-Path (Join-Path $RuntimeInstallRoot "helpers\ryvion-image-runtime.cmd"))) { return $false } if (-not (Test-Path (Join-Path $RuntimeInstallRoot "helpers\ryvion-image-runtime.ps1"))) { return $false } if (-not (Test-Path (Join-Path $RuntimeInstallRoot "backend\ryvion-oci-entry.ps1"))) { return $false } $backendCli = Get-OCIBackendCLI if (-not $backendCli) { return $false } try { & $backendCli version *> $null return $LASTEXITCODE -eq 0 } catch { return $false } } function Merge-ConfigValues { param([hashtable]$Target, $Source) if (-not $Source) { return } if ($Source -is [System.Collections.IDictionary]) { foreach ($key in $Source.Keys) { $Target[[string]$key] = $Source[$key] } return } foreach ($prop in $Source.PSObject.Properties) { $Target[$prop.Name] = $prop.Value } } Add-Type -AssemblyName System.IO.Compression.FileSystem function Get-RyvionParentPath { param([string]$PathValue) if ([string]::IsNullOrWhiteSpace($PathValue)) { return $null } $parent = [System.IO.Path]::GetDirectoryName($PathValue) if (-not [string]::IsNullOrWhiteSpace($parent)) { return $parent } return $null } function Write-RyvionZipEntry { param( [System.IO.Compression.ZipArchiveEntry]$Entry, [string]$DestinationPath ) if ([string]::IsNullOrWhiteSpace($DestinationPath)) { throw "runtime kit destination path is missing" } $parent = Get-RyvionParentPath $DestinationPath if ($parent) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } $entryStream = $Entry.Open() try { $fileStream = [System.IO.File]::Open($DestinationPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::Read) try { $entryStream.CopyTo($fileStream) } finally { $fileStream.Dispose() } } finally { $entryStream.Dispose() } } function Install-RyvionRuntimeFromZip { param([string]$ZipPath) $backendRoot = Get-RyvionParentPath $RuntimeBackendBinaryPath New-Item -ItemType Directory -Path $RuntimeInstallRoot -Force | Out-Null if ($backendRoot) { New-Item -ItemType Directory -Path $backendRoot -Force | Out-Null } $archive = [System.IO.Compression.ZipFile]::OpenRead($ZipPath) try { foreach ($entry in $archive.Entries) { $entryName = $entry.FullName.Replace("/", "\") switch ($entryName) { "ryvion-runtime.cmd" { Write-RyvionZipEntry -Entry $entry -DestinationPath $RuntimeBinaryPath } "payloads\ryvion-runtime.psdata" { Write-RyvionZipEntry -Entry $entry -DestinationPath (Join-Path $RuntimeInstallRoot "payloads\ryvion-runtime.psdata") } "manifest.json" { Write-RyvionZipEntry -Entry $entry -DestinationPath (Join-Path $RuntimeInstallRoot "manifest.json") } "backend\ryvion-oci.cmd" { Write-RyvionZipEntry -Entry $entry -DestinationPath $RuntimeBackendBinaryPath } "backend\ryvion-oci-entry.ps1" { if (-not $backendRoot) { throw "runtime backend directory is missing" } Write-RyvionZipEntry -Entry $entry -DestinationPath (Join-Path $backendRoot "ryvion-oci-entry.ps1") } "backend\ryvion-oci.psdata" { if (-not $backendRoot) { throw "runtime backend directory is missing" } Write-RyvionZipEntry -Entry $entry -DestinationPath (Join-Path $backendRoot "ryvion-oci.psdata") } "helpers\ryvion-image-runtime.cmd" { Write-RyvionZipEntry -Entry $entry -DestinationPath (Join-Path $RuntimeInstallRoot "helpers\ryvion-image-runtime.cmd") } "helpers\ryvion-image-runtime.ps1" { Write-RyvionZipEntry -Entry $entry -DestinationPath (Join-Path $RuntimeInstallRoot "helpers\ryvion-image-runtime.ps1") } } } } finally { $archive.Dispose() } } function Ensure-ManagedRuntime { Write-Host " [4/6] Provisioning managed execution runtime..." -ForegroundColor Yellow if (Test-ManagedRuntimeReady) { Write-Host " [OK] Managed execution runtime already ready" -ForegroundColor Green return } Write-Host " [....] Applying runtime kit artifact $RuntimeArtifactName" -ForegroundColor Yellow $artifactBase = Join-Path $env:TEMP ("ryvion-runtime-kit-" + [Guid]::NewGuid().ToString("N")) $artifact = "$artifactBase.zip" $artifactDir = $artifactBase $checksumContent = (Invoke-WebRequest -Uri $RuntimeChecksumURL -UseBasicParsing).Content $expectedChecksum = (($checksumContent -split '\s+') | Where-Object { $_ -and $_.Trim().Length -gt 0 })[0] Invoke-WebRequest -Uri $RuntimeArtifactURL -OutFile $artifact -UseBasicParsing $actualChecksum = (Get-FileHash -Path $artifact -Algorithm SHA256).Hash.ToLowerInvariant() if ($actualChecksum -ne $expectedChecksum.ToLowerInvariant()) { throw "managed runtime kit checksum mismatch" } Install-RyvionRuntimeFromZip -ZipPath $artifact try { & $RuntimeBinaryPath ensure if ($LASTEXITCODE -ne 0) { throw "managed runtime bootstrap failed" } } finally { Remove-Item $artifact -Force -ErrorAction SilentlyContinue } } Write-Host "" Write-Host " Ryvion Node Installer" -ForegroundColor Cyan Write-Host " =====================" -ForegroundColor Cyan Write-Host "" # Check administrator privileges $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { Write-Host " [ERROR] This script requires administrator privileges." -ForegroundColor Red Write-Host " Please right-click PowerShell and select 'Run as Administrator'." -ForegroundColor Yellow return } try { if ($HadExistingNodeIdentity) { Write-Host " [prep] Existing node identity detected; update will reuse it" -ForegroundColor Cyan } # Stop existing service before updating (Windows locks running .exe files) $existingSvc = Get-Service -Name "RyvionNode" -ErrorAction SilentlyContinue if ($existingSvc) { Write-Host " [0/6] Stopping existing service..." -ForegroundColor Yellow # Disable failure recovery before stopping to prevent SCM auto-restart sc.exe failureflag RyvionNode 0 2>$null | Out-Null sc.exe failure RyvionNode "reset= 0" "actions= " 2>$null | Out-Null Stop-Service -Name "RyvionNode" -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 # Kill any leftover process Get-Process -Name "ryvion-node" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 1 Write-Host " [OK] Existing service stopped" -ForegroundColor Green } Write-Host " [1/6] Downloading binary..." -ForegroundColor Yellow $releaseUrl = "$HubURL/download/windows/binary" $tempZip = Join-Path $env:TEMP "ryvion-node.zip" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest -Uri $releaseUrl -OutFile $tempZip -UseBasicParsing if (-not (Test-Path $tempZip)) { throw "Download failed" } $zipSize = (Get-Item $tempZip).Length Write-Host " [OK] Download completed ($([math]::Round($zipSize/1MB, 1)) MB)" -ForegroundColor Green Write-Host " [2/6] Installing..." -ForegroundColor Yellow New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null Expand-Archive -Path $tempZip -DestinationPath $InstallPath -Force $binPath = Join-Path $InstallPath "ryvion-node.exe" if (-not (Test-Path $binPath)) { throw "Binary not found after extraction at $binPath" } $binVersion = "" try { $binVersion = (& $binPath -version 2>$null | Out-String).Trim() if ($binVersion) { Write-Host " [OK] Installed node binary version: $binVersion" -ForegroundColor Green } } catch { Write-Host " [WARN] Could not read installed binary version" -ForegroundColor Yellow } Write-Host " [OK] Extracted to $InstallPath" -ForegroundColor Green Write-Host " [3/6] Configuring PATH..." -ForegroundColor Yellow $machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine") if ($machinePath -notlike "*$InstallPath*") { [Environment]::SetEnvironmentVariable("Path", "$machinePath;$InstallPath", "Machine") } Write-Host " [OK] PATH updated" -ForegroundColor Green if ($NativeOnly -eq "1") { Write-Host " [4/6] Native mode: skipping managed container runtime (no Podman required)" -ForegroundColor Green $RuntimeEngineBinaryPath = "" $RuntimeEngineKind = "" } else { Ensure-ManagedRuntime $RuntimeEngineBinaryPath = Get-OCIEngineCLI $RuntimeEngineKind = Get-OCIEngineKind -EngineCLI $RuntimeEngineBinaryPath } Write-Host " [5/6] Writing config..." -ForegroundColor Yellow $cfgDir = Join-Path $env:USERPROFILE ".ryvion" if (-not (Test-Path $cfgDir)) { New-Item -ItemType Directory -Path $cfgDir -Force | Out-Null } $cfgPath = Join-Path $cfgDir "config.json" $cfgObj = @{} if (Test-Path $cfgPath) { try { $rawCfg = Get-Content $cfgPath -Raw if ($rawCfg -and $rawCfg.Trim().Length -gt 0) { $existingCfg = $rawCfg | ConvertFrom-Json Merge-ConfigValues -Target $cfgObj -Source $existingCfg } } catch { Write-Host " [WARN] Existing config could not be parsed cleanly. Refreshing runtime metadata." -ForegroundColor Yellow } } $cfgObj["hub_url"] = $HubURL $cfgObj["device_type"] = "auto" $cfgObj["log_level"] = "info" $cfgObj["runtime_channel"] = $RuntimeChannel $cfgObj["runtime_channel_version"] = $RuntimeVersion $cfgObj["runtime_provider"] = $RuntimeProvider $cfgObj["runtime_mode"] = $RuntimeMode $cfgObj["runtime_source"] = $RuntimeSource $cfgObj["runtime_artifact"] = $RuntimeArtifactName $cfgObj["runtime_binary"] = $RuntimeBinaryPath $cfgObj["runtime_backend_binary"] = $RuntimeBackendBinaryPath $cfgObj["runtime_engine_binary"] = $RuntimeEngineBinaryPath $cfgObj["runtime_engine_kind"] = $RuntimeEngineKind $cfgObj["runtime_manifest_hash"] = $RuntimeManifestHash if ($BindToken -and $BindToken.Length -gt 0) { $cfgObj["bind_token"] = $BindToken } $cfgObj | ConvertTo-Json | Out-File -FilePath $cfgPath -Encoding ASCII -Force [Environment]::SetEnvironmentVariable("RYV_HUB_URL", $HubURL, "Machine") [Environment]::SetEnvironmentVariable("RYV_UI_PORT", $UIPort, "Machine") [Environment]::SetEnvironmentVariable("RYV_RUNTIME_CHANNEL", $RuntimeChannel, "Machine") [Environment]::SetEnvironmentVariable("RYV_RUNTIME_CHANNEL_VERSION", $RuntimeVersion, "Machine") [Environment]::SetEnvironmentVariable("RYV_RUNTIME_PROVIDER", $RuntimeProvider, "Machine") [Environment]::SetEnvironmentVariable("RYV_RUNTIME_MODE", $RuntimeMode, "Machine") [Environment]::SetEnvironmentVariable("RYV_RUNTIME_SOURCE", $RuntimeSource, "Machine") [Environment]::SetEnvironmentVariable("RYV_RUNTIME_ARTIFACT", $RuntimeArtifactName, "Machine") [Environment]::SetEnvironmentVariable("RYV_RUNTIME_BINARY", $RuntimeBinaryPath, "Machine") [Environment]::SetEnvironmentVariable("RYV_RUNTIME_BACKEND_BINARY", $RuntimeBackendBinaryPath, "Machine") [Environment]::SetEnvironmentVariable("RYV_RUNTIME_ENGINE_BINARY", $RuntimeEngineBinaryPath, "Machine") [Environment]::SetEnvironmentVariable("RYV_RUNTIME_ENGINE_KIND", $RuntimeEngineKind, "Machine") [Environment]::SetEnvironmentVariable("RYV_RUNTIME_MANIFEST_HASH", $RuntimeManifestHash, "Machine") [Environment]::SetEnvironmentVariable("PODMAN_CONNECTIONS_CONF", $RuntimePodmanConnectionsPath, "Machine") if ($BindToken -and $BindToken.Length -gt 0) { [Environment]::SetEnvironmentVariable("RYV_BIND_TOKEN", $BindToken, "Machine") } if ($NativeOnly -eq "1") { [Environment]::SetEnvironmentVariable("RYV_DISABLE_OCI", "1", "Machine") } else { [Environment]::SetEnvironmentVariable("RYV_DISABLE_OCI", $null, "Machine") } Write-Host " [OK] Config saved to $cfgPath" -ForegroundColor Green Write-Host " [6/6] Installing service..." -ForegroundColor Yellow $svcArgs = """$binPath"" -hub $HubURL -ui-port $UIPort" if (-not $existingSvc) { New-Service -Name "RyvionNode" -BinaryPathName $svcArgs -DisplayName "Ryvion Node" -StartupType Automatic -Description "Ryvion managed operator runtime" -ErrorAction Stop | Out-Null } else { $scOutput = sc.exe config RyvionNode "binPath= $svcArgs" 2>&1 if ($LASTEXITCODE -ne 0) { Write-Host " [WARN] Service Controller path update failed; writing ImagePath directly." -ForegroundColor Yellow Write-Host " [WARN] sc.exe output: $scOutput" -ForegroundColor DarkYellow Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\RyvionNode" -Name ImagePath -Value $svcArgs -ErrorAction Stop } } $svcRecord = Get-CimInstance Win32_Service -Filter "Name='RyvionNode'" -ErrorAction Stop $svcPath = [string]$svcRecord.PathName if ($svcPath -notlike "*$binPath*") { throw "RyvionNode service path mismatch. Expected $binPath but Windows reports: $svcPath" } Write-Host " [OK] Service path verified: $svcPath" -ForegroundColor Green # Configure automatic restart on failure (restart after 5s, 10s, 30s) sc.exe failure RyvionNode "reset= 86400" "actions= restart/5000/restart/10000/restart/30000" | Out-Null sc.exe failureflag RyvionNode 1 | Out-Null $svc = Get-Service -Name "RyvionNode" if ($svc.Status -ne "Running") { Start-Service -Name "RyvionNode" -ErrorAction Stop Start-Sleep -Seconds 3 $svc = Get-Service -Name "RyvionNode" } if ($svc.Status -eq "Running") { Write-Host " [OK] Service installed and running (auto-restart on failure)" -ForegroundColor Green } else { Write-Host " [WARN] Service status: $($svc.Status)" -ForegroundColor Yellow } $healthzStatus = $null try { for ($i = 0; $i -lt 5; $i++) { try { $healthzStatus = Invoke-RestMethod -Uri "http://127.0.0.1:$UIPort/healthz" -UseBasicParsing -TimeoutSec 5 Write-Host " [OK] Local operator API is reachable on http://127.0.0.1:$UIPort" -ForegroundColor Green break } catch { Start-Sleep -Seconds 1 } } } catch { Write-Host " [WARN] Could not confirm local operator API on port $UIPort yet" -ForegroundColor Yellow } if ($healthzStatus) { $runningVersion = [string]$healthzStatus.version if ($runningVersion) { Write-Host " [OK] Running node service version: $runningVersion" -ForegroundColor Green } if ($binVersion -and $runningVersion -and (-not $binVersion.Contains($runningVersion))) { throw "RyvionNode is running version $runningVersion but installed binary is $binVersion. Service path: $svcPath" } } try { $localStatus = $null for ($i = 0; $i -lt 6; $i++) { try { $localStatus = Invoke-RestMethod -Uri "http://127.0.0.1:$UIPort/api/v1/operator/status" -UseBasicParsing -TimeoutSec 15 break } catch { Start-Sleep -Seconds 1 } } if ($localStatus) { if ($localStatus.runtime) { $runtimeHealth = [string]$localStatus.runtime.runtime_health $runtimeReady = [string]$localStatus.runtime.runtime_ready if ($runtimeHealth) { Write-Host " [OK] Runtime heartbeat: health=$runtimeHealth ready=$runtimeReady" -ForegroundColor Green } } } elseif ($binVersion) { Write-Host " [WARN] Runtime status endpoint is still warming; service version was verified through /healthz." -ForegroundColor Yellow } } catch { Write-Host " [WARN] Runtime status check did not complete yet: $($_.Exception.Message)" -ForegroundColor Yellow } try { $nodeIdentity = (& $binPath identity --short 16 2>$null | Out-String).Trim() if ($nodeIdentity) { if ($HadExistingNodeIdentity) { Write-Host " [OK] Reusing node identity: $nodeIdentity" -ForegroundColor Green } else { Write-Host " [OK] Created node identity: $nodeIdentity" -ForegroundColor Green } } } catch { } Remove-Item $tempZip -Force -ErrorAction SilentlyContinue Write-Host "" Write-Host " Ryvion Node installed successfully!" -ForegroundColor Green Write-Host "" Write-Host " Next steps:" -ForegroundColor White Write-Host " - Check status: Get-Service RyvionNode" -ForegroundColor Gray Write-Host " - Local API: http://127.0.0.1:$UIPort/healthz" -ForegroundColor Gray Write-Host " - Dashboard: https://ryvion.ai/app/operator/nodes" -ForegroundColor Gray Write-Host "" } catch { Write-Host "" Write-Host " [ERROR] Installation failed: $($_.Exception.Message)" -ForegroundColor Red Write-Host "" Write-Host " Troubleshooting:" -ForegroundColor Yellow Write-Host " 1. Make sure you run PowerShell as Administrator" -ForegroundColor Gray Write-Host " 2. Check if antivirus blocked the binary or runtime package" -ForegroundColor Gray Write-Host " 3. Try: Stop-Service RyvionNode; then reinstall" -ForegroundColor Gray Write-Host "" } }