A Python port of the Invisible Internet Project (I2P)
at main 136 lines 4.2 kB view raw
1"""Windows service wrapper for the I2P router. 2 3Usage (from an elevated command prompt): 4 python service.py install # Register the service 5 python service.py start # Start the service 6 python service.py stop # Stop the service 7 python service.py remove # Unregister the service 8 python service.py debug # Run in foreground (for debugging) 9 10Requires pywin32: pip install pywin32 11""" 12 13from __future__ import annotations 14 15import asyncio 16import logging 17import os 18import sys 19import threading 20 21# pywin32 imports — only available on Windows 22try: 23 import servicemanager 24 import win32event 25 import win32service 26 import win32serviceutil 27 28 _HAS_PYWIN32 = True 29except ImportError: 30 _HAS_PYWIN32 = False 31 32DATA_DIR = os.path.join( 33 os.environ.get("PROGRAMDATA", r"C:\ProgramData"), "I2P" 34) 35LOG_FILE = os.path.join(DATA_DIR, "service.log") 36 37 38def _run_router(stop_event: threading.Event) -> None: 39 """Run the I2P router in an asyncio event loop until stop_event is set.""" 40 from i2p_router.bootstrap import RouterBootstrap 41 from i2p_router.config import RouterConfig 42 43 os.makedirs(DATA_DIR, exist_ok=True) 44 45 config = RouterConfig( 46 listen_host="0.0.0.0", 47 listen_port=9700, 48 health_port=9701, 49 data_dir=DATA_DIR, 50 ) 51 config = RouterConfig.with_env_overrides(config) 52 53 bootstrap = RouterBootstrap(config) 54 loop = asyncio.new_event_loop() 55 asyncio.set_event_loop(loop) 56 57 async def run() -> None: 58 await bootstrap.start() 59 # Poll the threading.Event since we can't use signal handlers 60 # on Windows in a service context 61 while not stop_event.is_set(): 62 await asyncio.sleep(1.0) 63 await bootstrap.shutdown() 64 65 try: 66 loop.run_until_complete(run()) 67 finally: 68 loop.close() 69 70 71if _HAS_PYWIN32: 72 73 class I2PRouterService(win32serviceutil.ServiceFramework): 74 """Windows service that runs the I2P Python router.""" 75 76 _svc_name_ = "I2PRouter" 77 _svc_display_name_ = "I2P Python Router" 78 _svc_description_ = "I2P anonymous overlay network router" 79 80 def __init__(self, args: list[str]) -> None: 81 win32serviceutil.ServiceFramework.__init__(self, args) 82 self._stop_event = threading.Event() 83 self._win32_stop = win32event.CreateEvent(None, 0, 0, None) 84 85 def SvcStop(self) -> None: 86 self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 87 self._stop_event.set() 88 win32event.SetEvent(self._win32_stop) 89 self._log_info("Service stop requested") 90 91 def SvcDoRun(self) -> None: 92 self.ReportServiceStatus(win32service.SERVICE_START_PENDING) 93 self._log_info("Service starting") 94 95 os.makedirs(DATA_DIR, exist_ok=True) 96 logging.basicConfig( 97 filename=LOG_FILE, 98 level=logging.INFO, 99 format="%(asctime)s %(levelname)-8s %(name)s: %(message)s", 100 ) 101 102 self.ReportServiceStatus(win32service.SERVICE_RUNNING) 103 104 try: 105 _run_router(self._stop_event) 106 except Exception as exc: 107 self._log_error(f"Service failed: {exc}") 108 raise 109 finally: 110 self._log_info("Service stopped") 111 112 def _log_info(self, msg: str) -> None: 113 servicemanager.LogInfoMsg(f"{self._svc_name_}: {msg}") 114 115 def _log_error(self, msg: str) -> None: 116 servicemanager.LogErrorMsg(f"{self._svc_name_}: {msg}") 117 118 119def cli() -> None: 120 """Entry point for command-line and SCM invocation.""" 121 if not _HAS_PYWIN32: 122 print("Error: pywin32 is required. Install with: pip install pywin32", 123 file=sys.stderr) 124 sys.exit(1) 125 126 if len(sys.argv) == 1: 127 # Started by the Windows Service Control Manager 128 servicemanager.Initialize() 129 servicemanager.PrepareToHostSingle(I2PRouterService) 130 servicemanager.StartServiceCtrlDispatcher() 131 else: 132 win32serviceutil.HandleCommandLine(I2PRouterService) 133 134 135if __name__ == "__main__": 136 cli()