A Python port of the Invisible Internet Project (I2P)
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()