Persist GNS transport configuration
This commit is contained in:
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user