Files
hq/04-ISSUES/128-the-hosts-file-is-written-whole/00-report.md

4.2 KiB

status, opened, located-in
status opened located-in
located 2026-09-26
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): 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'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.

The fix, as built (in review)

  • Host: a file resource may say "into": "block". The host owns only the lines between # BEGIN mesh <id> and # END mesh <id> and keeps everything outside them byte for byte. A new region goes at the end by default, or at the start ("at": "start") for files where a line's meaning depends on what stands above it; a region already present is never moved. Undeclared, what the region held before is put back, or the region is removed and nothing else. Replacing nothing, it is written on an adopted node without being held — so a machine gets the mesh's names before its private network is taken.
  • Controller: node-names is a fact written into a shared file, emitted as that region: the mesh's names only, no header, no localhost, no 127.0.1.1 line.
  • Order: a host older than the block mode refuses the whole declaration on an unknown into, so hosts are upgraded before the controller that emits it.

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.