Rebrand discovery service as straywild

This commit is contained in:
Alexander Sellite 2026-08-25 17:14:33 -04:00
parent e20d8e0553
commit de454ff48a
18 changed files with 95 additions and 95 deletions

View file

@ -1,6 +1,6 @@
# Contribution policy
The NETfishing Discovery Server is an owner-directed Woofmeow project and does
The straywild Discovery Server is an owner-directed Woofmeow project and does
not accept unsolicited contributions. Do not submit pull requests, patches,
code, documentation, or other implementation work. Pull requests are disabled.
@ -9,7 +9,7 @@ An Issue is not an invitation to provide implementation work.
[`CONTRIBUTOR-TERMS.md`](CONTRIBUTOR-TERMS.md) is retained for previously
accepted work and any specific future work expressly invited in writing by the
NETfishing owners. It is not a standing invitation to contribute.
straywild owners. It is not a standing invitation to contribute.
Keep changes focused, preserve protocol and security boundaries, add focused
tests, and do not commit credentials, deployment secrets, logs, caches,

View file

@ -1,8 +1,8 @@
# NETfishing Discovery Server contributor terms
# straywild Discovery Server contributor terms
This project does not accept unsolicited contributions. These terms are
retained for contributions previously accepted after their publication and
for specific future work expressly invited in writing by the NETfishing
for specific future work expressly invited in writing by the straywild
owners. They are not a request or standing invitation for submissions and do
not retroactively change ownership of earlier work or replace a separately
signed agreement.

View file

@ -1,6 +1,6 @@
# NETfishing Discovery Server
# straywild Discovery Server
An ephemeral public-room directory for NETfishing. It complements the game's existing direct ENet
An ephemeral public-room directory for straywild. It complements the game's existing direct ENet
transport: hosts advertise a room through HTTPS, browsers retrieve compatible rooms, and joining
still uses the authoritative host's UDP endpoint.
@ -22,7 +22,7 @@ There are no runtime Python package dependencies.
## Run locally
```bash
python3 -m netfishing_discovery
python3 -m straywild_discovery
```
The default listener is `127.0.0.1:7770`.
@ -31,7 +31,7 @@ The default listener is `127.0.0.1:7770`.
curl http://127.0.0.1:7770/health
```
Set `NETFISHING_DISCOVERY_BUILD_REVISION` to the deployed Git commit. The health response reports
Set `straywild_DISCOVERY_BUILD_REVISION` to the deployed Git commit. The health response reports
both the package version and that build revision, which makes production/source drift visible.
Run the focused tests with:
@ -43,27 +43,27 @@ python3 -m unittest discover -v
## Configuration
Environment variables are documented in
[`deploy/netfishing-discovery.env.example`](deploy/netfishing-discovery.env.example).
[`deploy/straywild-discovery.env.example`](deploy/straywild-discovery.env.example).
The intended public topology is:
```text
NETfishing client -> HTTPS reverse proxy -> 127.0.0.1:7770 discovery service
NETfishing client -----------------------> public UDP rendezvous:7771
NETfishing client -----------------------> advertised ENet/UDP host:port
straywild client -> HTTPS reverse proxy -> 127.0.0.1:7770 discovery service
straywild client -----------------------> public UDP rendezvous:7771
straywild client -----------------------> advertised ENet/UDP host:port
```
Configure the Godot client with the reverse proxy's HTTPS origin through
`network/discovery/base_url` in `project.godot`. For local development, the
same value can be overridden without modifying the project by setting
`NETFISHING_DISCOVERY_URL` before launching the game.
`straywild_DISCOVERY_URL` before launching the game.
The reverse proxy must preserve the client address in `X-Forwarded-For`, and
its own address must be listed in `NETFISHING_DISCOVERY_TRUSTED_PROXY_CIDRS`.
its own address must be listed in `straywild_DISCOVERY_TRUSTED_PROXY_CIDRS`.
Apply ordinary request-rate limits to the write endpoints at the proxy. Do not
expose the Python listener directly to the public Internet.
Use the sample systemd unit in [`deploy/netfishing-discovery.service`](deploy/netfishing-discovery.service)
Use the sample systemd unit in [`deploy/straywild-discovery.service`](deploy/straywild-discovery.service)
as a deployment starting point. The service keeps only active leases in memory, so it requires no
database, backups, or schema migrations.
@ -84,9 +84,9 @@ See [`docs/API.md`](docs/API.md) for the versioned HTTP contract.
## Repository relationship
This is deliberately a separate repository from the Godot game so the service can deploy and roll
back independently. Production tags follow the coordinated NETfishing release train: a
back independently. Production tags follow the coordinated straywild release train: a
`vX.Y.Z-alpha` tag identifies the discovery snapshot tested for the same game and dedicated-server
release. The versioned HTTP contract remains independent from NETfishing's save schema and gameplay
release. The versioned HTTP contract remains independent from straywild's save schema and gameplay
network protocol. Each tagged deployment accepts room advertisements only from its matching game
release so incompatible rooms are never presented as publicly available.
@ -96,7 +96,7 @@ Project-owned source code is licensed under the GNU General Public License,
version 3 or, at your option, any later version
([GPL-3.0-or-later](LICENSE)).
NETfishing and Woofmeow branding is not granted under the GPL; see
straywild and Woofmeow branding is not granted under the GPL; see
[`TRADEMARKS.md`](TRADEMARKS.md). Unsolicited contributions are not accepted;
see [`CONTRIBUTING.md`](CONTRIBUTING.md). Historical and expressly invited
work is governed by [`CONTRIBUTOR-TERMS.md`](CONTRIBUTOR-TERMS.md).

View file

@ -1,14 +1,14 @@
# NETfishing and Woofmeow branding
# straywild and Woofmeow branding
The NETfishing and Woofmeow names, logos, icons, trade dress, and other source
identifiers are reserved by the NETfishing owners, publishing as Woofmeow.
The straywild and Woofmeow names, logos, icons, trade dress, and other source
identifiers are reserved by the straywild owners, publishing as Woofmeow.
Their presence in this GPL-licensed repository does not grant trademark rights
or permission to represent another service as official.
No trademark registration is asserted by this notice. Applicable rights may
exist through use and other law.
Truthful references to compatibility with or support for NETfishing are
Truthful references to compatibility with or support for straywild are
permitted when they do not imply sponsorship, certification, endorsement, or
official status.
@ -19,4 +19,4 @@ identify itself as independent and unofficial.
These terms do not limit lawful nominative, descriptive, comparative, or fair
use. Permission for broader branding use must be obtained in writing from both
NETfishing owners.
straywild owners.

View file

@ -1,19 +0,0 @@
# Bind locally and place a TLS reverse proxy in front of the service.
NETFISHING_DISCOVERY_HOST=127.0.0.1
NETFISHING_DISCOVERY_PORT=7770
NETFISHING_DISCOVERY_ROOM_TTL=45
NETFISHING_DISCOVERY_MAX_ROOMS=4096
NETFISHING_DISCOVERY_MAX_ROOMS_PER_ADDRESS=32
NETFISHING_DISCOVERY_MAX_BODY_BYTES=8192
NETFISHING_DISCOVERY_LOG_LEVEL=INFO
# Set this to the exact deployed Git commit so /health identifies the build.
NETFISHING_DISCOVERY_BUILD_REVISION=development
# Public UDP rendezvous used to observe ENet mappings and coordinate hole punching.
NETFISHING_DISCOVERY_TRAVERSAL_HOST=0.0.0.0
NETFISHING_DISCOVERY_TRAVERSAL_PORT=7771
NETFISHING_DISCOVERY_TRAVERSAL_PUBLIC_HOST=discovery.netfishing.org
# Only trust X-Forwarded-For when the immediate peer is within one of these CIDRs.
# The loopback values suit a reverse proxy on the same host.
NETFISHING_DISCOVERY_TRUSTED_PROXY_CIDRS=127.0.0.0/8,::1/128

View file

@ -0,0 +1,19 @@
# Bind locally and place a TLS reverse proxy in front of the service.
straywild_DISCOVERY_HOST=127.0.0.1
straywild_DISCOVERY_PORT=7770
straywild_DISCOVERY_ROOM_TTL=45
straywild_DISCOVERY_MAX_ROOMS=4096
straywild_DISCOVERY_MAX_ROOMS_PER_ADDRESS=32
straywild_DISCOVERY_MAX_BODY_BYTES=8192
straywild_DISCOVERY_LOG_LEVEL=INFO
# Set this to the exact deployed Git commit so /health identifies the build.
straywild_DISCOVERY_BUILD_REVISION=development
# Public UDP rendezvous used to observe ENet mappings and coordinate hole punching.
straywild_DISCOVERY_TRAVERSAL_HOST=0.0.0.0
straywild_DISCOVERY_TRAVERSAL_PORT=7771
straywild_DISCOVERY_TRAVERSAL_PUBLIC_HOST=discovery.straywild.io
# Only trust X-Forwarded-For when the immediate peer is within one of these CIDRs.
# The loopback values suit a reverse proxy on the same host.
straywild_DISCOVERY_TRUSTED_PROXY_CIDRS=127.0.0.0/8,::1/128

View file

@ -1,15 +1,15 @@
[Unit]
Description=NETfishing public room discovery service
Description=straywild public room discovery service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=netfishing-discovery
Group=netfishing-discovery
WorkingDirectory=/var/www/netfishing.org.discovery
EnvironmentFile=/etc/netfishing-discovery.env
ExecStart=/usr/bin/python3 -m netfishing_discovery
User=straywild-discovery
Group=straywild-discovery
WorkingDirectory=/var/www/straywild.io.discovery
EnvironmentFile=/etc/straywild-discovery.env
ExecStart=/usr/bin/python3 -m straywild_discovery
Restart=on-failure
RestartSec=2
NoNewPrivileges=true

View file

@ -1,7 +1,7 @@
# Discovery API v1
The API stores public room advertisements as short-lived leases and coordinates direct UDP hole
punching. It does not proxy or relay gameplay traffic. NETfishing continues to connect to the
punching. It does not proxy or relay gameplay traffic. straywild continues to connect to the
returned host and UDP port through ENet.
All request and response bodies use `application/json`. Production clients must use HTTPS.
@ -9,8 +9,8 @@ All request and response bodies use `application/json`. Production clients must
## `GET /health`
Returns service status, package version, configured build revision, and the number of active,
unexpired room leases. Deployments should set `NETFISHING_DISCOVERY_BUILD_REVISION` to the exact
Git commit they are running. The package version follows the coordinated NETfishing release tag.
unexpired room leases. Deployments should set `straywild_DISCOVERY_BUILD_REVISION` to the exact
Git commit they are running. The package version follows the coordinated straywild release tag.
## `GET /v1/rooms`
@ -33,7 +33,7 @@ trusting an address supplied by the game client.
"current_players": 0,
"max_players": 8,
"game_version": "0.18.1-alpha",
"protocol_version": 10
"protocol_version": 11
}
```
@ -47,7 +47,7 @@ excluded from public listings until the service observes that endpoint.
The service applies configured global and per-observed-address active-room limits. Exceeding one
returns `429 room_limit`; expired leases stop counting automatically.
The discovery release accepts room advertisements only from the matching NETfishing game release.
The discovery release accepts room advertisements only from the matching straywild game release.
A mismatched `game_version` returns `409 game_version_mismatch` with `required_game_version`; the
room is not created or updated.
@ -94,7 +94,7 @@ that inbox was polled recently. There is no offline delivery.
All four requests include the normal `game_version` and `protocol_version`. Capability values are
64 lowercase hexadecimal characters and must be exchanged by the games during an authenticated,
live NETfishing session.
live straywild session.
## Error shape

View file

@ -1,3 +0,0 @@
"""NETfishing discovery service."""
__version__ = "0.18.1-alpha"

View file

@ -3,9 +3,9 @@ requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "netfishing-discovery-server"
name = "straywild-discovery-server"
version = "0.18.1-alpha"
description = "Ephemeral public-room directory for NETfishing"
description = "Ephemeral public-room directory for straywild"
requires-python = ">=3.11"
dependencies = []
license = { file = "LICENSE" }
@ -14,7 +14,7 @@ classifiers = [
]
[project.scripts]
netfishing-discovery-server = "netfishing_discovery.server:main"
straywild-discovery-server = "straywild_discovery.server:main"
[tool.setuptools.packages.find]
include = ["netfishing_discovery*"]
include = ["straywild_discovery*"]

View file

@ -0,0 +1,3 @@
"""straywild discovery service."""
__version__ = "0.18.1-alpha"

View file

@ -1,4 +1,4 @@
"""Thread-safe ephemeral room leases for the NETfishing directory."""
"""Thread-safe ephemeral room leases for the straywild directory."""
from __future__ import annotations

View file

@ -1,4 +1,4 @@
"""Small HTTP/JSON API for NETfishing public-room discovery."""
"""Small HTTP/JSON API for straywild public-room discovery."""
from __future__ import annotations
@ -30,9 +30,9 @@ from .registry import (
from .social_registry import FriendOfflineError, SocialRegistry
LOGGER = logging.getLogger("netfishing.discovery")
LOGGER = logging.getLogger("straywild.discovery")
DEFAULT_MAX_BODY_BYTES = 64 * 1024
TRAVERSAL_PACKET_PREFIX = b"NETFISHING_TRAVERSAL_V1 "
TRAVERSAL_PACKET_PREFIX = b"straywild_TRAVERSAL_V1 "
MAX_TRAVERSAL_PACKET_BYTES = 2048
@ -53,39 +53,39 @@ class ServerConfig:
@classmethod
def from_environment(cls) -> "ServerConfig":
proxy_cidrs: list[ipaddress.IPv4Network | ipaddress.IPv6Network] = []
raw_cidrs = os.getenv("NETFISHING_DISCOVERY_TRUSTED_PROXY_CIDRS", "")
raw_cidrs = os.getenv("straywild_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("NETFISHING_DISCOVERY_HOST", "127.0.0.1"),
bind_port=int(os.getenv("NETFISHING_DISCOVERY_PORT", "7770")),
room_ttl_seconds=float(os.getenv("NETFISHING_DISCOVERY_ROOM_TTL", "45")),
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")),
max_rooms=int(
os.getenv("NETFISHING_DISCOVERY_MAX_ROOMS", str(DEFAULT_MAX_ROOMS))
os.getenv("straywild_DISCOVERY_MAX_ROOMS", str(DEFAULT_MAX_ROOMS))
),
max_rooms_per_address=int(
os.getenv(
"NETFISHING_DISCOVERY_MAX_ROOMS_PER_ADDRESS",
"straywild_DISCOVERY_MAX_ROOMS_PER_ADDRESS",
str(DEFAULT_MAX_ROOMS_PER_ADDRESS),
)
),
max_body_bytes=int(
os.getenv("NETFISHING_DISCOVERY_MAX_BODY_BYTES", str(DEFAULT_MAX_BODY_BYTES))
os.getenv("straywild_DISCOVERY_MAX_BODY_BYTES", str(DEFAULT_MAX_BODY_BYTES))
),
trusted_proxy_cidrs=tuple(proxy_cidrs),
traversal_bind_host=os.getenv(
"NETFISHING_DISCOVERY_TRAVERSAL_HOST", "0.0.0.0"
"straywild_DISCOVERY_TRAVERSAL_HOST", "0.0.0.0"
),
traversal_port=int(
os.getenv("NETFISHING_DISCOVERY_TRAVERSAL_PORT", "7771")
os.getenv("straywild_DISCOVERY_TRAVERSAL_PORT", "7771")
),
traversal_public_host=os.getenv(
"NETFISHING_DISCOVERY_TRAVERSAL_PUBLIC_HOST", "127.0.0.1"
"straywild_DISCOVERY_TRAVERSAL_PUBLIC_HOST", "127.0.0.1"
),
build_revision=(
os.getenv("NETFISHING_DISCOVERY_BUILD_REVISION", "unknown").strip()
os.getenv("straywild_DISCOVERY_BUILD_REVISION", "unknown").strip()
or "unknown"
)[:64],
)
@ -123,7 +123,7 @@ class DiscoveryRequestHandler(BaseHTTPRequestHandler):
HTTPStatus.OK,
{
"status": "ok",
"service": "netfishing-discovery-server",
"service": "straywild-discovery-server",
"version": __version__,
"build_revision": self.server.config.build_revision,
"active_rooms": self.server.registry.room_count(),
@ -387,7 +387,7 @@ class DiscoveryRequestHandler(BaseHTTPRequestHandler):
HTTPStatus.CONFLICT,
"game_version_mismatch",
(
f"This discovery server only lists NETfishing {__version__} rooms. "
f"This discovery server only lists straywild {__version__} rooms. "
"Your room will not be listed until the game and discovery server "
"use the same version."
),
@ -520,7 +520,7 @@ 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("NETFISHING_DISCOVERY_LOG_LEVEL", "INFO"))
parser.add_argument("--log-level", default=os.getenv("straywild_DISCOVERY_LOG_LEVEL", "INFO"))
return parser
@ -556,14 +556,14 @@ def main() -> None:
traversal_thread.start()
_install_signal_handlers(server)
LOGGER.info(
"NETfishing discovery server %s listening on %s:%d (room TTL %.1fs)",
"straywild discovery server %s listening on %s:%d (room TTL %.1fs)",
__version__,
config.bind_host,
server.server_address[1],
config.room_ttl_seconds,
)
LOGGER.info(
"NETfishing traversal rendezvous listening on %s:%d/udp",
"straywild traversal rendezvous listening on %s:%d/udp",
config.traversal_bind_host,
traversal_server.server_address[1],
)

View file

@ -14,8 +14,8 @@ from typing import Any
from .registry import RoomAdvertisement, RoomNotFoundError, RoomRegistry, ValidationError
PRESENCE_DOMAIN = "NETFISHING_PRESENCE_V1:"
INVITE_DOMAIN = "NETFISHING_INVITE_V1:"
PRESENCE_DOMAIN = "straywild_PRESENCE_V1:"
INVITE_DOMAIN = "straywild_INVITE_V1:"
SOCIAL_TOKEN_LENGTH = 64
MAX_SOCIAL_CHANNELS_PER_REQUEST = 200
MAX_SOCIAL_RECORDS = 8192

View file

@ -7,8 +7,8 @@ import socketserver
import threading
import unittest
from netfishing_discovery.registry import RoomRegistry
from netfishing_discovery.server import (
from straywild_discovery.registry import RoomRegistry
from straywild_discovery.server import (
DiscoveryHTTPServer,
ServerConfig,
TraversalUDPServer,
@ -21,7 +21,7 @@ VALID_ROOM = {
"current_players": 1,
"max_players": 8,
"game_version": "0.18.1-alpha",
"protocol_version": 10,
"protocol_version": 11,
}
@ -86,7 +86,7 @@ class DiscoveryHTTPTests(unittest.TestCase):
self.assertIsInstance(room_id, str)
self.assertIsInstance(token, str)
status, listing = self.request("GET", "/v1/rooms?protocol_version=10")
status, listing = self.request("GET", "/v1/rooms?protocol_version=11")
self.assertEqual(status, 200)
assert listing is not None
listed_rooms = listing["rooms"]
@ -264,7 +264,7 @@ class DiscoveryHTTPTests(unittest.TestCase):
write_token = "d" * 64
channel = hashlib.sha256(
f"NETFISHING_PRESENCE_V1:{write_token}".encode()
f"straywild_PRESENCE_V1:{write_token}".encode()
).hexdigest()
status, _published = self.request(
"POST",

View file

@ -2,7 +2,7 @@ from __future__ import annotations
import unittest
from netfishing_discovery.registry import (
from straywild_discovery.registry import (
LeaseAuthorizationError,
RoomLimitError,
RoomNotFoundError,
@ -17,7 +17,7 @@ VALID_ROOM = {
"current_players": 1,
"max_players": 8,
"game_version": "0.18.1-alpha",
"protocol_version": 10,
"protocol_version": 11,
}
@ -112,7 +112,7 @@ class RoomRegistryTests(unittest.TestCase):
"203.0.113.11",
dict(VALID_ROOM, room_name="Older Room", protocol_version=3),
)
self.assertEqual(self.registry.list_rooms(protocol_version=10), [expected])
self.assertEqual(self.registry.list_rooms(protocol_version=11), [expected])
self.assertEqual(self.registry.list_rooms(game_version="missing"), [])
def test_room_limits_bound_untrusted_advertisements(self) -> None:

View file

@ -3,8 +3,8 @@ from __future__ import annotations
import hashlib
import unittest
from netfishing_discovery.registry import RoomRegistry, ValidationError
from netfishing_discovery.social_registry import (
from straywild_discovery.registry import RoomRegistry, ValidationError
from straywild_discovery.social_registry import (
FriendOfflineError,
INBOX_REACHABLE_SECONDS,
PRESENCE_TTL_SECONDS,
@ -13,7 +13,7 @@ from netfishing_discovery.social_registry import (
GAME_VERSION = "0.18.1-alpha"
PROTOCOL_VERSION = 10
PROTOCOL_VERSION = 11
VALID_ROOM = {
"room_name": "Pond Friends",
"port": 7777,
@ -50,7 +50,7 @@ class SocialRegistryTests(unittest.TestCase):
def test_presence_is_capability_addressed_and_expires(self) -> None:
write_token = "a" * 64
channel = capability_id("NETFISHING_PRESENCE_V1:", write_token)
channel = capability_id("straywild_PRESENCE_V1:", write_token)
self.social.publish_presence(
"203.0.113.10",
{