diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-30 22:17:30 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-30 22:17:30 +0200 |
| commit | 07e6e4271ea3a45e9cd364eb6ec05801a653d9e8 (patch) | |
| tree | 5d2dea4251ee2ecea32aeef80d09167d08085dd0 /packages/meshbay-node/src/meshbay_node/daemon.py | |
| parent | bf56b60b95c5413d592f0199320d637978792756 (diff) | |
| download | meshbay-07e6e4271ea3a45e9cd364eb6ec05801a653d9e8.tar.gz | |
feat: configurable STUN server fallbacks for WebRTC ICE
The WebRTC transport relied on a single Google STUN server — if it was
unreachable, ICE gathering waited the full 4s timeout. Now four public
servers are used by default (Google ×2, Cloudflare, Mozilla), configurable
via node.toml, the Node page UI, and the CLI (meshbay-node stun list|add|
remove|reset). Changes are hot-swapped on the live transport.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/daemon.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/daemon.py | 77 |
1 files changed, 72 insertions, 5 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/daemon.py b/packages/meshbay-node/src/meshbay_node/daemon.py index e1e4dee..51f78b7 100644 --- a/packages/meshbay-node/src/meshbay_node/daemon.py +++ b/packages/meshbay-node/src/meshbay_node/daemon.py @@ -269,6 +269,7 @@ class NodeDaemon: # Apply any roster overrides to node config (panel-edited values # take precedence over node.toml defaults). + from meshbay_node.config import DEFAULT_STUN_SERVERS nd = self._config.node defaults = { "invite_ttl_hours": nd.invite_ttl_hours, @@ -276,6 +277,7 @@ class NodeDaemon: "device_request_ttl_minutes": nd.device_request_ttl_minutes, "max_concurrent_streams": nd.max_concurrent_streams, "transcode_incompatible_video": nd.transcode_incompatible_video, + "stun_servers": nd.stun_servers if nd.stun_servers else list(DEFAULT_STUN_SERVERS), } effective = await self._roster.node_settings(defaults) for k, v in effective.items(): @@ -486,6 +488,7 @@ class NodeDaemon: denylist=denylist, max_concurrent_streams=self._config.node.max_concurrent_streams, transcode_incompatible_video=self._config.node.transcode_incompatible_video, + stun_servers=self._config.node.stun_servers or None, ) # No global chat_store here: each group's store lives in # groups_ctx[gid]["chat_store"] and is resolved per session via @@ -1604,22 +1607,25 @@ def main() -> None: parser.add_argument("command", nargs="?", choices=["init", "reset", "status", "ui", "gek-init", "gek", "operator", "member", "group", "file", - "video", "denylist", "reload", "restart-daemon", - "calibrate-argon2"], + "video", "denylist", "stun", "reload", + "restart-daemon", "calibrate-argon2"], help="init: provision config + keystore | reset: erase all " "node state | status: node state and keys " "| ui: print the admin UI URL | operator pair: pair a " "browser with this node | member list|invite|revoke|unpin " "| group list|add|remove | gek init|rotate | file list|rm " "| video rematch: re-resolve TMDB matches for a group's " - "videos | denylist show|clear | reload: re-read node.toml " + "videos | denylist show|clear " + "| stun list|add|remove|reset " + "| reload: re-read node.toml " "(systemctl --user reload) | restart-daemon: restart " "the systemd unit (systemctl --user restart) " "| calibrate-argon2: benchmark") parser.add_argument("subcommand", nargs="?", help="'pair' for operator; list|invite|revoke|unpin for " "member; list|add|remove for group; init|rotate for gek; " - "list|rm for file; rematch for video; show|clear for denylist") + "list|rm for file; rematch for video; show|clear for " + "denylist; list|add|remove|reset for stun") parser.add_argument("target", nargs="?", help="username for member invite|revoke|unpin; group name " "for group add; file id for file rm; identifier for " @@ -1645,7 +1651,7 @@ def main() -> None: # Query commands print a report; library logging would interleave with it. quiet = args.command in ("status", "ui", "gek-init", "gek", "operator", "member", "group", "file", "video", "denylist", - "reload", "restart-daemon", "reset") + "stun", "reload", "restart-daemon", "reset") logging.basicConfig( level=logging.ERROR if quiet else getattr(logging, args.log_level), format="%(asctime)s %(levelname)-8s %(name)s: %(message)s", @@ -2087,6 +2093,67 @@ def main() -> None: print("usage: meshbay-node denylist show|clear [identifier] [--yes]") sys.exit(1) + if args.command == "stun": + cfg = load_config(args.config or DEFAULT_CONFIG_PATH) + sub = args.subcommand or "list" + + if sub == "list": + out = _daemon_api(cfg, "/api/node-settings") + servers = out.get("stun_servers", []) + if not servers: + print("stun servers (none configured)") + return + for i, s in enumerate(servers, 1): + print(f" {i}. {s}") + return + + if sub == "add": + url = args.target + if not url: + print("usage: meshbay-node stun add <stun:host:port>") + sys.exit(1) + if not url.startswith("stun:"): + print(f"error: STUN URL must start with stun: — got {url!r}") + sys.exit(1) + out = _daemon_api(cfg, "/api/node-settings") + servers = out.get("stun_servers", []) + if url in servers: + print(f"already present: {url}") + return + servers.append(url) + _daemon_api(cfg, "/api/node-settings", method="PUT", + body={"stun_servers": servers}) + print(f"added {url} ({len(servers)} servers total)") + return + + if sub == "remove": + url = args.target + if not url: + print("usage: meshbay-node stun remove <stun:host:port>") + sys.exit(1) + out = _daemon_api(cfg, "/api/node-settings") + servers = out.get("stun_servers", []) + if url not in servers: + print(f"not found: {url}") + sys.exit(1) + servers.remove(url) + _daemon_api(cfg, "/api/node-settings", method="PUT", + body={"stun_servers": servers}) + print(f"removed {url} ({len(servers)} servers remaining)") + return + + if sub == "reset": + from meshbay_node.config import DEFAULT_STUN_SERVERS + _daemon_api(cfg, "/api/node-settings", method="PUT", + body={"stun_servers": list(DEFAULT_STUN_SERVERS)}) + print("STUN servers reset to defaults:") + for s in DEFAULT_STUN_SERVERS: + print(f" {s}") + return + + print("usage: meshbay-node stun list|add|remove|reset [url]") + sys.exit(1) + if args.command == "file": cfg = load_config(args.config or DEFAULT_CONFIG_PATH) sub = args.subcommand or "list" |