Keep pre-rebrand discovery installs compatible

This commit is contained in:
Alexander Sellite 2026-08-25 22:18:43 -04:00
parent de454ff48a
commit ee3982e428
8 changed files with 154 additions and 16 deletions

View file

@ -90,6 +90,12 @@ release. The versioned HTTP contract remains independent from straywild's save s
network protocol. Each tagged deployment accepts room advertisements only from its matching game
release so incompatible rooms are never presented as publicly available.
Existing NETfishing installations can be upgraded in place. The legacy
`netfishing_discovery` module, command name, and `NETFISHING_DISCOVERY_*`
environment variables remain accepted, while new installations should use the
Straywild names shown above. When both forms of an environment setting exist,
the Straywild value takes precedence.
## Licensing
Project-owned source code is licensed under the GNU General Public License,

View file

@ -0,0 +1,5 @@
"""Compatibility package for pre-Straywild discovery installations."""
from straywild_discovery import __version__
__all__ = ["__version__"]

View file

@ -0,0 +1,7 @@
"""Run the Straywild discovery service through its legacy module name."""
from straywild_discovery.server import main
if __name__ == "__main__":
main()

View file

@ -15,6 +15,7 @@ classifiers = [
[project.scripts]
straywild-discovery-server = "straywild_discovery.server:main"
netfishing-discovery-server = "straywild_discovery.server:main"
[tool.setuptools.packages.find]
include = ["straywild_discovery*"]
include = ["straywild_discovery*", "netfishing_discovery*"]

View file

@ -36,6 +36,13 @@ TRAVERSAL_PACKET_PREFIX = b"straywild_TRAVERSAL_V1 "
MAX_TRAVERSAL_PACKET_BYTES = 2048
def _environment(primary: str, legacy: str, default: str) -> str:
"""Read the Straywild setting first, then its NETfishing predecessor."""
if primary in os.environ:
return os.environ[primary]
return os.getenv(legacy, default)
@dataclass(frozen=True, slots=True)
class ServerConfig:
bind_host: str = "127.0.0.1"
@ -53,39 +60,72 @@ class ServerConfig:
@classmethod
def from_environment(cls) -> "ServerConfig":
proxy_cidrs: list[ipaddress.IPv4Network | ipaddress.IPv6Network] = []
raw_cidrs = os.getenv("straywild_DISCOVERY_TRUSTED_PROXY_CIDRS", "")
raw_cidrs = _environment(
"straywild_DISCOVERY_TRUSTED_PROXY_CIDRS",
"NETFISHING_DISCOVERY_TRUSTED_PROXY_CIDRS",
"",
)
for value in raw_cidrs.split(","):
value = value.strip()
if value:
proxy_cidrs.append(ipaddress.ip_network(value, strict=False))
return cls(
bind_host=os.getenv("straywild_DISCOVERY_HOST", "127.0.0.1"),
bind_port=int(os.getenv("straywild_DISCOVERY_PORT", "7770")),
room_ttl_seconds=float(os.getenv("straywild_DISCOVERY_ROOM_TTL", "45")),
bind_host=_environment(
"straywild_DISCOVERY_HOST", "NETFISHING_DISCOVERY_HOST", "127.0.0.1"
),
bind_port=int(_environment(
"straywild_DISCOVERY_PORT", "NETFISHING_DISCOVERY_PORT", "7770"
)),
room_ttl_seconds=float(_environment(
"straywild_DISCOVERY_ROOM_TTL",
"NETFISHING_DISCOVERY_ROOM_TTL",
"45",
)),
max_rooms=int(
os.getenv("straywild_DISCOVERY_MAX_ROOMS", str(DEFAULT_MAX_ROOMS))
_environment(
"straywild_DISCOVERY_MAX_ROOMS",
"NETFISHING_DISCOVERY_MAX_ROOMS",
str(DEFAULT_MAX_ROOMS),
)
),
max_rooms_per_address=int(
os.getenv(
_environment(
"straywild_DISCOVERY_MAX_ROOMS_PER_ADDRESS",
"NETFISHING_DISCOVERY_MAX_ROOMS_PER_ADDRESS",
str(DEFAULT_MAX_ROOMS_PER_ADDRESS),
)
),
max_body_bytes=int(
os.getenv("straywild_DISCOVERY_MAX_BODY_BYTES", str(DEFAULT_MAX_BODY_BYTES))
_environment(
"straywild_DISCOVERY_MAX_BODY_BYTES",
"NETFISHING_DISCOVERY_MAX_BODY_BYTES",
str(DEFAULT_MAX_BODY_BYTES),
)
),
trusted_proxy_cidrs=tuple(proxy_cidrs),
traversal_bind_host=os.getenv(
"straywild_DISCOVERY_TRAVERSAL_HOST", "0.0.0.0"
traversal_bind_host=_environment(
"straywild_DISCOVERY_TRAVERSAL_HOST",
"NETFISHING_DISCOVERY_TRAVERSAL_HOST",
"0.0.0.0",
),
traversal_port=int(
os.getenv("straywild_DISCOVERY_TRAVERSAL_PORT", "7771")
_environment(
"straywild_DISCOVERY_TRAVERSAL_PORT",
"NETFISHING_DISCOVERY_TRAVERSAL_PORT",
"7771",
)
),
traversal_public_host=os.getenv(
"straywild_DISCOVERY_TRAVERSAL_PUBLIC_HOST", "127.0.0.1"
traversal_public_host=_environment(
"straywild_DISCOVERY_TRAVERSAL_PUBLIC_HOST",
"NETFISHING_DISCOVERY_TRAVERSAL_PUBLIC_HOST",
"127.0.0.1",
),
build_revision=(
os.getenv("straywild_DISCOVERY_BUILD_REVISION", "unknown").strip()
_environment(
"straywild_DISCOVERY_BUILD_REVISION",
"NETFISHING_DISCOVERY_BUILD_REVISION",
"unknown",
).strip()
or "unknown"
)[:64],
)
@ -520,7 +560,14 @@ def _argument_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--host", help="bind address (overrides environment)")
parser.add_argument("--port", type=int, help="bind port (overrides environment)")
parser.add_argument("--log-level", default=os.getenv("straywild_DISCOVERY_LOG_LEVEL", "INFO"))
parser.add_argument(
"--log-level",
default=_environment(
"straywild_DISCOVERY_LOG_LEVEL",
"NETFISHING_DISCOVERY_LOG_LEVEL",
"INFO",
),
)
return parser

View file

@ -15,6 +15,7 @@ from .registry import RoomAdvertisement, RoomNotFoundError, RoomRegistry, Valida
PRESENCE_DOMAIN = "straywild_PRESENCE_V1:"
LEGACY_PRESENCE_DOMAIN = "NETFISHING_PRESENCE_V1:"
INVITE_DOMAIN = "straywild_INVITE_V1:"
SOCIAL_TOKEN_LENGTH = 64
MAX_SOCIAL_CHANNELS_PER_REQUEST = 200
@ -108,7 +109,14 @@ class SocialRegistry:
online = payload.get("online")
if not isinstance(online, bool):
raise ValidationError("online must be a boolean")
channels = [_capability_id(PRESENCE_DOMAIN, token) for token in write_tokens]
channels = [
channel
for token in write_tokens
for channel in (
_capability_id(PRESENCE_DOMAIN, token),
_capability_id(LEGACY_PRESENCE_DOMAIN, token),
)
]
now = self._clock()
with self._lock:
self._purge_locked(now)

View file

@ -0,0 +1,43 @@
from __future__ import annotations
import os
import unittest
from unittest.mock import patch
from straywild_discovery.server import ServerConfig
class ServerConfigCompatibilityTests(unittest.TestCase):
def test_legacy_environment_remains_accepted(self) -> None:
with patch.dict(
os.environ,
{
"NETFISHING_DISCOVERY_HOST": "127.0.0.9",
"NETFISHING_DISCOVERY_PORT": "7791",
"NETFISHING_DISCOVERY_BUILD_REVISION": "legacy-config",
},
clear=True,
):
config = ServerConfig.from_environment()
self.assertEqual(config.bind_host, "127.0.0.9")
self.assertEqual(config.bind_port, 7791)
self.assertEqual(config.build_revision, "legacy-config")
def test_straywild_environment_wins_over_legacy_names(self) -> None:
with patch.dict(
os.environ,
{
"straywild_DISCOVERY_HOST": "127.0.0.8",
"NETFISHING_DISCOVERY_HOST": "127.0.0.9",
"straywild_DISCOVERY_PORT": "7792",
"NETFISHING_DISCOVERY_PORT": "7791",
},
clear=True,
):
config = ServerConfig.from_environment()
self.assertEqual(config.bind_host, "127.0.0.8")
self.assertEqual(config.bind_port, 7792)
if __name__ == "__main__":
unittest.main()

View file

@ -77,6 +77,27 @@ class SocialRegistryTests(unittest.TestCase):
[],
)
def test_presence_keeps_pre_rebrand_friend_channels_reachable(self) -> None:
write_token = "c" * 64
legacy_channel = capability_id("NETFISHING_PRESENCE_V1:", write_token)
channels = self.social.publish_presence(
"203.0.113.10",
{
"write_tokens": [write_token],
"online": True,
"display_name": "Voyager",
"room_id": self.room.room_id,
"game_version": GAME_VERSION,
"protocol_version": PROTOCOL_VERSION,
},
)
self.assertIn(legacy_channel, channels)
presence = self.social.query_presence(
[legacy_channel], GAME_VERSION, PROTOCOL_VERSION, self.rooms
)
self.assertEqual(len(presence), 1)
self.assertEqual(presence[0]["channel"], legacy_channel)
def test_invitation_requires_a_current_live_poll(self) -> None:
inbox_token = "b" * 64
with self.assertRaisesRegex(