From 749e577ee571088516335aa4e0ae3cda6e4d812f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 10 Jun 2026 10:21:21 +0000 Subject: [PATCH] feat(mesh): default networks to the MyOwnMesh wss/stun/turn endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New networks were seeded with Google's STUN pool, empty signaling, and no TURN — so out of the box the app showed and used a different setup than the MyOwnMesh substrate, and symmetric-NAT peers couldn't connect without configuring TURN by hand. Align both the displayed settings and the behaviour with the project's semi-public defaults: - signaling -> wss://myownmesh.com (the daemon already resolves an empty list to this; new networks now spell it out so it's visible) - STUN -> stun.myownmesh.com:3478 - TURN -> turn.myownmesh.com:3478 with the shared guest credential mergeNetwork seeds these for new networks and upgrades the exact legacy Google STUN default to the new one on load (a customised list is left untouched). The Add-network modal + Addresses tab seed and describe the defaults, and the networks agent-tool snippets are updated to match. https://claude.ai/code/session_0162sBGEfRqzPdVNPm4BzxLV --- src/agent-tools.ts | 16 ++-- src/config.ts | 97 ++++++++++++++++++----- src/types.ts | 10 ++- src/ui/settings/AddNetworkModal.svelte | 25 +++--- src/ui/settings/CloudMeshAddresses.svelte | 41 +++++----- 5 files changed, 132 insertions(+), 57 deletions(-) diff --git a/src/agent-tools.ts b/src/agent-tools.ts index d70e169..e18b707 100644 --- a/src/agent-tools.ts +++ b/src/agent-tools.ts @@ -626,7 +626,9 @@ export const NETWORKS_TOOL: Tool = { description: "For set_signaling_servers / set_stun_servers: array of URL strings. " + "For set_turn_servers: array of {url, username?, credential?} objects. " + - "Empty array clears the list (Trystero / Google defaults will be used).", + "Empty array clears the list — an empty signaling list still falls back " + + "to the default wss://myownmesh.com relay; empty STUN/TURN opts out of " + + "that helper.", }, }, required: ["action"], @@ -1054,8 +1056,9 @@ const NETWORKS_TOOL_SNIPPET: string = "(status, peers, saved networks, accepting policy, diagnostic log, " + "signaling/STUN/TURN servers, import/export of portable settings).\n\n" + "How the mesh works:\n" + - "- Devices that share a Network ID find each other through public Nostr relays " + - "(or a self-hosted one) and connect peer-to-peer over WebRTC.\n" + + "- Devices that share a Network ID find each other through the default " + + "wss://myownmesh.com relay (or your own) over Nostr, then connect " + + "peer-to-peer over WebRTC.\n" + "- Joining still requires explicit approval per device, gated by a 6-char " + "verification code that both sides should read out and confirm.\n" + "- The user can save multiple networks; exactly one is active at a time.\n" + @@ -1080,8 +1083,11 @@ const NETWORKS_TOOL_SNIPPET: string = "- `action='export_settings'` returns the same envelope for the active (or " + "named) network, ready to share to another device.\n" + "- `action='set_signaling_servers' / 'set_stun_servers' / 'set_turn_servers'` " + - "replace the respective list on a network. Empty array restores defaults " + - "(Trystero public Nostr relays / Google STUN / no TURN)."; + "replace the respective list on a network. New networks ship the MyOwnMesh " + + "defaults (signaling wss://myownmesh.com, STUN stun.myownmesh.com:3478, TURN " + + "turn.myownmesh.com:3478 with the shared guest credential). Passing an empty " + + "array clears that list — an empty signaling list still falls back to " + + "wss://myownmesh.com; empty STUN/TURN opts out of that helper."; const WEB_SEARCH_TOOL_SNIPPET: string = "## `web_search` tool — search the web\n\n" + diff --git a/src/config.ts b/src/config.ts index 8b9d24e..003bb8b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -56,18 +56,30 @@ const DEFAULT_REMOTE_UI: RemoteUiConfig = { port: 1474, }; -// Signaling is handled by Trystero over Nostr relays. The default -// per-network `signaling_servers` list is empty so the mesh client -// uses Trystero's built-in 52-relay public pool, bumped to -// redundancy 8 at connect time (see `DEFAULT_SIGNALING_REDUNDANCY` -// in mesh-client.svelte.ts). Anyone who wants to point at a -// self-hosted Nostr relay (or a private one for office/LAN use) -// adds entries from the Cloud Mesh → Addresses tab and those -// replace the default pool entirely. STUN servers default to -// Google's public pool, which is the de-facto baseline. +// Per-network transport defaults. The frontend bridges each saved +// network into the bundled `myownmesh serve` daemon (see +// `networkConfigToDaemonShape`), which owns signaling / ICE. Every +// new network is seeded with the project's semi-public MyOwnMesh +// defaults so a fresh install connects out of the box AND the user +// can see what it's using in Settings → Cloud Mesh → Addresses +// rather than staring at an empty list: +// +// - signaling → wss://myownmesh.com — the reference Nostr relay, +// reached over standard wss:// (443) so it sails through +// restrictive firewalls. The daemon resolves an empty list to +// this same relay, so seeding it explicitly is purely so the +// value is visible and editable in the UI. +// - STUN → stun.myownmesh.com:3478 — server-reflexive discovery. +// - TURN → turn.myownmesh.com:3478 with the shared guest +// credential, so symmetric-NAT / CGNAT peers (phone hotspots) +// relay out of the box. Bandwidth-capped and the credential is +// deliberately not a secret; point at your own server for +// sustained throughput. +// +// All three are overridable per-network from the Addresses tab. // // Legacy entries from earlier PeerJS-based commits get stripped -// on load so testers don't end up pointing Trystero at a +// on load so testers don't end up pointing the relay layer at a // peerjs-server URL it can't speak to. const LEGACY_PEERJS_SIGNALING_URLS = [ "wss://0.peerjs.com:443/", @@ -75,17 +87,36 @@ const LEGACY_PEERJS_SIGNALING_URLS = [ "wss://mesh.myownllm.net/signal", ]; -/** Defaults for newly-added networks. Empty signaling = Trystero's - * public Nostr relays; Google's STUN pool for NAT helpers; empty - * per-network TURN by default. Applied by `createNetwork` and by - * the legacy-config migration so a pre-multi-network install - * lands with sane per-network defaults. */ -export const DEFAULT_NETWORK_SIGNALING: string[] = []; -export const DEFAULT_NETWORK_STUN: string[] = [ +/** The pre-MyOwnMesh STUN default (Google's public pool). A network + * still carrying exactly this list never made a deliberate STUN + * choice — it's just the old baseline — so it's upgraded to the + * current default on load. A customised list (anything added or + * removed) won't match and is left untouched. */ +const LEGACY_GOOGLE_STUN: string[] = [ "stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302", ]; +/** Default signaling relay for newly-added networks: the project's + * reference relay over standard wss:// (443). The daemon resolves an + * empty list to the same relay, so this exists mainly so the value + * is visible in the UI. */ +export const DEFAULT_NETWORK_SIGNALING: string[] = ["wss://myownmesh.com"]; +/** Default STUN servers for newly-added networks — the project's + * reference STUN for server-reflexive NAT discovery. */ +export const DEFAULT_NETWORK_STUN: string[] = ["stun:stun.myownmesh.com:3478"]; +/** Default TURN servers for newly-added networks — the project's + * reference TURN with its shared semi-public guest credential, so + * symmetric-NAT / CGNAT peers relay out of the box. Bandwidth-capped; + * point at your own server for sustained throughput. */ +export const DEFAULT_NETWORK_TURN: TurnServer[] = [ + { + url: "turn:turn.myownmesh.com:3478", + username: "guest", + credential: "theguestpassword", + }, +]; + const DEFAULT_CLOUD_MESH: CloudMeshConfig = { enabled: false, networks: [], @@ -515,11 +546,26 @@ function coerceAccepting(raw: unknown): NetworkConfig["accepting"] { } /** Strip legacy PeerJS signaling URLs that may linger in old - * configs from a pre-Trystero branch commit. */ + * configs from a pre-daemon branch commit. */ function cleanSignaling(raw: string[] | undefined): string[] { return (raw ?? []).filter((s) => !LEGACY_PEERJS_SIGNALING_URLS.includes(s)); } +/** STUN list for a saved/added network. Fills the MyOwnMesh default + * when the field is absent (a fresh network) and upgrades the exact + * legacy Google default to the current one. Any other explicit list + * — including a deliberately-empty opt-out (`[]`) — is preserved. */ +function defaultedStun(raw: string[] | undefined): string[] { + if (raw === undefined) return [...DEFAULT_NETWORK_STUN]; + if ( + raw.length === LEGACY_GOOGLE_STUN.length && + raw.every((u, i) => u === LEGACY_GOOGLE_STUN[i]) + ) { + return [...DEFAULT_NETWORK_STUN]; + } + return raw; +} + /** Build a `NetworkConfig` from a partial saved entry, filling * per-network defaults. Used both for normal loads (where most * fields are present) and for the legacy single-network migration @@ -544,9 +590,18 @@ function mergeNetwork( const net: NetworkConfig = { id: raw.id || newNetworkInternalId(), network_id: raw.network_id || "", - signaling_servers: cleanSignaling(raw.signaling_servers), - stun_servers: raw.stun_servers ?? [...DEFAULT_NETWORK_STUN], - turn_servers: raw.turn_servers ?? [], + // Absent transport fields seed the MyOwnMesh defaults (a fresh + // network); an explicit list on disk — including an empty + // opt-out — is preserved. Signaling is left empty when the saved + // value is empty (the daemon resolves that to wss://myownmesh.com + // anyway), but a brand-new network gets it spelled out so it's + // visible in the Addresses tab. + signaling_servers: + raw.signaling_servers === undefined + ? [...DEFAULT_NETWORK_SIGNALING] + : cleanSignaling(raw.signaling_servers), + stun_servers: defaultedStun(raw.stun_servers), + turn_servers: raw.turn_servers ?? [...DEFAULT_NETWORK_TURN], accepting: coerceAccepting(raw.accepting), // Default to enabled so existing networks (saved before this // field existed) keep their pre-toggle "auto-sync everywhere" diff --git a/src/types.ts b/src/types.ts index 59c070d..672dd7c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -275,9 +275,13 @@ export interface NetworkConfig { auto_approve?: boolean; /** Per-network signaling / NAT settings. Each network can point * at a different relay pool — home / office / public mesh all - * configurable independently. Empty signaling = Trystero's - * built-in Nostr default pool at redundancy 8; empty stun = no - * NAT helpers; empty turn = no relay fallback. */ + * configurable independently. New networks are seeded with the + * MyOwnMesh defaults (signaling `wss://myownmesh.com`, STUN + * `stun.myownmesh.com:3478`, TURN `turn.myownmesh.com:3478` with + * the shared guest credential) so they connect out of the box; + * each list is overridable from the Addresses tab. An empty + * signaling list still resolves to `wss://myownmesh.com` in the + * daemon; an empty stun / turn list opts out of that helper. */ signaling_servers: string[]; stun_servers: string[]; turn_servers: TurnServer[]; diff --git a/src/ui/settings/AddNetworkModal.svelte b/src/ui/settings/AddNetworkModal.svelte index 93a0a79..962986b 100644 --- a/src/ui/settings/AddNetworkModal.svelte +++ b/src/ui/settings/AddNetworkModal.svelte @@ -28,7 +28,9 @@ import { onMount } from "svelte"; import { addNetwork, + DEFAULT_NETWORK_SIGNALING, DEFAULT_NETWORK_STUN, + DEFAULT_NETWORK_TURN, importNetworkSettings, tryParseNetworkSettings, type NetworkSettingsExport, @@ -55,9 +57,9 @@ // (which carries transport fields end-to-end) instead of the // simple `addNetwork` path. let advancedExpanded = $state(false); - let signalingDraft = $state([]); + let signalingDraft = $state([...DEFAULT_NETWORK_SIGNALING]); let stunDraft = $state([...DEFAULT_NETWORK_STUN]); - let turnDraft = $state([]); + let turnDraft = $state(DEFAULT_NETWORK_TURN.map((t) => ({ ...t }))); let turnEntry = $state({ url: "", username: "", credential: "" }); // Import flow state. Either a parsed blob ready to apply or null @@ -71,10 +73,10 @@ // a plain new network or applying transport overrides. let hasOverrides = $derived( importDraft !== null || - signalingDraft.some((s) => s.trim() !== "") || - // Custom STUN list = different from defaults. + // Any edit away from the seeded MyOwnMesh defaults. + JSON.stringify(signalingDraft) !== JSON.stringify(DEFAULT_NETWORK_SIGNALING) || JSON.stringify(stunDraft) !== JSON.stringify(DEFAULT_NETWORK_STUN) || - turnDraft.length > 0, + JSON.stringify(turnDraft) !== JSON.stringify(DEFAULT_NETWORK_TURN), ); onMount(() => { @@ -274,10 +276,13 @@ {#if advancedExpanded}

- Optional. Leave blank to use Trystero's public Nostr relays - and Google's STUN pool. Override here when you want a - private relay, a custom STUN, or TURN for symmetric-NAT - peers (phone hotspot / CGNAT). + Optional. These are seeded with the MyOwnMesh defaults — + signaling wss://myownmesh.com, STUN + stun.myownmesh.com, and the shared-guest TURN + relay turn.myownmesh.com — so a fresh network + connects out of the box, even for symmetric-NAT peers (phone + hotspot / CGNAT). Override here to point at a private relay, + your own STUN, or your own TURN server.

@@ -379,7 +384,7 @@
signaling
{importDraft.signaling_servers.length === 0 - ? "(default pool)" + ? "(default · wss://myownmesh.com)" : importDraft.signaling_servers.join(", ")}
STUN
diff --git a/src/ui/settings/CloudMeshAddresses.svelte b/src/ui/settings/CloudMeshAddresses.svelte index 7d28178..1466a96 100644 --- a/src/ui/settings/CloudMeshAddresses.svelte +++ b/src/ui/settings/CloudMeshAddresses.svelte @@ -357,13 +357,14 @@ {#if editing}for {editing.network_id}{/if}
- Networks use Trystero - for peer discovery — currently over Nostr relays. By default - Trystero picks from a built-in pool of public relays - maintained by the Nostr community, so MyOwnLLM operates no - signaling infrastructure of its own. Add your own relay - URLs below to use specific or self-hosted relays instead; - leave the list empty to keep the defaults. + Peers rendezvous over Nostr relays. By default a network uses + the project's reference relay + wss://myownmesh.com (standard wss:// + on 443, so it clears restrictive firewalls), with a reactive + fallback to a pool of public community relays only while the + default can't be reached. Add your own relay URLs below to pin + specific or self-hosted relays instead; leave the list empty to + keep the default.
{#each signalingRelays as _, i (i)} @@ -396,10 +397,10 @@

A Nostr relay is a tiny WebSocket service that proxies - signed messages between subscribed clients — Trystero - piggybacks on this to relay WebRTC offers/answers - between MyOwnLLM peers. The relay never sees mesh - content, only the small offer/answer envelopes during + signed messages between subscribed clients — the mesh + signaling driver piggybacks on this to relay WebRTC + offers/answers between MyOwnLLM peers. The relay never sees + mesh content, only the small offer/answer envelopes during connection setup.

@@ -426,7 +427,7 @@

More featureful, persists messages across restarts - (which Trystero doesn't need but doesn't hurt). + (which the signaling driver doesn't need but doesn't hurt).

docker run -d -p 8080:8080 scsibug/nostr-rs-relay @@ -453,8 +454,10 @@ {#if editing}for {editing.network_id}{/if}
- Public NAT-traversal helpers. Defaults to Google's public STUN - pool, which works for the majority of home networks. + Public NAT-traversal helpers. Defaults to the project's + reference STUN, stun:stun.myownmesh.com:3478, + which works for the majority of home networks. Leave the list + empty to skip STUN.
{#each stunServers as _, i (i)} @@ -483,11 +486,13 @@
Relay servers used when direct peer connections can't be - established. Required for peers behind symmetric NAT (phone + established — required for peers behind symmetric NAT (phone hotspots, carrier-grade NAT, restrictive corporate or guest - Wi-Fi) because STUN-only hole-punching can't traverse those. - TURN consumes real bandwidth, so there's no free - no-signup public service that reliably stays up. Use one of: + Wi-Fi), where STUN-only hole-punching can't traverse. New + networks default to the project's shared-guest relay + turn:turn.myownmesh.com:3478 so this works out of + the box. That relay is bandwidth-capped per connection, so for + sustained throughput point at your own: