From d24e8c58e3a963b089af1b48fe10c1c25f252c14 Mon Sep 17 00:00:00 2001 From: Nomads_Reach <144523850+NomadsReach@users.noreply.github.com> Date: Sun, 16 Aug 2026 00:49:10 -0400 Subject: [PATCH] Persist GNS transport configuration --- server/config.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/server/config.py b/server/config.py index 74cc636..ecbba68 100644 --- a/server/config.py +++ b/server/config.py @@ -20,6 +20,25 @@ MAX_PLAYERS_HARD_LIMIT = 256 DEFAULT_ADMIN_PORT = 7779 + +def _parse_bool(value: Any, *, field_name: str, default: bool = False) -> bool: + if value is None: + return default + if isinstance(value, bool): + return value + if isinstance(value, int) and not isinstance(value, bool) and value in (0, 1): + return bool(value) + if isinstance(value, str): + text = value.strip().lower() + if text in {"1", "true", "yes", "on"}: + return True + if text in {"0", "false", "no", "off", ""}: + return False + raise ValueError( + f"{field_name} must be a boolean or one of true/false, yes/no, on/off, 1/0" + ) + + @dataclass class Config: """Server configuration model.""" @@ -30,6 +49,8 @@ class Config: max_players: int = 16 log_verbosity: str = "info" admin_port: int = DEFAULT_ADMIN_PORT + enable_gns_transport: bool = False + gns_bridge_path: str | None = None def to_dict(self) -> dict[str, Any]: """Convert config to dictionary.""" @@ -41,6 +62,8 @@ class Config: "max_players": self.max_players, "log_verbosity": self.log_verbosity, "admin_port": self.admin_port, + "enable_gns_transport": self.enable_gns_transport, + "gns_bridge_path": self.gns_bridge_path, } @classmethod @@ -53,6 +76,20 @@ class Config: except (TypeError, ValueError) as error: raise ValueError(f"Invalid numeric config field: {error}") from error + enable_gns_transport = _parse_bool( + data.get("enable_gns_transport"), + field_name="enable_gns_transport", + default=False, + ) + raw_bridge_path = data.get("gns_bridge_path") + if raw_bridge_path is None: + gns_bridge_path = None + elif isinstance(raw_bridge_path, str): + normalized_bridge_path = raw_bridge_path.strip() + gns_bridge_path = normalized_bridge_path or None + else: + raise ValueError("gns_bridge_path must be a string or null") + return cls( host=str(data.get("host", "0.0.0.0")), port=port, @@ -61,6 +98,8 @@ class Config: max_players=max_players, log_verbosity=str(data.get("log_verbosity", "info")), admin_port=admin_port, + enable_gns_transport=enable_gns_transport, + gns_bridge_path=gns_bridge_path, ) @@ -145,6 +184,18 @@ def _is_valid_bind_host(host: str) -> bool: return False +def _is_valid_gns_bind_host(host: str) -> bool: + """Return True for the strict IPv4 bind form used by the native GNS bridge.""" + text = host.strip() + if not text: + return False + try: + socket.inet_pton(socket.AF_INET, text) + return True + except OSError: + return False + + def validate_config(config: Config) -> tuple[bool, list[str]]: """ Validate configuration values. @@ -163,6 +214,11 @@ def validate_config(config: Config) -> tuple[bool, list[str]]: errors.append( f"host '{config.host}' is not a valid IPv4 address or resolvable hostname" ) + elif config.enable_gns_transport and not _is_valid_gns_bind_host(str(config.host)): + errors.append( + "enable_gns_transport requires host to be an explicit IPv4 bind address " + "such as 0.0.0.0 or 127.0.0.1" + ) if config.port < 1 or config.port > 65535: errors.append(f"port must be 1-65535, got {config.port}")