#!/usr/bin/env pwsh # remoteagent Windows installer. # # Usage: # irm https://remoteagent.online/install.ps1 | iex # ./install.ps1 -Version v2.11.0 # # Releases: downloads the exact GitHub release asset and verifies it against # SHA256SUMS before extraction. main branch: downloads the rolling archive and # clearly labels it as unversioned. [CmdletBinding()] param( [string]$Version = '', [string]$InstallDir = "$HOME\.remoteagent", [switch]$DryRun ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $Repo = 'remoteagent-online/remoteagent' if ($DryRun) { Write-Host "DRY RUN — nothing will be changed" } $node = Get-Command node.exe -ErrorAction SilentlyContinue if (-not $node) { throw 'Node.js 20+ is required (https://nodejs.org)' } $major = & node.exe -p "process.versions.node.split('.')[0]" if ([int]$major -lt 20) { throw "Node.js 20+ required (found $major)" } # State-dir migration (rebrand: ~/.mona-agent → ~/.remoteagent). # Non-destructive: move the legacy dir, leave a symlink behind. Never delete. $legacyDir = Join-Path $HOME '.mona-agent' if ((Test-Path $legacyDir) -and ((Get-Item $legacyDir).LinkType -ne 'SymbolicLink') -and -not (Test-Path $InstallDir)) { if ($DryRun) { Write-Host "Would migrate state $legacyDir -> $InstallDir" } else { Move-Item $legacyDir $InstallDir try { New-Item -ItemType SymbolicLink -Path $legacyDir -Target $InstallDir | Out-Null } catch { } Write-Host "Migrated state $legacyDir -> $InstallDir" } } $tmp = Join-Path ([IO.Path]::GetTempPath()) ("remoteagent-" + [guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $tmp | Out-Null try { $Tarball = '' $ShaUrl = '' if ($Version) { $Tarball = "remoteagent-$Version.tar.gz" $Url = "https://github.com/$Repo/releases/download/$Version/$Tarball" $ShaUrl = "https://github.com/$Repo/releases/download/$Version/SHA256SUMS" } else { $Tarball = 'main.tar.gz' $Url = "https://github.com/$Repo/archive/refs/heads/main.tar.gz" } Write-Host "Downloading $Url" if ($DryRun) { return } $Archive = Join-Path $tmp $Tarball try { Invoke-WebRequest -Uri $Url -OutFile $Archive } catch { if ($Version) { # Pre-rebrand release tags only carry the legacy archive name. $Tarball = "mona-agent-$Version.tar.gz" $Url = "https://github.com/$Repo/releases/download/$Version/$Tarball" Invoke-WebRequest -Uri $Url -OutFile $Archive } else { throw } } if ($Version) { if ($env:MONA_REQUIRE_CHECKSUM -and $env:MONA_REQUIRE_CHECKSUM -ne '1') { throw 'Refusing insecure release install: MONA_REQUIRE_CHECKSUM must remain 1' } Invoke-WebRequest -Uri $ShaUrl -OutFile (Join-Path $tmp 'SHA256SUMS') $lines = Get-Content (Join-Path $tmp 'SHA256SUMS') $matches = @($lines | Where-Object { $_ -match "^[0-9a-fA-F]{64}\s+\*?$([regex]::Escape($Tarball))$" }) if ($matches.Count -ne 1) { throw "SHA256SUMS must contain exactly one valid entry for $Tarball" } $expected = ($matches[0] -split '\s+')[0] $actual = (Get-FileHash -Algorithm SHA256 $Archive).Hash.ToLowerInvariant() if ($actual -ne $expected.ToLowerInvariant()) { throw "Checksum mismatch for $Tarball`nexpected: $expected`nactual: $actual" } Write-Host 'SHA-256 verified against the release manifest' } else { Write-Warning 'Installing an unversioned main-branch archive; no checksum verification is available' } New-Item -ItemType Directory -Path (Join-Path $tmp 'src') | Out-Null tar -xzf $Archive -C (Join-Path $tmp 'src') --strip-components 1 if ($LASTEXITCODE -ne 0) { throw 'tar extraction failed (tar.exe ships with Windows 10+)' } if ($Version) { $pkgVersion = & node.exe -p "require('$(Join-Path $tmp 'src\package.json')').version" 2>$null if ($pkgVersion -and $pkgVersion -ne $Version.TrimStart('v')) { throw "Extracted version $pkgVersion does not match requested tag $Version" } } Push-Location (Join-Path $tmp 'src') try { npm ci --omit=dev --ignore-scripts --no-audit --no-fund | Out-Null } finally { Pop-Location } $agentDir = Join-Path $InstallDir 'agent' Remove-Item -Recurse -Force $agentDir -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $agentDir -Force | Out-Null Copy-Item -Recurse -Force (Join-Path $tmp 'src\*') $agentDir $bin = Join-Path $agentDir 'apps\desktop\bin\remoteagent.js' $link = Join-Path $HOME '.local\bin\remoteagent.cmd' $legacyLink = Join-Path $HOME '.local\bin\mona-agent.cmd' New-Item -ItemType Directory -Path (Split-Path $link) -Force | Out-Null "@echo off`r`nnode `"$bin`" %*" | Set-Content -Encoding ASCII $link "@echo off`r`nnode `"$bin`" %*" | Set-Content -Encoding ASCII $legacyLink # legacy alias Write-Host "Installed to $agentDir" Write-Host "Command: $link (add $($HOME)\.local\bin to PATH)" Write-Host '' Write-Warning 'Windows service installation requires an elevated PowerShell:' Write-Host ' Start-Process powershell -Verb RunAs -ArgumentList "-Command", "node ""'"$bin"'"" daemon install"' Write-Host 'Installing as LocalSystem uses a separate data directory and credential scope. See docs/WINDOWS.md.' } finally { Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue }