"""Windows service wrapper for the I2P router. Usage (from an elevated command prompt): python service.py install # Register the service python service.py start # Start the service python service.py stop # Stop the service python service.py remove # Unregister the service python service.py debug # Run in foreground (for debugging) Requires pywin32: pip install pywin32 """ from __future__ import annotations import asyncio import logging import os import sys import threading # pywin32 imports — only available on Windows try: import servicemanager import win32event import win32service import win32serviceutil _HAS_PYWIN32 = True except ImportError: _HAS_PYWIN32 = False DATA_DIR = os.path.join( os.environ.get("PROGRAMDATA", r"C:\ProgramData"), "I2P" ) LOG_FILE = os.path.join(DATA_DIR, "service.log") def _run_router(stop_event: threading.Event) -> None: """Run the I2P router in an asyncio event loop until stop_event is set.""" from i2p_router.bootstrap import RouterBootstrap from i2p_router.config import RouterConfig os.makedirs(DATA_DIR, exist_ok=True) config = RouterConfig( listen_host="0.0.0.0", listen_port=9700, health_port=9701, data_dir=DATA_DIR, ) config = RouterConfig.with_env_overrides(config) bootstrap = RouterBootstrap(config) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) async def run() -> None: await bootstrap.start() # Poll the threading.Event since we can't use signal handlers # on Windows in a service context while not stop_event.is_set(): await asyncio.sleep(1.0) await bootstrap.shutdown() try: loop.run_until_complete(run()) finally: loop.close() if _HAS_PYWIN32: class I2PRouterService(win32serviceutil.ServiceFramework): """Windows service that runs the I2P Python router.""" _svc_name_ = "I2PRouter" _svc_display_name_ = "I2P Python Router" _svc_description_ = "I2P anonymous overlay network router" def __init__(self, args: list[str]) -> None: win32serviceutil.ServiceFramework.__init__(self, args) self._stop_event = threading.Event() self._win32_stop = win32event.CreateEvent(None, 0, 0, None) def SvcStop(self) -> None: self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) self._stop_event.set() win32event.SetEvent(self._win32_stop) self._log_info("Service stop requested") def SvcDoRun(self) -> None: self.ReportServiceStatus(win32service.SERVICE_START_PENDING) self._log_info("Service starting") os.makedirs(DATA_DIR, exist_ok=True) logging.basicConfig( filename=LOG_FILE, level=logging.INFO, format="%(asctime)s %(levelname)-8s %(name)s: %(message)s", ) self.ReportServiceStatus(win32service.SERVICE_RUNNING) try: _run_router(self._stop_event) except Exception as exc: self._log_error(f"Service failed: {exc}") raise finally: self._log_info("Service stopped") def _log_info(self, msg: str) -> None: servicemanager.LogInfoMsg(f"{self._svc_name_}: {msg}") def _log_error(self, msg: str) -> None: servicemanager.LogErrorMsg(f"{self._svc_name_}: {msg}") def cli() -> None: """Entry point for command-line and SCM invocation.""" if not _HAS_PYWIN32: print("Error: pywin32 is required. Install with: pip install pywin32", file=sys.stderr) sys.exit(1) if len(sys.argv) == 1: # Started by the Windows Service Control Manager servicemanager.Initialize() servicemanager.PrepareToHostSingle(I2PRouterService) servicemanager.StartServiceCtrlDispatcher() else: win32serviceutil.HandleCommandLine(I2PRouterService) if __name__ == "__main__": cli()