issue 128: the machine's hosts file is written whole, and on a workstation it is shared

This commit is contained in:
jochen
2026-09-26 23:58:48 +02:00
parent 8deecf620a
commit ee31f9f761
@@ -0,0 +1,59 @@
---
status: open
opened: 2026-09-26
located-in: [mesh-controller internal/catalogue/facts.go, mesh-host internal/apply]
---
# 128 — the machine's hosts file is written whole, and on a workstation it is shared
## What was observed
The private network asks for the `node-names` fact, and the mesh delivers it as
`/etc/hosts`. `nodeNames` composes a **complete** file — its own header, `localhost`, the
machine's own name, and every name in the mesh — and the host writes it over whatever is there.
On an adopted workstation the file the mesh holds contains, besides the predecessor's block of
mesh names:
- the distribution's own lines (`localhost`, the machine's `.localdomain` name);
- two marked blocks (`# BEGIN … # END …`) maintained by a local-development tool, pointing a
dozen development hostnames at `127.0.0.1` — rewritten by that tool whenever its project
list changes;
- hand-added entries of the operator's.
Today the file is only **held** ([ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md)):
the private network was assigned, not yet taken, so nothing was lost. Taking it — or
converging the node, which takes everything — replaces the file. The development tool's
entries disappear, its projects stop resolving, and every later write it makes is overwritten
at the next change to the mesh's names (a machine joins, a route is contributed), silently and
without a failure anywhere: the development tool thinks it wrote its block, and the mesh thinks
it owns the file.
This is [ADR 0102](../../02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md)'s
failure exactly — a file the mesh shares with software it did not install, written over — in a
file 0102 did not name, because its merge verb is structured (`into: json`) and a hosts file is
not JSON.
A second, smaller finding from the same reading: the fact's contents depend on which machines
hold the private network. A machine that is enrolled but not yet assigned the private network
is in neither `node-names` nor `node-zones`; its name resolves on the others only for as long
as a predecessor's hosts block survives. Taking the hosts file before every machine is on the
private network loses that name too.
## What would have prevented it
- A **marked-region** merge in the host's vocabulary: `into: "block"` (or similar) — the host
owns only the lines between its own begin and end markers, keeps everything outside them
byte for byte, records what the region held before, and on undeclare removes the region and
nothing else. The shape local tools already use for this very file.
- The `node-names` fact written as that region — no header of its own, no `localhost`, no
machine name — so the distribution's lines and every other tool's stay where they are.
- A converge preview that names a held file the take would replace *whole*, with its line
count before and after, so a person sees "hosts: 31 lines → 12" before the flip.
## Evidence to carry into diagnosis
- `internal/catalogue/facts.go`, `nodeNames`: the complete file is built here.
- The host's file resource supports `into: "json"` only; anything else is a whole write.
- `node show <node>` on the adopted workstation: `holds file /etc/hosts
mesh-wireguard.fact-node-names`, original kept.