MyOwnMesh

Peer-to-peer mesh networking.
Pure Rust. Embed it in anything.

A headless daemon, an embeddable library, and a desktop GUI — one workspace, three personas. ed25519 mutual auth with eyeballs, Trystero-wire-compatible Nostr signaling, and a seven-tier reconnection ladder that survives sleep, NAT churn, and ICE failures.

MIT macOS · Linux · Windows · Pi 98 tests passing

One workspace, three personas

bin
myownmesh
Headless daemon + CLI.
crates/myownmesh
lib
myownmesh-core
Runtime, engine, protocol. Embed it in anything.
crates/myownmesh-core
app
myownmesh-gui
Desktop GUI — Tauri + Svelte 5, a client of the daemon.
gui/

Plus two supporting libraries the daemon and embedders share: myownmesh-signaling (Nostr driver + LocalBroker) and myownmesh-updater (self-update with configurable feed).

What it gives you

ed25519 mutual auth, with eyeballs

Every peer encounter exchanges a hello + auth_response where each side signs the other's nonce under myownmesh-mesh-auth-v1:. A 6-char [a-z0-9] verification code rides along for out-of-band confirmation — "the code I see matches what you read me". Approved peers land in a per-network roster and skip the prompt on reconnect.

A 7-tier reconnection ladder

Steady → Wake probe → ICE watchdog → ICE restart → Re-handshake → Room rejoin → Stop-and-start. Each tier is the cheapest action that still recovers from the failure class above it. Every tunable constant is documented in CONNECTION-ENGINE.md with the field bug it was discovered through.

Trystero-wire-compatible Nostr signaling

Same room-handle derivation as JS Trystero v0.24 (SHA-256(app_id || ":" || network_id)), same deterministic relay shuffle. Five published-fix patches against @trystero-p2p/core are baked in natively — catalogued in source so upstream-tracking is a code-level diff, not a patches/ folder.

Selectable topologies

Ring (default, sorted-lex with 2 neighbours + shortcuts), Star (explicit hub), and FullMesh. All built on the same shelving primitive; both sides of every pair run the same pure-function selector over the same sorted input, so the result is symmetric without coordination.

Typed pub/sub + generic RPC

Channel<T> is a typed publish/subscribe channel keyed by name. Rpc::call / serve / call_stream / serve_stream is the generic request/response surface. Embedders define their own message types — the mesh treats payloads opaquely.

Embed without the GUI or updater

The daemon, the library, and the desktop GUI are separate crates. An app embedding myownmesh-core doesn't pull in the HTTP self-updater or the Tauri stack. The GUI itself is a client of the daemon (over a local control socket) so crashing the UI never disturbs the running mesh.

One identity, many networks

Per-device long-lived ed25519 keypair under ~/.myownmesh/.secrets/identity.json (0600). Per-network rosters at ~/.myownmesh/mesh/rosters/{network_id}.json. Switching the active network swaps rosters but preserves identity.

Get started

Pick the persona that matches what you're doing — none of them depend on each other, so any combination works on the same box.

1. Run a node (pre-built daemon)

Pre-built binaries land on GitHub Releases for every tagged version — portable headless binaries (myownmesh-<platform>.{tar.gz,zip} + .sha256 sidecar) for all five platforms in the matrix. myownmesh update self-applies against the same artifacts once a newer release is published.

# macOS / Linux — example for linux-x86_64; replace the platform suffix as needed
curl -fsSL https://github.com/mrjeeves/MyOwnMesh/releases/latest/download/myownmesh-linux-x86_64.tar.gz | tar -xz
./myownmesh serve
# Windows
Invoke-WebRequest -Uri "https://github.com/mrjeeves/MyOwnMesh/releases/latest/download/myownmesh-windows-x86_64.zip" -OutFile myownmesh.zip
Expand-Archive myownmesh.zip
.\myownmesh.exe serve

2. Run a node (build from source)

git clone https://github.com/mrjeeves/MyOwnMesh
cd MyOwnMesh
just setup                                    # Rust toolchain via rustup
cargo install --path crates/myownmesh         # daemon + CLI on $PATH
# or run without installing:
cargo run -p myownmesh -- serve
# or with debug logging:
just serve                                    # MYOWNMESH_LOG=debug cargo run -p myownmesh -- serve

3. Run the desktop GUI

Pre-built installers (.deb / .AppImage / .dmg / .msi / .exe) ship in the same release as the daemon. The GUI auto-spawns the daemon as a child process, so installing only the GUI bundle gets you both.

From source, two shells:

just serve   # one shell — daemon + control socket (with debug logging)
just dev     # another shell — Tauri GUI with hot reload

Embed in your Rust app

The library crates aren't on crates.io yet — pull them as git dependencies pinned to a release tag. Cargo dedupes git deps by URL, so both crates resolve out of the same checkout.

[dependencies]
myownmesh-core      = { git = "https://github.com/mrjeeves/MyOwnMesh", tag = "v0.1.0" }
myownmesh-signaling = { git = "https://github.com/mrjeeves/MyOwnMesh", tag = "v0.1.0" }  # Nostr driver
tokio = { version = "1", features = ["full"] }
use myownmesh_core::{Mesh, MeshConfig, NetworkConfig, TopologyMode};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mesh = Mesh::open(MeshConfig::load().unwrap_or_default()).await?;

    let net = mesh.join(NetworkConfig {
        id: "home".into(),
        network_id: "my-cool-mesh".into(),
        label: "Home mesh".into(),
        topology: TopologyMode::default(),       // Ring
        signaling: Default::default(),            // Nostr defaults
        stun_servers: Default::default(),
        turn_servers: Default::default(),
        roster_path: None,
        auto_approve: false,
    }).await?;

    let _nostr = myownmesh_core::engine::attach_nostr(&net.state());

    let mut events = mesh.events();
    while let Ok(event) = events.recv().await {
        println!("{event:?}");
    }
    Ok(())
}

Override MYOWNMESH_HOME=~/.yourapp/mesh to keep your app's identity + rosters under its own directory tree (defaults to ~/.myownmesh/). Narrative walkthrough in docs/QUICKSTART.md.

Daemon + CLI

myownmesh                  # start the daemon (alias for `serve`)
myownmesh serve            # run the daemon in foreground
myownmesh identity show    # print this device's id
myownmesh ctl status       # query a running daemon
myownmesh ctl networks list
myownmesh update check     # poll the release feed
myownmesh config edit      # open ~/.myownmesh/config.json in $EDITOR

The daemon reads ~/.myownmesh/config.json, joins every network listed there, attaches the Nostr signaling driver per network, and listens for myownmesh ctl … clients on a local socket (~/.myownmesh/daemon.sock on Unix, named pipe on Windows).

Platforms

linux-x86_64
linux-aarch64incl. Raspberry Pi 4 / 5
macos-aarch64
macos-x86_64
windows-x86_64

The release matrix builds and uploads bundles for all five. Linux is pinned to Ubuntu 22.04 (glibc 2.35) so binaries run on Debian 12, Ubuntu 22.04+, and other distros still on glibc 2.35 / 2.36 / 2.38.