Mesh Platform Support - Dark-Alex-17/coyote GitHub Wiki
The Coyote Mesh runs with full parity on Linux, macOS and Windows. The product code is not platform-gated, with one exception: how the identity file is protected, which is the part of the filesystem that differs between unix permission bits and Windows ACLs.
| Platform | How the key is protected | What is refused |
|---|---|---|
| unix |
0600 file inside a 0700 mesh/ directory |
A key readable by others: `Mesh identity file '<path>' is readable by other users (mode <mode>). A private key other users can read must not be used. Run `chmod 600 <path>` and try again.`
|
| Windows | Created owner-only with a protected, single-ACE DACL | A widened DACL or a wrong owner; the refusal carries the curative remedy below |
On Windows the refusal reads:
Mesh identity file '<path>' <problem>. A private key whose permissions cannot be vouched for must not be used. Run <remedy> and try again, or delete the file to mint a new identity; any trust other peers hold for the old identity is lost.
where <problem> is one of is owned by another user, has no DACL, so every user can read it,
inherits permissions from its directory,
carries an access-control entry that is not a plain allow for the current user or grants access to nobody, and
<remedy> is up to three commands, printed separately because PowerShell 5.1 has no &&:
-
takeown /F "<path>", only when another user owns the file. It has to run from an elevated (Administrator) prompt; the refusal appends a note saying so when this step is included. icacls "<path>" /reseticacls "<path>" /inheritance:r /grant:r *<your SID>:F
Caveats the code cannot close:
- A local Administrator can always take ownership. This is parity with
0600against root, not absolute protection. - On FAT32 or exFAT the DACL is a silent no-op. Coyote warns and proceeds:
Mesh identity file '<path>' is on a <FAT32|exFAT> volume, which stores no file permissions: every user of this machine can read the key. Keep the config dir on an NTFS or ReFS volume to keep the key private. - The instance lock is mandatory on Windows rather than advisory, so a refusal there can omit the holder's pid: the locked file cannot be read through another handle.
Default directories on Windows: the config dir is %APPDATA%\coyote, so the key is
%APPDATA%\coyote\mesh\identity.key; the cache dir is %LOCALAPPDATA%\coyote.
The default mesh.interfaces dials a local Reticulum daemon on 127.0.0.1:4242; Mesh Deployment
has what the setup script writes. Per platform:
- Linux:
scripts/mesh-relay.shregisters the systemd user unitcoyote-rnsd;loginctl enable-linger <user>keeps it running when you are logged out. - macOS: the script registers the LaunchAgent
com.coyote.rnsd(~/Library/LaunchAgents/com.coyote.rnsd.plist, log~/Library/Logs/coyote-rnsd.log). A changed plist is rewritten, but launchd cannot reload it in place, so thelaunchctl bootout gui/<uid>/com.coyote.rnsd && launchctl bootstrap gui/<uid> <plist>pair is printed, not run. - Windows:
scripts/mesh-relay.ps1(or the installer with-WithMesh) registers the Scheduled TaskCoyote rnsd, which runsrnsdhidden at logon; log%LOCALAPPDATA%\coyote\logs\rnsd.log, config%USERPROFILE%\.reticulum\config.
The config enables AutoInterface, so macOS and Windows ask whether to allow incoming connections. Both scripts print:
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).
On macOS this is followed by
macOS will ask whether python/rnsd may accept incoming connections; allow it or discovery of LAN hosts will not work.
and on Windows by
Windows will ask whether python/rnsd may accept incoming connections; allow it or discovery of LAN hosts will not work.
Windows mesh behaviour is covered by the unit tests, the platform-independent vectors and manual verification, not by
the Python interop matrix. The windows-latest CI leg runs the non-runtime unit tests (codecs, stores, trust, identity,
the DACL tests included), the conformance vectors and the fuzz harness; the tests that start a node or drive a live
link are unix-only, since they use unix-only loopback fixtures. The Python interop harness is unix-only and the
network namespace suite Linux-only, and the Mesh Interop CI job that runs them runs on Linux only; the manual
procedure below is what stands in for them. See How it is verified.
Note: This procedure has not been executed in the run that produced this page. The result is to be recorded in the pull request that ships it.
-
Two Windows machines, W1 and W2, on one network.
-
rnsdon both, set up byscripts/mesh-relay.ps1; Coyote keeps the shippedprivate 127.0.0.1:4242and the two daemons find each other over the LAN through AutoInterface (allow the firewall prompt). The alternative is a relay both reach: the propagation node container on any host, or anrnsdon another host with thescripts/mesh-relay.shconfig andlisten_ip = 0.0.0.0on its TCP server interface (Setting up rnsd quotes the[[Coyote Sessions]]stanza to edit; the relay authenticates nobody: keep 4242 firewalled to W1 and W2). Each machine's rnsd then dials it with-Relay <relay>:4242, or Coyote dials it directly withhost: <relay>in place of its own daemon (Running without rnsd). -
Coyote on both, each with a
config.yamlcarryingfunction_calling_support: trueand:mesh: enabled: true # the REPL joins at start when a session is open; started without --session it prints the no-session refusal and step 2 joins announce: true display_name: w1 # w2 on the other machine interfaces: - type: private host: 127.0.0.1 # <relay> for the alternative port: 4242
The display name is what the knock line in step 10 quotes and what
.mesh peersshows in place of a hash. -
Optionally, a global hook on every
mesh.*event that appends%COYOTE_EVENT%and theCOYOTE_MESH_*variables to a file, to check the hook column below. See Hooks.
- Start rnsd on both (the script does;
rnstatuslists its interfaces). Expect:rnsd is listening on 127.0.0.1:4242from the script, andrnstatusshowsCoyote LocalandCoyote Sessions. - W1:
coyote,.session w1,.mesh on. Expect: the preview namesprivate 127.0.0.1:4242: the nodes joined to that relay; after confirming,Mesh is on for this session.withidentity:,destination:,instance:andinterfaces:rows.%APPDATA%\coyote\mesh\identity.keyexists andicaclson it shows a single entry for your user with(F)and nothing inherited. No FAT32/exFAT warning on NTFS. Hook log:mesh.startedwithCOYOTE_MESH_INTERFACES=private. - W2: the same with
.session w2. Expect: within a few seconds.mesh peerson W1 listsw2with trustuntrusted. W2 does not list W1 yet: the relay does not replay W1's announce, and Coyote re-announces every 15 minutes, so W2 listsw1only when that heartbeat lands (up to 15 minutes). Wait until both.mesh peersshow the other before step 4. Hook on each:mesh.peer.discoveredwithCOYOTE_MESH_FIRST_SEEN=true. - W2:
.mesh status <W1-destination>. Expect: refused locally with... Status is only requested from trusted destinations. - W2:
.mesh trust <W1-destination>. Expect:Trust <short>?, thenTrusted <dest-short> (identity <id-short>) - added.Hook:mesh.trust.grantedwithCOYOTE_MESH_TRUST_TIER=destination. - W2:
.mesh status <W1-destination>again. Expect:Asking <short> for its status card; Ctrl-C cancels..., then after the 30 s request timeoutNo response to the mesh request /status within 30.0s. A status request is answered live or not at all; nothing queues it for later.W1 drops the request silently because W2's identity is not on its list at any tier: nothing is printed there, no hook fires, and.mesh knocksshowsNo knocks. An untrusted node that wants in appears here. - W1:
.mesh trust <W2-destination>. Expect:Trust <short>?, thenTrusted <dest-short> (identity <id-short>) - added.Hook:mesh.trust.grantedwithCOYOTE_MESH_TRUST_TIER=destination. - W2:
.mesh status <W1-destination>. Expect: the card (name:,objective:,state:, ...,served:). - W2:
.mesh reply <W1-destination> "hello from w2". Expect:This sends your text to w1 (<dest8>, trust: trusted) over the mesh., thenSend it?(or pass--yes), thenSent <id8> to <dest8> (via direct).W1's envoy answers (a model call on W1) with a reply carryingin_reply_to; W2 asked no question (onlymesh__askopens one), so it delivers the answer as a message: W2 prints[mesh:message] w1 says: ...and.mesh inboxon W2 shows[message] from w1 (<dest8>) via direct at HH:MM UTC: ... (reply to <id8>).[mesh:reply]and[reply]appear only for answers tomesh__askquestions. Hooks:mesh.message.senton W2 withCOYOTE_MESH_VIA=direct;mesh.message.receivedon W1 withCOYOTE_MESH_ROUTED=envoy. - Knock check. W2:
.mesh off,.mesh on --fresh(a new destination under the same identity), then.mesh status <W1-destination>. Expect: first`Destination <hash> cannot be reached yet: its announce has not been heard since this node started. Wait for it to announce, or check `.mesh peers`.`because the rejoined W2 has a fresh transport and has not heard W1's announce yet (.mesh peersstill listsw1from the saved peer table, but that is not a route). Wait for W1's next heartbeat (last seenforw1refreshes, up to 15 minutes), then repeat the request. Expect W2 to seeThe mesh peer refused the request: no access (0xf1). A status request is answered live or not at all; nothing queues it for later.and W1 to print"w2" (identity <hash>) knocks from instance <hash>with thetrust: | who: | silence:line, and.mesh knockslists it: W2's identity is on W1's list through the step 7 grant, but the new instance is not, which is the one case that is refused out loud and registers a knock. Hook:mesh.knock.receivedwithCOYOTE_MESH_VIA=direct. W1:.mesh trust <new W2 destination>clears it. - Rotation check. W2:
.mesh off,.mesh rotate(the dry run prints its block),.mesh rotate --confirm rotate-<short>,.mesh on. Expect on W1, once W2's announce lands (W1 hears it at once; W2 again needs W1's next heartbeat before it can contact W1):.mesh peersshows W2's trusted instance with thekey changed: announced under identity ...marker and thekey changesrow of.mesh inforeads1 instance(s) announced under another identity; see .mesh peers. W1:.mesh trust <W2's post-rotation destination>clears the mark (... key-change mark is cleared.); read it from.mesh peerson W1 or thedestination:row of.mesh onon W2. - Both:
.mesh off. Expect:`Mesh is off for this session. Run `.mesh on` to join again.`Hook:mesh.stopped.
Record per step: pass or fail, plus the observed lines.