Status: living design note, 2026-07-01. Captures the node-management design
worked out alongside the single-overlay-TUN fork (buw). Implementation:
deploy/openwrt/install-node.sh + deploy/openwrt/files/usr/sbin/mjolnir-apply
(bead mjolnir-mesh-6e5); trajectory: mjolnir-mesh-0kx (OTA over the
overlay), mjolnir-mesh-buw.9 (CRDT peer announcement).
A mjolnir node is meant to be an appliance: someone gets a box, plugs it in, and it joins whatever is around it — the 802.11s mesh locally, iroh tunnels over any internet egress it can find. That only works as a product if the box can also be operated without physical access:
- the owner can securely configure their node and receive updates remotely (IdentiKey key-based authorization — implementation pending);
- the maintainers of a fleet can push binaries and config to every node without walking an ethernet cable to each one.
The failure mode this design retires: every routine update used to require
plugging into the node's LAN port at 192.168.1.1, because the update itself
(the wpad-basic→wpad-mesh swap, setup-wireless.sh) bounces the very
radio you'd be SSHing over.
Every node already has a unique, stable, derivable address:
10.254.<blake3(node_id)> on the backhaul, in the shared 10.254.0.0/16,
routed everywhere by babel. That address space is the management plane:
ssh root@10.254.x.yworks from any device on the mesh (the mesh interface sits in thelanfirewall zone), across multiple radio hops, and — once thebuwoverlay carries cross-site traffic — across the internet.- The address is computed forward from the node id known at enrollment, not
discovered. A maintainer's static inventory (name → node id →
10.254.x) is sufficient tooling today. - Ethernet at
192.168.1.1is demoted to recovery of last resort. It is the one address space where every node collides, and it requires physical presence — the opposite of the appliance story.
For finding nodes dynamically (liveness, human names, cross-site), the
answer is the gossip/CRDT peer announcement (buw.9): node id → name →
overlay address → last-seen, queryable locally (mjolnirctl nodes shape).
Management/fleet inventory is a second consumer of that CRDT, alongside
cross-site dialing.
mDNS is explicitly not the answer for management discovery: it only
floods the flat-L2 island, doesn't cross br-lan/br-mesh without
reflectors on every node, and dies entirely in the L3-routed future
(mesh_fwding=0, 0yb) and across sites. mDNS remains what it already is —
a link-local bootstrap signal — nothing more.
You cannot depend on a live session through the interface you are replacing.
So updates never do: they use the commit-confirm pattern (Cisco
commit confirmed, LuCI's uci rollback), implemented in
deploy/openwrt/files/usr/sbin/mjolnir-apply:
- Stage everything first, non-disruptively: binary, init scripts,
config, and prefetched packages — both wpad variants, so even
rollback needs no internet mid-apply. A local
pkg-cache/covers boxes with no WAN at all. - Apply detached (
setsid): the operator's SSH session dying during the wifi bounce is expected and harmless. - Health-gate: after a disruptive apply, a mesh peer or a pre-apply
10.254.xneighbour must answer within a timeout, or the node rolls itself back (configs, previous binary, previous wpad) and comes back up on the old config. Either way the operator SSHes back in; a result file reportsOK/ROLLED_BACK/FAILED. - Applies are idempotent: a re-run on an up-to-date node touches nothing and never bounces wifi.
Mechanics and runbook: deploy/openwrt/README.md.
The applier was written to become the OTA agent. The delta from here to "boxes in the field update themselves":
- the payload arrives over an iroh stream instead of
scp— same staging dir, same applier, same health gate; - payloads and management actions are signed and authorized via IdentiKey key-based auth (the current blocker — implementation incomplete);
- nodes advertise version/health through the CRDT peer announcement
(
buw.9), so the fleet inventory is live rather than hand-kept; - the maintainer's local node list remains the bootstrap/recovery inventory.
The through-line: the mesh manages itself over itself, with physical access only as the escape hatch.