Mesh Deployment - Dark-Alex-17/coyote GitHub Wiki
This page is about getting a Coyote Mesh node onto a network. The mesh runs over Reticulum, a networking
stack that works over ordinary IP links as well as radios and serial lines. The usual way to run Reticulum is one daemon per machine
to own the network interfaces: rnsd, a small background program that lets several programs on one machine share
them. Coyote itself only dials that daemon.
A node joins exactly the interfaces listed under mesh.interfaces; nothing is auto-detected and there is no fallback
between types. The shipped default is a single private 127.0.0.1:4242 entry: loopback, the machine itself, which is
where the local rnsd listens. Every Coyote session on the machine dials it. One command sets the daemon up:
curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/coyote/refs/heads/main/scripts/mesh-relay.sh | bashpowershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/Dark-Alex-17/coyote/refs/heads/main/scripts/mesh-relay.ps1 | iex"or pass --with-mesh / -WithMesh to the installer, which runs the same script after installing Coyote.
Two more words come up in the choices below. A relay is a node that forwards traffic between others; Reticulum calls
it a transport node. A propagation node holds a message for a recipient who is offline until they fetch it, which is
what "store-and-forward" means; lxmd is the reference program for it.
Which setup? Coyote does not manage network interfaces itself. It talks to the mesh through a small daemon or relay that does, and the only question is where that daemon runs:
-
Default: rnsd on this machine. Run
scripts/mesh-relay.sh(Linux and macOS),scripts/mesh-relay.ps1(Windows) or the installer with--with-mesh/-WithMeshonce per machine. It installs and startsrnsd, the Reticulum daemon, and every Coyote session keeps the shipped defaultprivate 127.0.0.1:4242and dials it. This covers several sessions on one machine, the office LAN (rnsd finds the other machines' daemons by itself, through its AutoInterface) and VPN or internet links: a relay your team runs goes into rnsd's config, and--relay host:portwrites that[[Team Relay]]stanza for you. -
A store-and-forward node for a team. Run one
lxmd, the propagation node fromdeployment/propagation-node, with your teammates' identities in itsallowedfile. A message to someone who is offline waits there until they fetch it. The node is a relay as well, so it is the natural--relaytarget for every machine's rnsd, but sessions on one machine never need it to reach each other. -
Running without rnsd. Point the loopback entry at a team relay (
private host:port) instead, or uselan, which joins the local network directly from Coyote: one session per machine, LAN only, no LoRa or I2P.
Sessions exchange nothing until they are mutually trusted, that is until each side has trusted the other (Mesh Trust Model); there is no interface-level switch and none is needed. Other transports (RNode/LoRa, I2P, UDP, serial) are configured in rnsd and reached through the loopback hop.
Each rung reaches further than the one before it.
The default. private makes Coyote a TCP client to a Reticulum transport node
(TCP Server Interface); with the
shipped private 127.0.0.1:4242 that node is the host's own rnsd. Every session on the host dials it, the daemon's
AutoInterface (Reticulum's automatic discovery of other machines on the same local network) reaches the other hosts
on the LAN, and .mesh info reports reach: the configured relay and the peers it reaches. Setup is in
Setting up rnsd.
A relay your team runs, reachable over VPN or the internet, goes into rnsd's config as a TCPClientInterface:
scripts/mesh-relay.sh --relay host:port writes it as the [[Team Relay]] stanza. Every node joined to that relay
hears every other. The
propagation node container
is one such relay, since it is a transport node too; see Propagation nodes.
"Private" means network reachability, not access control. The relay authenticates nobody: anyone who can reach port
4242 joins it and sees every announce, the periodic "I am here" broadcast by which nodes learn of each other (it
carries display_name, if set). The firewall or VPN in front of the port is the boundary; Coyote's trust list decides
who gets a status card or a message, and nothing else.
public has the same wire shape as private, but the relay is a community transport node, so your announce is
visible to anyone who reaches it. .mesh on asks before joining:
This node will be WORLD-VISIBLE through public node.example.com:4242 (world-visible): anyone reaching that relay can see its announce and knock on it. Join anyway?
display_name is withheld from every announce while a public entry is configured unless
display_name_on_public: true.
Coyote ships no list of community nodes. Addresses rot silently, and publishing them would promise an availability
this project does not control, so public needs a host and port you choose. The upstream starting points are the
Reticulum manual's
Bootstrapping connectivity
and
Connect to the distributed backbone
sections, which name the community directories, and the Reticulum README's
community links. The historical public testnet entry point was decommissioned upstream, so check those pages rather
than older posts.
scripts/mesh-relay.sh (Linux and macOS) and scripts/mesh-relay.ps1 (Windows) do the same four things:
-
Find or install
rnsd, trying in this order: anrnsdthat already works in the bin dir or onPATHis compared to the pinned version: equal, reused as is; older, upgraded through the tool that installed it (uv tool install --force,pipx install --force, orpip installin the script's own venv; an rnsd of unknown origin is kept with a warning and the upgrade command), unless--keep-rnsd/-KeepRnsdkeeps it; newer, kept with one warning that it is newer than the version the script was tested with; elseuv tool install --python 3.12 "rns==1.5.2"(the version Coyote is tested with); elsepipx install "rns==1.5.2"; else a virtualenv under the Coyote config dir (<config dir>/mesh/rns-venv). Thernsdcommand in/usr/local/binwhen it is writable, else~/.local/bin, is a symlink to the real one (a two-line shim for the venv; Windows:rnsd.cmdin%LOCALAPPDATA%\coyote\bin).--versionsets the version to install or upgrade to. -
Write the Reticulum config only if it is absent, in the directory rnsd itself reads, in rnsd's order:
/etc/reticulumwhen it holds aconfig(then the script prints the stanzas and skips the service, since that daemon belongs to the system), else~/.config/reticulumwhen it holds aconfig, else~/.reticulum(Windows:%USERPROFILE%\.config\reticulumwhen it holds aconfig, else%USERPROFILE%\.reticulum). The plan prints which one and why (Config dir: rnsd reads ~/.config/reticulum because it already holds a config), and the service definition passes that directory withrnsd --config <dir>, so the daemon and the script never disagree about the file. An existing config is never overwritten; the script printsalready exists, not touched. Make sure it carries these settings and stanzas:followed by the[reticulum],[logging]and[interfaces]sections below (not the four header comment lines), and leaves the merge to you; when that config lacksenable_transport = Trueit addsWARNING: <config> does not set enable_transport = True; without it this rnsd will not relay between Coyote sessions., and when it enables an AutoInterface the firewall sentence below follows. The written file is (the Windows script putswritten by scripts/mesh-relay.ps1in the first line):# Reticulum configuration written by scripts/mesh-relay.sh for Coyote mesh. # This daemon owns the interfaces; every Coyote session on this host dials it # with mesh.interfaces: [{type: private, host: 127.0.0.1, port: 4242}]. # Interface options: https://markqvist.github.io/Reticulum/manual/interfaces.html [reticulum] # Relay announces and paths between the Coyote sessions that dial in. A transport # node also forwards traffic for any other Reticulum peer it hears on its interfaces. enable_transport = True # Coyote sessions announce hourly (3600 s plus a minute of slack, so the relay never counts # a heartbeat as early): a transport node rate-limits announces per destination, Reticulum's # defaults being announce_rate_target = 3600 s with an announce_rate_grace of 5 (RNS # Interfaces/Interface.py, DEFAULT_AR_TARGET and DEFAULT_AR_GRACE), and past that grace it # blocks a destination's rebroadcasts until an hour after its last accepted announce. Do not # lower announce_rate_target here, nor default_ar_target in the [reticulum] section: team # relays and community nodes keep the default. # Shared instance left at the default so rnstatus, rnpath, Sideband and NomadNet on # this host attach to this daemon instead of binding 4242 / AutoInterface again. # Any other local account on this host can attach to this daemon too, over # 127.0.0.1:4242 and the shared instance; that is inherent to Reticulum's shared-instance model. [logging] # rnsd applies its -v flags only when this section exists (Reticulum.py:459-467). # 4 = LOG_INFO, the value RNS's own default config ships. loglevel = 4 [interfaces] # Link-local discovery of other hosts on the same LAN. [[Coyote Local]] type = AutoInterface enabled = Yes # The loopback hop every Coyote session on this host dials. [[Coyote Sessions]] type = TCPServerInterface enabled = Yes listen_ip = 127.0.0.1 listen_port = 4242 # Reticulum holds announces from unknown peers by default, which would delay a # brand-new Coyote identity's first announce. Off here, as on the repo's test relay. ingress_control = NoWith
--relay HOST:PORTthis stanza is appended:# Your team's relay (a propagation node or another rnsd reachable over the network). [[Team Relay]] type = TCPClientInterface enabled = Yes target_host = HOST target_port = PORT -
Register a user service so the daemon outlives the shell: a systemd user unit
coyote-rnsd($XDG_CONFIG_HOME/systemd/user/coyote-rnsd.service, so~/.config/systemd/user/coyote-rnsd.serviceby default;Restart=on-failure, enabled withsystemctl --user enable --now coyote-rnsd; logs withjournalctl --user -u coyote-rnsd; the script hints'loginctl enable-linger <user>' keeps it running when you are logged out), a launchd agentcom.coyote.rnsd(~/Library/LaunchAgents/com.coyote.rnsd.plist, log~/Library/Logs/coyote-rnsd.log), or a Windows Scheduled TaskCoyote rnsdthat runsrnsdhidden at logon (log%LOCALAPPDATA%\coyote\logs\rnsd.log). A service that is already running is never restarted; when its definition changed, the restart command is printed instead (systemctl --user restart coyote-rnsd; on macOS thelaunchctl bootout gui/<uid>/com.coyote.rnsd && launchctl bootstrap gui/<uid> <plist>pair, with your numeric uid filled in, because launchd cannot reload a loaded agent in place; on WindowsStop-ScheduledTask -TaskName 'Coyote rnsd'; Start-ScheduledTask -TaskName 'Coyote rnsd'). -
Wait for the daemon:
rnsd is listening on 127.0.0.1:4242, or exit 3 after 30 s, after printing which config the service passes (The service starts rnsd with --config <dir>, so it read <dir>/config) and the last 10 lines of the service log.
On Linux without a user session bus (systemctl --user cannot connect: an ssh session with no lingering user
instance, a container, WSL without systemd) the unit is written, the enable command is printed
(systemctl --user daemon-reload && systemctl --user enable --now coyote-rnsd), nothing is started, the wait is
skipped and the exit is still 0.
The script refuses to run as root or Administrator. On Linux and macOS:
running as root is a usage error: rnsd, its config and its service belong to your user. Re-run without sudo, or pass --allow-root.
On Windows:
running as Administrator is a usage error: rnsd, its config and its task belong to your user. Re-run from a normal PowerShell, or pass -AllowRoot.
It ends with Diagnostics: 'rnstatus' attaches to the shared instance and lists its interfaces and
Next: start coyote and run `.mesh on`; rnstatus is the first thing to run when a session cannot reach the
daemon. It lists the daemon's interfaces, not Coyote sessions; the block to look at is the LAN one:
AutoInterface[Coyote Local]
Status : Up
Peers : 1 reachable
Peers counts the other machines' daemons on the same network, so it reads 1 reachable once a second machine's
rnsd is up. That machine's Coyote session itself shows up in .mesh peers, after its announce.
Before that, when it wrote the config (or would have, on a dry run) or found an existing one with an AutoInterface enabled, it prints the firewall sentence:
Firewall: AutoInterface listens for LAN peers, and with enable_transport this rnsd forwards traffic for any Reticulum peer on the LAN (and on to the Team Relay when one is configured).
The ports behind that sentence: AutoInterface discovers on UDP 29716 and carries traffic on UDP 42671, both over
IPv6 link-local multicast, the same two ports Coyote's own lan type uses. A Team Relay is an outbound TCP connection
from this machine and needs no inbound rule. macOS and Windows append a prompt warning; see
Mesh Platform Support.
mesh-relay.sh |
mesh-relay.ps1 |
Effect |
|---|---|---|
--relay <host:port> |
-Relay <host:port> |
Also dial your team's relay (TCPClientInterface) |
--version <X> |
-Version <X> |
rns version to install (default: 1.5.2) |
--bin-dir <dir> |
-BinDir <path> |
Where the rnsd command goes (default: /usr/local/bin or ~/.local/bin); Windows %LOCALAPPDATA%\coyote\bin
|
--no-service |
-NoService |
Skip the user service (Windows: skip the Scheduled Task); nothing starts the daemon and the 30 s wait is skipped; start rnsd yourself before .mesh on
|
--keep-rnsd |
-KeepRnsd |
Keep an older rnsd that is already installed instead of upgrading it to the pinned version |
--dry-run |
-DryRun |
Print the plan and write nothing |
--allow-root |
-AllowRoot |
Permit running as root (Windows: as Administrator) |
-h, --help |
-Help |
Show help |
Exit codes: 0 done or nothing to do; 1 usage; 2 unsupported OS or no way to install rnsd; 3 service installed
but 127.0.0.1:4242 did not come up in 30 s. The installer with --with-mesh / -WithMesh exits 3 when the mesh
setup failed; Coyote itself is still installed. On a TTY without the flag, when no rnsd is in its bin dir yet and it
is not running as root, the installer asks Set up the local Reticulum daemon for Coyote mesh now? [y/N]; the
PowerShell installer asks the same question as a Yes/No choice, without the [y/N] suffix, and not at all when CI is set.
Two people, two laptops on the same Wi-Fi, and the two sessions should see each other and exchange a first message. Call the laptops A and B; every step runs on both unless it names one.
-
Install Coyote and run the
rnsdone-liner from Setting up rnsd; accept the firewall prompt it triggers (the ports are on Mesh Platform Support). A guest Wi-Fi that isolates its clients keeps the two daemons from hearing each other; use the same ordinary network. -
In
config.yaml, keepfunction_calling_support: true(the top-level key under# ---- Function Calling ----) and set themesh:block to:mesh: enabled: true interfaces: - type: private host: 127.0.0.1 port: 4242
The
interfaces:entry is the shipped default, spelled out so it is visible; every othermeshkey keeps its default. Startcoyote --session aon A (--session bon B). Withenabled: truethe REPL joins by itself, printingmesh.enabled is true in config.yaml: joining the mesh for this session (`.mesh off` leaves it).and then:Mesh is on for this session. identity: <32 hex> destination: <32 hex> instance: <32 hex> interfaces: private 127.0.0.1:4242 Turn it off with `.mesh off`.Started without
--session, it printsMesh needs a session: ...instead;.session athen joins by itself with the same notice and block..mesh offkeeps the mesh off for the rest of the run (.mesh onjoins again), and.exit sessionleaves the mesh, which the next.sessionjoins again. AWARNING:line after the block means an interface did not come up; see Joining with some interfaces down. -
Seeing each other:
.mesh peers. The laptop that joined second is listed on the first at once; the first is listed on the second only after its next announce, up to an hour later, and nothing hurries it (see Relay gotchas). Brand-new peers appear within an hour; known peers are reachable at once. Until then the answer isNo peers heard yet. Nodes appear here as their announces arrive.; after, one row per node under thename dest identity trust hops last seenheader,(no name) <dest8> <id8> untrusted 1 12s ago(<dest8>is the first 8 characters of the destination;display_name, when set, replaces(no name)). Wait until both tables show the other. -
Trusting: the person at A reads A's
destination:row from the block above (thedestinationrow of.mesh infois the same value) to the person at B, over a call or the chat you already use, and B compares it character for character. Once A is listed in B's.mesh peers, the 8 characters that table shows are enough to read aloud and to type:.mesh trust <A-dest8>; the full hash is the out-of-band check and always works. On B:.mesh trust <A-destination>. It previewsThis trusts instance <dest8>: a peer you trust can message this node and ask it questions., asksTrust <dest8>?and printsTrusted <dest8> (identity <id8>) - added.Then the mirror on A with B's destination. Once each side has trusted the other,.mesh peerson both shows the other with trusttrusted. -
First exchange, on A:
.mesh reply <B-destination> "hello from A". It previewsThis sends your text to ..., asksSend it?and printsSent <id8> to <dest8> (via direct).On B, between prompts:[mesh:message] <A's name or dest8> said: hello from A; envoy replied: ...once B's envoy has answered (a model call on B), and.mesh inboxon B lists the message as[message] from ... via direct at HH:MM UTC: hello from Aand the answer under it. The answer reaches A as a[mesh:message] ... says: ...line, as step 6 of Two sessions on one host describes. Every other verb is on Mesh Commands. -
When it does not work:
- No peers after an hour:
rnstatuson both; theAutoInterface[Coyote Local]block quoted in Setting up rnsd must read1 reachable, else the daemons do not hear each other: the network or the firewall ports above. -
.mesh onfails withNothing is listening on 127.0.0.1:4242: rnsd is not running; the service commands in Setting up rnsd start it. - Peers listed but
.mesh trustrefused: the identity is denied or blocked; see Mesh Trust Model.
- No peers after an hour:
.mesh on joins every configured interface on its own. With at least one up, the node is on: the summary is printed
with the connected interfaces on its interfaces: row, then one WARNING: line per interface that did not come up,
on stderr, in one of three shapes:
WARNING: <label> is unreachable (<reason>); sessions on this machine cannot reach each other until a local Reticulum daemon (rnsd) is listening there.
WARNING: lan is unreachable (<reason>); peers on this link-local segment are out of reach.
WARNING: <label> is unreachable (<reason>); peers behind that relay are out of reach.
The first is a loopback relay, the second lan, the third any other relay; <label> is private 127.0.0.1:4242,
lan or <type> <host>:<port>; <reason> is the TCP client's last error as the transport words it (it names the
endpoint), or connection timed out when the connect never finished. Autostart with mesh.enabled: true prints the
same lines.
With none up, .mesh on fails with the first interface's failure text:
Mesh relay <endpoint> (type: <kind>) is unreachable: <reason>. The node cannot join the mesh until that relay is reachable; fix mesh.interfaces or the relay.
and, when every configured interface is a loopback relay, this hint is appended to it:
Nothing is listening on <endpoint>. The default configuration expects a local Reticulum daemon (rnsd) there. Run scripts/mesh-relay.sh, or use type: lan to join the LAN without one; see Mesh-Deployment.
A lan entry that cannot bind fails with:
Failed to bind the mesh lan interface: <err>. Another process may hold the discovery port; remove the lan entry from mesh.interfaces or stop that process. Two Coyote sessions on one host need a local relay; see Mesh-Deployment.
A relay that failed keeps retrying every 5 s for as long as the node is on. When it connects, the idle line
[mesh] <label> connected is printed and the destination is announced over it once. mesh.started fired with the
interfaces that were connected at the time, so nothing refires for it (COYOTE_MESH_INTERFACES on that event names
those). The lan bind failure is not retried.
.mesh info while the node is on lists one row per configured interface in the node block, in place of the config
block's interfaces[i] rows: interfaces[i] <label> connected, unreachable, retrying: <reason> (a TCP relay)
or unreachable: <reason> (a lan bind failure).
Without a daemon, point the loopback entry at a team relay instead (private host:port; everything under
The ladder about reachability applies), or join the LAN directly with lan.
lan is the Reticulum AutoInterface: IPv6 link-local multicast. It reaches the link-local segment your machine is on
and nothing else; .mesh info reports reach: this link-local segment only. It does not cross sbx sandboxes,
WireGuard, Tailscale or OpenVPN, and there is no LoRa or I2P on it.
Only one process per host can bind it. A second Coyote on the same host fails with an address-in-use error on the
AutoInterface data port :42671 (discovery is on 29716), and a config listing lan twice is refused at load with
mesh.interfaces lists lan more than once; only one lan interface can be bound per process. With a daemon, a second
session on the same host dials the local rnsd instead.
Two REPLs on one host both dial the local rnsd. With the same config dir, .session a in one and .session b in
the other share the identity and the cache (peers.json is last-writer-wins between them, which is harmless) and get
their own instance ids and destinations; nothing needs splitting to run. mesh/trust.yaml is shared live: a trust,
untrust or block made in one REPL is seen by the other before its next decision, and two REPLs never overwrite each
other's records; the write is serialised through mesh/trust.yaml.lock. To exercise trust from both sides, two
identities are needed, and then both directories are split, so that each identity keeps its own trust, knock cache
and propagation store:
mkdir -p ~/mesh-a ~/mesh-a-cache ~/mesh-b ~/mesh-b-cache
COYOTE_CONFIG_DIR=$HOME/mesh-a COYOTE_CACHE_DIR=$HOME/mesh-a-cache coyote
COYOTE_CONFIG_DIR=$HOME/mesh-b COYOTE_CACHE_DIR=$HOME/mesh-b-cache coyoteEach config dir holds your API keys and, once the node has joined, its identity key; each config.yaml carries your
real clients: block and keeps the default mesh.interfaces. The .mesh commands need no model at all; only the
digest and the envoy make model calls, so any clients: block that loads is enough.
In each: .session <name>, then .mesh on. Then:
-
.mesh peersin B lists A once A's announce lands (and vice versa). The side that joined second is listed at once; the side that joined first is listed only after its next heartbeat announce, up to an hour later (see Relay gotchas below). Wait until both tables show the other before going on. -
.mesh status <A-destination>in B is refused locally until B trusts A:... Status is only requested from trusted destinations. -
.mesh trust <A-destination>in B. Now B's request reaches A, and A drops it without a word: B's identity is not on A's list at any tier, so B is a stranger, and a stranger's request is neither answered nor recorded. B waits out the 30 s request timeout and printsNo response to the mesh request /status within 30.0s. A status request is answered live or not at all; nothing queues it for later.A prints nothing and its.mesh knocksstays empty. The silence is deliberate: a stranger gets no answer at all, while ano access (0xf1)refusal (the number is the protocol's code for that refusal) is what a peer gets when its identity is already on the list but the requesting instance is not (see Knocks). -
.mesh trust <B-destination>in A. -
.mesh status <A-destination>in B renders A's card. -
.mesh reply <A-destination> "text"in B previewsThis sends your text to ...and asksSend it?(or pass--yes); afterSent <id8> to <dest8> (via direct).the answer comes back: A's envoy answers (a model call on A) with a reply that carriesin_reply_to. B opened no question for it, since onlymesh__askdoes, so B delivers the answer as a message: the idle line is[mesh:message] <A's name> says: ...and.mesh inboxon B shows[message] from <A's name> (<dest8>) via direct at HH:MM UTC: ... (reply to <id8>).[mesh:reply]and[reply]are only ever printed for answers tomesh__askquestions.
- The relay does not replay cached announces to a late joiner, so a node lists a peer in
.mesh peersonly once it hears that peer announce. Coyote announces at join and then hourly (plus a minute of slack, so the relay never counts a heartbeat as early): the node that joined last lists the earlier one only after the earlier one's next announce (at most an hour;last seenrefreshes when it lands), while the earlier joiner lists the late one at once..mesh trust <destination>needs the row, so on first contact turn both nodes on, do something else, and check.mesh peerson each side now and then. - When the relay restarts (an rnsd upgrade, a host reboot) it forgets every path it held through the closed
sockets. Each Coyote session's client reconnects on its own, and the node announces again as soon as the client
is back, printing
[mesh] <label> reconnected; announced(for example[mesh] private 127.0.0.1:4242 reconnected; announced), so peers reach each other again within seconds and no.mesh off/.mesh onis needed. When the node announced less than five minutes earlier it prints[mesh] <label> reconnected; the heartbeat will announce it (last announce <age> ago)instead, and the next heartbeat carries the announce. - A peer already in the table is another matter. The transport is built afresh on every
.mesh on, so.mesh offfollowed by.mesh onforgets every route, but not the peer table: a request to a trusted peer the transport has no route to asks the network for a path and waits up to 5 s for the answer, which the relay gives from its own path table (it keeps a path for days). So a request to one peer (.mesh status,.mesh reply, amesh__ask) from a restarted node reaches a peer it already knew at once, without waiting for a heartbeat. A bulletin (.mesh broadcast) still goes only to peers the transport already has a route to, so right after a restart it reaches none of the persisted peers until their next heartbeat. The answer to a path request is not a sighting:last seendoes not move, andmesh.peer.discovereddoes not fire. Only when nothing on the network answers for the peer does the request fail, and then within those 5 s:`Destination <hash> could not be reached: this node asked the network for a route to it and got no answer within 5 s. The peer may be offline or on a network this node cannot reach. Try again in a minute; nothing was queued for it, because a message can be held on a propagation node only after the peer's announce has been heard. `.mesh peers` shows when it was last heard.`Listings never ask:.mesh peersreads the table, andmesh__peerswithwith_status: truereports such a peer unreachable at once rather than waiting 5 s for each. -
ingress_control = Noon the relay's TCP server interface keeps Reticulum from holding back a burst of announces from new identities. It is a per-interface setting; under[reticulum]it is never read. -
lannodes need IPv6 duplicate-address detection to finish (about 2 s after link-up) before they are heard.
LXMF, the messaging layer on top of Reticulum, has
propagation nodes: store-and-forward. A message that cannot be
delivered over a direct link (a direct encrypted connection between two nodes) is posted to one, which holds it until
the recipient fetches it. A .mesh knock whose peer is unreachable takes the same path. Coyote is never a propagation
node itself; the
deployment/propagation-node
directory builds one from the reference daemon, lxmd, for a private team network.
The container is python:3.12-slim with rns==1.5.2 and lxmf==0.9.6, an entrypoint that seeds the volume on first
start and then runs lxmd, and everything on the /data volume: the config, the allowed file, the identity and the
message storage. A fresh volume mints a fresh node identity, so copy the whole /data to move the node, and never run
two copies of it. It publishes TCP 4242 and runs with enable_transport = Yes: it is a team relay and
store-and-forward node, which each host's rnsd dials with --relay, and never the way sessions on one host reach each
other. The shipped config sets [propagation] enable_node = yes, auth_required = yes and autopeer = no, so the
node never replicates its store to another propagation node. auth_required reads /data/lxmd/allowed: one
Reticulum identity hash per line, 32 hex characters and nothing else on the line, LF or CRLF, read once at start. The
hash to list is a Coyote node's identity: line from .mesh on or .mesh info, not its destination. An empty list
fails closed: every fetch is answered no-access. auth_required gates fetching only; posting is stamp-gated and open
to anyone who reaches 4242, and so is relaying, so the same rule as for the relay applies: the firewall or VPN in
front of the port is the boundary, not Coyote's trust list.
Today Coyote discovers propagation nodes from their announces (.mesh info lists them under propagation_nodes,
nearest by hops first) and posts to one when a peer's direct link fails (the send reports via: "store_and_forward"
in the tool result, (via store-and-forward) on the REPL line and COYOTE_MESH_VIA=store-and-forward to hooks). Held
messages are fetched back automatically: once a propagation node is heard after the node joins and then every
mesh.propagation_sync_interval_secs seconds (default 300; 0 and announce: false both leave only .mesh sync),
and .mesh sync runs one now (see Mesh Commands). The node table is not kept across
restarts, so after a restart the first fetch waits for the node's next announce (up to announce_interval, 30 minutes
with the shipped lxmd.config). You cannot pick a node by hand; the nearest by hops is used.
The identity is the mesh/ directory under the config dir. Same config dir, same identity: copy that directory to
move an identity to another machine, and know that two machines sharing one config dir share one identity. The
instance id lives in the session file, so a session file copied along with the identity announces the same
destination as the original; join with .mesh on --fresh on one side when that is not what you want.
coyote --sandbox without --fresh copies the host config dir, mesh/ included, into the sandbox, so a node started
inside it runs under the host's identity and trust list; see Sandboxes.
Both nodes list each other in .mesh peers as trusted, .mesh status <destination> renders the other side's card
within a few seconds, and a .mesh reply produces a mesh.message.received hook event on the receiver and, once the
receiver's envoy has answered, a [mesh:message] <name> says: ... line on the sender (the answer is a reply on the
wire, but the sender asked no question, so it lands as a message). Containers are on Mesh Containers;
Windows machines on Mesh Platform Support.