A Python port of the Invisible Internet Project (I2P)
at main 173 lines 5.9 kB view raw
1"""Router configuration with sensible defaults and environment variable overrides. 2 3Ported from net.i2p.router.RouterContext configuration handling. 4""" 5 6from __future__ import annotations 7 8import os 9from dataclasses import dataclass, field, fields 10 11 12@dataclass 13class RouterConfig: 14 """I2P router configuration with sensible defaults.""" 15 16 router_name: str = "i2p-python-router" 17 listen_host: str = "0.0.0.0" 18 listen_port: int = 9700 19 20 # Tunnel pool settings 21 inbound_tunnel_count: int = 3 22 outbound_tunnel_count: int = 3 23 tunnel_length: int = 3 24 tunnel_lifetime_seconds: int = 600 25 26 # Bandwidth 27 bandwidth_limit_kbps: int = 0 # 0 = unlimited 28 29 # NetDB 30 floodfill: bool = False 31 reseed_urls: list[str] = field(default_factory=list) 32 33 # Timeouts 34 handshake_timeout_seconds: int = 30 35 idle_timeout_seconds: int = 300 36 37 # Health endpoint 38 health_port: int = 9701 39 40 # Data directory 41 data_dir: str = "~/.i2p-python" 42 43 @classmethod 44 def from_dict(cls, d: dict) -> "RouterConfig": 45 """Create a RouterConfig from a dictionary, ignoring unknown keys. 46 47 Parameters 48 ---------- 49 d: 50 Dictionary of configuration values. Unknown keys are ignored. 51 52 Returns 53 ------- 54 RouterConfig 55 A new config instance with provided values overriding defaults. 56 """ 57 valid_keys = {f.name for f in fields(cls)} 58 filtered = {k: v for k, v in d.items() if k in valid_keys} 59 return cls(**filtered) 60 61 @classmethod 62 def with_env_overrides(cls, base: "RouterConfig | None" = None) -> "RouterConfig": 63 """Apply environment variable overrides to a base config. 64 65 Environment variable mapping: 66 I2P_ROUTER_NAME -> router_name 67 I2P_LISTEN_HOST -> listen_host 68 I2P_LISTEN_PORT -> listen_port (int) 69 I2P_INBOUND_TUNNEL_COUNT -> inbound_tunnel_count (int) 70 I2P_OUTBOUND_TUNNEL_COUNT -> outbound_tunnel_count (int) 71 I2P_TUNNEL_LENGTH -> tunnel_length (int) 72 I2P_TUNNEL_LIFETIME -> tunnel_lifetime_seconds (int) 73 I2P_BANDWIDTH_LIMIT_KBPS -> bandwidth_limit_kbps (int) 74 I2P_FLOODFILL -> floodfill (bool: "true"/"false") 75 I2P_HANDSHAKE_TIMEOUT -> handshake_timeout_seconds (int) 76 I2P_IDLE_TIMEOUT -> idle_timeout_seconds (int) 77 I2P_DATA_DIR -> data_dir 78 79 Parameters 80 ---------- 81 base: 82 Base config to apply overrides to. If None, uses defaults. 83 84 Returns 85 ------- 86 RouterConfig 87 Config with environment variable overrides applied. 88 """ 89 if base is None: 90 base = cls() 91 92 # String overrides 93 _str_overrides = { 94 "I2P_ROUTER_NAME": "router_name", 95 "I2P_LISTEN_HOST": "listen_host", 96 "I2P_DATA_DIR": "data_dir", 97 } 98 99 # Int overrides 100 _int_overrides = { 101 "I2P_LISTEN_PORT": "listen_port", 102 "I2P_INBOUND_TUNNEL_COUNT": "inbound_tunnel_count", 103 "I2P_OUTBOUND_TUNNEL_COUNT": "outbound_tunnel_count", 104 "I2P_TUNNEL_LENGTH": "tunnel_length", 105 "I2P_TUNNEL_LIFETIME": "tunnel_lifetime_seconds", 106 "I2P_BANDWIDTH_LIMIT_KBPS": "bandwidth_limit_kbps", 107 "I2P_HANDSHAKE_TIMEOUT": "handshake_timeout_seconds", 108 "I2P_IDLE_TIMEOUT": "idle_timeout_seconds", 109 "I2P_HEALTH_PORT": "health_port", 110 } 111 112 # Bool overrides 113 _bool_overrides = { 114 "I2P_FLOODFILL": "floodfill", 115 } 116 117 for env_key, attr in _str_overrides.items(): 118 val = os.environ.get(env_key) 119 if val is not None: 120 object.__setattr__(base, attr, val) 121 122 for env_key, attr in _int_overrides.items(): 123 val = os.environ.get(env_key) 124 if val is not None: 125 object.__setattr__(base, attr, int(val)) 126 127 for env_key, attr in _bool_overrides.items(): 128 val = os.environ.get(env_key) 129 if val is not None: 130 object.__setattr__(base, attr, val.lower() == "true") 131 132 return base 133 134 def validate(self) -> None: 135 """Validate configuration values. 136 137 Raises 138 ------ 139 ValueError 140 If any configuration value is invalid. 141 """ 142 if not (0 <= self.listen_port <= 65535): 143 raise ValueError( 144 f"listen_port must be 0-65535, got {self.listen_port}" 145 ) 146 if self.inbound_tunnel_count < 0: 147 raise ValueError( 148 f"inbound_tunnel_count must be non-negative, got {self.inbound_tunnel_count}" 149 ) 150 if self.outbound_tunnel_count < 0: 151 raise ValueError( 152 f"outbound_tunnel_count must be non-negative, got {self.outbound_tunnel_count}" 153 ) 154 if self.tunnel_length < 0: 155 raise ValueError( 156 f"tunnel_length must be non-negative, got {self.tunnel_length}" 157 ) 158 if self.tunnel_lifetime_seconds < 0: 159 raise ValueError( 160 f"tunnel_lifetime_seconds must be non-negative, got {self.tunnel_lifetime_seconds}" 161 ) 162 if self.bandwidth_limit_kbps < 0: 163 raise ValueError( 164 f"bandwidth_limit_kbps must be non-negative, got {self.bandwidth_limit_kbps}" 165 ) 166 if self.handshake_timeout_seconds < 0: 167 raise ValueError( 168 f"handshake_timeout_seconds must be non-negative, got {self.handshake_timeout_seconds}" 169 ) 170 if self.idle_timeout_seconds < 0: 171 raise ValueError( 172 f"idle_timeout_seconds must be non-negative, got {self.idle_timeout_seconds}" 173 )