Add Python agent, WebSocket relay, real viewer, enrollment tokens
- WebSocket relay service (FastAPI) bridges agents and viewers - Python agent with screen capture (mss), input control (pynput), script execution, and auto-reconnect - Windows service wrapper, PyInstaller spec, NSIS installer for silent mass deployment (RemoteLink-Setup.exe /S /SERVER= /ENROLL=) - Enrollment token system: admin generates tokens, agents self-register - Real WebSocket viewer replaces simulated canvas - Linux agent binary served from /downloads/remotelink-agent-linux - DB migration 0002: viewer_token on sessions, enrollment_tokens table - Sign-up pages cleaned up (invite-only redirect) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
98
agent/service_win.py
Normal file
98
agent/service_win.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Windows Service wrapper for the RemoteLink Agent.
|
||||
Installs/uninstalls/runs as a Windows Service via pywin32.
|
||||
|
||||
Usage:
|
||||
remotelink-agent-service.exe install
|
||||
remotelink-agent-service.exe start
|
||||
remotelink-agent-service.exe stop
|
||||
remotelink-agent-service.exe remove
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
# Only import win32 on Windows
|
||||
if sys.platform == "win32":
|
||||
import win32serviceutil
|
||||
import win32service
|
||||
import win32event
|
||||
import servicemanager
|
||||
|
||||
class RemoteLinkService(win32serviceutil.ServiceFramework):
|
||||
_svc_name_ = "RemoteLinkAgent"
|
||||
_svc_display_name_ = "RemoteLink Agent"
|
||||
_svc_description_ = "RemoteLink remote support agent service"
|
||||
|
||||
def __init__(self, args):
|
||||
win32serviceutil.ServiceFramework.__init__(self, args)
|
||||
self.stop_event = win32event.CreateEvent(None, 0, 0, None)
|
||||
self._loop = None
|
||||
self._agent = None
|
||||
|
||||
def SvcStop(self):
|
||||
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
||||
if self._agent:
|
||||
self._agent.stop()
|
||||
win32event.SetEvent(self.stop_event)
|
||||
|
||||
def SvcDoRun(self):
|
||||
servicemanager.LogMsg(
|
||||
servicemanager.EVENTLOG_INFORMATION_TYPE,
|
||||
servicemanager.PYS_SERVICE_STARTED,
|
||||
(self._svc_name_, ""),
|
||||
)
|
||||
self._run()
|
||||
|
||||
def _run(self):
|
||||
# Set up logging to Windows Event Log + file
|
||||
log_path = os.path.join(
|
||||
os.environ.get("PROGRAMDATA", "C:\\ProgramData"),
|
||||
"RemoteLink", "agent.log"
|
||||
)
|
||||
os.makedirs(os.path.dirname(log_path), exist_ok=True)
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
handlers=[logging.FileHandler(log_path)],
|
||||
)
|
||||
|
||||
# Import here to avoid circular issues when this module is imported
|
||||
from agent import Agent, load_config, heartbeat
|
||||
import json
|
||||
|
||||
config = load_config()
|
||||
if not config:
|
||||
logging.error("No agent config found. Run enrollment first.")
|
||||
return
|
||||
|
||||
self._loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(self._loop)
|
||||
|
||||
agent = Agent(
|
||||
config["server_url"],
|
||||
config["machine_id"],
|
||||
config["access_key"],
|
||||
config.get("relay_url", ""),
|
||||
)
|
||||
self._agent = agent
|
||||
|
||||
try:
|
||||
self._loop.run_until_complete(agent.run())
|
||||
finally:
|
||||
self._loop.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) == 1:
|
||||
servicemanager.Initialize()
|
||||
servicemanager.PrepareToHostSingle(RemoteLinkService)
|
||||
servicemanager.StartServiceCtrlDispatcher()
|
||||
else:
|
||||
win32serviceutil.HandleCommandLine(RemoteLinkService)
|
||||
|
||||
else:
|
||||
print("Windows service wrapper — only runs on Windows.")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user