Conversation
Nothing in the system knew where a node lived. Nodes are configured with the server's address and push to it, so the reverse mapping existed only inside the datagrams themselves and was thrown away on arrival. Finding node 3 to manage it meant scanning all 254 addresses for an open :8032 and asking each responder its node_id -- and at a 1 s connect timeout that missed two of nine, because a congested node answers slowly. The UDP receiver already has the source address; it is now recorded on NodeState at all three ingestion sites (CSI frames, vitals, sync packets) and returned as `ip` on /api/v1/nodes. Observed, never configured, so it follows a node across DHCP leases with no intervention -- which matters, since node 3 changed address twice today. This is the prerequisite for managing nodes from the web UI: the server cannot proxy a config or firmware request to a node whose address it does not know. Co-Authored-By: claude-flow <ruv@ruv.net> (cherry picked from commit bb77643)
Reconfiguring a node meant a USB cable and a serial console, or hand-written curl against an address nobody recorded. This adds a Nodes tab that lists the fleet and lets an operator read and change node configuration and see firmware state, using the address each node reports on its own packets. The server proxies rather than exposing the key. The firmware gates every mutating endpoint (POST /config, /ota, /calibrate) behind a pre-shared key checked in constant time. A browser must never hold that key -- a PSK delivered to a web page is a PSK published to anything that can read the page or its traffic -- so the server reads it from --ota-psk-file, keeps it in memory, and never logs it or returns it from any endpoint. FAILS CLOSED. Without --ota-psk-file, node management is disabled and only the read-only fleet views are served. The key is never written to disk by the server and never leaves it. SECURITY NOTE FOR REVIEWERS. Once an operator does supply the key, the server's web UI becomes the real trust boundary: anyone who can reach it can reconfigure or reflash the fleet, because the server holds the credential on their behalf. The firmware's authentication is intact but is no longer the control that matters. This is a deliberate trade for a single-operator LAN deployment and it is off by default, but a multi-user deployment wants an authentication layer in front of the server before enabling it. Co-Authored-By: claude-flow <ruv@ruv.net>
The node table forced a Manage click for the two fields you most want when scanning. Die temperature and firmware version are exactly what you look for to find the odd node out of nine, and needing a click each to compare them defeats the point of having a table. Both are now columns. They are fetched differently on purpose: - Temperature comes from `/api/v1/mesh`, which the server already collects for the whole fleet: one request, no per-node fan-out, no credentials in the browser. Coloured at the firmware's own warn threshold so the cell means what the node means. A node with no reading shows a dash, not 0 -- "0 C" and "sensor never installed" are different states, and conflating them would hide the failure actually worth seeing. - Firmware has no fleet-wide endpoint, so it is one request per node through the existing `/api/v1/nodes/:id/firmware` proxy. Deliberately NOT awaited: the table paints from data it already has and versions fill in as they land, so one slow or unreachable node cannot delay the rows that are fine. Cached across refreshes so a node that answered once keeps its version on screen while briefly unreachable. Scoped to this file. Our tree also carries a Tools menu listing several diagnostic pages, but those pages are not in this repository yet, so bringing the menu across would produce dead entries. Each page's menu line belongs in the PR that adds that page. Co-Authored-By: claude-flow <ruv@ruv.net>
clonea1
force-pushed
the
contrib/server-node-management
branch
from
September 4, 2026 21:14
13d2164 to
67d2b61
Compare
…r gateway MEASURED 2026-09-09 on the live fleet: five of nine nodes had their recorded address replaced by 172.18.0.1 -- the sink container's own bridge gateway -- at some point during a 13 h uptime. Every management proxy for those nodes then failed: config pushes, firmware status, and the on-node log all resolved to Terry's gateway rather than the board, and answered "node N at 172.18.0.1 did not answer" with nothing to say why. The addresses were CORRECT at startup and rotted while running, and a restart restored all nine. That is the tell that this is learned state going bad, not routing: every node is provisioned to 192.168.1.29 and every node answers directly on :8032, so nothing about the network was wrong. last_src_ip is written from three sites that all read the same UDP socket, so the server was faithfully recording a source that genuinely arrives as the gateway. A CSI node reaches the sink through a published port from the LAN and can never legitimately appear to originate at the container's own gateway, so that source is Docker's, not a node's. All three sites now go through record_node_src_ip, which refuses loopback and unspecified outright, and refuses to REPLACE a known address with one from Docker's 172.16.0.0/12 pool. It only declines to overwrite, never to learn, so a fleet genuinely deployed on that range still resolves on first contact, and a node that moves on the LAN is still followed. Tests cover the observed failure and both directions of the guard, and were negative-controlled: disabling it fails exactly the two tests that describe the bug while the two "must still work" cases keep passing. 483 pass. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01PVWMiHQifoYXL7uL3bphrZ
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reconfiguring a node meant a USB cable and a serial console, or hand-written
curl against an address nobody recorded. This adds a Nodes tab that lists the
fleet and lets an operator read and change node configuration and see firmware
state, using the address each node reports on its own packets.
The server proxies rather than exposing the key. The firmware gates every
mutating endpoint (POST /config, /ota, /calibrate) behind a pre-shared key
checked in constant time. A browser must never hold that key -- a PSK
delivered to a web page is a PSK published to anything that can read the page
or its traffic -- so the server reads it from --ota-psk-file, keeps it in
memory, and never logs it or returns it from any endpoint.
FAILS CLOSED. Without --ota-psk-file, node management is disabled and only the
read-only fleet views are served. The key is never written to disk by the
server and never leaves it.
SECURITY NOTE FOR REVIEWERS. Once an operator does supply the key, the server's
web UI becomes the real trust boundary: anyone who can reach it can reconfigure
or reflash the fleet, because the server holds the credential on their behalf.
The firmware's authentication is intact but is no longer the control that
matters. This is a deliberate trade for a single-operator LAN deployment and it
is off by default, but a multi-user deployment wants an authentication layer in
front of the server before enabling it.
Rebased onto current
mainbefore opening: staged before today's seven merges, so it needed replaying to avoid reading as a revert of them. Clean rebase, no files deleted.