Files
remotelink-docker/agent/service_win.py
monoadmin e16a2fa978 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>
2026-04-10 16:25:10 -07:00

99 lines
3.0 KiB
Python

"""
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)