The exact symptoms, what they mean, and the command that fixes them. Every entry starts from the message you have in front of you.
For everything else: Docs/Setup/guide (installation),
Docs/WebConsole/guide (console),
Docs/Apps/fleet (remote management),
Docs/Architecture/gotchas.md (internal traps).
When: you click Update on a managed node (the update capability).
What is happening. The systemd unit install.sh writes is hardened, and one
of its directives is NoNewPrivileges=yes. The kernel then refuses every
setuid binary for that process and all its children, permanently — and sudo
is one of them. The right to update does exist (the root wrapper, the sudoers
rule); it is simply unusable from that process. Two hardening decisions in the
same project were contradicting each other.
The fix. On the machine concerned:
cd /opt/nmesh # or your installation prefix
sudo ./install.sh --allow-updateThe unit is rewritten with confinement compatible with the right that was granted, and the service restarts. Check it:
systemctl cat nmesh | grep -E 'NoNewPrivileges|ProtectSystem'
# NoNewPrivileges=no
# ProtectSystem=noSince that fix, confinement follows the grant, and the grant is now the
default: a plain ./install.sh gives the node its one root command and writes
the unit that lets it use it. ./install.sh --no-allow-update removes the
sudoers rule and re-hardens the unit — the two choices can no longer be made
separately, whichever way round you want them.
So the entry above is now a symptom of an older install, or of one someone
deliberately locked down: re-running ./install.sh at all is the fix.
Why three directives and not one. NoNewPrivileges=yes blocks sudo;
ProtectSystem=full mounts /usr read-only, so a package manager could write
nothing even if sudo worked; PrivateDevices=yes hides devices some
post-install scripts need. PrivateTmp=yes stays — it gets in nobody's way.
Without restarting right now? There is no workaround: the flag is set when
the process starts and the kernel never removes it. An update run by hand on the
machine (sudo apt update && sudo apt dist-upgrade) still works — that is your
shell, not the node's process.
In a container. The same message appears with
--security-opt no-new-privileges (sometimes set by default). Remove it, or
update the image rather than the container: a node running from an image updates
by pulling a newer image, and the console says so.
The node answered that it cannot update itself. The exact reason is in the refusal shown when you click Update. The three cases:
| Message | Cause | Fix |
|---|---|---|
no package manager this node knows how to drive |
unrecognised distribution (or a minimal image with no apt/dnf/apk…) |
update the machine some other way; NMesh does not guess a package manager |
no sudo or doas on this machine, and the node is not root |
the node runs under an unprivileged account and nothing allows escalation | sudo ./install.sh on the machine (the grant is the default; --allow-update names it explicitly) |
NoNewPrivileges |
see the entry above | sudo ./install.sh |
After an update of NMesh (Settings → Updates) the node replaces its own
files and restarts. If it is managed by a service (systemd, OpenRC, launchd)
it comes back on its own and the console says so. Otherwise the old version is
kept: the post-update message gives the backup path.
The state directory belongs to root while the node runs under another account —
typically an installation done with sudo before the node had its own service
account. Run ./install.sh again: it repairs ownership as it goes.
A systemd service starts with no HOME. Fixed: start.sh now derives the home
directory from the account's passwd entry. If you still see this message your
tree predates the fix — update, or add Environment=HOME=<prefix> to the unit.
It should not any more: the result is cached per wrapper version
(/var/cache/nmesh/liboqs-<v> as root). If the build starts again, either the
cache is not writable or the wrapper version changed. The message from
start.sh says which.
On the machine:
cd /opt/nmesh && sudo ./install.sh --reset-passwordA new password is generated and printed once. This is deliberately the only route: it requires access to the state directory, which is exactly the level of privilege such a power deserves.
You do not need it. A machine deployed from Fleet → Discover & deploy
generates its own password on first start and prints it to a log nobody reads,
so the deploy form grants manage and passwordless by default: pick that
node in the console's context selector and it lets you straight in, on the
strength of the grant. Set a password of your own from Settings → Password
once you are there, and untick passwordless on that node if you would rather
be asked for it. Deployed before this existed? Reset it on the machine as
above, or grant passwordless from that node's own Fleet page.
A ticket carries only an address and a code: the scanner has nothing else to go
on. Issuing one therefore requires a confirmed world address — an inbound
connection has actually arrived on it — not an address believed to be public. On
a node behind NAT with no port forwarding, use the full join (block exchange) or
a relay.
Open the peer's details (Network → Peers → Details): the Addresses table
says what each address did — in-use, timeout, refused, untried — with
the reason and the duration. That is nearly always enough to tell "firewall"
from "wrong address" from "never tried".
Look at jitter and loss in the same view, and the transport's own counters below them: on UDP, retransmits climbing while the RTT looks fine is the signature of a path losing packets. The expanded map (click the map on Overview) turns those links amber.
internet comes from a bounded outbound probe. On a network that filters
probes it fails with the mesh entirely unaffected. Re-check network runs it
again; actual reachability is the business of the Reachability line just
above it.
It appears only if the fleet app is running and at least one node has
granted you the manage capability. Otherwise there is nothing to pick. See
Docs/Apps/fleet.
The remote session expired (an hour of inactivity), the remote node restarted,
its console password changed, or it took the passwordless right back. Select
it again: it will ask for the password, or let you in on the grant alone if that
right is still there. That is deliberate — the grant opens the channel, and the
password (or passwordless) opens the session.
Fixed. The console's CSP (default-src 'self', without unsafe-inline) makes
the browser ignore silently any style= attribute, so bars written that way
never filled. They are <progress> elements now. If you still see it, your tree
predates the fix.
It is self-signed, which is expected. Its SHA-256 fingerprint is printed when
the node starts: compare it, then accept it. Over loopback, --no-tls is a
reasonable option.
Remote deployment installs nothing itself: it delivers the tree and calls its
install.sh. On failure the console keeps the last lines of output from the
target machine — the real cause is nearly always in there (a missing package,
a full disk, no compiler for liboqs).
Fixed: deployment allocates a pty and answers the prompts without ever writing the secret to disk or onto a command line. If you still see it, update the operating node — that is the one driving SSH.
It joins after installing its dependencies, which can take several minutes on a small machine (building liboqs). Its invitation lives far longer than a hand-typed code, precisely for that. The Activity tab shows where the deployment has got to.
Fixed (a FIND_NODE/FOUND_NODE loop): ~3 Mbit/s at rest became ~2 kbit/s. To
check on your own machine: Settings → Diagnostics → Protocol trace, which
gives the volume per message type without ever recording any content.
| What | Where (system installation) |
|---|---|
| the tree | /opt/nmesh |
| the state (identity, sessions, certificates) | /var/lib/nmesh, mode 700 |
| the configuration | <prefix>/nmesh.conf |
| the update wrapper | /usr/local/lib/nmesh/nmesh-update (root) |
| the sudoers rule | /etc/sudoers.d/nmesh |