Say what the host process is: a root service, installed as a package

The design described what the host does and never what it is at runtime. The
words daemon, long-running, interval, poll and heartbeat appeared nowhere in it
or in the relevant decisions. What exists is a command that runs and exits;
what the design needs is a process holding a link. Nobody had written down that
those differ, so several questions had no answer.

0057 settles them. It runs on every node -- the host is what makes a machine
managed, so a machine without one is not a node. Root, because no useful subset
of the job is unprivileged. A systemd unit, because something must survive a
reboot to hold the link.

It never manages its own unit. The temptation is obvious and it ends with a
host stopping itself half way through an apply, leaving a machine with nothing
running to fix it. The installation owns the host; the host owns everything
else.

Installed as a package, with a tarball as the floor. The package carries the
unit file, the state directory and an upgrade path, which a bare binary does
not. But the mesh's package repository is hosted on the mesh, so any route that
needs the mesh to install the thing that joins the mesh is a circle -- the
tarball is the path that must never acquire a dependency.

Reconciles on start, on a declaration, on a timer and on reconnect. The timer
is the one easy to leave out, and without it `owned` reports what the host
applied rather than what is there -- ADR 0035 violated by omission.

The records checker caught this commit on its first attempt: 05 listed 0057 in
its frontmatter while 0057 is still proposed, and a to-be document may not rest
on an unaccepted record. The section now says so in the body instead.
This commit is contained in:
2026-08-27 21:06:00 +02:00
parent 2330d74c1b
commit 3ab11c96ef
2 changed files with 220 additions and 1 deletions
+94 -1
View File
@@ -2,7 +2,7 @@
layer: to-be
status: in-progress
code: [mesh-host]
updated: 2026-08-26
updated: 2026-08-27
decisions:
- 02-DECISIONS/0030-the-repository-structure.md
- 02-DECISIONS/0036-a-node-is-a-managed-machine.md
@@ -113,6 +113,99 @@ work: a node is a node, and what varies between them is here rather than in the
What this machine *is* — its identity, what it holds, what it has applied. Reported upward over
the link; never asked downward.
## The process
> **Proposed, not yet decided** —
> [ADR 0057](../../02-DECISIONS/0057-the-host-is-a-root-service-installed-as-a-package.md) is
> awaiting review. This section is written against it and moves into the document's `decisions:`
> when the record is accepted.
**One `root` service on every node, plus command-line entry points for a person.** A machine
without one is not a node ([ADR 0036](../../02-DECISIONS/0036-a-node-is-a-managed-machine.md)) —
the host is what makes a machine managed, so there is no agentless node and no partial mode.
Root because there is no useful unprivileged subset: it writes under `/etc`, installs packages,
manages units and runs containers.
### Installing it
Two steps, and there is nothing else:
```
# 1 — put the host on the machine
pacman -S nox-mesh-host
systemctl enable --now nox-mesh-host
# 2 — hand it the mesh
nox-mesh-host enrol --token <one-time token>
```
The token carries the broker's address, the fingerprint to expect, and the right to join
([ADR 0051](../../02-DECISIONS/0051-the-enrolment-token-carries-the-mesh.md)). After that the
node is in the mesh and takes declarations like every other one.
**On a machine with no package repository, or no mesh to serve one:**
```
curl -fsSL https://<release>/mesh-host-<version>-x86_64.tar.gz | tar -xz -C /usr/local/bin
```
That path must never acquire a dependency. **The mesh's package repository is hosted on the
mesh**, so any installation route that needs the mesh is a circle — a first node cannot use it,
and neither can anyone repairing a mesh that is down.
### The unit
```ini
[Unit]
Description=Novox Mesh node host
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/bin/nox-mesh-host run
Restart=always
RestartSec=5s
StateDirectory=mesh-host
[Install]
WantedBy=multi-user.target
```
**The package owns this file. The host never does.** It manages `service` resources, and its own
unit is a service — the temptation is obvious and it ends with a host stopping itself half way
through an apply, leaving a machine with nothing running to fix it. A declaration naming the
host's own unit is **refused**, and that refusal is a test rather than a convention.
The line to hold: **the installation owns the host; the host owns everything else.**
### What it does while running
| | |
|---|---|
| holds the **link** | one outbound connection, node-initiated, nothing listening ([ADR 0039](../../02-DECISIONS/0039-the-link-is-the-security-boundary.md)) |
| **applies** what arrives | declarations of known shape, in the order given |
| **reads back and reports** | what it did, and what it now owns |
| **reconciles** | on start, on a declaration, on a timer, and on reconnect |
**The timer is the one that is easy to leave out.** Without it, a machine that drifted — a
person edited a file, a package upgrade replaced a config — stays drifted until somebody happens
to change a declaration. `owned` would then report what the host *applied* rather than what is
*there*, which is [ADR 0035](../../02-DECISIONS/0035-a-picture-is-read-from-what-runs.md)
violated by omission. **Ten minutes, configurable.**
**Disconnected is a working state, not a degraded one.** The host keeps reconciling against its
own store, so a laptop shut for a week comes back and reconciles rather than coming back and
asking what it is. That is
[ADR 0036](../../02-DECISIONS/0036-a-node-is-a-managed-machine.md) made operational.
### Upgrading it
**By the package manager, not by the mesh.** A running process that replaces its own binary and
restarts part-way through an apply is the self-management problem again. Recorded as a limit:
a fleet-wide host upgrade is not currently a mesh operation.
## Where a declaration comes from
One behaviour, two sources