A topology may carry no hub. Its meshes then peer every member with every
other one, which is what a Compose deployment of several hosts needs: there
is no manager to route through.
MeshSpec loses hub_group, spoke_groups and spoke_group_prefixes: membership
is no longer part of a declaration but resolved from the inventory into a
Topology of one hub and the spokes of every mesh.
A spoke's AllowedIPs for the hub was its own mesh subnet, so a worker on
10.100.0.0/24 had no route to the NFS server on 10.100.1.11 and could not
reach the plane it is deliberately not a member of -- which is the entire
reason the hub sits in both.
The symptom would have been misleading: every tunnel reports a healthy
handshake, the gate passes because it only pings peers the host is configured
for, and the failure surfaces later as an NFS mount that hangs. It points at
storage rather than at routing.
Spokes now route the declared pool through the hub while the hub keeps each
spoke pinned to a single address; routing the pool in both directions would
blackhole every member behind whichever spoke came first.
Found by running the writer against the prefixed five-host topology before the
deploy rather than during it.
Every credential generator in this package is single-host scoped.
credentials_generator shells out once per (app_id, host_vars_file) pair, and
that subprocess sees one host's inventory with no knowledge of any other. A
mesh credential breaks the assumption: the value one host receives is derived
from a secret minted for a different host, because a peer holds the public half
of a key whose private half lives elsewhere.
This package is the missing writer that sees the whole host set at once, so a
mesh is written complete or not at all. It is specified as cross-host
credential generation rather than as a WireGuard utility -- the application id
is a parameter and a mesh is a named hub-and-spoke grouping of inventory hosts,
so a later consumer needing paired secrets reuses it unmodified.
Planning is a pure function of the inventory, the spec and the public keys that
already exist, so mesh completeness, key pairing and rotation are provable
without a deploy. Keys are minted in-process with Curve25519 in the encoding
WireGuard expects, so the controller needs no wg binary to plan a mesh it never
joins, and a public key is always derived from its private half rather than
stored, because a stored pair can drift into a mesh that looks configured and
never handshakes.
Group membership is read across every inventory file it is spread over: the
swarm lab keeps its backup host in a sibling backup.yml, so a mesh spanning
both planes is only complete when both files have been read.
Vault encryption is salted, so a member that already holds a key is left
untouched rather than re-encrypted; otherwise every run would rewrite the file
and no deploy could be a no-op.