mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
b19ce85d84
Replace the scheduled-task approach (required user to stay logged in) with a proper Windows Service using pywin32. The service runs as LocalSystem, starts at boot, and auto-restarts on failure — no PowerShell window needed. Agent changes (jarvis-agent-windows.py): - Add Windows Service class via pywin32 (JarvisAgentService) - Cleanly handles SvcStop by setting a threading.Event - main() loop uses _stop_event.wait() instead of time.sleep() so stop is immediate - self_update() signals the stop event when running as a service (SCM restarts it) - __main__ block dispatches to SCM entry point or HandleCommandLine (install/stop/remove) - Falls back to direct run if pywin32 not installed (for debugging) Installer changes (install-windows.ps1): - pip install pywin32 + postinstall (registers service runner DLLs) - Python search prefers system-wide install (accessible by LocalSystem) - Downloads Python 3.11 directly from python.org for Win 8.1 machines without winget - Removes legacy JARVIS-Agent scheduled task if present - Registers JARVISAgent service with --startup auto - Configures sc.exe failure recovery (restart at 5s/10s/30s) - Updated management commands in summary (Start-Service, Stop-Service, etc.) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
259 lines
12 KiB
PowerShell
259 lines
12 KiB
PowerShell
# JARVIS Agent Installer — Windows (PowerShell)
|
|
# Registers the agent as a proper Windows Service (Win 8.1+, no open window required).
|
|
# Requires pywin32. Runs the service as LocalSystem.
|
|
#
|
|
# Run as Administrator:
|
|
# Set-ExecutionPolicy Bypass -Scope Process
|
|
# .\install-windows.ps1 -JarvisUrl https://jarvis.orbishosting.com -Key YOUR_KEY
|
|
#
|
|
# One-liner (PowerShell as Admin):
|
|
# irm https://jarvis.orbishosting.com/agent/install-windows.ps1 | iex
|
|
|
|
param(
|
|
[string]$JarvisUrl = "https://jarvis.orbishosting.com",
|
|
[string]$Key = "",
|
|
[string]$AgentName = $env:COMPUTERNAME.ToLower()
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$InstallDir = "C:\ProgramData\jarvis-agent"
|
|
$AgentScript = "$InstallDir\jarvis-agent-windows.py"
|
|
$ConfigFile = "$InstallDir\config.json"
|
|
$ServiceName = "JARVISAgent"
|
|
$OldTaskName = "JARVIS-Agent" # legacy scheduled-task name
|
|
|
|
Write-Host ""
|
|
Write-Host " ====================================" -ForegroundColor Cyan
|
|
Write-Host " JARVIS Agent Installer v3.1 " -ForegroundColor Cyan
|
|
Write-Host " Windows Service Edition " -ForegroundColor Cyan
|
|
Write-Host " ====================================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
# ── Require admin ──────────────────────────────────────────────────────────────
|
|
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
|
|
[Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
|
Write-Error "Run PowerShell as Administrator and try again."
|
|
}
|
|
|
|
# ── Prompt if not provided ─────────────────────────────────────────────────────
|
|
$JarvisUrl = $JarvisUrl.TrimEnd("/")
|
|
if (-not $Key) { $Key = Read-Host "Enter registration key" }
|
|
|
|
# ── Find or install Python 3 (system-wide so LocalSystem service can reach it) ─
|
|
Write-Host "[1/6] Checking for Python 3..." -ForegroundColor Cyan
|
|
|
|
$pythonPath = $null
|
|
|
|
# Search for a system-wide Python first (accessible by LocalSystem)
|
|
$systemPaths = @(
|
|
"C:\Program Files\Python313\python.exe",
|
|
"C:\Program Files\Python312\python.exe",
|
|
"C:\Program Files\Python311\python.exe",
|
|
"C:\Program Files\Python310\python.exe",
|
|
"C:\Program Files\Python39\python.exe",
|
|
"C:\Python313\python.exe",
|
|
"C:\Python312\python.exe",
|
|
"C:\Python311\python.exe",
|
|
"C:\Python310\python.exe"
|
|
)
|
|
foreach ($p in $systemPaths) {
|
|
if (Test-Path $p) {
|
|
try {
|
|
$ver = & $p --version 2>&1
|
|
if ("$ver" -match "Python 3") { $pythonPath = $p; break }
|
|
} catch {}
|
|
}
|
|
}
|
|
|
|
# Fall back to PATH
|
|
if (-not $pythonPath) {
|
|
foreach ($cmd in @("python", "python3", "py")) {
|
|
try {
|
|
$ver = & $cmd --version 2>&1
|
|
if ("$ver" -match "Python 3") {
|
|
$resolved = (Get-Command $cmd -ErrorAction SilentlyContinue)
|
|
if ($resolved) { $pythonPath = $resolved.Source; break }
|
|
}
|
|
} catch {}
|
|
}
|
|
}
|
|
|
|
if (-not $pythonPath) {
|
|
Write-Host " Python 3 not found. Installing system-wide..." -ForegroundColor Yellow
|
|
|
|
# Try winget first (Win 10 1709+ / Win 11)
|
|
$wingetOk = $false
|
|
try {
|
|
$null = Get-Command winget -ErrorAction Stop
|
|
Write-Host " Using winget..." -NoNewline
|
|
winget install Python.Python.3.12 --silent --scope machine `
|
|
--accept-package-agreements --accept-source-agreements 2>&1 | Out-Null
|
|
$wingetOk = $true
|
|
Write-Host " done." -ForegroundColor Green
|
|
} catch {}
|
|
|
|
if (-not $wingetOk) {
|
|
# Direct download — works on Win 8.1 without winget
|
|
# Python 3.11 is explicitly documented to support Win 8.1+
|
|
Write-Host " Downloading Python 3.11 installer (Win 8.1+ compatible)..." -NoNewline
|
|
$pyInstaller = "$env:TEMP\python-installer.exe"
|
|
$pyUrl = "https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe"
|
|
try {
|
|
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
|
$wc = New-Object System.Net.WebClient
|
|
$wc.DownloadFile($pyUrl, $pyInstaller)
|
|
Write-Host " downloaded." -ForegroundColor Green
|
|
} catch {
|
|
Write-Error "Could not download Python installer. Install Python 3.11+ from https://python.org (choose 'Install for all users') then re-run."
|
|
}
|
|
Write-Host " Running Python installer (system-wide, silent)..." -NoNewline
|
|
$proc = Start-Process -FilePath $pyInstaller `
|
|
-ArgumentList "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0" `
|
|
-Wait -PassThru
|
|
if ($proc.ExitCode -ne 0) {
|
|
Write-Error "Python installer exited with code $($proc.ExitCode). Install manually from https://python.org then re-run."
|
|
}
|
|
Write-Host " done." -ForegroundColor Green
|
|
Remove-Item $pyInstaller -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
# Refresh PATH and locate installed Python
|
|
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH","Machine") + ";" +
|
|
[System.Environment]::GetEnvironmentVariable("PATH","User")
|
|
foreach ($p in $systemPaths) {
|
|
if (Test-Path $p) { $pythonPath = $p; break }
|
|
}
|
|
if (-not $pythonPath) {
|
|
foreach ($cmd in @("python", "python3")) {
|
|
try {
|
|
$ver = & $cmd --version 2>&1
|
|
if ("$ver" -match "Python 3") {
|
|
$resolved = (Get-Command $cmd -ErrorAction SilentlyContinue)
|
|
if ($resolved) { $pythonPath = $resolved.Source; break }
|
|
}
|
|
} catch {}
|
|
}
|
|
}
|
|
if (-not $pythonPath) {
|
|
Write-Error "Python 3 still not found after installation. Open a new Admin PowerShell and re-run this installer."
|
|
}
|
|
}
|
|
|
|
Write-Host " Python: $pythonPath" -ForegroundColor Green
|
|
|
|
# ── Install pywin32 (required for Windows service support) ────────────────────
|
|
Write-Host "[2/6] Installing pywin32..." -ForegroundColor Cyan
|
|
try {
|
|
& $pythonPath -m pip install --quiet --upgrade pywin32
|
|
# Run postinstall to register service runner DLLs system-wide
|
|
& $pythonPath -c "import pywin32_postinstall; pywin32_postinstall.install()" 2>$null
|
|
Write-Host " pywin32 installed." -ForegroundColor Green
|
|
} catch {
|
|
Write-Error "pip install pywin32 failed: $_`nEnsure pip is available: $pythonPath -m ensurepip"
|
|
}
|
|
|
|
# ── Create install directory and download agent ────────────────────────────────
|
|
Write-Host "[3/6] Downloading agent..." -ForegroundColor Cyan
|
|
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
|
|
|
try {
|
|
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
|
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
|
|
$wc = New-Object System.Net.WebClient
|
|
$wc.Headers.Add("User-Agent", "JARVIS-Installer/3.1")
|
|
$wc.DownloadFile("$JarvisUrl/agent/jarvis-agent-windows.py", $AgentScript)
|
|
Write-Host " Downloaded to $AgentScript" -ForegroundColor Green
|
|
} catch {
|
|
Write-Error "Download failed: $_"
|
|
}
|
|
|
|
# ── Write config ───────────────────────────────────────────────────────────────
|
|
Write-Host "[4/6] Writing config..." -ForegroundColor Cyan
|
|
$agentId = "${AgentName}_windows"
|
|
$config = [ordered]@{
|
|
jarvis_url = $JarvisUrl
|
|
host_header = ""
|
|
ssl_verify = $true
|
|
registration_key = $Key
|
|
agent_type = "windows"
|
|
hostname = $AgentName
|
|
agent_id = $agentId
|
|
poll_interval = 30
|
|
heartbeat_every = 10
|
|
update_check_hours = 24
|
|
watch_services = @("WinDefend", "Spooler")
|
|
} | ConvertTo-Json -Depth 3
|
|
|
|
[System.IO.File]::WriteAllText($ConfigFile, $config, [System.Text.UTF8Encoding]::new($false))
|
|
Write-Host " Config: $ConfigFile" -ForegroundColor Green
|
|
|
|
# ── Remove legacy scheduled task if present ────────────────────────────────────
|
|
try {
|
|
$oldTask = Get-ScheduledTask -TaskName $OldTaskName -ErrorAction SilentlyContinue
|
|
if ($oldTask) {
|
|
Stop-ScheduledTask -TaskName $OldTaskName -ErrorAction SilentlyContinue
|
|
Unregister-ScheduledTask -TaskName $OldTaskName -Confirm:$false -ErrorAction SilentlyContinue
|
|
Write-Host " Removed legacy scheduled task '$OldTaskName'." -ForegroundColor Yellow
|
|
}
|
|
} catch {}
|
|
|
|
# ── Register Windows service ───────────────────────────────────────────────────
|
|
Write-Host "[5/6] Registering Windows service '$ServiceName'..." -ForegroundColor Cyan
|
|
|
|
# Stop + remove any existing service first
|
|
$existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
|
if ($existing) {
|
|
if ($existing.Status -eq "Running") {
|
|
Write-Host " Stopping existing service..." -NoNewline
|
|
& $pythonPath $AgentScript stop 2>&1 | Out-Null
|
|
Start-Sleep -Seconds 3
|
|
Write-Host " stopped." -ForegroundColor Yellow
|
|
}
|
|
Write-Host " Removing existing service..." -NoNewline
|
|
& $pythonPath $AgentScript remove 2>&1 | Out-Null
|
|
Start-Sleep -Seconds 2
|
|
Write-Host " removed." -ForegroundColor Yellow
|
|
}
|
|
|
|
# Install the service (--startup auto = start at boot)
|
|
& $pythonPath $AgentScript --startup auto install
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Error "Service registration failed (exit $LASTEXITCODE). Check that pywin32 postinstall completed."
|
|
}
|
|
|
|
# Configure failure recovery: restart after 5s, 10s, 30s
|
|
sc.exe failure $ServiceName reset= 86400 actions= restart/5000/restart/10000/restart/30000 | Out-Null
|
|
Write-Host " Service registered with auto-restart on failure." -ForegroundColor Green
|
|
|
|
# ── Start the service ──────────────────────────────────────────────────────────
|
|
Write-Host "[6/6] Starting service..." -ForegroundColor Cyan
|
|
& $pythonPath $AgentScript start
|
|
Start-Sleep -Seconds 4
|
|
|
|
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
|
$status = if ($svc) { $svc.Status } else { "NotFound" }
|
|
$color = if ($status -eq "Running") { "Green" } else { "Yellow" }
|
|
Write-Host " Service status: $status" -ForegroundColor $color
|
|
|
|
Write-Host ""
|
|
Write-Host " ====================================" -ForegroundColor Green
|
|
Write-Host " Installation complete! " -ForegroundColor Green
|
|
Write-Host " ====================================" -ForegroundColor Green
|
|
Write-Host ""
|
|
Write-Host " Machine : $AgentName ($agentId)" -ForegroundColor White
|
|
Write-Host " JARVIS : $JarvisUrl" -ForegroundColor White
|
|
Write-Host " Python : $pythonPath" -ForegroundColor White
|
|
Write-Host " Logs : $InstallDir\jarvis-agent.log" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " Manage the service:" -ForegroundColor Gray
|
|
Write-Host " Get-Service JARVISAgent" -ForegroundColor Gray
|
|
Write-Host " Start-Service JARVISAgent" -ForegroundColor Gray
|
|
Write-Host " Stop-Service JARVISAgent" -ForegroundColor Gray
|
|
Write-Host " Restart-Service JARVISAgent" -ForegroundColor Gray
|
|
Write-Host " Get-Content '$InstallDir\jarvis-agent.log' -Tail 30 -Wait" -ForegroundColor Gray
|
|
Write-Host ""
|
|
Write-Host " To uninstall:" -ForegroundColor Gray
|
|
Write-Host " Stop-Service JARVISAgent" -ForegroundColor Gray
|
|
Write-Host " & '$pythonPath' '$AgentScript' remove" -ForegroundColor Gray
|
|
Write-Host ""
|