Sync from GitHub main #1
@@ -20,6 +20,25 @@ MAX_PLAYERS_HARD_LIMIT = 256
|
|||||||
DEFAULT_ADMIN_PORT = 7779
|
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
|
@dataclass
|
||||||
class Config:
|
class Config:
|
||||||
"""Server configuration model."""
|
"""Server configuration model."""
|
||||||
@@ -30,6 +49,8 @@ class Config:
|
|||||||
max_players: int = 16
|
max_players: int = 16
|
||||||
log_verbosity: str = "info"
|
log_verbosity: str = "info"
|
||||||
admin_port: int = DEFAULT_ADMIN_PORT
|
admin_port: int = DEFAULT_ADMIN_PORT
|
||||||
|
enable_gns_transport: bool = False
|
||||||
|
gns_bridge_path: str | None = None
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
"""Convert config to dictionary."""
|
"""Convert config to dictionary."""
|
||||||
@@ -41,6 +62,8 @@ class Config:
|
|||||||
"max_players": self.max_players,
|
"max_players": self.max_players,
|
||||||
"log_verbosity": self.log_verbosity,
|
"log_verbosity": self.log_verbosity,
|
||||||
"admin_port": self.admin_port,
|
"admin_port": self.admin_port,
|
||||||
|
"enable_gns_transport": self.enable_gns_transport,
|
||||||
|
"gns_bridge_path": self.gns_bridge_path,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -53,6 +76,20 @@ class Config:
|
|||||||
except (TypeError, ValueError) as error:
|
except (TypeError, ValueError) as error:
|
||||||
raise ValueError(f"Invalid numeric config field: {error}") from 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(
|
return cls(
|
||||||
host=str(data.get("host", "0.0.0.0")),
|
host=str(data.get("host", "0.0.0.0")),
|
||||||
port=port,
|
port=port,
|
||||||
@@ -61,6 +98,8 @@ class Config:
|
|||||||
max_players=max_players,
|
max_players=max_players,
|
||||||
log_verbosity=str(data.get("log_verbosity", "info")),
|
log_verbosity=str(data.get("log_verbosity", "info")),
|
||||||
admin_port=admin_port,
|
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
|
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]]:
|
def validate_config(config: Config) -> tuple[bool, list[str]]:
|
||||||
"""
|
"""
|
||||||
Validate configuration values.
|
Validate configuration values.
|
||||||
@@ -163,6 +214,11 @@ def validate_config(config: Config) -> tuple[bool, list[str]]:
|
|||||||
errors.append(
|
errors.append(
|
||||||
f"host '{config.host}' is not a valid IPv4 address or resolvable hostname"
|
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:
|
if config.port < 1 or config.port > 65535:
|
||||||
errors.append(f"port must be 1-65535, got {config.port}")
|
errors.append(f"port must be 1-65535, got {config.port}")
|
||||||
|
|||||||
Reference in New Issue
Block a user