- 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>
99 lines
3.0 KiB
Python
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)
|