mesh-bootstrap: the installer, and the two things its first real run found #8

Merged
jschoubben merged 9 commits from fix/bootstrap-first-run into main 2026-09-11 19:56:57 +00:00
2 changed files with 85 additions and 5 deletions
Showing only changes of commit 26ff447aa3 - Show all commits
+45 -5
View File
@@ -154,14 +154,32 @@ func Enrol(ctx context.Context, o Options, sys system.System, control controlPla
func runTheHost(ctx context.Context, o Options, sys system.System, control controlPlane,
say func(string)) (string, error) {
// Asked first, and of the mesh rather than of the process table. What matters is not that a
// process exists but that the mesh has heard from it, and only one of those two is the thing
// every later step depends on.
// **Asked of both, because either one alone lies.**
//
// A process in the table may be wedged and never collect anything, which is why this asked the
// mesh instead. But the mesh having heard from a node proves only that something spoke to it
// *once*, and `mesh-host enrol` — run moments earlier, by this very step — is itself that
// something. So straight after enrolling, the mesh has always heard from this machine and no
// agent is running; skipping the start on that signal alone is exactly wrong.
//
// What it costs is silent and total. Every later step is the control plane being *told*
// things, and nothing it is told reaches a machine with no agent to collect it: the push at
// step 7 is accepted, the mesh records the module, and no container is ever created. It
// surfaces three minutes later as "the registry is not there at all" — one step from its
// cause, looking nothing like it.
if heard, err := heardFrom(ctx, control, o.Node); err != nil {
return "", err
} else if heard {
say(" host running the mesh has heard from " + o.Node)
return "already running", nil
running, err := agentIsRunning(ctx, o, sys, control)
if err != nil {
return "", err
}
if running {
say(" host running the mesh has heard from " + o.Node)
return "already running", nil
}
say(" host running the mesh has heard from " + o.Node + ", and no agent is running " +
"here — enrolling speaks once, which is not the same thing")
}
how := ""
@@ -242,6 +260,28 @@ func runTheHost(ctx context.Context, o Options, sys system.System, control contr
}
}
// agentIsRunning is whether a host agent is on this machine now — the other half of the question
// the mesh cannot answer.
//
// Deliberately not an error when there is nothing to find: "no unit here" and "not running" are
// both simply *not running* to this caller, and the branch that starts one says far more about a
// missing unit than this could, naming what to install.
func agentIsRunning(ctx context.Context, o Options, sys system.System, control controlPlane) (bool, error) {
if o.HostInBackground {
// No unit to ask, so the process table is all there is. The exit status is the whole
// answer; anything it printed is not this function's business.
if _, err := control.run(ctx, "pgrep", "-f", o.Host+" run"); err != nil {
return false, nil
}
return true, nil
}
state, err := sys.ServiceState(ctx, control.run, o.HostService)
if err != nil {
return false, nil
}
return state == "running", nil
}
// heardFrom asks the mesh whether this node has spoken to it.
func heardFrom(ctx context.Context, control controlPlane, node string) (bool, error) {
listing, err := control.tell(ctx, "node", "list")
+40
View File
@@ -59,12 +59,17 @@ func TestAMachineThatHasAlreadyEnrolledIsNotEnrolledAgain(t *testing.T) {
switch {
case strings.Contains(joined, "node list"):
return "anchor here 01J0\n", nil
// An agent is running here too. Both halves are needed: enrolling makes the mesh hear from
// a machine once, so the first answer alone also describes a machine with no agent at all.
case name == "pgrep":
return "4242\n", nil
}
return "", fmt.Errorf("unexpected: %s %v", name, args)
}}
out, err := Enrol(context.Background(), Options{
Node: "anchor", State: alreadyEnrolled(t, "anchor"), Timeout: time.Second,
Host: "/usr/local/bin/mesh-host", HostInBackground: true,
}, arch(t), controlPlane{container: "temp-mesh-control", run: runtime.run, timeout: time.Second},
func(string) {})
if err != nil {
@@ -82,6 +87,41 @@ func TestAMachineThatHasAlreadyEnrolledIsNotEnrolledAgain(t *testing.T) {
}
}
// The mesh having heard from a machine is not the same as an agent running on it, and enrolling is
// itself the thing that makes the mesh hear. Taken as proof of life it ends the step believing an
// agent it never started, and everything after is the control plane being told things that never
// reach the machine: the next push is accepted, recorded, and applied by nobody.
func TestAMeshThatHasHeardFromAMachineWithNoAgentStartsOne(t *testing.T) {
runtime := &asked{answer: func(name string, args []string) (string, error) {
joined := strings.Join(args, " ")
switch {
case strings.Contains(joined, "node list"):
// Exactly what enrolling leaves behind, with nothing running.
return "anchor here 01J0\n", nil
case name == "pgrep":
return "", fmt.Errorf("exit status 1")
case name == "sh":
return "", nil
}
return "", fmt.Errorf("unexpected: %s %v", name, args)
}}
out, err := Enrol(context.Background(), Options{
Node: "anchor", State: alreadyEnrolled(t, "anchor"), Timeout: time.Second,
Host: "/usr/local/bin/mesh-host", HostInBackground: true,
}, arch(t), controlPlane{container: "temp-mesh-control", run: runtime.run, timeout: time.Second},
func(string) {})
if err != nil {
t.Fatal(err)
}
if out.Agent == "already running" {
t.Fatal("a machine with no agent was reported as already running it")
}
if !runtime.ran("nohup") {
t.Errorf("no agent was started on a machine that has none: %v", runtime.commands)
}
}
// A machine already enrolled under ANOTHER name is refused, with what to do about it. Re-enrolling
// replaces the identity the mesh recorded, which is a deliberate act and not something an
// installer does on its own.