Status: accepted; built
services publishes a local port on the mesh under a name — immich:2283
becomes immich.nas.mesh (ADR-019). Nothing announces it. Every node knows only
what it publishes itself, so the roster can show your own services and never
anyone else's, and finding out what a peer offers means remembering, or asking
the person.
The obvious improvement is to put them on the announce. The obvious worry is that this hands every member a list of what you run.
Less than it first appears, and not nothing.
An announce is readable by every member and by nobody else. It is sealed under the per-epoch key derived from the network key; a passive observer on the shard sees a fixed-size ciphertext (SECURITY.md).
Members can already enumerate services. They listen on the device's overlay address at known ports, and that address is in the announce. Scanning a single /128 for a few hundred common ports is seconds of work. So announcing buys discoverability, not access — it does not make anything reachable that was not reachable before.
What it does add is intent, in names. "immich", "home-assistant", "jellyfin" tells a reader what you run and what to try, which a port scan does not. On a mesh of your own machines that is worth nothing. On a mesh shared with other people it is an inventory you did not mean to hand over, and the people on it are exactly the ones for whom a list is useful.
That asymmetry is the whole decision, and it maps onto something that already exists: meshes are separate, with separate configs.
Worth stating plainly, because it is easy to mistake this setting for a
security boundary: it is not one. Whether a service is published on a mesh is
the access decision, and services is per mesh. A service published on your
own mesh and not on a shared one is unreachable from the shared one — different
prefix, different WireGuard device, different AllowedIPs — whatever any
announce says.
Announcing is metadata about something a member can already reach. Someone you have admitted to a mesh can scan the device and connect to it; hiding the names does not change that, and treating this switch as protection would be worse than not having it.
What survives is smaller and still real: an inventory shortens the work for whoever has compromised a member's laptop, and service names can be personal in a way open ports are not. Enough to default it off on a mesh with other people on it. Not enough to refuse the feature.
Announce services, per mesh, off by default.
services = ["immich:2283", "jellyfin:8096"]
announce_services = "true" # this mesh's peers may see the names
mesh.shared.key = "D4R5TBD..." # a mesh with other people on it —
# nothing said about services here
Carried in their own control message, not on the announce. Announces are padded to 512 or 1024 bytes and a credential already forced the larger size; a service list would compete for that budget and be trimmed exactly when there is most to say. A separate message, sealed the same way and sent every few minutes, keeps the announce small and lets the list be trimmed independently.
Names stay scoped to the device that claims them. A service name is
self-asserted, like a device name (ADR-008): a member can publish a service
called immich on their own machine. Displaying it as immich.their-laptop.mesh
— which is already how the names work — makes that visible rather than
confusing. The roster should show the device, always.
A service list is a claim, not a promise. It says what a device intends to
publish, not what is running. shrooms status on the publishing node is the only
thing that knows whether the port is actually answering, so a peer's list should
be shown as names to try, not as a health display.
Host and SNI, so a name from the roster is
directly usable: no port to remember, which was the point of ADR-019.