Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/agent-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down Expand Up @@ -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" +
Expand All @@ -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" +
Expand Down
97 changes: 76 additions & 21 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,36 +56,67 @@ 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/",
"wss://0.peerjs.com:443/peerjs",
"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: [],
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down
10 changes: 7 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
25 changes: 15 additions & 10 deletions src/ui/settings/AddNetworkModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import { onMount } from "svelte";
import {
addNetwork,
DEFAULT_NETWORK_SIGNALING,
DEFAULT_NETWORK_STUN,
DEFAULT_NETWORK_TURN,
importNetworkSettings,
tryParseNetworkSettings,
type NetworkSettingsExport,
Expand All @@ -55,9 +57,9 @@
// (which carries transport fields end-to-end) instead of the
// simple `addNetwork` path.
let advancedExpanded = $state(false);
let signalingDraft = $state<string[]>([]);
let signalingDraft = $state<string[]>([...DEFAULT_NETWORK_SIGNALING]);
let stunDraft = $state<string[]>([...DEFAULT_NETWORK_STUN]);
let turnDraft = $state<TurnServer[]>([]);
let turnDraft = $state<TurnServer[]>(DEFAULT_NETWORK_TURN.map((t) => ({ ...t })));
let turnEntry = $state<TurnServer>({ url: "", username: "", credential: "" });

// Import flow state. Either a parsed blob ready to apply or null
Expand All @@ -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(() => {
Expand Down Expand Up @@ -274,10 +276,13 @@
{#if advancedExpanded}
<div class="advanced">
<p class="advanced-hint">
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 <code>wss://myownmesh.com</code>, STUN
<code>stun.myownmesh.com</code>, and the shared-guest TURN
relay <code>turn.myownmesh.com</code> — 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.
</p>

<div class="adv-block">
Expand Down Expand Up @@ -379,7 +384,7 @@
<dt>signaling</dt>
<dd>
{importDraft.signaling_servers.length === 0
? "(default pool)"
? "(default · wss://myownmesh.com)"
: importDraft.signaling_servers.join(", ")}
</dd>
<dt>STUN</dt>
Expand Down
41 changes: 23 additions & 18 deletions src/ui/settings/CloudMeshAddresses.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -357,13 +357,14 @@
{#if editing}<span class="scope-tag" title="These signaling relays apply to '{editing.network_id}' only.">for {editing.network_id}</span>{/if}
</h3>
<div class="block-hint">
Networks use <a href="https://trystero.dev" target="_blank" rel="noopener">Trystero</a>
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
<code>wss://myownmesh.com</code> (standard <code>wss://</code>
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.
</div>
<div class="list">
{#each signalingRelays as _, i (i)}
Expand Down Expand Up @@ -396,10 +397,10 @@
<div class="self-host">
<p>
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.
</p>

Expand All @@ -426,7 +427,7 @@
</div>
<p>
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).
</p>
<code class="self-host-cmd">
docker run -d -p 8080:8080 scsibug/nostr-rs-relay
Expand All @@ -453,8 +454,10 @@
{#if editing}<span class="scope-tag" title="These STUN servers apply to '{editing.network_id}' only.">for {editing.network_id}</span>{/if}
</h3>
<div class="block-hint">
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, <code>stun:stun.myownmesh.com:3478</code>,
which works for the majority of home networks. Leave the list
empty to skip STUN.
</div>
<div class="list">
{#each stunServers as _, i (i)}
Expand Down Expand Up @@ -483,11 +486,13 @@
</h3>
<div class="block-hint">
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
<code>turn:turn.myownmesh.com:3478</code> so this works out of
the box. That relay is bandwidth-capped per connection, so for
sustained throughput point at your own:
</div>
<ul class="turn-options">
<li>
Expand Down
Loading