summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/daemon.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/daemon.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/daemon.py77
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"