mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
Windows agent: run as background Windows Service (Win 8.1+)
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>
This commit is contained in:
@@ -1,7 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
JARVIS Agent for Windows — system monitor that reports metrics to JARVIS HUD.
|
||||
Install via PowerShell (as Admin): irm https://jarvis.orbishosting.com/agent/install-windows.ps1 | iex
|
||||
Runs as a Windows Service (Win 8.1+) via pywin32.
|
||||
|
||||
Install (run PowerShell as Admin):
|
||||
irm https://jarvis.orbishosting.com/agent/install-windows.ps1 | iex
|
||||
|
||||
Service management:
|
||||
python jarvis-agent-windows.py --startup auto install # register service
|
||||
python jarvis-agent-windows.py start # start
|
||||
python jarvis-agent-windows.py stop # stop
|
||||
python jarvis-agent-windows.py remove # uninstall
|
||||
python jarvis-agent-windows.py debug # run in console (for testing)
|
||||
|
||||
Config: C:\ProgramData\jarvis-agent\config.json
|
||||
Logs: C:\ProgramData\jarvis-agent\jarvis-agent.log
|
||||
"""
|
||||
@@ -12,6 +23,7 @@ import platform
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
@@ -23,7 +35,11 @@ INSTALL_DIR = Path(r"C:\ProgramData\jarvis-agent")
|
||||
CONFIG_PATH = INSTALL_DIR / "config.json"
|
||||
STATE_PATH = INSTALL_DIR / "state.json"
|
||||
LOG_PATH = INSTALL_DIR / "jarvis-agent.log"
|
||||
AGENT_VERSION = "3.0"
|
||||
AGENT_VERSION = "3.1"
|
||||
|
||||
# Set by the service wrapper so self_update knows to stop instead of exec
|
||||
_is_service = False
|
||||
_stop_event = threading.Event()
|
||||
|
||||
# ── Logging ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -403,7 +419,12 @@ def self_update(cfg: dict) -> bool:
|
||||
log(f"Update verified — replacing {script_path} and restarting...")
|
||||
with open(script_path, "wb") as f:
|
||||
f.write(new_content)
|
||||
os.execv(sys.executable, [sys.executable] + sys.argv)
|
||||
if _is_service:
|
||||
# Signal the main loop to exit; SCM failure-recovery will restart us
|
||||
log("Running as service — stopping for SCM-managed restart after update.")
|
||||
_stop_event.set()
|
||||
else:
|
||||
os.execv(sys.executable, [sys.executable] + sys.argv)
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
@@ -490,7 +511,8 @@ def main():
|
||||
api_key = register(cfg, state)
|
||||
if not api_key:
|
||||
log("[ERROR] Could not register with JARVIS. Retrying in 60s...")
|
||||
time.sleep(60)
|
||||
if _stop_event.wait(60):
|
||||
return
|
||||
|
||||
headers = {"X-Agent-Key": api_key}
|
||||
last_metrics = 0
|
||||
@@ -498,7 +520,7 @@ def main():
|
||||
|
||||
log(f"Agent v{AGENT_VERSION} (Windows) running. Polling {jarvis_url} every {heartbeat_every}s.")
|
||||
|
||||
while True:
|
||||
while not _stop_event.is_set():
|
||||
now = time.time()
|
||||
try:
|
||||
hb = api_post(f"{jarvis_url}/api/agent/heartbeat", {}, headers, ssl_verify=ssl_verify)
|
||||
@@ -527,8 +549,59 @@ def main():
|
||||
except Exception as e:
|
||||
log(f"[ERROR] Loop error: {e}")
|
||||
|
||||
time.sleep(heartbeat_every)
|
||||
if _stop_event.wait(heartbeat_every):
|
||||
break # service stop was requested
|
||||
|
||||
log("Agent stopped.")
|
||||
|
||||
|
||||
# ── Windows Service wrapper ────────────────────────────────────────────────────
|
||||
|
||||
try:
|
||||
import win32serviceutil
|
||||
import win32service
|
||||
import win32event
|
||||
import servicemanager
|
||||
_HAS_WIN32 = True
|
||||
except ImportError:
|
||||
_HAS_WIN32 = False
|
||||
|
||||
if _HAS_WIN32:
|
||||
class JarvisAgentService(win32serviceutil.ServiceFramework):
|
||||
_svc_name_ = "JARVISAgent"
|
||||
_svc_display_name_ = "JARVIS AI Agent"
|
||||
_svc_description_ = "JARVIS system monitoring and AI agent — reports metrics to JARVIS HUD"
|
||||
|
||||
def __init__(self, args):
|
||||
win32serviceutil.ServiceFramework.__init__(self, args)
|
||||
self._svc_stop_event = win32event.CreateEvent(None, 0, 0, None)
|
||||
|
||||
def SvcStop(self):
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
_stop_event.set()
|
||||
win32event.SetEvent(self._svc_stop_event)
|
||||
|
||||
def SvcDoRun(self):
|
||||
global _is_service
|
||||
_is_service = True
|
||||
servicemanager.LogMsg(
|
||||
servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STARTED,
|
||||
(self._svc_name_, ""),
|
||||
)
|
||||
main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
if _HAS_WIN32:
|
||||
if len(sys.argv) == 1:
|
||||
# No args — SCM is starting us as a service
|
||||
servicemanager.Initialize()
|
||||
servicemanager.PrepareToHostSingle(JarvisAgentService)
|
||||
servicemanager.StartServiceCtrlDispatcher()
|
||||
else:
|
||||
# install / start / stop / remove / debug
|
||||
win32serviceutil.HandleCommandLine(JarvisAgentService)
|
||||
else:
|
||||
# pywin32 not available — run directly (useful for testing)
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user