v2.12.0 — the site network
v2.12.0
The directory is now where the WireGuard cluster is configured. Sites, devices,
LAN mapping and internet exits all live here; each site's gateway reads the
roster and configures itself. Requires theta-gateway v3.0.0.
- feat: the site network. A site that joins the directory is on the network
— no separate mesh to set up.siteIdIS the site'sldapServerId, already
allocated once cluster-wide by the master at join time (under a lock, after
two spokes were once handed the same one), so the mesh needs no allocator of
its own. That one number drives LDAP replication, the gateway's
172.24.0.<id>address, and the site's10.<id>.0.0/16. - feat:
/networkpage, visible to any signed-in user because enrolling
your own devices is not an admin task. Sites tab shows each site's addresses
and whether its gateway has actually published a key — joined-but-not-started
is a real state that used to look identical to healthy. Devices tab enrols
hardware and picks an exit. Exit Access tab is admin-only. - feat: devices with optional key custody. Supply a public key and the
private half never reaches the server; omit it and one is generated, rendered
into a config once, and forgotten — never stored, never logged, not
recoverable. Addresses come from the site's own pool, lowest-free so a
removed device returns its address rather than a counter marching upward. - feat: per-device internet exits. Two independent things: a site marked
exitOpenis saying it is willing to carry traffic; a grant says a user may
use it. Revoking a grant immediately drops affected devices back to local
breakout rather than leaving them routed somewhere they may no longer go.
Exit choice is a routing rule on the gateway, so switching exits produces a
byte-identical device config and needs no reconnect. - feat: DNS is pushed as the mapped address, not the physical one. A device
handed192.168.1.1only resolves while sitting on that LAN; over the tunnel
what is routed is the shadow range. The site form shows the translation as
you type and warns when an address is outside both mapped LANs, where it
cannot be mapped and devices would get no resolver at all. - feat: MTU clamped to 1380 in device configs. Mesh-then-exit is WireGuard
inside WireGuard, and a device sized for one hop blackholes large packets on
the second — the classic "SSH works, HTTPS hangs". - feat: devices running theta-agent are configured over the agent's existing
websocket instead of a human copying a file. The push deliberately carries no
private key: the agent holds its own. - feat: the hub — the site carrying
10.0.0.0/8as a catch-all — is chosen in
the UI rather than implied by which site holds the master directory, since
the natural hub is a cheap always-up VPS. - change!:
utils/mesh_route.jsstops being a workaround. It existed because
WireGuard lived inside the gateway container's namespace, so the only path to
a peer was a userspace relay on a port derived from the site index. A peer
site's directory is now simply10.<siteId>.0.2:3001, and the derived-port
contract the two repos had to keep in sync is gone. An address in the retired
scheme deliberately resolves to nothing rather than silently naming a
different site. The public-endpoint fallback stays, so a deployment whose
containers have no route into the mesh still replicates over the internet. - fix: the roster now reaches every site. It is written at each site (a
gateway publishes to its own directory) but replication only flows
master -> spoke, so two halves were missing: a spoke's public key never left
the spoke, and a spoke never learned any other site existed. Either alone
means the mesh works only at whichever site happens to be the master. Spokes
now forward their gateway details overPOST /api/site/spokes— the channel
they already have, with the credential they already hold — and the master
carries the whole roster in its export. Roster edits push a resync
immediately instead of waiting for an unrelated catalog change. A site's own
row is never overwritten by an incoming export, since the local copy is
always at least as fresh. - fix: exit interfaces need their own key. A gateway's exit interface and
its mesh interface presented the same public key to the same remote, which
keeps one endpoint and one session per peer key — so the remote's endpoint
flapped between the two and they invalidated each other's session. Verified
against wireguard-go: with one key on two interfaces the remote settled on
whichever handshook last while both kept re-handshaking; with separate keys
it holds two stable peers. Gateways now publish a secondgatewayExitPublicKey
andGET /api/mesh/peerstells an exit site which gateways to accept under
it, allowed only the device addresses actually using that exit. - docs: The Site Network.